1. 背景
最初是某公众号转发的一篇西安某小区业主关于“西安一码通崩溃”的文章,一群技术背景的小区业主分析故障原因,并提出了建设意见,个人觉得挺有意思的。
原文《西安一码通BUG分析——雲著君建言献策,盼一码通稳定运行》,详见参考资料第一条。
2. 原因分析
外界其实很难分析出真正的原因,毕竟互联网公司故障很多对外的口径都是“网络问题”。
但从表象看,应该是流量骤增,打挂了服务,至于是链路的哪一环节,不得而知。
简而言之,系统的限流、扩容、链路优化都没做好。
3. 简单设计
下面我简单的分析下需求,画下架构图,罗列下设计点,但不做生产级的方案设计。
3.1 需求分析
3.1.1 健康码展示
健康码展示包括红码、黄码、绿码、疫苗注射信息。该接口流量最大,但是返回数据量小,数据一致性要求不高。
3.1.2 行程码展示
行程码不放在首页,并且大部分情况下无需查看,接口流量相对小些,返回数据量也不大,数据一致性要求不高。
3.1.4 核酸检测结果
一般情况下不放在首页,大部分情况下无需查看,只不过西安封城期间,必须展示 48 小时内核酸检测结果,所以该接口流量可能会有暴涨的情况,数据量稍大些,一致性要求不高。
3.1.4 其他服务
C 端个人信息的登记修改、后台修改个人数据、核酸检测登记、健康码计算引擎等服务,流量都很小。
其中健康码计算引擎才是难度系数高的,根据各种算法规则定义一个人的健康码结果。
3.2 流量预估
西安 1300 万人口,日活应该不超过 1000 万,高峰期 QPS 大约 10 万左右(我之前负责过的业务,日活 3000 多万,最高 QPS 也才 20万)。
3.3 架构设计
3.3.1 架构图
3.4 详细设计
3.4.1 应用拆分设计
首先,架构图中的查询服务需和维护服务位于不同应用,这两个服务的流量级别不是一个等级的,分开部署便于弹性扩容。
其次,如果条件(成本)允许,健康码查询服务最好也和行程码、核酸结果查询服务分属不同应用。
3.4.2 缓存设计
健康码、行程码、核酸结果这些查询接口都不是实时性要求很高的接口,所以都需要上缓存。
缓存设计有两种方案,一种是基于 LRU 的多级缓存方案,一种是基于全量内存的缓存方案。
3.4.2.1 LRU多级缓存方案
JVM 缓存—> Redis 缓存—> MySQL,需要监控各级缓存命中率,调整缓存过期时间。
需要注意以下几点:
- 评估缓存容量(可用 JOL评估缓存对象大小)
- 缓存预加载(启动预热、上班高峰期前定时预热)
- 缓存穿透问题(布隆过滤器/缓存空对象、黑名单机制)
- 缓存同步问题(Kafka广播、过期失效、定时刷新等)
3.4.2.2 全量内存缓存方案
健康码缓存数据量简单评估:
1 | { |
监控码结果(红黄绿)只需要数字(0、1、2)表示,疫苗注射结果(一针、两针、三针)也可以用(1、2、3)表示,简而言之,健康码的缓存数据量极小。可以考虑上全量的 JVM 缓存。
将 1000 多万的健康码结果直接在应用初始化时全量加载到内存中,定时(比如 5 分钟)按照数据库中修改时间增量更新缓存。(我经历的峰值 20 万 QPS 的项目在初期使用过该方法,只是后期迫于数据量的增长才不得不切换方案)
所有监控码接口只读内存、该方案最为简单,不用担心缓存穿透问题。
注意:该方法仅适合数据量基本不会大幅上涨的情况,而西安的人口显然不会大幅上涨。
3.4.3 其他说明
- 限流:网关、业务服务均可
- 黑名单机制
- MySQL 读写分离(可选,其实大部分流量都走缓存了,MySQL 真没啥流量)
- 弹性扩容:k8s
- 容灾:上云或者在西安多部署几个机房
- 监控:监控机器各项指标、流量、成功率、缓存命中率等
健康码虽然有所谓的高并发,但是只是读的高并发,业务比秒杀场景的高并发可简单太多了,所以并没有什么复杂性。
小结&&吐槽
整体上来看,《西安一码通BUG分析——雲著君建言献策,盼一码通稳定运行》这篇博文还是有技术参考的,但是博文里的有些内容多少有点哗众取宠、博人眼球的意思了,堆砌了很多技术栈、引用了不少看起来“高大上”的词汇,其实很多并不适合健康码这种场景。
简单列举一下:
- 文中提到的“另外早高峰可以采用MQ进行异步消峰处理”,早高峰是读请求,与 MQ 异步有何关系?
- 稳定性建议中有一条“建立中台,减少直接请求后台数据”,就这么简单的一个需求,上中台的意义何在?
- 系统设计建议中有一条“数据库可以采用TiDB这种分布式数据库”,就这么点数据量,没多少并发,MySQL 绰绰有余。
博文中还有一些不合理的建议就不一一列举了。
个人认为,选择合理的、简洁的、高效的、最优成本的技术方案才是最重要的,明明只需要自行车,就不要强行造火箭了。
上述方案仅一家之言,不可全信,欢迎交流。