1、前言
自 2020 年 1 月,新型冠状病毒肺炎被证实人传人后,无论是普通民众还是政府部门,都想着怎么去控制病情。而口罩成为防疫病情的第一需求,口罩很多时候一罩难求,是全国人民的刚需产品。
我算是半个湖北人,年前因为各种因素没去湖北,但身为一个灾区的亲属,一直在想:作为一个架构师,如何用自己的所学为疫情中的人们出一份力?碰巧 2 月底,我收到了一个地方政府口罩预约系统的优化援助信息,当时心里很是高兴,我终于也能除了关注、捐款外,用自己的所长贡献出更大的力量!
2、需求收集
01 / 需求
口罩在疫情期间是一个全民性的需求,该地方政府每日会采购一批口罩供应市场,市民们采用线上预约的方式进行抢购,由于预约人数过多,现有的口罩预约系统无法支持高并发的抢购,急需进行优化。
需求如下:
- 该地有百万人口,每日供应口罩量将达到几十万个以上,系统要求最高支持十万级以上人同时预约。
- 政府为了满足公平性,采用在线多端预约的方式:每日不定时开放预约,并更新销售网点。
- 每个预约成功者可到定点网点一次性采购5个口罩。
- 凡预约成功者,自预约成功之日起 5 日后才可重新预约。通过预约人的身份证和手机号双限制。
- 口罩预约是一个民生急需需求。要求预约系统能够稳定,快速上线。
- 现有硬件资源有限,暂时不考虑变更。
02 / 系统现状
当时的口罩预约系统是一个使用 PHP 语言实现的完整后台管理系统。
- 视图层:因为 PHP 是快速建站的首选,所以是没有前后分离的。
- 存储层:原先使用 Access 数据库,后面因为预约人数太火爆,改用 MySQL。
需求分析
针对第一点需求。我们可以分析到:百万人口级别的市场需求,最高10万级别的预约记录。是一个高并发的写请求,如果直接入库的话,对于MySQL有比较大的性能压力,而且也非常浪费资源。更何况一开始还使用Access 数据库。所以我们只可以选择异步批量插入数据库来保障需求。
针对第二点需求。我们可以分析到:这其实是一个秒杀行为。对于秒杀,因为有各种限制和库存的处理,为提高市民体验性,所以我们需要采用内存秒杀处理。
针对第三、四点需求。我们可以分析到:需要有黑名单机制、商品扣除校验等业务需求。
针对第五点需求。我们可以分析到:原有系统的峰值波动不稳定性,已经引起公众的不满,有一定的投诉量。所以必须保证峰值的高可用和流畅度。
针对第六点需求。我们可以分析到:身为架构人员,要做的就是降本增效的设计。怎样将有限的资源更大利用化其实是咱们最大的工作。
3、架构方案设计
因为是一个性能优化的援助,所以可以确定为遗留系统改造的需求。从架构师的思维出发,先进行总体架构的设计模式选择。针对遗留系统的改造,我们通常选择绞杀者模式、修缮者模式。
01 / 绞杀者模式
绞杀者模式是指在遗留系统外围,将新功能用新的方式构建为新的服务。随着时间的推移,新的服务逐渐“绞杀”老的遗留系统。
流程如下:
02 / 修缮者
就如修房或修路一样,将老旧待修缮的部分进行隔离,用新的方式对其进行单独修复。修复的同时,需保证与其他部分仍能协同功能。
大神 Martin Fowler 在 《branch by abstraction》有详细的介绍,流程如下:
绞杀者模式适合对于那些老旧庞大难以更改的遗留系统进行全局改造。然而我们接收的是一个优化援助,是一个紧急的事情。那么我们只能选择修缮者模式。
4、详细设计
现有系统有三张表关系到秒杀的基本业务(不含安全风控)。
- 配置表,口罩的数量配置表
- 网点表,记录网点信息
- 登记表,记录购买用户信息
- 表设计如下:
配置表主要有:id、日期、总数、余数、状态(0 - 过期,1 - 开放,2 - 关闭)
网点表主要有:id,名字,地点,排序
预约表主要有:id,姓名,身份证,手机号,日期,网点 id
系统有完整的前后台及一些运维风控设置,时间又比较紧张的前提下,咱们使用修缮者模式将秒杀场景隔离出来,使用独立的项目进行执行,协同原有的后台进行支撑。
- 登记流程:
从流程中我们发现预约的瓶颈有以下几点:
1、 预约页面的初始化,需要余量展示以及销售网点的展示。高并发下有一定的性能的损耗,影响客户的体验度。
2、 黑名单校验,原本通过数据库查询来限制。一样有性能损耗,影响预约流畅度。
3、 抢购预约,大量数据库插入,数据库连接不足。引起系统崩溃,系统可用性及其低下。
根据瓶颈,我们分析以下,如果全内存加载:
1、 每条网点信息在UTF8MB4下会占用1KB。最多千条,全内存加载1M。因为存在后台更新网点,日更级别。使用修缮者模式+快速上线,先不做系统对接。所以可以定时异步全刷新网点缓存,更新频率根据实际分析,每30分钟刷新一次。
2、 黑名单校验,通过身份证和手机号来限制,计算下存储空间:
- 每个限制:11 + 18 = 29B
- 平均记录数:10万
- 天数:5天
- 合计 29 × 10万 × 5,大约 15M。
结论:完全可以内存加载。
即使上升一个量级,100 万登记也才 150M。
3、 数据插入在秒杀流程是一个超高频的操作,也是性能的最大屏蔽。所以我们可以采用异步批量插入数据的形式进行优化。
5、伪代码实现
01 / 预约逻辑
02 / 批量更新逻辑
03 / 黑名单加载机制
04 / 网点加载机制
05 / 配置异步刷新机制
06 / 缓存静态变量类
07 / 异常处理机制
因为是单 Tomcat,为了防止系统故障,使用了优雅停机及 Tomcat 崩溃恢复 mock
6、成果展示
前后经过7小时的优化,完成了预约单功能的修缮协同。
优化后的页面如下:
测试结果如下:
生产运营环境:
上线当天平稳支撑了 80000 次并发的预约。全程改造不加任何中间件,在原有的服务器和软件上,独立部署一个Tomcat,使用重定向协同完成对客户的无感知预约。