后端思维篇:手把手教你写一个并行调用模板

网站建设5年前发布
33 0 0

如果让你设计一个APP首页查询的接口,它需要查用户信息、需要查banner信息、需要查标签信息等等。一般情况,小伙伴会实现如下:,这段代码会有什么问题嘛?其实这是一段挺正常的代码,但是这个方法实现中,查询用户、banner、标签信息,是串行的。如果查询用户信息耗时200ms,查询banner信息100ms,查询标签信息200ms的话,耗时就是500ms啦。,其实为了优化性能,我们可以修改为并行调用的方式,耗时可以降为200ms,如下图所示:,对于上面的例子,如何实现并行调用呢?,有小伙伴说,可以使用Future+Callable实现多个任务的并行调用。但是线程池执行批量任务时,返回值用Future的get()获取是阻塞的,如果前一个任务执行比较耗时的话,get()方法会阻塞,形成排队等待的情况。,而CompletionService是对定义ExecutorService进行了包装,可以一边生成任务,一边获取任务的返回值。让这两件事分开执行,任务之间不会互相阻塞,可以获取最先完成的任务结果。,CompletionService的实现原理比较简单,底层通过FutureTask+阻塞队列,实现了任务先完成的话,可优先获取到。也就是说任务执行结果按照完成的先后顺序来排序,先完成可以优先获取到。内部有一个先进先出的阻塞队列,用于保存已经执行完成的Future,你调用CompletionService的poll或take方法即可获取到一个已经执行完成的Future,进而通过调用Future接口实现类的get方法获取最终的结果。,接下来,我们来看下,如何用CompletionService,实现并行查询APP首页信息哈。思考步骤如下:,我们先把查询用户信息的任务,放到线程池,如下:,如果想把查询banner信息的任务,也放到这个线程池的话,发现不好放了,因为返回类型不一样,一个是UserInfoDTO,另外一个是BannerDTO。那这时候,我们把泛型声明为Object即可,因为所有对象都是继承于Object的。如下:,这里会有个问题,就是获取返回值的时候,我们不知道哪个Object是用户信息的DTO,哪个是BannerDTO?怎么办呢?这时候,我们可以在参数里面做个扩展嘛,即参数声明为一个基础对象BaseRspDTO,再搞个泛型放Object数据的,然后基础对象BaseRspDTO有个区分是UserDTO还是BannerDTO的唯一标记属性key。代码如下:,到这里为止,一个基于CompletionService实现并行调用的例子已经实现啦。是不是很开心,哈哈。,我们回过来观察下第2小节,查询app首页信息的demo:CompletionService实现了并行调用。不过大家有没有什么其他优化想法呢?比如,假设别的业务场景,也想通过并行调用优化,那是不是也得搞一套类似第2小节的代码。所以,我们是不是可以抽取一个通用的并行方法,让别的场景也可以用,对吧?这就是后端思维啦!,基于第2小节的代码,我们如何抽取通用的并行调用方法呢。,首先,这个通用的并行调用方法,不能跟业务相关的属性挂钩,所以方法的入参应该有哪些呢?,方法的入参,可以有Callable。因为并行,肯定是多个Callable任务的。所以,入参应该是一个Callable的数组。再然后,基于上面的APP首页查询的例子,Callable里面得带BaseRspDTO泛型,对吧?因此入参就是List>> list。,那并行调用的出参呢?你有多个Callable的任务,是不是得有多个对应的返回,因此,你的出参可以是List>。我们抽取的通用并行调用模板,就可以写成酱紫:,既然我们是抽取通用的并行调用方法,那以上的方法是否还有哪些地方需要改进的呢?,我们再次优化一下这个通用的并行调用模板,代码如下:,以后别的场景也需要用到并行调用的话,直接调用你的这个方法即可,是不是有点小小的成就感啦,哈哈。,我们把抽取的那个公用的并行调用方法,应用到App首页信息查询的例子,代码如下:,基于以上代码,小伙伴们,是否还有其他方面的优化想法呢?比如这几个Callable查询任务,我们是不是也可以抽取一下?让代码更加简洁。,二话不说,现在我们直接建一个BaseTaskCommand类,实现Callable接口,把查询用户信息、查询banner信息、label标签信息的查询任务放进去。,代码如下:,以上这块代码,构造函数还是有比较多的参数,并且call()方法中,有多个if...else...,如果新增一个条件分支(比如查询浮层信息),那又得在call方法里修改了,并且BaseTaskCommand的构造器也要修改了。,大家是否有印象,当程序中出现多个if...else...时,我们就可以考虑使用策略模式+工厂模式优化。,我们声明多个策略实现类,把条件分支里的实现,搬到策略类,如下:,然后这几个策略实现类,怎么交给spring管理呢?我们可以实现ApplicationContextAware接口,把策略的实现类注入到一个map,然后根据请求方不同的策略请求类型(即userInfoDTO还是bannerDTO等),去实现不同的策略类调用。其实这类似于工厂模式的思想。代码如下:,有了策略工厂类TaskStrategyFactory,我们再回来优化下BaseTaskCommand类的代码。它的构造器已经不需要多个IUserService userService, IBannerService bannerService, ILabelService labelService啦,只需要传入策略工厂类TaskStrategyFactory即可。同时策略也不需要多个if...else...判断了,用策略工厂类TaskStrategyFactory代替即可。优化后的代码如下:,因此整个app首页信息并行查询,就可以优化成这样啦,如下:,总结以上代码整体优化下来,已经很简洁啦。那还有没有别的优化思路呢。,其实还是有的,比如,把唯一标记的key定义为枚举,而不是写死的字符串"userInfoDTO"、"bannerDTO","labelDTO"。还有,除了CompletionService,有些小伙伴喜欢用CompletableFuture实行并行调用,大家可以自己动手操戈写一写。,本文大家学到了哪些知识呢?,如何优化接口性能?某些场景下,可以使用并行调用代替串行。,如何实现并行调用呢?可以使用CompletionService。,学到的后端思维是?日常开发中,要学会抽取通用的方法、或者工具。,策略模式和工厂模式的应用,本文的话,设计模式这块还不是很详细,下一篇,给大家讲讲,我是如何在现有代码基础上,抽取设计模式的哈。

© 版权声明

相关文章