• 【Distributed】分布式锁


    一、概述

    1.1 分布式解决的核心思路

    在多台服务器中,只能保证一台服务器的jvm进行操作

    1.2 分布式锁一般有三种实现方式

    1. 数据库乐观锁;
    2. 基于Redis的分布式锁 setnx 也可 以存入key,如果存入key成功返回1,如果存入的key已经存在了,返回0。
      • 多个客户端(jvm) ,使用setnx命令方式,同时在redis 上创建相同的一个key,因为rediskey不能够允许重复的,只要谁能够创建key成功,谁就能够获取到锁,没有创建key成功,就会进行等待。释放锁:执行操作完成的时候,删除key,解决办法是给key设置有效期,每个对应的key都有自己的有效期。
    3. 基于ZooKeeper的分布式锁,大致思想即为:每个客户端对某个功能加锁时,在zookeeper上的与该功能对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
      • 多个客户端(jvm) ,同时在zk上创建相同的一个临时节点,因为临时节点路径是保证唯一,只要谁能够创建节点成功,谁就能够获取到锁,没有创建成功节点,就会进行等待,当释放锁的时候,采用事件通知给客户端重新获取锁的资源。

    二、基于Redis的分布式锁

    2.1 使用常用命令

    
    #当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
    setnx key val
    
    #为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
    expire key timeout
    
    # 删除key    
    delete key
    
    

    在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。

    2.2 实现思路

    1.获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
    2.获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
    3.释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

    2.3 核心代码

    Maven依赖信息

    <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
    </dependency>
    

    LockRedis

     public class LockRedis {
    
        private JedisPool jedisPool;
    
        public LockRedis(JedisPool jedisPool) {
            this.jedisPool = jedisPool;
        }
    
        /**
         * redis 上锁方法 
         * 
         * @param lockKey
         *            锁的key<br>
         * @param acquireTimeout
         *            在没有上锁之前,获取锁的超时时间<br>
         * @param timeOut
         *            上锁成功后,锁的超时时间<br>
         * @return
         */
        public String lockWithTimeout(String lockKey, Long acquireTimeout, Long timeOut) {
            Jedis conn = null;
            String retIdentifierValue = null;
            try {
                // 1.建立redis连接
                conn = jedisPool.getResource();
                // 2.随机生成一个value
                String identifierValue = UUID.randomUUID().toString();
                // 3.定义锁的名称
                String lockName = "redis_lock" + lockKey;
                // 4.定义上锁成功之后,锁的超时时间
                int expireLock = (int) (timeOut / 1000);
                // 5.定义在没有获取锁之前,锁的超时时间
                Long endTime = System.currentTimeMillis() + acquireTimeout;
                while (System.currentTimeMillis() < endTime) {
                    // 6.使用setnx方法设置锁值
                    if (conn.setnx(lockName, identifierValue) == 1) {
                        // 7.判断返回结果如果为1,则可以成功获取锁,并且设置锁的超时时间
                        conn.expire(lockName, expireLock);
                        retIdentifierValue = identifierValue;
                        return retIdentifierValue;
                    }
                    // 8.否则情况下继续循环等待
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (conn != null) {
                    conn.close();
                }
            }
            return retIdentifierValue;
        }
    
        /**
         * 释放锁
         * 
         * @return
         */
        public boolean releaseLock(String lockKey, String identifier) {
    
            Jedis conn = null;
            boolean flag = false;
            try {
    
                // 1.建立redis连接
                conn = jedisPool.getResource();
                // 2.定义锁的名称
                String lockName = "redis_lock" + lockKey;
                // 3.如果value与redis中一直直接删除,否则等待超时
                if (identifier.equals(conn.get(lockName))) {
                    conn.del(lockName);
                    System.out.println(identifier + "解锁成功......");
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (conn != null) {
                    conn.close();
                }
            }
            return flag;
        }
    }
    

    Service

    public class LockService {
        private static JedisPool pool = null;
    
        static {
            JedisPoolConfig config = new JedisPoolConfig();
            // 设置最大连接数
            config.setMaxTotal(200);
            // 设置最大空闲数
            config.setMaxIdle(8);
            // 设置最大等待时间
            config.setMaxWaitMillis(1000 * 100);
            // 在borrow一个jedis实例时,是否需要验证,若为true,则所有jedis实例均是可用的
            config.setTestOnBorrow(true);
            pool = new JedisPool(config, "39.107.69.43", 6379, 3000);
        }
    
        LockRedis lockRedis = new LockRedis(pool);
    
        public void seckill() {
            String identifier = lockRedis.lockWithTimeout("itmayiedu", 5000l, 5000l);
            if (StringUtils.isEmpty(identifier)) {
                // 获取锁失败
                System.out.println(Thread.currentThread().getName() + ",获取锁失败,原因时间超时!!!");
                return;
            }
            System.out.println(Thread.currentThread().getName() + "获取锁成功,锁id identifier:" + identifier + ",执行业务逻辑");
            try {
                Thread.sleep(30);
            } catch (Exception e) {
    
            }
            // 释放锁
            boolean releaseLock = lockRedis.releaseLock("itmayiedu", identifier);
            if (releaseLock) {
                System.out.println(Thread.currentThread().getName() + "释放锁成功,锁id identifier:" + identifier);
            }
        }
        }
    
        class ThreadRedis extends Thread {
        private LockService lockService;
    
        public ThreadRedis(LockService lockService) {
            this.lockService = lockService;
        }
    
        @Override
        public void run() {
            lockService.seckill();
    
        }
    
        }
    
    

    测试代码

    public class Test001 {
    
        public static void main(String[] args) {
            LockService lockService = new LockService();
            for (int i = 0; i < 50; i++) {
                ThreadRedis threadRedis = new ThreadRedis(lockService);
                threadRedis.start();
            }
        }
    
        }
    
    
    • 在分布式环境中,对资源进行上锁有时候是很重要的,比如抢购某一资源,这时候使用分布式锁就可以很好地控制资源。,当然,在具体使用中,还需要考虑很多因素,比如超时时间的选取,获取锁时间的选取对并发量都有很大的影响,上述实现的分布式锁也只是一种简单的实现,主要是一种思想。

    三、三种分布式对比

    • 上面几种方式,哪种方式都无法做到完美。就像CAP一样,在复杂性、可靠性、性能等方面无法同时满足,所以,根据不同的应用场景选择最适合自己的才是王道。
    从理解的难易程度角度(从低到高)
    数据库 > 缓存 > Zookeeper
        
    从实现的复杂性角度(从低到高)
    Zookeeper >= 缓存 > 数据库
        
    从性能角度(从高到低)
    缓存 > Zookeeper >= 数据库
        
    从可靠性角度(从高到低)
    Zookeeper > 缓存 > 数据库
    

    Redis实现分布式锁与Zookeeper实现分布式锁区别

    使用redis实现分布式锁

    • redis中的set nx 命令,当key不存在时,才能在redis中将key添加成功,利用该属性可以实现分布式锁,并且redis对于key有失效时间,可以控制当某个客户端加锁成功之后挂掉,导致阻塞的问题。

    使用Zookeeper实现分布式锁

    • 多个客户端在Zookeeper上创建一个相同的临时节点,因为临时节点只能允许一个客户端创建成功,那么只要任意一个客户端创建节点成功,谁就成功的获取到锁,当释放锁后,其他客户端同样道理在Zookeeper节点。
  • 相关阅读:
    nginx能访问html静态文件但无法访问php文件
    LeetCode "498. Diagonal Traverse"
    LeetCode "Teemo Attacking"
    LeetCode "501. Find Mode in Binary Search Tree"
    LeetCode "483. Smallest Good Base" !!
    LeetCode "467. Unique Substrings in Wraparound String" !!
    LeetCode "437. Path Sum III"
    LeetCode "454. 4Sum II"
    LeetCode "445. Add Two Numbers II"
    LeetCode "486. Predict the Winner" !!
  • 原文地址:https://www.cnblogs.com/haoworld/p/distributed-fen-bu-shi-suo.html
Copyright © 2020-2023  润新知