本文为『InfoQ x 苏宁 2018 双十一』技术特别策划系列文章之一。

背景

苏宁会员任务平台是覆盖聚合电商、体育、金融、PPTV、直播、红孩子等各个业态,平台会实时获取用户的画像信息来计算用户在客群中的分布及画像属性,从而实时判断用户是否满足相关场景下任务,若满足相关场景以后可以领取任务下所有奖项;任务类型包含了订单红包、母婴、Super会员、直播、双签、金融升级存等等。在大促特别是双十一期间,任务中心产品对于各个业态的引流,会员的留存及转化来说是一个重要的工具。

问题

因任务平台业务逻辑复杂、实时性要求高,涉及多个外围系统服务及数据调用;一期系统上线后部分功能遇到性能问题,例如聚合页打开时间过长,首先聚合页上要展示用户能看到的任务列表,以及当前用户是否达到领取条件,其次每个任务需要展示的状态依赖于后台多种信息的聚合,包括不在有效时间范围内、当前时段库存、可供领取的总库存、领取频次等。复杂逻辑和实时要求导致TPS在上线压测的时候没有能够达到一个理想预期效果。

即将到来的”双十一”流量高峰, 可以预见会使得超过现有的任务系统的TPS的峰值, 从而导致任务系统在”双十一”的场景下很容易触碰到性能瓶颈,影响用户体验;因此需要对苏宁任务平台的核心功能做性能优化, 提升实时性复杂业务逻辑场景下的性能, 以便于应对任务平台的流量暴涨以及双十一流量高峰。

定位

现有的每个任务可能依赖于多个异构系统的服务或者数据,例如直播任务及订单任务来自于不同的系统的服务,并且有些场景是基于外围系统的数据进行逻辑计算,有些则是通过服务接口调用的方式。

代码示例:

public ResultDTO checkAndGetInfo() {    A a = getA();    B b = getB();    C c = getC();    ......    ResultDTO result = computeResult(a, b, c ...);    return resultDTO;}

由于页面实时性要求高,逻辑复杂,对于某个任务是否展示需要调用多个外围接口,响应时间不可控,理论上根据任务的复杂性可能涉及多个客群,调用次数及响应时间不可控。性能主要在响应时间不可控。

某个任务状态要调用多个本地接口或者外围接口。

主要思路:异步,缓存,线程池

针对以上定位到位问题,考虑到实时调用外围接口的方案会导致响应时间不可控,采用NIO的思想,对整个调用链进行梳理,尽量异步化调用,同时增加适当过期时间的缓存,达到性能优化的目的。

在一期设计的时候已经从业务逻辑的角度做了拆分,将不同生命周期的逻辑异步化处理,例如奖励是通过kafka推送到奖励资源系统异步发放的。

上述从业务生命周期角度分析,通过切分业务流程,达到优化的方式已经不能满足系统性能需求,需要从技术上考虑更细粒度的异步化处理方式。

优化方案的选择及演进

Kilim

Kilim是一个java的协程框架,利用字节码技术编织技术将普通代码转化为支持协程的代码,当时是基于同步的思路下,想利用协程优化同步并发处理的能力。经过调研业界实践应用相对较少,因此考虑到项目开发周期等因素,没有采用Kilim方案。

Guava Listenable Future:

JDK 5引入了Future模式。 Future接口是Java多线程Future模式的实现,在java.util.concurrent包中,可以来进行异步计算。

Future模式是多线程设计常用的一种设计模式。Future模式可以理解成:有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时间之后,我就便可以从Future那儿取出结果。

ExecutorService executor = ...;Future f = executor.submit(...);f.get();

Future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:

  1. 将多个异步计算的结果合并成一个
  2. 等待Future集合中的所有任务都完成
  3. Future完成事件(即,任务完成以后触发执行动作)

Future虽然可以实现获取异步执行结果的需求,但是它没有提供通知的机制,我们无法得知Future什么时候完成。

要么使用阻塞,在future.get()的地方等待future返回的结果,这时又变成同步操作。要么使用isDone()轮询地判断Future是否完成,这样会耗费CPU的资源。

Guava的Listenable Future对其做了改进,支持注册一个任务执行结束后回调函数。

ListenableFuture\u0026lt;String\u0026gt; listenableFuture = listeningExecutor.submit(new Callable\u0026lt;String\u0026gt;() {    @Override    public String call() throws Exception {        return \u0026quot;\u0026quot;;    }});Futures.addCallback(ListenableFuture\u0026lt;V\u0026gt;,FutureCallback\u0026lt;V\u0026gt;, Executor)

其中FutureCallback是一个包含onSuccess(V),onFailure(Throwable)的接口:

Futures.addCallback(ListenableFuture, new FutureCallback\u0026lt;Object\u0026gt;() {    public void onSuccess(Object result) {        // do something on success    }    public void onFailure(Throwable thrown) {        // do something on failure    }});

这也是一开始试验的方案,确定好了异步化的思路,自然联想到了增强版的Listenable Future,虽然在任务完成时可以回调函数通知,但是仍然是阻塞的,主线程仍然要等待异步线程完成任务通知。

Completable Future

Java8的CompletableFuture参考了Guava的ListenableFuture的思路,CompletableFuture能够将回调放到与任务不同的线程中执行,也能将回调作为继续执行的同步函数,在与任务相同的线程中执行。它避免了传统回调最大的问题,那就是能够将控制流分离到不同的事件处理器中。

CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。

CompletableFuture completableFuture = new CompletableFuture();completableFuture.whenComplete(new BiConsumer() {    @Override    public void accept(Object o, Object o2) {        //handle complete    }}); // complete the taskcompletableFuture.complete(new Object());//api methodcompletableFuture.thenApply(Function f); //api methodcompletableFuture.thenAccept(Consumer c); //api method

CompletableFuture 提出了CompletionStage的概念,代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段。

一个阶段的计算执行可以是一个Function,Consumer或者Runnable。比如:

stage.thenApply(x -\u0026gt; square(x)).thenAccept(x -\u0026gt; System.out.print(x)).thenRun(() -\u0026gt; System.out.println());

一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发。

与Guava ListenableFuture相比,CompletableFuture不仅可以在任务完成时注册回调通知,而且可以指定任意线程,实现了真正的异步非阻塞。

Servlet 3.0

传统Servlet 2.x web容器处理http请求时是为每一个请求分配一个线程,处理完请求再释放线程,如果请求处理的比较慢或者请求过多,就可能达到线程池达到上限,这时候后续的用户请求就会处于等待状态或者超时,这里用户请求和处理请求是一个线程,Servlet 3.0 开始提供了AsyncContext用来支持异步处理请求,主要是把请求线程和工作线程分开,将耗时的业务处理工作交给另外一个线程来完成。

@WebServlet(urlPatterns = \u0026quot;/servlet3\u0026quot;,asyncSupported = true)public class Servlet3 extends HttpServlet {    public void doGet(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {               //在子线程中执行业务调用,并由其负责输出响应,主线程退出        AsyncContext ctx = request.startAsync();        new Thread(new Executor(ctx)).start();    }    public void doPost(HttpServletRequest request, HttpServletResponse response)            throws ServletException, IOException {        doGet(request, response);    }}class Executor implements Runnable {    private AsyncContext ctx = null;    public Executor(AsyncContext ctx){        this.ctx = ctx;    }    public void run(){        try {            Thread.sleep(3000);            ServletRequest request = ctx.getRequest();            ctx.dispatch(\u0026quot;/index.jsp\u0026quot;);            ctx.complete();        } catch (Exception e) {            e.printStackTrace();        }    }}

最终方案

最终选定Completable Future + Servlet 3.0的方案,前台web接口层采用Serlvet 3.0,后台服务层采用Completable Future。

验证

优化前压测数据:

图1:在访问聚合页100并发情况下的数据,TPS值3235

【图1】


图2:在访问聚合页200并发情况下的数据,TPS值3322,在用户并发量增加的时候,因依赖外部接口服务和原有的系统设计接口调用方法导致TPS基本不会随并发量的增加而提高。

【图2】


优化后压测数据

在访问聚合页100并发情况下的数据,TPS值5869,相对于优化之前的TPS有明显的提升。

【图3】


在访问聚合页150并发情况下的数据TPS值8581,在提高并发量的时TPS有显著的提高,说明优化后的效果很明显,也证实了优化方案是可行的。

【图4】

总结

利用异步化来提升系统性能是一个整体、全链路的工作,仅仅依靠业务上的异步化,或者服务层的异步化远远不够,随着不同技术方案的选择及演进,对异步非阻塞模型有了更深入的了解之后,从前台用户请求到后端服务层处理,根据一整条链路的上每一层场景的不同,需要选取不同的异步化技术方案,才能达到系统整体性能提升的目的。

作者

葛苏杰,现担任苏宁易购IT总部技术经理职位,从事多年的电商系统2C业务开发,对于高可用、高并发的分布式系统的JVM性能调优、SQL优化、Cache、NIO、NGINX等相关技术有丰富的经验。

苏宁11.11:如何基于异步化打造会员任务平台?相关推荐

  1. 2021大数据助力精准医疗产业沙龙 | 有孚网络吕鑫:基于专有云打造生物信息云平台

    ​2021年5月12日,大数据助力精准医疗产业沙龙在上海国际医学园区站成功举办.此次会议由中国医药生物技术协会基因检测技术分会.上海市浦东新区生物产业行业协会.中国遗传学会遗传诊断分会.长三角一体化基 ...

  2. 苏宁会员任务平台:基于异步化的性能优化实践

    背景 苏宁会员任务平台是覆盖聚合电商.体育.金融.PPTV.直播.红孩子等各个业态,平台会实时获取用户的画像信息来计算用户在客群中的分布及画像属性,从而实时判断用户是否满足相关场景下任务,若满足相关场 ...

  3. 基于插件化的企业级开发平台JXADF(开源)

    产品简介: http://osgi.jxtech.net 功能演示: 转载于:https://blog.51cto.com/wmzsoft/1685432

  4. 基于SaaS化的低代码平台设计思路(二)

    经过多日的整理,数据库结构算是把初版本弄出来了,准备采用DDD的模式进行,类似于金蝶这样的产品,直接通过BOS设计器进行业务单据的开发.摒弃传统的手工一行一行敲代码,将业务进行细化,用业务驱动整个平台 ...

  5. 基于MQ对登录系统核心流程进行异步化改造,提升系统性能-11

    基于MQ对登录系统核心流程进行异步化改造,提升系统性能 1.会员表与实体类定义 2.登录RocketMQ 参数定义 3.登录消息生产者 4.登录服务(LoginService) 5.登录Control ...

  6. 苏宁11.11:如何 hold 住大促红包

    红包,这几年最火的营销系统.各大厂,无论双11.春节都花费了大力气,五花八门的产品竭力吸引眼球. 那么如何设计一个能抗住亿级并发的红包系统了.这恐怕对任何一个团队来说,都是一个很大的挑战.经过这几年的 ...

  7. 够大牌才够质感:京东11.11数读产品品质化新趋

    "今年11.11跟往年不一样的是,可以明显感受到用户的消费形态正逐步迈向品质.精致化的消费时代."在11月11日举办的京东11.11全球热爱季媒体开放日上,京东零售集团时尚居家平台 ...

  8. 苏宁双11超级工程排头兵—会员系统架构演进

    http://tech.it168.com/a2017/1110/3178/000003178923.shtml 1990年创业至今的28年间,苏宁不仅完成了从线下零售商向O2O互联网企业的转型,而且 ...

  9. 基于用户画像的实时异步化视频推荐系统

    前言 这个月做的事情还是蛮多的.上线了一个百台规模的ES集群,还设计开发了一套实时推荐系统. 标题有点长,其实是为了突出该推荐系统的三个亮点,一个是实时,一个是基于用户画像去做的,一个是异步化. 实时 ...

最新文章

  1. ORB_SLAM2程序入口(System.cc)
  2. 极光推送 请检查参数合法性_极光(JG.US)牵手中国人寿数据中心,合作助推智能用户运营_行业动态...
  3. 计算机组成原理实验八报告,计算机组成原理实验八报告
  4. python修改文件后缀_python实现遍历文件夹修改文件后缀
  5. 今天研究了一下关于Asp.net文件上传时的内存消耗问题
  6. Elasticsearch启动报错:Exception in thread “main“ java.nio.file.AccessDeniedException:
  7. 移动端UI自动化Appium测试——Appium server两种启动方式
  8. 深入了解Blazor组件
  9. 一杯水怎么测试_天气渐热食欲差、体质虚怎么办?每天一杯能量水——驼奶
  10. 关于android的各种disk images(看过的讲android image比较细致的好文)
  11. linux 设置自动定时清理日志
  12. 作为一个iOS攻城狮不得不了解的网络知识
  13. 互联网盈利模式研习笔记 1:流量变现
  14. Ubuntu18.04 实现串口通信
  15. html中以菱形为边框,深入浅析css3 border-image边框图像详解
  16. 如何运用VR3d模型线上展示构建博物馆展厅与展馆
  17. Allegro使用总结-查看Layout基本操作:
  18. Windows中使用cmd查看磁盘及其格式
  19. python 的几个源生方法
  20. 深圳市晶光华电子有限公司 - 晶振选型的四个重要参数是什么?

热门文章

  1. 行列式按k行展开(拉普拉斯定理)
  2. 2005年上半年(第21次)全国计算机等级考试(NCRE)广东考区报考简章
  3. 如何把draw.io的流程图导入到word
  4. 再见,Eclipse !
  5. no protocal specified
  6. Nginx静态网站部署
  7. 五句话了解区块链,扫盲系列
  8. 边缘计算、网络功能虚化
  9. SPC5607B的eDMA模块使用示例
  10. c语言/c++转Java学习笔记---基础问题