最近西安疫情特别严重,前一阵子还出现了一码通崩溃的事件,网络上对此也有各种各样的评论和说法。对于各种言论和说法我们没有权力去评头论足,但是可以从技术的角度聊一聊,如果是我们接到了这样的需求,应该来如何设计这个系统。使得它可以在关键时刻经得住考验,为防疫工作提供方便做出贡献。
首先我们分析一码通大致有哪些基本需求需要实现,应该会有个人信息登记注册以及修改的需求。还会有个人健康信息查询需求,也就是健康码了。也有个人行程信息记录需求,也就是平时我们进出小区,商场,乘坐公共交通等等的时候扫码操作。应该还有后台修改个人数据的需求,例如更改个人的红绿黄码,个人核酸检测结果等。可以对上面需求做一个如下总结:
- 个人信息登记注册以及修改:由用户端驱动,既有读操作也有写操作,实时性要求较高,写操作需要立即得到结果,但是并发量不大(毕竟大家同时修改个人信息的概率比较低)。
- 个人健康信息查询:由用户端驱动,只有读操作,实时性要求较高,并发量比较大(大家同时刷健康码的概率非常大,这次崩溃的就是这个服务)。
- 个人行程信息记录:由用户端驱动,只有写操作,写操作不需要立即得到结果给用户,实时性要求较不高,并发量不大(毕竟疫情期间蜂拥而出情况不多)。
- 后台修改个人数据:非用户驱动,应该是由后台的 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端口
- 莫名其妙的消息
- 可以看到前端调试信息按钮等
- 友情提示一下证书还有半年多就要过期了,可以尽快换证书。
当然上面的设计和思路也只是笔者的一家之言和浅浅的见解,不一定是最好最合适的方法,也许还有什么纰漏和漏洞,也欢迎大家多多交流,多提意见,一起学习。另外虽然一码通也经历了崩溃事件以及有不专业的地方,但是它毕竟也为这座城市的防疫数字化做出了贡献,也方便了防疫工作和大家,还是这座城市防疫工作中不可缺少的必要工具。希望大家都可以在疫情期间保持身心健康,注意安全,学习到更多的东西,也希望疫情早日过去。