• 基于Redis的分布式锁实现


    工作中涉及到了不同服务器并发获取Token的需求,但是后一次获取会覆盖前一次获取的Token,因此需要对获取Token这一操作做一次分布式加锁。这次我使用redis来解决这个问题,首先提供一个加锁的类:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.dao.DataAccessException;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.core.RedisCallback;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import java.util.concurrent.TimeUnit;
    /**
     * 基于redis的分布式锁实现
     */
    public class RedisLockImpl implements RedisDistributionLock {
        private static final Logger LOG = LoggerFactory.getLogger(RedisLockImpl.class);
        //加锁超时时间,单位毫秒, 即:加锁时间内执行完操作,如果未完成会有并发现象
        private long lockTimeout;
        private StringRedisTemplate redisTemplate;
        public RedisLockImpl(StringRedisTemplate redisTemplate, long timeout) {
            this.redisTemplate = redisTemplate;
            this.lockTimeout = timeout;
        }
        /**
         * 加锁
         * 取到锁加锁,取不到锁就返回
         *
         * @param lockKey
         * @param threadName
         * @return
         */
        @Override
        public synchronized long lock(String lockKey, String threadName) {
            LOG.debug(threadName + "开始执行加锁");
            //锁时间
            Long lock_timeout = currtTimeForRedis() + lockTimeout + 1;
            if (redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    //定义序列化方式
                    RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                    byte[] value = serializer.serialize(lock_timeout.toString());
                    boolean flag = redisConnection.setNX(lockKey.getBytes(), value);
                    return flag;
                }
            })) {
                //如果加锁成功
                LOG.debug(threadName + "加锁成功+1");
                //设置超时时间,释放内存
                redisTemplate.expire(lockKey, lockTimeout, TimeUnit.MILLISECONDS);
                return lock_timeout;
            } else {
                //获取redis里面的时间
                String result = redisTemplate.opsForValue().get(lockKey);
                Long currt_lock_timeout_str = result == null ? null : Long.parseLong(result);
                //锁已经失效
                if (currt_lock_timeout_str != null && currt_lock_timeout_str < System.currentTimeMillis()) {
                    //判断是否为空,不为空时,说明已经失效,如果被其他线程设置了值,则第二个条件判断无法执行
                    //获取上一个锁到期时间,并设置现在的锁到期时间
                    Long old_lock_timeout_Str = Long.valueOf(redisTemplate.opsForValue().getAndSet(lockKey, lock_timeout.toString()));
                    if (old_lock_timeout_Str != null && old_lock_timeout_Str.equals(currt_lock_timeout_str)) {
                        //多线程运行时,多个线程签好都到了这里,但只有一个线程的设置值和当前值相同,它才有权利获取锁
                        LOG.debug(threadName + "加锁成功+2");
                        //设置超时间,释放内存
                        redisTemplate.expire(lockKey, lockTimeout, TimeUnit.MILLISECONDS);
                        //返回加锁时间
                        return lock_timeout;
                    }
                }
            }
            return -1;
        }
        /**
         * 解锁
         *
         * @param lockKey
         * @param lockValue
         * @param threadName
         */
        @Override
        public synchronized void unlock(String lockKey, long lockValue, String threadName) {
            LOG.debug(threadName + "执行解锁==========");//正常直接删除 如果异常关闭判断加锁会判断过期时间
            //获取redis中设置的时间
            String result = redisTemplate.opsForValue().get(lockKey);
            Long currt_lock_timeout_str = result == null ? null : Long.valueOf(result);
            //如果是加锁者,则删除锁, 如果不是,则等待自动过期,重新竞争加锁
            if (currt_lock_timeout_str != null && currt_lock_timeout_str == lockValue) {
                redisTemplate.delete(lockKey);
                LOG.debug(threadName + "解锁成功------------------");
            }
        }
        /**
         * 多服务器集群,使用下面的方法,代替System.currentTimeMillis(),获取redis时间,避免多服务的时间不一致问题!!!
         *
         * @return
         */
        @Override
        public long currtTimeForRedis() {
            return redisTemplate.execute(new RedisCallback<Long>() {
                @Override
                public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
                    return redisConnection.time();
                }
            });
        }
    }

    使用方法:

                if ((lockTime = redisLock.lock(tokenLockName, threadName)) != null) {
                    //开始执行任务
                    int tryCount = 3;
                    while (token == null && --tryCount > 0) {
                        token = getTokenInternal(username, password);
                    }
                    //加入redis缓存
                    if (token != null)
                        redisService.set(String.format(ACCESS_TOKEN_KEY_TEMPLATE, username), token, tokenTimeout);
                    else
                        logger.info("{}获取token失败!", username);
                    //任务执行完毕 关闭锁
                    redisLock.unlock(tokenLockName, lockTime, threadName);
                }
  • 相关阅读:
    Web APIs
    变量替换
    用shell脚本实现文件、代码同步上线
    20.31 expect脚本同步文件 20.32 expect脚本指定host和要同步的文件 20.33 构建文件分发系统 20.34 批量远程执行命令
    20.27 分发系统介绍 20.28 expect脚本远程登录 20.29 expect脚本远程执行命令 20.30 expect脚本传递参数
    20.23/20.24/20.25 告警系统邮件引擎 20.26 运行告警系统
    20.16/20.17 shell中的函数 20.18 shell中的数组 20.19 告警系统需求分析
    20.5 shell脚本中的逻辑判断 20.6 文件目录属性判断 20.7 if特殊用法 20.8/20.9 case判断
    16进制数据拆分
    16进制数据拼接成一个
  • 原文地址:https://www.cnblogs.com/yuananyun/p/6834815.html
Copyright © 2020-2023  润新知