作者:林生生,GrowingIO 运营产品线研发经理,主要负责 GrowingIO 智能运营产品线研发管理工作。
背景
GrowingIO 在 2018 年初推出了基于底层数据能力的智能运营平台,结合精准的用户分群,数据采集以及多种运营方式,帮助企业客户用数据驱动用户运营,随时验证假设,助力产品增长。产品有以下特点:
支持多种触达用户的渠道 :站内:弹窗、资源位,站外:Push、短信、Webhook。 多平台支持,弹窗支持:App、Web、H5和小程序。 轻松建立数据运营的闭环。
下图是运营平台站外触达业务流程图。用户可以随时发起一次站外运营活动,通常是一个站外的触点(推送、短信、Webhook)。后台系统需要查询底层的数据平台接口,获取此次活动对应的人群信息,同时组装活动数据并对外投递任务。
在这个业务场景,需要解决如下几个问题:
- 系统外界输入是突发的,无法提前预估量级,系统需要在不断变化的负载中保持即时响应。
依赖底层数据服务,如果外部系统无法工作,为了保证回弹性需要有熔断和恢复机制。 业务流程较长,为保证及时响应需要对任务进行异步处理。
综上,为了最大化利用服务器资源、提高服务稳定性和优化终端用户体验,GrowingIO 服务端团队在异步与反应式编程上做了一些实践。本文将介绍在优化过程中的探索与思考,希望能为读者带来帮助。
异步与响应式
传统服务端程序一般采用同步阻塞模型,通过分配更多线程来支撑更多请求,这符合常人思维模式,但在突发流量的情况下,同步模型可能会导致线程池耗尽,基于一个请求一个线程的服务模式无法做到动态伸缩。
而异步编程的做法是基于一个共享的线程池,所有操作都是回调。如果遇到耗时的操作,线程并不会阻塞等待操作完成,而是会被释放回线程池中继续接受新的请求。等到耗时操作完成后(一般都是 IO 操作),通过消息机制重新向线程池申请线程恢复之前的请求代码。
我们可以简单写个程序简单实验一下,实现相同逻辑:
- 查询 db
- 查询外部系统
- 组装信息返回。
唯一区别是一个是同步调用的实现,另一个是采用完全异步的方式实现。
本地使用相同的 jmeter 参数模拟并发测试,得到结果如下,从左到右每列的含义分别为:请求名称、请求数目、失败请求数目、错误率(本次测试中出现错误的请求的数量/请求的总数)、平均响应时间、最短响应时间、最大响应时间、90% 用户响应时间、95% 用户响应时间、99% 用户响应时间、吞吐量。
总体测试结果如下:
同步代码总共完成了 260 次请求,平均响应时间约 5 秒,因为阻塞程序耗尽了线程池导致程序出现了拒绝服务的情况,产生了 13% 的错误率。
异步代码整体吞吐量有明显提升,相同时间内完成了 3000 次请求。错误率为 0 ,并且整体没有出现拒绝服务的情况。
可以看到基于消息驱动机制的异步系统能极大提高资源利用率,提高系统的吞吐量。而响应式系统则在消息驱动的基础上增加了三个要求:及时响应性、回弹性和可扩展性。
简单来说具备以下四个特点的系统可以称为一个响应式系统:
-
即时响应性,这个是响应式系统的核心目标。一个具有响应性的系统就是一个无论在什么情况下都能快速对客户的操作做出反馈的系统,包括事件、用户请求、失败场景,最终目的是保证客户