欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于Redis实现分布式锁

程序员文章站 2022-07-05 13:22:07
...

分布式锁是控制分布式系统之间同步访问共享资源的一种方式

锁接口定义

定义一个锁通用接口,对外提供锁服务

import java.util.concurrent.TimeUnit;

public interface LockService {
    /** 
     * 尝试获取锁
     * @param 锁名
     */
    public boolean tryLock(String name);
    /** 
     * 在指定时间内尝试获取锁
     * @param 锁名
     * @param 尝试时间
     */
    public boolean tryLock(String name, long timeout, TimeUnit unit);
    /** 
     * 获取锁,阻塞方法,直到获取锁成功为止
     * @param 锁名
     */
    public void lock(String name) throws Exception;
    /** 
     * 释放锁
     * @param 锁名
     */
    public void unlock(String name);

}

redis锁的实现:

public class RedisLockService implements LockService {

    private static final String NAMESPACE = "lock-service:";
    private static final Logger log = LogManager.getLogger(RedisLockService.class);

    @Autowired
    private RedisTemplate<String, String> stringRedisTemplate;

    /** 锁默认超时时间,单位:秒 */
    @Value("${lockService.timeout:300}")
    private int timeout;      

    /** 服务ID,用于区分释放锁权限。 */
    @Value("${instance.id:locks}")
    private String instanceId;


    @Override
    public boolean tryLock(String name) {
        String key = NAMESPACE + name;
        String value = getValue();
        boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, value);    //如果不存在key,则set
        if (success)
            stringRedisTemplate.expire(key, this.timeout, TimeUnit.SECONDS);            //设置默认锁有效时间
        return success;
    }

    @Override
    public boolean tryLock(String name, long timeout, TimeUnit unit) {
        if (timeout <= 0)
            return tryLock(name);
        String key = NAMESPACE + name;
        String value = getValue();
        boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
        long millisTimeout = unit.toMillis(timeout);
        long start = System.currentTimeMillis();
        while (!success) {          //若获取锁不成功,则在限定时间内不断尝试,直到获取成功,或超时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                return false;
            }
            if ((System.currentTimeMillis() - start) >= millisTimeout)
                break;
            success = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
        }
        if (success)
            stringRedisTemplate.expire(key, this.timeout, TimeUnit.SECONDS);
        return success;
    }

    @Override
    public void lock(String name) {
        String key = NAMESPACE + name;
        String value = getValue();
        boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
        while (!success) {
            try {
                Thread.sleep(100);          //每间隔100毫秒,再次尝试获取锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            success = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
            if (success)
                stringRedisTemplate.expire(key, this.timeout, TimeUnit.SECONDS);
        }

    }

    @Override
    public void unlock(String name) {
        String key = NAMESPACE + name;
        String value = getValue();
        //一行lua脚本,如果KEY的值等于VALUE,则删除KEY,否则返回0
        String str = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";
        RedisScript<Long> script = new DefaultRedisScript<Long>(str, Long.class);
        Long ret = stringRedisTemplate.execute(script, Collections.singletonList(key), value);
        if (ret == 0)
            log.warn("Lock [{}] is not hold by instance [{}]", name, value);
    }

    /**
     * 生成由服务ID+线程名组成的线程唯一value。用于保证只能被拥有锁的线程解锁
     */
    private String getValue(){
        return instanceId + Thread.currentThread().getName();
    }

}