基于akka的多线程框架在考拉APP后端的应用

达芬奇密码2018-08-22 11:02

问题背景

App品牌页由于多版本的迭代,页面承载模块越来越多,如品牌基本信息、优惠券、热销、上新、品牌商品筛选、相关品牌推荐等,如下图所示,用户访问品牌页有时候会感觉加载较慢,主要原因是App品牌页所有的数据都是通过一个HTTP请求返回数据,这个请求需要调用三次搜索、一次推荐、若干次缓存查询和DB查询,而且是顺序同步执行,请求响应时间较长,导致的结果是:

1、用户体验较差,当可降级服务(热销/上新搜索、品牌推荐)某个环节处理较慢时,整个请求的响应时间会拉的很长,即使dubbo配置了超时,多个降级服务的最大响应时间叠加也是不能忍受的。

2、在用户使用峰值,如每日秒杀或大促时间,慢请求会导致Dubbo线程阻塞。我们不怕请求多,我们就怕慢请求。

在已发版的客户端无法修改的情况下,只能通过服务端加快处理逻辑,及时返回数据,解决慢请求的问题。

 

多线程解决方案

针对以上问题,我们打算采取多线程方案解决,JDK 7提供了两种线程池方案,ThreadPoolExecutorForkJoinPool,对两者原理研究和源码阅读后,我们进行了一个比较:

l   适用场景:Fork/JoinThreadPoolExecutor更适合任务层层分解、处理、结果合并的场景。

l   性能:Fork/Join的工作流窃取算法,能提供最大化的并行程度。

我们选定ForkJoinPool来解决问题,但是多线程编程有一定的难度,主要原因有:

l  

l   线程间通信

l   异常处理

l   代码可读性

l   ForkJoinPool线上时间经验

我们需要一个简单、易用、抽象层次更高的并发框架,我们又查阅了Actor模型的Java实现Akka

 

Actor模型:Akka

Actor模型,一种用于处理并发计算的数学模型,特点如下:

l   异步消息方式通信

l   状态机

l   无共享

l   无锁的并发处理方式

l   并行性

AkkaActor模型的Java实现,底层线程池默认为ForkJoinPool,是比较理想的并发框架选择,它的编程难度相比直接用JDK的线程池更低,简单、易用、抽象层次更高。

我们进一步做的工作是结合Spring框架抽象封装Akka,对外隐藏Akka的细节,提供更简单的服务,进一步简化多线程编程的难度。

 

效果

上线后,通过哨兵对比,App品牌页性能提升约25%左右,如下图所示:

相同的方案应用在用户页,平均响应时间提升约50%左右,如下图所示:

用户在访问App品牌页和用户页响应速度更快,体验更佳,系统dubbo线程池彪满的风险进一步降低,效果达到了预期。

未来该解决方案还会有更多的应用场景,敬请期待!


网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者黄晓军授权发布。