Redis高并发有序队列的设想

当前京东相关系统的任务储存在task表,加上历史任务,数据/任务量已经达到千万级别了,面临而来的就是数据库严重的性能问题.每天工作时间上千台Host会不停的请求任务,高峰期的话每秒请求并发数量在3k/TPS左右,这样一个数据量和并发量,基本上aws上最贵的Mysql实例也扛不住了。so,需要对当前系统的架构做个调整,否则今后的业务基本上没法做了。

抢任务这种场景,一般可以用MQ来做。但是京东相关系统的任务对执行时间要求非常高,任务必须在所在时间内才允许被领取出来并执行。并且需要能随时在某个时间点内增加新任务,普通的MQ非常难实现这种对时序性要求高的场景,例如当前的MQ中有 1,2,3,4,5,6,7,8,9,10 这10个任务,但是我只想从5号任务开始依次获取,这一点MQ是非常难做到了。Redis的SortedSet其实非常适合这个场景,以任务的id和执行时间timestamp存在SortedSet,每次根据score获取当前的任务,然后在redis中删除这个任务,取到任务id后,去数据库修改任务附属属性(任务已经领取/归属host/开始时间/开始地区等)。

但是,最大的问题还是并发的问题。例如同时有10个进程获取了当前时间内最靠前的任务,那么如何保证只有其中的一个进程能够获取这个任务,而其他进程获取失败? 虽然redis是单线程,但是获取-删除 这个操作并不是原子性的,需要借助一些附加的复杂操作来完成。

简单用笔画了个流程图:

Redis高并发有序队列的设想

Redis高并发有序队列的设想

处理并发问题:

1. 进程拿到当前时间的任务id(后可能被其他进程同时拿到)
2. 将自己拿到的任务id尝试写入到一个check_set中(redis单线程/原子性保证只有第一个写入的进程成功,其他尝试写入的进程失败)
3. 写入check_set成功的进程确认自己是唯一一个成功者,然后操作数据库修改任务信息
4. 写入check_set失败的进程会发现出现了并发错误,可以尝试用while循环稍作等待后重新获取SortedSet的最小值(或直接通知host并发冲突,由host重新发起申请,避免进程一直while)

然后需要有个进程持续的把当天的任务“装填”到redis的SortedSet中,而host的领取任务借口不直接访问数据库获取当前需要执行的任务(千万级别的数据,即使在使用索引的情况下获取一次合适的任务,成本也很高,尤其是高并发情况下),由redis作为“缓冲器”,接口从redis获取任务后,使用任务id直接修改任务。 这样改造下来数据库高并发的问题一般情况下能够解决。

Leave a Reply

Your email address will not be published. Required fields are marked *