分布式共享锁的设计

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhou920786312/article/details/89184177

| date: 20190410

功能介绍

针对某种资源,需要被整个系统的各台服务器共享访问,但是只允许一台服务器同时访问。比如说订单服务是做成集群的,当两个以上结点同时收到一个相同订单的创建指令,这时并发就产生了,系统就会重复创建订单。而分布式共享锁就是解决这类问题。

##流程图
在这里插入图片描述

代码实现

 /**
 * @Author feizhou
 * @Description 分布式锁模板
 * @Date 10:39 2019/4/9
 * @Param [key, actionLog, expireSecond]
 * @return java.lang.Boolean
 **/
public static  Boolean distributedLock_v2(String key,String actionLog, long milliseconds,boolean isDelLock){
    RedisBaseDao redisDao = RedisUtil.getRedisDao();
    boolean isGetLock=false;
   String requestId = UUID.randomUUID().toString();
    try {
        isGetLock = redisDao.getDistributedLock(key,requestId , milliseconds);
        if(!isGetLock){
            logger.error("分布式锁拦截,不能重复操作,"+key+",actionLog="+actionLog);
        }
        return isGetLock;
    } catch (Exception e) {
        e.printStackTrace();
        if(e instanceof RedisException){
            logger.error("redis 分布式锁异常,可能存在重复操作的的可能性,key="+key+",actionLog="+actionLog+",e="+e);
            return true;
        }
    }finally {
        if(isGetLock&&isDelLock){
            try {
                redisDao.releaseDistributedLock(key,requestId);
            } catch (Exception e) {
                e.printStackTrace();
                logger.error("分布式锁释放锁失败,key="+key+",actionLog="+actionLog+","+e);
            }
        }
    }
    return false;
}

  /**
 * 尝试获取分布式锁
 * @param lockKey 锁
 * @param requestId 请求标识
 * @param milliseconds 超期时间
 * @return 是否获取成功
 */

private static final Long RELEASE_SUCCESS = 1L;
public   boolean getDistributedLock(String lockKey, String requestId, Long milliseconds) {
      return   this.setNx(lockKey, requestId, milliseconds);
}
/**
 * 释放分布式锁
 * @param lockKey 锁
 * @param requestId 请求标识
 * @return 是否释放成功
 */
public   boolean releaseDistributedLock( String lockKey, String requestId) {
    return this.deleteKeyForSameValue(lockKey,requestId);
}


 public Boolean setNx(  String key,   String value,Long expireTime) {
    Boolean isSet = redisTemplate.execute(new RedisCallback<Boolean>() {
        @Override
        public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
            //过期时间好处:即使服务器宕机了,也能保证锁被正确释放。
            //setNx原子性操作,防止同一把锁在同一时间可能被不同线程获取到
            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(key, value, "nx", "px", expireTime);
            if("OK".equals(result)){
                return true;
            }
            return false;
        }
    });
    return isSet;
}
public Boolean deleteKeyForSameValue(  String key,   String value) {
            return  redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                Jedis jedis = (Jedis) redisConnection.getNativeConnection();
                //删除key的时候,先判断该key对应的value是否等于先前设置的随机值,只有当两者相等的时候才删除该key
                //防止释放其他客户端获取到的锁
                //原子性操作
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value));
                if (RELEASE_SUCCESS.equals(result)) {
                    return true;
                }
                return false;
            }
        });
}

方案优点

多个服务器竞争资源,需要排队,解决类似一个订单被多个服务器提交问题。

方案缺点

  • 试用与一主多从的redis集群,如果多主多从,不能解决共享锁问题
    -这个问题解决方案https://yq.aliyun.com/articles/674394,https://blog.csdn.net/chen_kkw/article/details/81433470
  • 同时当一主多从服务器,主机宕机,有丢失锁的风险,概率很小。
    • 场景
    • 在Redis的master节点上拿到了锁,但是这个加锁的key还没有同步到slave节点,master故障,发生故障转移,slave节点升级为master节点; 导致锁丢失。概率很小,可以不考虑。

待改善点

对待完善点进行列举,以便后续改进

其他说明

猜你喜欢

转载自blog.csdn.net/zhou920786312/article/details/89184177
今日推荐