【系统设计】2WTPS生产级数据处理系统设计Review
欢迎来到啾啾的博客🐱。
记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。
有很多很多不足的地方,欢迎评论交流,感谢您的阅读与评论😄。
目录
- 反正能用的系统
- 问题分析
- 方案一:简单多实例增加些许容错
- 方案二:任务调度机制
- 方案三:Kubernetes+主备模式
反正能用的系统
几年前,我曾经参与过一个月工单10亿级别(TPS数万/秒)的系统开发,当时的架构设计大致如下。
图以数据流为导向,省去了一些微服务架构组件。
很显然,这个架构存在一个明显的问题:数据分区后由不同的服务专门处理,若这个专职的服务挂掉,该分区的数据在服务恢复前都将无法得到处理。
那么,我们应该怎么改善这个设计呢?
问题分析
-
主要待解决的问题
-
功能性问题
专职服务挂掉后,分区数据得不到处理。 -
拓展问题
当前的数据分割方式是在系统设计时静态划分。如果数据量超出设计预期,系统处理不了,也不好拓展。 -
性能问题
不能保障数据均匀地分布在各个分区,也不能保障每个专职服务处理速度一致。可能Server1处理完数据了,Server2还有一半没有处理,服务器性能得不到充分利用。
-
-
其他问题
略…
方案一:简单多实例增加些许容错
每个专职服务都部署成多实例,从而提升容错能力,解决部分功能性问题。
多实例会带来一个问题:多个服务竞争同一批任务。
这是很简单的资源竞争问题,可以简单使用锁来避免资源竞争。
在分布式架构中,使用第三方存储即可解决,如Redis,ZooKeeper。
另外设计兜底机制,如果多个服务实例中存在不靠谱实例,竞争到了资源但是没有完成,使用告警机制重新竞争处理。
以及从底层设计支持幂等,防止重复消费带来问题。
- 基于Redis或Zookeeper设计任务分片获取
关于任务:任务应该有一个唯一ID、状态(如:待处理、处理中、已完成、失败)、处理实例ID(可选,用于追踪)、尝试次数等字段。
实例需要尝试获取并锁定一个“未处理”的任务批次。
因此,我们需要一种方式来标识数据分片。
如果数据本身有连续ID或者可以按某种规则分批,那是最好的。如果不行,可能需要预先在DB中标记好批次,或者有一个专门的“任务池”表。为了简化,我们假设数据可以按ID范围划分。
简单流程图如下:
选定几个实例负责加载任务,所有实例从任务池中获取任务,并对任务加锁(Redis的SETNX)。
详细UML如下(AI生成):
但是这样设计容错率还是不够、且不能无法解决其他分区的动态资源分配问题。对所有服务来说性能也没有利用到极致。
方案二:任务调度机制
我们可以补充设计一个资源调度系统来解决所有问题。
简单草图如下:
很显然,我们需要有
-
服务管理与检测
需要获取服务状态以分配任务:需要知道服务实例的处理状态,哪些服务可以分配任务,以及还能接收多少数据也会损害服务性能。 -
动态分区计算
需要支持动态扩展服务。以及最快最好分配数据处理任务到服务,充分利用资源。
原来的数据库分库分表结构不做更改,需要分库分表来缓解数据查询压力。
调度服务需要获取数据与分配数据。
任务(数据)获取注意事项:需要能感知数据源数据量、数据概要信息(比如区间信息,用于分区)。获取连续数据、内存数据、多线程获取并汇总信息用于分配。
任务分配:要避免多个分配者竞争。
- 容错设计
如果调度服务挂掉了怎么办?
管理服务注册表,服务状态检测。
很显然,我们可以使用Kafka来解决上述问题。任务均发送至Kafka,由Kafka的rebalance机制进行任务调度,且Kafka消费者组可以很大程序上解决容错问题。也较易扩展。
方案三:Kubernetes+主备模式
最后但是的团队在一次迭代中选择了容器化+主备模式的方式来解决容错问题。
即,为每个专职服务设置一个备用实例。并且将服务容器化。