一.引言
系统服务按运行模式可以简单划分为接口服务(外界使用tcp/http等方式触发服务)和后台任务(后台一次性/周期性触发任务)。对于接口服务,在分布式场景下使用负载均衡(DNS/NAT/ARP等方式都是可选的软件负载均衡方式)是常见的提高系统可用性和扩展性的方式。
对于后台任务,在分布式场景下是否有相应的负载均衡方式,可以提高系统的可用性和扩展性呢?
二.后台任务负载均衡方式
后台任务的典型应用场景是生产者-消费者模型,假设task以关系型数据库的方式发布。
抢占式
抢占式即各个consumer获取到任务即锁住,确保该任务不会被其他consumer重复消费。
对于使用关系型数据库进行任务发布的场景,可以在任务中引入状态位避免任务被重复消费。以下是一个比较通用的任务状态扭转示例。任务发布时,会经历CREATED, DELETED, READY三个状态,任务消费时,会经历RUNNING, SUCCESS, FAIL三个状态。其中READY状态属于可抢占状态,consumer获取后立即设置为RUNNING,并根据运行结果设置SUCCESS或者FAIL。
协商式
抢占式需要对任务进行改造,引入状态位。协商式则不需要改造任务,而是通过引入注册中心的方式,进行一致性哈希分发任务到各个consumer。
以下是使用关系型数据库作为注册中心的示例。service_name标识消费者类型,instance_name使用{host_ip}:{process_id}标识每一个消费者任务进程,expired_time_stamp标识该消费者任务进程有效截止时间,created_time_stamp标识该消费者任务进程注册时间。
每个消费者都能获取到该全局注册视图,并使用一致性哈希方式来获取到相应的任务。
instance_name | expired_time_stamp | created_time_stamp |
---|---|---|
10.104.63.157/1082 | 2021-08-09 01:22:39 | 2021-08-08 13:22:39 |
10.104.63.157/1083 | 2021-08-09 01:22:38 | 2021-08-08 13:22:38 |
10.104.63.157/1085 | 2021-08-09 01:22:38 | 2021-08-08 13:22:38 |
10.104.63.157/1087 | 2021-08-09 01:22:38 | 2021-08-08 13:22:38 |
一致性哈希,是指对消费者和待处理任务都进行哈希映射,待处理任务归属于顺时针最近的消费者。相比于普通哈希,一致性哈希拥有良好的单调性,即新增消费者时,待处理数据会被映射到新增消费者或者原有消费者。
下图给出了消费者新增和消费者失效场景的数据映射关系,可见一致性哈希极大减少了扩缩容场景下的数据迁移。
三. 扩展与总结
在分布式任务系统中,采用上述方式进行负载均衡,还有以下两个问题需要考虑
Exactly Once问题
无论是抢占式还是协商式任务分发,提供的是at least once的服务。大部分场景都可以做到exactly once,但是极端场景下还是会有任务被重复消费的可能。一方面可以引入task lock机制来确保exactly once,一方面也可以由业务侧来消除任务被重复消费的影响。
多队列问题
多队列是指任务区分类型,每个任务即一个任务队列。对于抢占式可以通过在任务中引入task_type来实现;对于协商式可以通过在注册表中引入task_name来实现,每一类task_type即形成一个哈希分发环。
task_type | instance_name | expired_time_stamp | created_time_stamp |
---|---|---|---|
task_a | 10.104.63.157/1082 | 2021-08-09 01:22:39 | 2021-08-08 13:22:39 |
task_a | 10.104.63.157/1083 | 2021-08-09 01:22:38 | 2021-08-08 13:22:38 |
task_b | 10.104.63.157/1085 | 2021-08-09 01:22:38 | 2021-08-08 13:22:38 |
task_b | 10.104.63.157/1087 | 2021-08-09 01:22:38 | 2021-08-08 13:22:38 |
总结下,抢占式和协调式在实际应用中,都能提供高可用且易于水平扩展,且不需要任务之间相互通信感知,易于实现。
实现特征 | 实现机制 | 是否支持多机多进程 | 是否支持exactly once | 是否支持多对列 |
---|---|---|---|---|
抢占式 | 状态机 | 支持 | at least once | 任务可引入task_type支持 |
协调式 | 服务注册 一致性哈希 | 支持 | at least once | 注册表可引入task_type |