最近西安疫情特别严重,前一阵子还出现了一码通崩溃的事件,网络上对此也有各种各样的评论和说法。对于各种言论和说法我们没有权力去评头论足,但是可以从技术的角度聊一聊,如果是我们接到了这样的需求,应该来如何设计这个系统,使得它可以在关键时刻经得住考验,为防疫工作提供方便做出贡献。
首先我们来分析下一码通大致有哪些基本需求需要实现。应该会有个人信息登记注册以及修改的需求;还会有个人健康信息查询需求,也就是健康码;也有个人行程信息记录需求,也就是平时我们进出小区,商场,乘坐公共交通等等时候进行的扫码操作;还应该还有后台修改个人数据的需求,例如更改个人的红绿黄码,呈现个人核酸检测结果等。
可以对上面需求做一个如下总结。
- 个人信息登记注册以及修改:由用户端驱动,既有读操作也有写操作,实时性要求较高,写操作需要立即得到结果,但是并发量不大(毕竟大家同时修改个人信息的概率比较低)。
- 个人健康信息查询:由用户端驱动,只有读操作,实时性要求较高,并发量比较大(因为大家同时刷健康码的概率非常大,这次崩溃的就是这个服务)。
- 个人行程信息记录:由用户端驱动,只有写操作,写操作不需要立即得到结果给用户,实时性要求较不高,并发量不大(毕竟疫情期间蜂拥而出的情况不多)。
- 后台修改个人数据:非用户驱动,应该是由后台的 job 或者相关工作人员来驱动的,只有写操作,并发量不大(毕竟非用户驱动的操作还是可控的)。
数据中心
对于这种 mission critical 的系统还是建议从数据中心的角度建立多个 site,每个数据中心的接入点都申请不同的 FQDN 域名,从接入层就利用 DNS 的来分流到多个数据中心。
当然针对这个需求,这里可以不必那么复杂,不必引入 GTM 把流量基于地理位置分发到不同的地区的数据中心,毕竟大家都在一个地区。
接入层负载均衡以及 CDN
对于每个数据中心的服务来说一定是有负载均衡的。负载均衡基于不同的维度有很多种类,有三四层负载均衡,七层负载均衡,基于应用的负载均衡,基于操作系统内核的负载均衡,还有基于硬件的负载均衡。
这个系统在接入层也不需要有复杂的负载均衡策略,可以追求速度,所以可以选择更快的三四层负载均衡,或者硬件负载均衡。
另外系统一定是有静态资源的,例如图片或者 html/css 等等,这些资源可以完全放在 CDN 来管理,以减轻系统负载,加速静态资源访问。
服务层拆分
经过上面的需求分析,我们可以根据基本需求的读写特性和并发量从业务上拆分不同的服务。
- 个人信息登记注册以及修改:读写实时性较高,但是并发量不大,所以这个服务可以直接访问我们的存储 storage。
- 个人健康信息查询:并发量比较大,这个服务不可以直接访问我们的存储,需要引入缓存来加速访问。
- 个人行程信息记录:写操作不需要立即得到结果给用户,实时性要求较不高,并发量不大,所以可以引入消息队列 MQ 来加速并解耦这个服务和存储。
- 后台修改个人数据:和上面的个人行程信息记录一样。
上面的服务层一定需要有快速的动态扩容和发布的能力,所以可以考虑基于当前比较流行的 Kunbernetes 平台或者 Service Mesh 平台。另外对于服务的协议,如果追求速度可以考虑使用二进制的 RPC 协议(例如 GRPC)来代替传统的 HTTPS+JSON 格式的协议。
缓存的引入
上面的分析指出,对于只读的、并且流量大的服务,例如个人健康信息查询,我们是一定需要引入分布式缓存的。对于分布式缓存我们可以考虑下面的几点。
- 缓存容量:西安常住人口大约 1200万 人,一个人分配 10KB 的缓存估算,大约就需要 120GB,在加上 25% 的 Buffer,所以需要大约总共 150GB 的缓存。当然这么大的缓存不可能是单机的,一定是分布式的,需要利用一些基于缓存数据分片的 sharding 方式把他们均匀的缓存在不同的机器上。
- 缓存预加载:我们不可以指望通过应用程序查询缓存,发现缓存里没有数据的时候,再从存储里获取,放到缓存里,这样在高并发的时候会有大量请求直接访问存储,导致响应比较慢,所以需要有缓存的预加载过程,当然我们可以基于数据 sharing 分片的方式去加载,例如可以基于人所属的区域,分不同的批次做,这样也能提高效率。
- 缓存击穿:如果查询一个不存在的对象,例如不存在的缓存 key,即使缓存里没有也依然会去访问存储的。所以对于缓存击穿的情况,我们可以给它设置一个短暂的缓存时间,以及一个空的值。
- 缓存雪崩:当我们设置缓存的时候,如果不注意缓存过期时间,当同一时刻大批量的缓存失效时,就会有大量的访问同时进入存储。所以我们可以基于数据 sharing 分片设置不同的缓存时间。另外我们还可以有一个缓存续约服务,对于那些没有数据更新的缓存,定期批量地延长缓存时间。当然这个服务也可以基于数据 sharing 分片提高效率。
- 缓存同步:有缓存就有缓存同步的问题,我们可以引入缓存同步服务,来定期把有更改的数据批量同步到缓存里。当然这里的数据一定不是实时性要求很高的数据,比方说红绿码变更,近期核酸检测结果等。对于实时性高的数据,例如个人信息登记和修改,一定是要同时更新存储和缓存的。
存储的引入
对于存储这部分,数据量一定是比较大的,而且根据不同时期的防御政策一定会有不同的动态数据加入,数据结构变化可能比较频繁,所以可以引入 NoSQL 来做数据存储。另外,不仅仅有存储的问题,也一定会有大数据的分析需求,有实时性要求比较高的流处理,可以进行等待的批处理,以及将数据汇报给国家防疫平台的处理等,这里我们不展开讨论。
监控和预警的引入
对于这种 mission critical 的系统一定要有完善的监控和预警,需要从不同维度上来对整个系统来监控和预警的引入。
- 基础设施和操作系统维度:也就是我们经常会提到的计算维度的 CPU、存储维度的 Memory/Disk、网络维度的吞吐量等等。
- 中间件维度:对各种中间件的监控,例如缓存、线程池、连接池、数据库、消息队列、应用服务器、负载均衡器等等。
- 应用程序维度:对应用程序本身的监控,也就是我们常常所说的 APM 这个概念,可以更细节地了解应用本身的运行。
总体架构设计
综合上面分析,high level 的设计可以如下。
另外我们从一码通小程序的详细信息上也能看到一些不是很专业的地方,例如:
- 莫名其妙的9443端口
- 莫名其妙的消息
- 可以看到前端调试信息按钮等
- 友情提示一下——证书还有半年多就要过期了,可以尽快换证书。
总结
当然上面的设计和思路也只是笔者的一家之言和浅薄见解,不一定是最好、最合适的方法,也许还有一些纰漏和漏洞,所以欢迎大家多多交流,多提意见,一起学习。
另外,虽然一码通也经历了崩溃事件,以及有不那么完美的地方,但是它毕竟也为这座城市的防疫数字化做出了很大的贡献,也方便了相关部门的防疫工作和大家的生活,所以它还是这座城市防疫工作中不可缺少的必要工具。而且一座城市的并发量肯定很大,维护一码通的相关人员也很辛苦,大家理性看待,也希望技术的优化能够带来更多便利。