redis实现分布式锁

当前java开发最火热的开发方式应该都是使用springboot来进行开发,springboot的优点这里就不做过多叙述
这里使用springboot RedisTemplate实现分布式锁;
上代码前先说明,redis实现分布式锁,是利用redis唯一key的特性,唯一key创建时如果成功会返回1(true),已经存在无法创建会返回0(false),
另redis存在事件通知订阅功能,利用该功能可以监听key删除或过期来唤醒等待锁的线程及时竞争锁(该键空间通知功能需要在redis中开启,否则不会生效)

可以利用这个特性来进行实现分布式锁;废话不多说,直接上代码

编写RedisUtils用于封装redis工具,方便外部调用

package com.miyou.demo.utils.lock.impl;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import java.util.AbstractQueue;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class RedisUtils  implements MessageListener {

    private final Map<String, AbstractQueue<MessageListener>> messageMaps = new HashMap<>();

    private RedisTemplate<String, Object> template;

    /**
     * retemplate相关配置
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        template = new RedisTemplate<>();
        // 配置连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // 值采用json序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        // 设置hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * redis事件监听
     * @param redisConnectionFactory
     * @param redisUtils
     * @return
     */
    @Bean
    public RedisMessageListenerContainer keyEventMessageListener(RedisConnectionFactory redisConnectionFactory, RedisUtils redisUtils) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisConnectionFactory);
        container.addMessageListener((message, bytes) -> {
            log.info("删除key----->{}",message.toString());
            redisUtils.onMessage(message, bytes);
        },new PatternTopic("__keyspace@*__:del"));
        container.addMessageListener((message, bytes) -> {
            log.info("key----->{},已过期",message.toString());
            redisUtils.onMessage(message, bytes);
        },new PatternTopic("__keyevent@*__:expired"));
        return container;
    }

    /**
     * redis唯一key
     * @return 保存成功/保存失败
     */
    public Boolean setIfAbsent(String key,Object value,long timeOut){
        return template.opsForValue()
                .setIfAbsent(key,value,timeOut, TimeUnit.MILLISECONDS);
    }

    public Object get(String key){
        return template.opsForValue().get(key);
    }

    /**
     * 删除缓存
     * @param key 键
     */
    public Boolean del(String key){
        return template.delete(key);
    }

    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(毫秒)
     */
    public void expire(String key, long time){
        template.expire(key, time, TimeUnit.MILLISECONDS);
    }

    @Override
    public void onMessage(Message message, byte[] bytes) {
        AbstractQueue<MessageListener> listeners = messageMaps.get(message.toString());
        if(listeners != null){
            listeners.forEach(s->s.onMessage(message, bytes));
        }
    }

    /**
     * 将事件通知回调方法存入队列
     * @param key 键
     * @param messageListener 回调对象
     */
    public void addMessageValueMap(String key,MessageListener messageListener){
        if(!messageMaps.containsKey(key)) {
            synchronized (messageMaps) {
                if(!messageMaps.containsKey(key)) {
                    messageMaps.put(key,new ConcurrentLinkedQueue<>());
                }
            }
        }
        messageMaps.get(key).add(messageListener);
    }

    /**
     * 删除事件通知回调方法
     * @param key 键
     * @param messageListener 回调对象
     */
    public void removeMessageValue(String key,MessageListener messageListener){
        messageMaps.get(key).remove(messageListener);
    }
}


定义Lock接口,声明锁的基本功能

package com.miyou.demo.utils.lock;

/**
 * 锁接口类
 */
public interface Lock {
    /**
     * 获取锁
     */
    void lock();

    /**
     * 尝试获取锁
     *
     * @return
     */
    boolean tryLock() ;

    /**
     * 在指定时间内尝试获取锁
     * @param timeout 毫秒
     * @return
     */
    boolean tryLock(long timeout);

    /**
     * 释放锁
     */
    void unlock();

}

编写RedisLock实现分布式锁

package com.miyou.demo.utils.lock.impl;

import com.miyou.demo.utils.lock.Lock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.MessageListener;

import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * redis分布式锁
 * @author lrx
 */
@Slf4j
public class RedisLock implements Lock {

    /**
     * 将当前线程锁基本信息存入ThreadLocal
     */
    private ThreadLocal<RedisLockEntity> localEntity = ThreadLocal.withInitial(RedisLockEntity::new);

    private final static String ROOT_PATH = "REDIS_LOCK";

    private RedisUtils redisUtils;

    private String key;

    private long timeout = 20000;

    /**
     * 创建锁对象
     * @param key 锁key
     */
    public RedisLock(String key, RedisUtils redisUtils){
        this.redisUtils = redisUtils;
        this.key = ROOT_PATH+":"+key;
    }

    /**
     * 创建锁对象
     * @param key 锁key
     * @param timeout 锁超时时间(耗秒)
     */
    public RedisLock(String key, long timeout, RedisUtils redisUtils){
        this(key,redisUtils);
        this.timeout = timeout;
        //如果指定了超时时间则默认不续期
        this.localEntity.get().setRenewFlg(false);
    }

    @Override
    public void lock(){
        while (!tryLock()) {
            this.await(-1);
        }
    }

    @Override
    public boolean tryLock() {
        RedisLockEntity entity = localEntity.get();
        //没有拿到锁时才去获取锁
        if(!entity.isLock()) {
            entity.setLock(redisUtils.setIfAbsent(key,entity.getLockValue(),timeout));
            //如果拿到锁了则启动自动续期
            if(entity.isLock()) {
                this.renew();
            }
        }
        return entity.isLock();
    }

    @Override
    public boolean tryLock(long timeout) {
        boolean state;
        long startTime = System.currentTimeMillis();
        long waitTime = 0;
        for(;;){
            state = tryLock();
            if(state || waitTime >= timeout){break;}//如果拿到锁,或等待时间使用完则停止自旋
            this.await(timeout);
            waitTime = System.currentTimeMillis()-startTime;
        }
        return state;
    }

    @Override
    public void unlock() {
        RedisLockEntity entity = localEntity.get();
        entity.setRenewFlg(false);//停止续期
        //只能释放自己的锁
        if(entity.isLock() && entity.getLockValue().equals(redisUtils.get(key))){
            entity.setLock(false);
            redisUtils.del(key);
        }else {
            log.error("线程:{}未获取到锁无法释放!!",Thread.currentThread().getId());
            throw new RuntimeException("The current thread has not acquired the lock");
        }
    }

    /**
     * 监听事件通知
     */
    private void  await(long timeout){
        CountDownLatch downLatch = new CountDownLatch(1);
        MessageListener messageListener = (message, bytes) -> downLatch.countDown();
        redisUtils.addMessageValueMap(key,messageListener);
        try {//因redis事件通知不可靠,如果没有指定等待时间则只等待30秒
            downLatch.await(timeout>0?timeout:30000,TimeUnit.MILLISECONDS);
        } catch (InterruptedException ignored) {}
        redisUtils.removeMessageValue(key,messageListener);
    }

    /**
     * 自动续期
     */
    private void renew(){
        RedisLockEntity entity = localEntity.get();
        if(entity.isRenewFlg() && timeout > 10000){
            new Thread( () -> {
                //如果启动了自动续期,且是自己的锁,则循环续期
                while (entity.isRenewFlg() && entity.getLockValue().equals(redisUtils.get(key))){
                    try {
                        Thread.sleep(timeout-10000);
                    } catch (InterruptedException ignored) {}
                    redisUtils.expire(key, timeout);
                    log.info("{}-->自动续期完成!",key);
                }
            }).start();
        }
    }

    static class RedisLockEntity{
        private boolean lock = false;
        private String lockValue = UUID.randomUUID().toString();
        private boolean renewFlg = true;

        public void setLock(boolean lock) {
            this.lock = lock;
        }

        public boolean isLock() {
            return lock;
        }

        public void setRenewFlg(boolean renewFlg){
            this.renewFlg = renewFlg;
        }

        public boolean isRenewFlg() {
            return renewFlg;
        }

        public String getLockValue() {
            return lockValue;
        }
    }

}


以上为redis分布式锁的基本实现原理和思路,当然现在已经有基于redis实现分布式锁的组件redisson,该组件redis锁接口继承了java Lock接口,因此在使用时可以像java Lock接口一样使用更加简单方便,下篇记录redisson组件redis分布式锁的使用

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值