jedis 与 redission 实现分布式锁

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 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




标签: redis

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
11月前
|
存储 NoSQL Java
分布式锁中的王者方案 - Redission
分布式锁中的王者方案 - Redission
初识Redission分布式锁
在微服务系统中,某些场景需要阻塞所有节点的所有线程,对共享资源的访问。比如并发时“超卖”和“余额减为负数”等情况,需要对同一资源进行加锁,这些就需要进行分布式。
初识Redission分布式锁
|
3月前
|
NoSQL Java Redis
通过Redis 实现分布式锁_利用Jedis 客户端
通过Redis 实现分布式锁_利用Jedis 客户端
|
4月前
|
存储 NoSQL Java
分布式锁,Redission,其它实现问题讲解,以及面试题回答案例
分布式锁,Redission,其它实现问题讲解,以及面试题回答案例
176 1
|
4月前
|
缓存 NoSQL 数据库
关于高并发下缓存失效的问题(本地锁 && 分布式锁 && Redission 详解)
关于高并发下缓存失效的问题(本地锁 && 分布式锁 && Redission 详解)
220 0
|
4月前
|
分布式计算 NoSQL Java
基于内存的分布式NoSQL数据库Redis(四)Jedis:使用方式
基于内存的分布式NoSQL数据库Redis(四)Jedis:使用方式
206 0
|
10月前
|
NoSQL Java Redis
jedis分布式锁实现
jedis分布式锁实现
118 0
|
Java Maven
Redission 实现分布式锁
Redission 实现分布式锁
132 1
|
人工智能 NoSQL Java
springboot 高级教程 如何优雅使用redission分布式锁
springboot 高级教程 如何优雅使用redission分布式锁
1018 0
|
存储 NoSQL Java
基于springboot+Redis的前后端分离项目之分布式锁-redission(五)-【黑马点评】
基于setnx实现的分布式锁存在下面的问题: 重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都是使用synchronized修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的。