Redis分布式锁应用解析

原创
08/09 11:05
阅读数 0

Redis 简介

Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。

Redis 与其他 key - value 缓存产品有以下三个特点:

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

优势

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

应用场景:

1.缓存,2.任务队列,3.应用排行榜,4.网站访问统计,5.数据过期处理,6.分布式集群架构中的session分离。最主要的应用场景为缓存。今天主要就讲一下在分布式中redis的应用。

分布式锁的原理

基于redis的setnx命令,setnx的作用就是设置一个key值,如果在redis中该key值不存在就设置成功,如果存在就会设置失败。在分布式集群环境下,多个服务器的线程同时设置一个key,哪个服务器的线程设置成功,就表示该服务器的线程获得了锁对象,其他线程必须等待。获得锁的线程需要记得,在某个时刻进行锁的释放(删除那个key)。下面是一个简单的加锁、解锁的代码片段。

 
/**
 * @date 2021/08
 */
@Component
public class RedisLock {
 
    @Autowired
    private RedisTemplate redisTemplate;
 
 
    private static final Long SUCCESS = 1L;
    private long timeout = 9999; //获取锁的超时时间
 
 
 
    /**
     * 加锁,无阻塞
     *
     * @param
     * @param
     * @return
     */
    public Boolean tryLock(String key, String value, long expireTime) {
 
 
        Long start = System.currentTimeMillis();
        try{
            for(;;){
                //SET命令返回OK ,则证明获取锁成功
                Boolean ret = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
                if(ret){
                    return true;
                }
                //否则循环等待,在timeout时间内仍未获取到锁,则获取失败
                long end = System.currentTimeMillis() - start;
                if (end>=timeout) {
                    return false;
                }
            }
        }finally {
 
        }
 
    }
 
 
    /**
     * 解锁
     *
     * @param
     * @param
     * @return
     */
    public Boolean unlock(String key, String value) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
 
        RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
 
        Object result = redisTemplate.execute(redisScript, Collections.singletonList(key),value);
        if(SUCCESS.equals(result)) {
            return true;
        }
 
        return false;
    }
}

分布式锁实现的关键是在分布式的应用服务器外,搭建一个存储服务器,存储锁信息,这时候我们很容易就想到了Redis。首先我们要搭建一个Redis服务器,用Redis服务器来存储锁信息。

实现的时候要注意的几个关键点:

1、锁信息必须设置过期超时,不能让一个线程长期占有一个锁而导致死锁;

2、同一时刻只能有一个线程获取到锁。

几个要用到的redis命令:

setnx(key, value):“set if not exits”,若该key-value不存在,则成功加入缓存并且返回1,否则返回0。

get(key):获得key对应的value值,若不存在则返回nil。

getset(key, value):先获取key对应的value值,若不存在则返回nil,然后将旧的value更新为新的value。

expire(key, seconds):设置key-value的有效期为seconds秒。

下图是一个lua脚本:

图片.png

上面的代码,是在理想情况下的操作逻辑,如果出现了以下情况,就要按照实际业务进行调整

  1. redis发现锁失败了要怎么办?中断请求还是循环请求?、
  2. 循环请求的话,如果有一个获取了锁,其它的在去获取锁的时候,是不是容易发生抢锁的可能?
  3. 锁提前过期后,客户端A还没执行完,然后客户端B获取到了锁,这时候客户端A执行完了,会不会在删锁的时候把B的锁给删掉?

针对问题1:使用循环请求,循环请求去获取锁
针对问题2:针对第二个问题,在循环请求获取锁的时候,加入睡眠功能,等待几毫秒在执行循环
针对问题3:在加锁的时候存入的key是随机的。这样的话,每次在删除key的时候判断下存入的key里的value和自己存的是否一样.

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部