问题背景
App品牌页由于多版本的迭代,页面承载模块越来越多,如品牌基本信息、优惠券、热销、上新、品牌商品筛选、相关品牌推荐等,如下图所示,用户访问品牌页有时候会感觉加载较慢,主要原因是App品牌页所有的数据都是通过一个HTTP请求返回数据,这个请求需要调用三次搜索、一次推荐、若干次缓存查询和DB查询,而且是顺序同步执行,请求响应时间较长,导致的结果是:
1、用户体验较差,当可降级服务(热销/上新搜索、品牌推荐)某个环节处理较慢时,整个请求的响应时间会拉的很长,即使dubbo配置了超时,多个降级服务的最大响应时间叠加也是不能忍受的。
2、在用户使用峰值,如每日秒杀或大促时间,慢请求会导致Dubbo线程阻塞。我们不怕请求多,我们就怕慢请求。
在已发版的客户端无法修改的情况下,只能通过服务端加快处理逻辑,及时返回数据,解决慢请求的问题。
多线程解决方案
针对以上问题,我们打算采取多线程方案解决,JDK 7提供了两种线程池方案,ThreadPoolExecutor和ForkJoinPool,对两者原理研究和源码阅读后,我们进行了一个比较:
l 适用场景:Fork/Join比ThreadPoolExecutor更适合任务层层分解、处理、结果合并的场景。
l 性能:Fork/Join的工作流窃取算法,能提供最大化的并行程度。
我们选定ForkJoinPool来解决问题,但是多线程编程有一定的难度,主要原因有:
l 锁
l 线程间通信
l 异常处理
l 代码可读性
l 无ForkJoinPool线上时间经验
我们需要一个简单、易用、抽象层次更高的并发框架,我们又查阅了Actor模型的Java实现Akka。
Actor模型:Akka
Actor模型,一种用于处理并发计算的数学模型,特点如下:
l 异步消息方式通信
l 状态机
l 无共享
l 无锁的并发处理方式
l 并行性
Akka是Actor模型的Java实现,底层线程池默认为ForkJoinPool,是比较理想的并发框架选择,它的编程难度相比直接用JDK的线程池更低,简单、易用、抽象层次更高。
我们进一步做的工作是结合Spring框架抽象封装Akka,对外隐藏Akka的细节,提供更简单的服务,进一步简化多线程编程的难度。
效果
上线后,通过哨兵对比,App品牌页性能提升约25%左右,如下图所示:
相同的方案应用在用户页,平均响应时间提升约50%左右,如下图所示:
用户在访问App品牌页和用户页响应速度更快,体验更佳,系统dubbo线程池彪满的风险进一步降低,效果达到了预期。
未来该解决方案还会有更多的应用场景,敬请期待!
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者黄晓军授权发布。