面试专题中间件

第八章----Redis分布式锁

2020-03-29  本文已影响0人  枫子夜

1. 什么是分布式锁

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候便需要使用到分布式锁。

如:多个系统同时在操作某件商品的库存值,如果不能保证互斥性就会造成数据错乱!

分布式锁特性:

互斥(只能有一个客户端获取锁)
不能死锁
容错(只要大部分 redis 节点创建了这把锁就可以)

2. 为什么使用分布式锁

在传统单体应用单机部署的情况下,可以使用Java并发处理相关的API(synchronized)进行互斥控制。但随着业务的发展单体项目演变成分布式架构,即多个机器多个线程去完成一件事,这种时候传统的锁机制就已经失效了,需要运用分布式锁来保证数据一致性。

3. 常见的分布式锁有哪些

  1. 基于MySQL的自身锁机制实现要求数据库支持行级锁,性能差,高并发容易死锁。

  2. 基于ZooKeeper的节点特性和watch机制,性能比较好,占用资源少,稳定。

  3. 基于Redis的setnx命令加锁及lua脚本释放锁,性能好,但会占用一部分资源。

4. Redis分布式锁

1. 加锁:当key不存在时给key加上一把值为value的锁,且30s后自动释放锁(具有原子性)。

SET key value NX PX 30000
  1. NX:表示只有 key 不存在的时候才会设置成功。(如果此时 redis 中存在这个 key,那么设置失败,返回 nil)

  2. PX 30000:意思是 30s 后锁自动释放。别人创建的时候如果发现已经有了就不能加锁了。(避免死锁)

  3. value值防止误删别人的锁,如:A线程执行时间太长锁已经自动释放,而B线程又获取到了锁,如果此时没有value值校验直接删除就会把B线程的锁删掉。

2. 释放锁:就是删除 key ,但是一般可以用 lua 脚本删除,判断 value 一样才删除。

-- 删除锁的时候,找到 key 对应的 value,跟自己传过去的 value 做比较,如果是一样的才删除。
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

3. StringBoot案例

//定义释放锁的lua脚本
    private final static DefaultRedisScript<Long> UNLOCK_LUA_SCRIPT = new DefaultRedisScript<>(
            "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return -1 end"
            , Long.class
    );

/**
 * 加锁(原子性)
 *setnx是『SET if Not eXists』(如果不存在,则 SET)的简写
 * @param key 锁的 key 值
 * @param requestId 请求id,防止解了不该由自己解的锁 (随机生成)
 * @param expireTime 锁的超时时间(秒)
 * @param retryTimes 获取锁的重试次数
 * @return true 成功  false 失败
 */
public boolean lock(String key, String requestId, long expireTime, int retryTimes) throws InterruptedException {
    int count = 0;
    while (true) {
        if (redisTemplate.opsForValue().setIfAbsent(key, requestId, expireTime, TimeUnit.SECONDS)) {
            System.out.println("--jxb--RedisUtil--lock:第"+count+"次加锁成功,   key:"+key+",   requestId:"+requestId);
            return true;
        } else {
            count++;
            System.out.println("--jxb--RedisUtil--lock:第"+count+"次加锁失败,   key:"+key+",   requestId:"+requestId);
            if (retryTimes == count) {
                return false;
            } else {
                Thread.sleep(1000);
                continue;
            }
        }
    }
}

/**
 * 释放锁(value确保不会误删除)
 * @param key
 * @param requestId
 */
public void unlock(String key,String requestId){
    redisTemplate.execute(UNLOCK_LUA_SCRIPT, Arrays.asList(key), requestId);
}
private static final String CART = "lock:";
private static final long TIME = System.currentTimeMillis() / 1000;

@Autowired
private RedisUtil redisUtil;

/**
 * Description: 修改产品库存
 * Author: jxb
 * Date: 2020-03-29 15:13:11
 */
@RequestMapping(value = "/updateProduct/{productId}", method = RequestMethod.GET)
public JsonResult<Object> updateProduct(@PathVariable Integer productId) {
    // 加锁key
    String lockKey = CART + productId;
    // requestId, 防止误删别人的锁
    String requestId = UUID.randomUUID().toString().replace("-","");
    try {
        if(redisUtil.lock(lockKey,requestId,30,5)){
            // 加锁成功 处理业务逻辑
            Thread.sleep(10000);

        }else{
            // 重试5次,加锁失败抛出异常
            return new JsonResult(false, "1001", "服务繁忙,请稍后重试!");
        }
    }catch (Exception e ){
        e.printStackTrace();
    }finally {
        // 释放锁
        redisUtil.unlock(lockKey,requestId);
    }
    return buildJsonResult(null);
}
输出测试

4. Redlock 算法

如5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作:

5. Redis VS ZooKeeper 分布式锁的对比


你的好运藏在你努力的每一天里

上一篇 下一篇

猜你喜欢

热点阅读