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