问题背景
分布式系统模块存在定时任务,当模块被部署多个实例时,不同实例的定时任务在运行时就存在从数据源中获取相同数据的情况,
虽然通过乐观锁(数据记录增加version,每次update则set verion+=1 where version=?)的处理方式可以避免多实例更新数据造成脏数据入库的问题,
但这无疑是降低了分布式系统部署的初衷(通过横向部署多实例增加系统的处理能力)。
经过分析,总结如下几种处理方式。
处理方式
1、数据置标识位
在数据库记录中增加flag,当数据被读取后则更改flag的值,避免记录被再次读取。
具体步骤如下:
- 锁定分布式锁
- 查询指定数量的数据
- 更新已查询出数据的标识位
- 释放分布式锁
当处理完成时: 增加数据version,重置标识位
2、单实例读取,多实例处理
读取数据Job:利用Redis的分布式锁保障一个实例拥有读取数据的步骤,读取出来的数据再存放到Redis中的需处理数据队列。
处理业务Job:所有部署实例通过监控Redis的需处理数据队列如果有数据则take一个数据出来进行业务处理。
具体步骤如下:
- 锁定分布式锁
- 检查需要处理的数据队列是否为空,如为空,则跳过当前读取任务。
- 获取需要处理的数据并存入Redis的处理队列中
- 释放分布式锁
由于读取数据的步骤由单进程来获取,所以就避免了并发读取数据的问题
add by 2017.8.10 : 增加处理中的数据队列,在读取数据job时需要排除到正在处理的数据。用于当有数据在处理且数据未更新时,加载数据进行加载时将数据加载出的Bug。
add by 2017.9.13 : 将锁定分布式锁扩展到第一步骤,防止并发情况下通过队列容量检查,成功获取到锁后加载到重复记录。
3、利用自增ID
利用分布式锁+Redis存储已处理ID+数据的自增ID来获取数据数据。
具体步骤如下:
- 锁定分布式锁
- 从Redis中取出已处理数据的最大ID值(此处用x代替)
- 从数据库中取出大于x获取出y(固定数据量)的数据
- 更新Redis已处理的数据最大ID值
- 释放分布式锁
存在的问题
问题描述: 数据任务在进行业务处理的过程中,如处理失败则再也不会被以上步骤再次获取。
解决方法1: 当业务处理失败时,在Redis中记录下失败的数据ID。在获取数据步骤中增加获取失败数据的步骤。
解决方法2: 当业务处理失败时,在Redis中记录下失败的数据信息。在业务处理环节增加处理已失败的数据重试步骤。
4、数据取余
利用zk获取到总实例数,计算模块实例需要处理的余值。
具体步骤如下:
- 模块启动注册到zk,并计算当前实例需要处理的余值为x (当前实例index % 总实例数)
- 从数据库取数据时,将数据主键%总实例数,如果余值为x,则取出数据,否则忽略此数据。
- 当模块总实例数变化时,重新计算当前实例需要处理的余值。
风险: 由于模块实例所需要处理的数据固定,当数据取余后的值可能会命中同一个实例,造成模块各实例处理压力不平均。
观点仅代表自己,期待你的留言。