zoukankan      html  css  js  c++  java
  • jedis 与 redission 实现分布式锁

    本文为博主原创,未经允许不得转载:

    目录:

      1. Jedis 实现分布式锁

      2. Redission 实现分布式锁

      为了确保分布式锁可用,至少要保证锁的实现同时满足以下几个条件

    • 互斥性:在任意时刻只有一个客户端能持有锁
    • 不会死锁:即使有一个客户端在持有锁的期间发生崩溃而没有主动解锁,也能保证后续其它客户端能加锁
    • 容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁
    • 解铃还须系铃人:加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解除

    1. Jedis 实现分布式锁

        1.1 引入 jedis 依赖:

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

        1.2 Jedis 封装工具类,封装分布式锁及解锁方法

    package com.example.demo.util;
    
    import redis.clients.jedis.Jedis;
    
    import java.util.Collections;
    
    public class JedisUtils {
        private static final String LOCK_SUCCESS = "OK";
        private static final String SET_IF_NOT_EXIST = "NX";
        private static final String SET_WITH_EXPIRE_TIME = "PX";
        private static final Long RELEASE_SUCCESS = 1L;
    
        /**
         * 尝试获取分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @param expireTime 超期时间
         * @return 是否获取成功
         */
        public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    
            String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
    
            if (LOCK_SUCCESS.equals(result)) {
                return true;
            }
            return false;
        }
    
        /**
         * 释放分布式锁
         * @param jedis Redis客户端
         * @param lockKey 锁
         * @param requestId 请求标识
         * @return 是否释放成功
         */
        public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
    
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));
    
            if (RELEASE_SUCCESS.equals(result)) {
                return true;
            }
            return false;
    
        }
    }

       获取分布式锁使用的是 Jedis 内部封装的  set(String key, String value, String nxxx, String expx, int time) 方法。该方法可保证 redis 获取分布式锁操作的原子性。在网上会看到通过 jedis 的 setnx (lockKey, requestId) 与 jedis.expire(lockKey, expireTime) 两步来获取分布式锁,这种将获取分布式锁 分为两步或三步的,并添加逻辑校验的,往往看起来并没什么问题,但这种破坏了原子性,在高并发场景性,会存在丢锁的场景。建议使用以上的方式 获取分布式锁。同理分布式锁解锁也一样,而 lua 脚本具有天然的原子性,可以保证执行的安全性。

      

    2. Redission 实现分布式锁

      redisson简单易用、支持锁重入、支持阻塞等待、Lua脚本原子操作等,内部封装了很多redis 的解决方案,使用reids 客户端时,优先推荐使用 redission 客户端。

      2.1 引入依赖:

       <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>3.8.2</version>
            </dependency>

      2.2 定义 redisision 客户端配置

    package com.example.demo.config;
    
    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class RedissonConfig {
        @Bean
        public RedissonClient getClient() {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://127.0.0.1:6379");
            RedissonClient redisson = Redisson.create(config);
            return redisson;
        }
    
    }

      2.3 常用方法:

    package com.example.demo.util;
    
    import org.redisson.api.RLock;
    import org.redisson.api.RedissonClient;
    import org.springframework.stereotype.Service;
    
    import java.util.concurrent.TimeUnit;
    
    @Service
    public class RedissionUtils {
    
        private RedissonClient redissonClient;
    
        public void test(){
            RLock lock = redissonClient.getLock("lockName");
            try{
                // 1. 最常见的使用方法
                lock.lock();
                boolean lockResult = lock.tryLock();
                // 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
                //lock.lock(10, TimeUnit.SECONDS);
                // 3. 尝试加锁,最多等待2秒,上锁以后8秒自动解锁
                boolean res = lock.tryLock(2, 8, TimeUnit.SECONDS);
                if(res){ //成功
                    //处理业务
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    
    }

      方法说明:

         lock.lock(); 与 boolean lockResult = lock.tryLock(); 方法均为获取分布式锁,前面方法无返回值,后面方法返回值为 boolean 类型。该方法获取分布式锁会自动续锁,即通过redission 内部封装的看门狗进行任务续时,jedis 分布式锁不支持任务续时,如果在锁时间内,任务尚未执行完,则会丢锁。

         lock.lock(); 与 lock.tryLock();  方法如果带有入参,则不会执行看门狗模式,即不会分布式锁续时。

        boolean tryLock(long waitTime, long leaseTime, TimeUnit var5) throws InterruptedException;  获取分布式最多等待时间waitTime,分布式锁过期时间leaseTime。

        续时任务执行的源码中可看到看门狗模式的执行,代码如下:
        
    private RFuture<Boolean> tryAcquireOnceAsync(long leaseTime, TimeUnit unit, final long threadId) {
            if (leaseTime != -1L) {
           // 传参
    return this.tryLockInnerAsync(leaseTime, unit, threadId, RedisCommands.EVAL_NULL_BOOLEAN); } else {
           // 不传参。
    getLockWatchdogTimeout ,看门狗监听超时,如果超时则续时
    RFuture<Boolean> ttlRemainingFuture = this.tryLockInnerAsync(this.commandExecutor.getConnectionManager().getCfg().getLockWatchdogTimeout(), TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_NULL_BOOLEAN); ttlRemainingFuture.addListener(new FutureListener<Boolean>() { public void operationComplete(Future<Boolean> future) throws Exception { if (future.isSuccess()) { Boolean ttlRemaining = (Boolean)future.getNow(); if (ttlRemaining) { RedissonLock.this.scheduleExpirationRenewal(threadId); } } } }); return ttlRemainingFuture; } }
        lock.unlock(); 释放分布式锁。


    3. redis 分布式锁优化思考
        

      每秒上千订单场景下的分布式锁高并发优化实践!【石杉的架构笔记】:https://mp.weixin.qq.com/s/RLeujAj5rwZGNYMD0uLbrg



  • 相关阅读:
    MySQL 重要参数 innodb_flush_log_at_trx_commit 和 sync_binlog
    mysql物理日志和逻辑日志_mysql物理日志redo log和逻辑日志 binlog
    Navicat Premium for Mac 破解版
    qps是什么
    如何实现扫码登录功能?
    goland debug
    [Golang] 初探之 sync.Once
    go语言:sync.Once的用法
    Golang进程权限调度包runtime三大函数Gosched,Goexit,GOMaXPROCS
    Go unsafe 包之内存布局
  • 原文地址:https://www.cnblogs.com/zjdxr-up/p/15173883.html
Copyright © 2011-2022 走看看