为了解决上述问题,可以利用分布式锁来实现。
重新复制一份redis,配置文件都是刚下载时候的不用更改,然后启动redis服务和redis客户。
redis存在这样的命令:和set命令差不多,但是它有一个机制,当指定的key不存在的时候,才能进行插入,实际上就是 set if not exists的缩写,当key被删除后可以进行插入。
setnx key value
利用这种特性,可以再不同的服务中实现分布式锁。但是若某个服务加了锁并且卡顿了,或者崩溃,那么这把锁永远无法释放了,因此可以加过期时间:
set a666 EX 5 NX
如果学习过JUC并发编程,会发现若在超时之前那一刻进入到释放锁的阶段,获取到的值肯定还是自己,但是在即将执行删除之前,由于超时机制导致被删除并且其他任务也加锁了,那么这时候在删除,仍会导致删除其他任务加的锁
导入Redisson依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.75.Final</version>
</dependency>
不加锁的情况
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try(Jedis jedis = new Jedis("192.168.0.10", 6379)){
for (int j = 0; j < 100; j++) { //每个客户端获取a然后增加a的值再写回去,如果不加锁那么肯定会出问题
int a = Integer.parseInt(jedis.get("a")) + 1;
jedis.set("a", a+"");
}
}
}).start();
}
}
在redis中设置a=0,然后测试不加锁
取出a,得出结果不对
测试加锁
public static void main(String[] args) {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.0.10:6379"); //配置连接的Redis服务器,也可以指定集群
RedissonClient client = Redisson.create(config); //创建RedissonClient客户端
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try(Jedis jedis = new Jedis("192.168.0.10", 6379)){
RLock lock = client.getLock("testLock"); //指定锁的名称,拿到锁对象
for (int j = 0; j < 100; j++) {
lock.lock(); //加锁
int a = Integer.parseInt(jedis.get("a")) + 1;
jedis.set("a", a+"");
lock.unlock(); //解锁
}
}
System.out.println("结束!");
}).start();
}
}
执行完a的值加了1000,此时写入为正常
此时若用于存放锁的redis服务挂了,那么肯定会出问题的,这个时候可以用RedLock,它的思路是在多个redis服务器上保持锁,只需要超过半数的redis服务获取到锁,那么就真的获取到锁了,这样挂掉一部分节点,也能保证正常运行。