为什么要使用分布式锁,或者分布式锁的使用场景?
定时任务。在分布式场景下,只控制一台服务器执行定时任务,这就需要分布式锁
要控制定时任务在同一时间只有 1 个服务器能执行,方案有哪些?
分离定时任务程序和主程序
,只在 1 个服务器运行定时任务。成本太大写死配置
。每个服务器都执行定时任务,但是只有 ip 符合配置的服务器才真实执行业务逻辑,其他的直接返回。成本最低;但是我们的 IP 可能是不固定的,会把 IP 写的太死了动态配置
,配置是可以轻松的、很方便地更新的(代码无需重启),但是只有 ip 符合配置的服务器才真实执行业务逻辑。问题:服务器多了、IP 不可控还是很麻烦,还是要人工修改
分布式锁
,只有抢到锁的服务器才能执行业务逻辑。坏处:增加成本;好处:不用手动配置,多少个服务器都一样。锁
Java 实现锁:synchronized 关键字、并发包的类
问题:只对单个 JVM 有效
抢锁机制
怎么保证同一时间只有 1 个服务器能抢到锁?
核心思想 就是:先来的人先把数据改成自己的标识(服务器 ip),后来的人发现标识已存在,就抢锁失败,继续等待。等先来的人执行方法结束,把标识清空,其他的人继续抢锁。
实现方式有:
setnx
、lua
脚本,比较方便我们实现分布式锁。实现细节:
- 锁一定要加过期时间
- 用完锁要释放,释放的时候检查是否是自己的锁
- 如果方法执行时间过长,锁要能进行续约
- Redis 如果是集群(而不是只有一个 Redis),如果分布式锁的数据不同步怎么办? 使用红锁
Redisson 是一个 java 操作 Redis 的客户端,提供了大量的分布式数据集来简化对 Redis 的操作和使用,可以让开发者像使用本地集合一样使用 Redis,完全感知不到 Redis 的存在。
使用方式
RedissonConfig
/**
* Redisson 配置
*/
@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedissonConfig {
private String host;
private String port;
@Bean
public RedissonClient redissonClient() {
// 1. 创建配置
Config config = new Config();
String redisAddress = String.format("redis://%s:%s", host, port);
// 使用单个Redis,没有开集群 useClusterServers() 设置地址和使用库
config.useSingleServer().setAddress(redisAddress).setDatabase(3);
// 2. 创建实例
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
Redisson测试
@SpringBootTest
public class RedissonTest {
@Resource
private RedissonClient redissonClient;
@Test
void test() {
// list,数据存在本地 JVM 内存中
List<String> list = new ArrayList<>();
list.add("yupi");
System.out.println("list:" + list.get(0));
list.remove(0);
// 数据存在 redis 的内存中
RList<String> rList = redissonClient.getList("test-list");
rList.add("yupi");
System.out.println("rlist:" + rList.get(0));
rList.remove(0);
// map
Map<String, Integer> map = new HashMap<>();
map.put("yupi", 10);
map.get("yupi");
RMap<Object, Object> map1 = redissonClient.getMap("test-map");
// set
// stack
}
}
Redisson会开一个监听线程,如果锁的方法还没执行完,就帮你重置 redis 锁的过期时间。
原理:
30
秒,每 10
秒续期一次(补到 30 秒)