冰恋 发表于 2017-12-21 23:29:14

基于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>    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 + &quot;开始执行加锁&quot;);      //锁时间      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 + &quot;加锁成功+1&quot;);            //设置超时时间,释放内存            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 + &quot;加锁成功+2&quot;);                  //设置超时间,释放内存                  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 + &quot;执行解锁==========&quot;);//正常直接删除 如果异常关闭判断加锁会判断过期时间      //获取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 + &quot;解锁成功------------------&quot;);      }    }    /**   * 多服务器集群,使用下面的方法,代替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(&quot;{}获取token失败!&quot;, username);                //任务执行完毕 关闭锁                redisLock.unlock(tokenLockName, lockTime, threadName);            }
页: [1]
查看完整版本: 基于Redis的分布式锁实现