定时任务实现-Redisson实现分布式锁
定时任务实现-Redisson实现分布式锁
主要内容(后端):
- 分布式定时任务执行控制
- 锁的概念
- 分布式锁概念和常见问题
- 分布式锁实践
定时任务实现
- Spring Scheduler (spring boot 默认整合了,推荐使用这种方式)
- Quartz (独立于 Spring 存在的定时任务框架)
- XXL-Job 之类的分布式任务调度平台(界面 + SDK)
采用第一种方式:
- 主类开启 @EnableScheduling
- 给要定时执行的方法添加 @Scheduling , 指定 cron 表达式或者执行频率
cron 表达式 用现成的工具即可:
https://cron.qqe2.com/
https://www.matools.com/crontab/
控制定时任务的执行
要控制定时任务在同一时间只有 1 台服务器能执行
原因:
- 浪费资源
- 脏数据,比如重复插入
如何做?
方案种类:
- 分离定时任务程序和主程序,只在 1 个服务器运行定时任务,成本太大
- 写死配置,每个服务器都执行定时任务,但是只有 ip 符合配置的服务器才真正执行业务逻辑,其他的直接返回。成本最低;但是我们的 ip 可能不是固定的
- 动态配置,配置是可以轻松的、很方便地更新(代码无需重启),但是只有 ip 符合配置的服务器才真正执行业务逻辑。
- 数据库
- Redis
- 配置中心(Nacos、 Apollo、 Spring Cloud Config)
问题:服务器多了, ip 不可控还是很麻烦,还需要人工修改
分布式锁,只有抢到锁的服务器才能执行业务逻辑。
缺点:增加成本;
优点:不用手动配置,多少服务器都一样注意:只要是单机,就会存在单点故障
锁
有限资源的情况下,控制同一时间(段)只有某些线程(用户 / 服务器)能访问资源
Java 实现锁:sychronized 关键字、并发包的类
存在的问题:只对单个 JVM 有效
分布式锁
使用分布式锁的原因:
- 有限资源的情况下,控制同一时间(段)只有某些线程(用户 / 服务器)能访问资源
- Java 实现锁:sychronized 关键字、并发包的类
分布式锁的关键
抢锁机制
怎么保证同一时间只有 1 台服务器能抢到锁?
核心思想:先来的人先把数据改成自己的标识(服务器 ip),后来的人发现标识已存在,就抢锁失败,继续等待
等待先来的人执行方法结束,把标识清空,其他人继续抢锁
MYSQL 数据库:select for update 行级锁(最简单),或者用乐观锁
Redis 实现:内存数据库,读写速度快。支持 setnx、 lua 脚本,比较方便我们实现分布式锁
setnx: set if not exists 如果不存在,则设置;只有设置成功才返回 true ,否则返回 false
注意事项
1)用锁要释放
2)锁一定要加过期时间
3)如果方法执行时间过长,锁会提前过期
锁提前过期会导致的问题:
- 连锁反应:释放掉别人的锁
- 仍然存在多个方法同时执行的情况
解决方案:续期
比如:
1 | boolean end = false; |
4)释放锁的时候,有可能先判断出自己的锁,但这时锁过期了,最后也可能释放别人的锁
解决方案:Redis + lua 脚本保证操作原子性
1 | //原子操作 |
5)Redis 如果是集群(不是只有一个 Redis),如果分布式锁的数据不同步怎么办?
解决方案:https://blog.csdn.net/feiying0canglang/article/details/113258494
Redisson 实现分布式锁
Redisson 是一个 Java 操作 Redis 的客户端,提供了大量的分布式数据集来简化对 Redis 的操作和使用,可以让开发者像使用本地集合一样使用 Redis(继承了和 Java 相同的集合类)
关键词:Java Redis 客户端,分布式数据结构,实现了很多 Java 里支持的集合
两种引用方式
- spring boot starter 引入(不推荐,因为版本迭代太快,容易冲突):
https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter - 直接引入:https://github.com/redisson/redisson#quick-start
使用Redisson
示例代码,创建分布式列表、Map
1 | // list 数据存在本地 JVM 内存中 |
分布式锁保证定时任务不重复执行
实现代码
1 | void testWatchDog(){ |
注意:
- waitTime 设置为 0,只抢一次,抢不到就放弃
- 主要释放锁要写在 finally 语句块中,保证最后一定释放锁
Redisson 看门狗机制
Redisson 中提供的续期机制
开一个监听线程,如果方法还没执行完,就帮你重置 Redis 锁的过期时间
原理:
- 监听当前线程,默认过期时间是 30 秒,每 10 秒续期一次(续期到 30 秒)
- 如果线程挂掉(注意 debug 模式也会被它当成服务器宕机),则不会续期
详情参考文档:https://blog.csdn.net/qq_26222859/article/details/79645203