分布式锁在定时任务上的应用

前言

  在实际开发中,我们会有各种各样的定时任务,通过这些定时任务,我们可以定期发送数据、上线数据、重试错误数据等等功能。有时候我们需求是希望有且只有一台机器在运行,既避免单点问题,又不会出现大量重复的操作。为此我们需要一个分布式锁来控制哪台服务器执行定时任务,获取到锁的服务执行任务,没有获取到锁的服务器等待,直到获取到锁,类似双机互备的方案。若获取到锁的服务器宕机,则通过锁的过期机制自动释放锁。

考虑的问题

  1)锁竞争,有且只能有一台机器获取到锁

  2)重入锁,同一台机器再次获取锁时,不会因为锁已经被自身持有而出错

  3)死锁,持有锁的机器宕机时,锁需要释放

方案

  针对锁竞争的问题,这里的解决方式有很多,比如ZK的节点编号,最小节点编号为当前获取锁节点;或者通过redis的单线程特性,通过setnx命令来实现分布式锁。这里我使用的是redis,具体流程如下:

  1)首先通过setnx命令来设置K-V键值对并添加过期时间(过期时间一般为获取锁周期的三倍,即重试三次之后,当前服务器确实与redis断开连接)

  2)如果执行成功则为获取到锁,并将value设置为当前服务器ip(能唯一区分每台服务器即可)

  3)如果执行失败,说明redis中已经存在锁,此时在判断此value值与当前服务器ip是否相同

  4)若相同,说明当前服务器已经获取到锁,满足重入锁的需求,同时为该K-V添加过期时间(续命,若此时与redis断开连接,可以通过锁过期解决死锁的问题)

  5)若值与当前服务器ip不相同,说明锁被其他服务器持有

  6)重复上述的过程,直到获取到锁

流程图

  

架构图

  

  系统采用抢占的模式,放弃了复杂的调度模式,服务主动去抢占分布式锁,获得锁的服务执行任务

时序图

  

总结

上面只是简单描述了一下整体的思想,并没有任何代码,大家可以结合自己的实际情况进行更改,将代码打成jar包,应用在更多的地方。在实现最初,通过cglib实现了代理,但是觉得代码比较臃肿,而且生成的代理类也没有加入spring管理,后来直接把代理这块交给spring去做了,简化了很多,整体的思维也比较清晰,希望能够有帮助!

posted @ 2018-05-03 18:14  _Emotion丶小寳  阅读(1331)  评论(0编辑  收藏  举报