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);
    }
  • 相关阅读:
    编程入门之结构体
    编程入门之函数理解
    编程入门之编码风格
    Linux基础 30分钟GDB调试快速突破
    GDB实战
    linux下终端游戏
    DSP学习教程基于28335(一)
    Linux内核模块编程可以使用的内核组件
    Windows10下配置Linux下C语言开发环境
    Git常规配置与基本用法
  • 原文地址:https://www.cnblogs.com/edda/p/13744799.html
Copyright © 2011-2022 走看看