Fork me on GitHub
听雨轩
生命易破碎,梦想只争朝夕!

工作中涉及到了不同服务器并发获取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);
            }
posted on 2017-05-10 10:53  流水殇  阅读(5017)  评论(0编辑  收藏  举报