Redis能做的事情有很多,不仅于缓存,如分布式锁,也有很多很好的实现;
什么是分布式锁
首先锁的作用是为了避免资源争夺或者资源被篡改导致程序错误,如synchronized、Lock和ReentrantLock都可以实现资源的锁定,给资源添加一把锁,其他程序再次访问时,发现有锁就等待锁释放后再访问;
而集群模式下,程序内部锁最多只能保证一个服务只有一个线程在操作,但两个服务就会有两个线程,对于资源,又会出现两个线程并发处理的情况,所以我们就用到了分布式锁;
分布式锁:通过外部的一个锁变量,来保证资源只能由获得锁的人来访问;
使用场景
存储系统中,库存数量存储在redis中,假如某个时刻,redis里面的某个商品库存为1,此时两个请求同时到来,先校验库存,然后做减库存操作;单体服务中通过普通锁即可解决,达到持有锁的线程单独访问;
然而在多节点部署的情况下,假设此时两个用户的请求同时到来,但是落在了不同的机器上,那么这两个请求是可以同时执行了,此时就出现了并发问题
(link:一文搞懂分布式锁及其业务场景_chunqiu3351的博客-优快云博客)
如何实现分布式锁
1. set key value ex nx(推荐)
Redis中提供了如下的语法来帮我们实现分布式锁
set lock:key value ex 10 nx
del lock:key
这是Redis2.8之后提供的语法,指定锁名称lock:key,设置超时时间等,现在指定锁和设置超时时间都是原子性的了。
在释放锁的时候也会出现不同步问题,首先获取锁,判断锁是否存在,然后再释放,这时可以通过引入lua脚本,来实现语句的原子性
2. setnx + expire(不推荐)
2.8之前是通过两个命令
setnx lock:key true
expire key 10s
del lock:key
此时单纯的设置锁,
1. 可能会导致服务器宕机时锁未释放,进而发生死锁;
2. 添加过期时间之前服务器宕机,锁也不释放,导致死锁;
所以添加了上述的原子性可过期锁;
3. 通过lua脚本实现语法的原子性
public static void main(String[] args) {
JedisPool pool = new JedisPool("localhost", 6379);
Jedis jedis = pool.getResource();
String lock_lua = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then" +
" redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end";
Object result = jedis.eval(
lock_lua,
Collections.singletonList("lock:demo2"),
Arrays.asList("zhangsan", "5"));
//判断是否成功
System.err.println(result);
String del_lua = " if redis.call('get',KEYS[1]) == ARGV[1] then \n" +
" return redis.call('del',KEYS[1]) \n" +
" else\n" +
" return 0\n" +
" end;";
Object result2 = jedis.eval(
del_lua,
Collections.singletonList("lock:demo2"),
List.of("zhangsan"));
System.err.println(result2);
}
分布式锁框架
Redisson看门狗框架
Redis学习笔记---Redis的分布式锁框架Redisson_馒头太帅了的博客-优快云博客_redisson笔记
RedLock实现分布式半数请求锁
Shedlock-spring实现分布式锁
Shedlock可以选择多种存储库来实现分布式锁,当然redis也是可以的,在使用redis实现分布式锁的时候也是通过set key value ex nx方式实现的;