zoukankan      html  css  js  c++  java
  • Redisson实现分布式锁

    在分布式系统中,分布式锁是一个很常见的技术。即有很多个进程同时访问同一个共享资源没有同步访问,资源的载体可能是传统关系型数据库或者NoSQL。

    如果是在单机环境中,可以使用ReentrantLock或者synchronized代码块来实现,然而这些在分布式环境下却不能满足要求。

    例如有这样的一个场景:

    多台服务器同时从MQ消息队列接消息,接到消息后的处理逻辑是:根据消息中的业务id去查询数据库,如果数据库中不存在,则插入,反之则更新。

    这样就会出现这样一个问题:服务器A和服务器B中接收到的消息中业务id是一样的,2台服务器同时到数据库中查询,都不存在,则都会进行插入操作,最后的结果是表中有2条一样的记录(或者有主键约束的话报主建不唯一异常插入失败),显然这不是我能所期望的!

    分布式环境中多台服务器相当于多个独立的jvm环境,有多个进程(注意不是线程)对同一资源进行操作,jdk中用作同步的Lock或者synchronized就会失效。

    这里就需要用到分布式锁,目前实现分布式锁的主要技术大概有:

    • 基于Redis实现(或者其他缓存memcached,tair),主要基于redis的setnx(set if not exist)命令;

    • 基于Zookeeper实现;

    • 基于数据库表version字段实现,乐观锁,两个线程可以同时读取到原有的version值,但是最终只有一个可以完成操作;

    下面是redis实现分布式锁的方法,主要用到了Redisson,它是redis官方推荐的分布式java客户端,和Jedis相比它实现了分布式和可扩展的java数据结构,

    这里有一篇对比文章:Jedis与Redisson选型对比

    Redisson的介绍可以到:https://github.com/redisson/redisson/wiki/1.-%E6%A6%82%E8%BF%B0 这里去了解!

    具体实现代码:

    import org.redisson.Config;
    import org.redisson.Redisson;
    import org.redisson.RedissonClient;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    /** 
     * 
     * @author   created by dehong
     * @date     2018年3月4日 下午4:12:42
     * 
     */
    public class RedissonManager {
        
        private static final Logger logger = LoggerFactory.getLogger(RedissonManager.class);
    
        private static RedissonClient redisson;
        //private static Config config;
        
        /**
         * 通过配置文件初始化
         */
        static{
            logger.info("init redisson ...");
            Config config = null;
            try {
                config = Config.fromYAML(RedissonManager.class.getClassLoader().getResourceAsStream("redisson.yaml"));
                //config = Config.fromJSON(RedissonManager.class.getClassLoader().getResourceAsStream("redisson.json"));
            } catch (Exception e) {
                logger.error("RedissonClient init failed !", e);
            }
            redisson = Redisson.create(config);
        }
        
    
        /**
         * 获取Redisson的实例对象
         * @return
         */
        public static Redisson getRedisson(){
            if(redisson == null){
                logger.info("RedissonClient init failed !");
                return null;
            }
            return (Redisson) redisson;
        }
    
        /**
         * 测试Redisson是否正常
         * @param args
         */
        public static void main(String[] args) {
            Redisson redisson = RedissonManager.getRedisson();
            System.out.println("redisson = " + redisson);
    
        }
    
    
    }

    上面的RedissonClient初始化是用配置文件yaml的形式,也可以用json,可以去参考官方文档 https://github.com/redisson/redisson

    yaml示例(单机redis节点模式)

    ---
    singleServerConfig:
      idleConnectionTimeout: 10000
      pingTimeout: 1000
      connectTimeout: 10000
      timeout: 3000
      retryAttempts: 3
      retryInterval: 1500
      reconnectionTimeout: 3000
      failedAttempts: 3
      password: null
      subscriptionsPerConnection: 5
      clientName: null
      address: 
        - redis://192.168.43.84:6379
      subscriptionConnectionMinimumIdleSize: 1
      subscriptionConnectionPoolSize: 50
      connectionMinimumIdleSize: 32
      connectionPoolSize: 64
      database: 0
      dnsMonitoring: false
      dnsMonitoringInterval: 5000

    工具类:

    import java.util.concurrent.TimeUnit;
    import org.redisson.Redisson;
    import org.redisson.core.RLock;
    
    /** 
     * 
     * @author   created by dehong
     * @date     2018年3月4日 下午4:30:11
     * Redisson分布式锁 工具类
     */
    public class RedissonLockUtil {
    
        private static Redisson redisson = RedissonManager.getRedisson();
    
        private static final String LOCK_FLAG = "redissonlock_";
    
        /**
         * 根据name对进行上锁操作,redissonLock 阻塞事的,采用的机制发布/订阅
         * @param key
         */
        public static void lock(String key){
            String lockKey = LOCK_FLAG + key;
            RLock lock = redisson.getLock(lockKey);
            //lock提供带timeout参数,timeout结束强制解锁,防止死锁 :1分钟
            lock.lock(1, TimeUnit.MINUTES);
        }
    
        /**
         * 根据name对进行解锁操作
         * @param key
         */
        public static void unlock(String key){
            String lockKey = LOCK_FLAG + key;
            RLock lock = redisson.getLock(lockKey);
            //如果锁被当前线程持有,则释放
            if(lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    
    
    }

    注意:上面的unlock()中不加isHeldByCurrentThread()条件的话,在执行的task的时间超过timeout时,此时如果unlock,其实redisson已经主动unlock了,就会出现IllegalMonitorStateException 异常

    调用:

    try
    {
        //获取锁,这里的key可以上面场景中的业务id
        RedissonLockUtil.lock(key);
        //具体要执行的代码....
    } catch (Exception e)
    {
        e.printStackTrace();
    }finally {
        //释放锁
        RedissonLockUtil.unlock(key);
    }
  • 相关阅读:
    sparql学习sparql示例、dbpedia在线验证
    中国绿卡
    逾期率的水有多深,你知道吗?
    ICO和区块链区别
    What are the benefits to using anonymous functions instead of named functions for callbacks and parameters in JavaScript event code?
    Link static data in sql source control
    sql data compare
    viewbag
    多态的实际使用
    win10 sedlauncher.exe占用cpu处理
  • 原文地址:https://www.cnblogs.com/edda/p/13744799.html
Copyright © 2011-2022 走看看