背景:我们有一批数据在数据库里,要进行到期处理并推送到mq,处理并推送小于等于当前时间的数据. 需求: 1.我们要求任务处理服务要做到
高可靠
性,因此需要搞成多机分布式服务
,即使一个服务挂了,还有别的服务可以使用可以处理 2.保障各个服务均可以同时拿到数据进行高效处理
3.数据不能重复处理
方式一.悲观锁形式:事务 select for update
本文先提供一个没有采用的方式--采用事务加select for update的形式
- 1.开启事务
- 2.从task表里selce for update 锁定一批数据
- 3.处理数据
- 4.更新这批task
- 5.提交事务
这么做呢就有个非常严重的问题,---同一时刻只有一个有效服务
如果A系统拿到了数据,开始了事务但是没提交,那么B系统同样的条件也会查到同一批还没处理好提交的数据,此时B系统该查询线程就会阻塞等待A提交事务.这么看问题就来了,这里虽然保障了同一时刻只有一个服务可以拿到并处理一批数据,但是也导致了效率特别低,而且后面无论扩展多少服务应用都没啥用
方式二.预更新 lock_key方式 (无锁,我们目前采用的方式)
步骤解释:
- 0.数据库里增加一个字段lockkey
- 1.先去数据库拿小于等于当前时间的一批status为1(待发送)的数据
- 2.根据这批数据的id ids更新这批数据为status=2(发送中),lockKey=一个
唯一数
(防止两个服务拿到同一批更新的数据,我们用的是redis的一个自增id) - 3.根据ids和locaKey查询出本批次哪些数据被本轮处理函数更新了(这就是好处,如果我们没用加lock_key,那么第二个系统更新的数据条数不等于拿到的数据数据条数只能回滚从新拿,而我们这里加了lockkey之后更新了多少就代表本批次有效数据是多少,我们只处理有效数据即可,也不用回滚)
- 4.把这部分数据发到MQ上
- 5.异常捕捉
- 如果我们成功发送数据到MQ,将数据库该条数据状态置为3(成功发送)
- 如果过程中出现异常
- redis里记录该key的失败次数
- 如果次数小于5,将数据重新置为status=1,下次查询的时候可以重新拿到
- 3.如果次数大于5,将数据置为status=4,记录操作日志(基本不会出现,一般只有1,mq连不上,2,数据有问题)
方式三.redis zset 双写服务--强烈推荐
我这里只写了大致的方案,一些redis高可用以及数据幂等性自己考虑去.
- 1.向mysql写任务的时候同步的向redis zset里写一份,并设置score为自己的搜索条件,比如我这里是发送时间
- 2.所有的服务消费数据直接从redis zet直接消费,获取小于等于当前时间的一个批次数据(比如100条).
- 3.利用从redis拿的数据去mysql里拿数据
这种方式是我觉得最好的方案
了,完全保障了每个服务每次处理
mysql的数据都是互不相同的数据
,完全避免了竞争
问题.
但是我们目前没有用这种方案,原因是...目前我们redis内存只申请到一个比较小的内存,而zset采用的跳跃表结构虽然保障了数据查询非常快速,但是也非常占用内存
,预估了一下按照我们的数据量起码要存储300万数据,用到的内存量是3~4G
之间,好家伙直接把我们所有内存都用了,其他服务还用个屁...而且这玩意为了保障数据安全,不进行数据淘汰起码还要留个1G空闲安全空间....那肯定就用不了了
提供一个redis官方提供的容量预估功能的工具http://www.redis.cn/redis_memory/
如果你们的服务数据量够小
或者内存够大
,redis又做到了高可用,高可靠
,那么我还是十分推荐
用这种方案,毕竟很多服务都是性能为王!