zoukankan      html  css  js  c++  java
  • redis实现分布式锁

    如何设计安全的分布式锁

    在生产环境下一般不会使用单实例redis, 然而集群下的redis使用如下方式获取分布式锁存在安全性问题, 不管是 sentinel 模式或者 cluster 模式, 主要受限于 redis 数据同步模型. 

    从 CAP 理论上讲, redis 实现了 AP 即放弃了一致性, 然而分布式锁应该实现 CP 才对.

    不过, 在项目中如果可以容忍重复获取分布式锁, 可以考虑如下方案或者redlock.

    https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

    概要

    一个分布式锁需要满足以下三个条件:

    1. 分布式锁的加锁和释放锁要保证操作的原子性.
    2. 锁的拥有者因为异常退出, 锁在合理的时间范围内可以自动释放.
    3. 只有锁的拥有者可以释放锁.

    使用 setnx key value 实现分布式锁的缺点:

    1. 没有设置超时时间, 获取锁的服务器一旦发生OOM挂掉, 其他服务器将不再有机会获取到锁.
    2. 如果在value中设置时间, 对锁的存在和超时判断将不是原子性操作.

    加锁

    获取锁使用如下命令:

    set key value [expiration EX seconds|PX milliseconds] [NX|XX]

    此处的 value 存储了 requestId, 可以理解为锁的钥匙, 只有锁的拥有者知道. 可以是一个随机生成的字符串. 例如:

    # NX: SET_IF_NOT_EXIST
    # PX: SET_WITH_EXPIRE_TIME
    127.0.0.1:6379> set key value NX PX 3000
    OK
    127.0.0.1:6379> get key
    "value"

    加锁成功会返回 "OK"

    解锁

    解锁时要验证锁的拥有者, 验证 requestId 是否正确, 正确则可以删除锁. 这包含了两步操作, 验证和删除. 我们可以通过 lua 脚本把这两个操作合并成一个操作.
    redis 执行 lua 脚本具有原子性(注意: SharedJedis 客户端不支持 lua 脚本).

    if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end

    核心代码

    /**
     * redis 实现分布式锁
     *
     * @author xxx
     * @date 2021-01-15 15:50
     **/
    public class RedisDistributedLock implements DistributedLock {
    
        private static final String LOCK_SUCCESS = "OK";
        private static final Long RELEASE_SUCCESS = 1L;
        private static final String SET_IF_NOT_EXIST = "NX";
        private static final String SET_WITH_EXPIRE_TIME = "PX";
    
        @Override
        public boolean lock(String lockKey, String requestId, int expireMillis) {
            String result = RedisUtil.opsForValue()
                .set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireMillis);
            return LOCK_SUCCESS.equals(result);
        }
    
        @Override
        public boolean unLock(String lockKey, String requestId) {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = RedisUtil
                .eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
            return RELEASE_SUCCESS.equals(result);
        }
    }

    使用

     @Test
        public void DistributedLockTest(){
            DistributedLock distributedLock = new RedisDistributedLock();
            String lockKey = "xxx";
            try {
                if (distributedLock.lock(lockKey, "111", 3000)){
                    System.out.println("获取成功!");
                }
            }finally {
                if (distributedLock.unLock(lockKey, "222")){
                    System.out.println("222--释放成功!");
                } else {
                    System.out.println("222--释放失败!");
                }
                if (distributedLock.unLock(lockKey, "111")){
                    System.out.println("111--释放成功!");
                }else {
                    System.out.println("111--释放失败!");
                }
            }
        }

    输出结果:

    获取成功!
    222--释放失败!
    111--释放成功!
     
  • 相关阅读:
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    Codeforces Round #571 (Div. 2)-D. Vus the Cossack and Numbers
  • 原文地址:https://www.cnblogs.com/xxoome/p/14435731.html
Copyright © 2011-2022 走看看