教你如何使用redis分布式锁

文章目录

  • 一、redis客户端实现
    • 应用
      • 1.利用set nx命令实现分布式锁
      • 2.利用分布式锁命令 setnx
    • 问题
      • 1.为什么不直接调用jedis.del(key)方法而采用redis+lua实现?
      • 2.上述两种方式存在的问题?
      • 3.根本原因分析
  • 二、分布式场景Redission分布式锁的使用
    • 1.分布式锁的特性
    • 2.分布式锁的应用场景
  • 三、zookeeper分布式锁和redis分布式的区别
    • 1.对于 Redis 的分布式锁而言,它有以下缺点:
    • 2.对于 ZK 分布式锁而言:
    • 3.使用建议
    • 4.参数对比

一、redis客户端实现

应用

1.利用set nx命令实现分布式锁

    Jedis jedis = new Jedis("127.0.0.1", 6309);public boolean getLock(String lockKey, String requestId, int expireTime) {//NX:保证互斥性//hset 原子性操作 只要lockKey有效 则说明有进程在使用分布式锁// key:lockKey  value:requestId NX:仅在键不存在时设置键 EX:设置指定的到期时间(以秒为单位)String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);if ("OK".equals(result)) {return true;}return false;}/*** 释放分布式锁* @param lockKey* @param requestId*/public void releaseLock(String lockKey,String requestId) {if (requestId.equals(jedis.get(lockKey))) {jedis.del(lockKey);}}

2.利用分布式锁命令 setnx

    public static boolean getLock2(Jedis jedis, String lockKey, String requestId, int expireTime) {Long result = jedis.setnx(lockKey, requestId);//成功设置 进程down 永久有效 别的进程就无法获得锁if(result == 1) {jedis.expire(lockKey, expireTime);return true;}return false;}public static boolean releaseLock2(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 (result.equals(1L)) {return true;}return false;}public static void main(String[] args) {String redisLock = "my_lock";ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i <100; i++){executorService.execute(() -> {Jedis jedis = new Jedis("127.0.0.1", 6379);UUID uuid = UUID.randomUUID();System.out.println(uuid.toString() + "用户进来");boolean isLock = getLock2(jedis, redisLock, uuid.toString(), 30000);if (isLock){System.out.println(uuid.toString() + "用户尝试获得锁成功!");releaseLock2(jedis, redisLock, uuid.toString());System.out.println(uuid.toString() + "用户释放锁");}else{System.out.println(uuid.toString() + "用户获得锁失败");}});}}

在这里插入图片描述

问题

1.为什么不直接调用jedis.del(key)方法而采用redis+lua实现?

由于jedis.del(key)方法是删除当前key不会区别当前是哪个客户端,而采用redis+lua方式只有当前获得锁的客户端才有资格删除。例如,线程A获得分布式锁,线程B调用jedis.del(key)方法会把线程A的锁删除掉。

2.上述两种方式存在的问题?

  1. 单机,无法保证高可用
  2. 主从,无法保证数据强一致性,会去从库重复获得锁
    在这里插入图片描述
  3. 无法续租,超过expireTime后,不能继续使用

3.根本原因分析

CAP(Consistent一致性、Available可用性、Partition分区)原则,三者只能选其二,因此在分布式场景下p不能舍弃,那么只能是AP、CP原则。

二、分布式场景Redission分布式锁的使用

Redisson是架设在Redis基础上的一个java驻内存数据网格。
Redission在基于NIO的Netty框架上,生成环境使用分布式锁。

数据网格:是将空间上不均匀分布的数据,按一定方法(如滑动平均法、克里格法或其他适当的数值推算方法)归算成规则网格中的代表值(趋势值)的过程

Redis集群至少需要3个master节点,所以现在总共有6个节点,就只能是1master对应1slave这种方式,1master-2slave,redis集群需要9个节点,以此类推。

配置代码:

package com.learn.cache;import org.redisson.Redisson;
import org.redisson.config.Config;public class RedissonManager {private static Config config = new Config();private static Redisson redisson = null;static {config.useClusterServers()//集群状态扫描间隔时间,单位是毫秒.setScanInterval(2000).addNodeAddress("redis://127.0.0.1:7001").addNodeAddress("redis://127.0.0.1:7002").addNodeAddress("redis://127.0.0.1:7003").addNodeAddress("redis://127.0.0.1:7004").addNodeAddress("redis://127.0.0.1:7005").addNodeAddress("redis://127.0.0.1:7006");redisson = (Redisson) Redisson.create(config);}public static Redisson getRedisson() {return redisson;}}

使用demo:

package com.learn.cache;import org.redisson.Redisson;
import org.redisson.api.RLock;import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import static java.util.concurrent.TimeUnit.SECONDS;public class DistributedRedisLock {private static final String LOCK_TITLE = "redisLock_";//从配置类中获取redisson对象private static Redisson redisson = RedissonManager.getRedisson();//加锁public static boolean acquire(String lockName) {//声明key对象String key = LOCK_TITLE + lockName;//获取锁对象RLock mylock = redisson.getLock(key);//加锁,并且设置锁过期时间3秒,防止死锁的产生 uuid+threadIdmylock.lock(3, SECONDS);//加锁成功return true;}//锁的释放public static void release(String lockName) {//必须是和加锁时的同一个keyString key = LOCK_TITLE + lockName;//获取所对象RLock mylock = redisson.getLock(key);//释放锁(解锁)mylock.unlock();}public static void main(String[] args) {String key = "lock001";ExecutorService executorService = Executors.newFixedThreadPool(20);for (int i = 0; i < 100; i++){executorService.execute(() -> {//加锁boolean acquire = acquire(key);String uuid = UUID.randomUUID().toString();if (acquire){System.out.println(uuid + "用户获得锁成功");release(key);System.out.println(uuid + "用户释放锁");}else{System.out.println(uuid + "用户获得锁失败");}});}}}

1.分布式锁的特性

  1. 互斥性:任意时刻保持只能有一个客户端获得锁
  2. 同一性:锁只能被同一个客户端删除,不能被其他客户端删除,同上jedis.setnx(key)不能利用jedis.del(key)删除
  3. 可重入性:持有该锁的客户端可重复获得锁
  4. 容错性:锁对象由于服务挂掉,会自动释放锁,防止死锁

2.分布式锁的应用场景

  1. 抢红包、秒杀下单场景等,由于红包、库存的数量是确定的,如果此时同时有多个用户抢红包、下订单,此时如果不利用分布式锁处理的话,那么会出现并发问题。
  2. 详细应用场景可参考: 文章

三、zookeeper分布式锁和redis分布式的区别

彻底讲清楚ZooKeeper分布式锁的实现原理

zk分布式锁的使用案例

1.对于 Redis 的分布式锁而言,它有以下缺点:

  1. 它获取锁的方式简单粗暴,获取不到锁直接不断尝试获取锁,比较消耗性能。
  2. 另外来说的话,Redis的设计定位决定了它的数据并不是强一致性的,在某些极端情况下,可能会出现问题。锁的模型不够健壮。 即便使用 Redlock算法来实现,在某些复杂场景下,也无法保证其实现 100% 没有问题,关于 Redlock 的讨论可以看 How to do distributed locking。

但是另一方面使用 Redis 实现分布式锁在很多企业中非常常见,而且大部分情况下都不会遇到所谓的“极端复杂场景”。

所以使用 Redis 作为分布式锁也不失为一种好的方案,最重要的一点是 Redis 的性能很高,可以支撑高并发的获取、释放锁操作。

2.对于 ZK 分布式锁而言:

ZK 天生设计定位就是分布式协调,强一致性。锁的模型健壮、简单易用、适合做分布式锁。
如果获取不到锁,只需要添加一个监听器就可以了,不用一直轮询,性能消耗较小。

但是 ZK 也有其缺点:如果有较多的客户端频繁的申请加锁、释放锁,对于 ZK 集群的压力会比较大

3.使用建议

就个人而言的话,我比较推崇 ZK 实现的锁:因为 Redis 是有可能存在隐患的,可能会导致数据不对的情况。但是,怎么选用要看具体在公司的场景了。

如果有 ZK 集群条件,优先选用 ZK 实现,但是如果说公司里面只有 Redis 集群,没有条件搭建 ZK 集群。

那么其实用 Redis 来实现也可以,另外还可能是系统设计者考虑到了系统已经有 Redis,但是又不希望再次引入一些外部依赖的情况下,可以选用 Redis。这个是要系统设计者基于架构来考虑了。

4.参数对比

在这里插入图片描述
一个ap模型不适合强一致的场景 一个cp虽然适合,但是每次节点交互携带的数据会限制节点的数量。

zk写都在leader,不适合做高并发的分布式锁。

数据库实现分布式锁,性能太差。

具体采用何种,还需要根据自身业务场景去选择 !!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/508849.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【记录保存】批量删除进程

kill -9 ps -ef|grep redis-server | awk {print $2} print $2 选择进程id

本地缓存之Guava简单使用

文章目录使用场景Guava Cache 的优势Guava Cache使用CacheLoaderCallable删除主动删除过期删除基于容量删除引用删除高级用法并发设置更新锁定GuavaCache高级实战之疑难问题GuavaCache会oom&#xff08;内存溢出&#xff09;吗GuavaCache缓存到期就会立即清除吗GuavaCache如何找…

本地缓存之LIFO、LRU、FIFO、LFU实现

文章目录LIFO算法实现LRU算法算法核心实现FIFO算法LFU算法LIFO算法 后进先出,利用栈实现 实现 Testpublic void stackDemo(){Stack<Integer> stack new Stack<>();for (int i 1; i < 4; i){stack.add(i);}ArrayList<Integer> list new ArrayList<…

java中强引用、弱引用、软引用、虚引用学习

文章目录强引用弱引用软引用虚引用将引用之前首先让我们一起回顾一下java对象的生命周期强引用 在实际开发场景中&#xff0c;我们一般使用的都是强引用&#xff0c;只要强引用存在&#xff0c;垃圾回收即使OOM也不会回收&#xff0c;知道强引用释放以后&#xff0c;对象才会被…

mysql left join、right join、inner join、union、union all使用以及图解

左外连接&#xff1a;left join sql语法&#xff1a;LEFT JOIN LEFT OUTER JOIN 首先需要创建两张表做测试&#xff0c;表数据如下所示 table 1 表&#xff1a; table2 表&#xff1a; 查询sql&#xff1a; select * from table1 a LEFT JOIN table2 b on a.idb.id 总结&a…

java 将海外时区转换为北京时区

//默认为上海时区TimeZone tz TimeZone.getDefault();//北京时区tz TimeZone.getTimeZone("GMT8");Date date new Date(System.currentTimeMillis());// 获取默认的DateFormat&#xff0c;用于格式化DateSimpleDateFormat simpleDateFormat new SimpleDateFormat…

Java规则引擎-MVEL表达式解析器

MVEL是一个功能强大的基于Java应用程序的表达式语言。 目前最新的版本是2.0&#xff0c;具有以下特性&#xff1a; (1). 动态JIT优化器。当负载超过一个确保代码产生的阈值时&#xff0c;选择性地产生字 节代码,这大大减少了内存的使用量。新的静态类型检查和属性支持&#xff…

第十八章 Swing程序设计

Swing用于开发桌面窗体程序&#xff0c;是JDK的第二代GUI框架&#xff0c;其功能比JDK第一代GUI框架AWT更为强大、性能更加优良。但因为Swing技术推出时间太早&#xff0c;其性能、开发效率等不及一些其他流行技术&#xff0c;所以目前市场上大多数桌面窗体程序都不是由Java开发…

ConcurrentLinkedQueue常用方法

Testpublic void queuedemo(){ConcurrentLinkedQueue<Integer> queue new ConcurrentLinkedQueue();for (int i 0; i < 5; i){queue.add(i);}//获取元素 不移除头结点for (int i 0; i < 3; i){System.out.println("peek: " queue.peek());}//返回在此…

redis stream学习总结

文章目录streamStream基本概念消息id消息内容增删查改消息生产添加消息 xadd查看消息长度 xlen限制stream最大长度1.xadd 中添加**maxlen**:2.xtrim查询消息 xrange正向排序&#xff1a;消费id从小到大排反向查询&#xff1a;消费id从大到小排删除消息消息消费独立消费 xread消…

jedis StreamEntryID参数解释

//$ 在给定Stream中已经包含的最大ID&#xff0c;在xread、xcreategroup中标识消费着只能消费最新消息 StreamEntryID.LAST_ENTRY; “>” 在消费者组的上下文中使用&#xff0c;在xread、xreadgroup总标识消费未消费过的消息 StreamEntryID.UNRECEIVED_ENTRY; 如果传入的为…

RabbitMQ TTL、死信队列在订单支付场景的应用

基于RabbitMQ的TTL以及死信队列&#xff0c;使用SpringBoot实现延迟付款&#xff0c;手动补偿操作。 1、用户下单后展示等待付款页面 2、在页面上点击付款的按钮&#xff0c;如果不超时&#xff0c;则跳转到付款成功页面 3、如果超时&#xff0c;则跳转到用户历史账单中查看…

阿里巴巴Java开发手册-使用JDK8的Opional类来防止出现NPE问题

/*** https://www.baeldung.com/java-optional*/Testpublic void optionalTest(){Peo peo new Peo("weijie", 18);/*** of、ofNullable*///of 判断peo是否为空&#xff0c;如果不为空程序继续执行Optional<Peo> _of Optional.of(peo);//程序直接抛出NullExce…

阿里巴巴Java开发手册-finally块必须对资源对象、流对象进行关闭操作,如果有异常也要做try-cach操作

对于JDK7及以上版本&#xff0c;可以使用try-with-resources方式 使用方式&#xff1a; /*** https://www.cnblogs.com/itZhy/p/7636615.html* 其实这种方式只是语法糖&#xff0c;反编译以后还是tryCacheThrowTest()中的代码* https://www.cnblogs.com/langtianya/p/5139465.h…

阿里巴巴Java开发手册-日志规约

1.【强制】 应用中不可直接使用日志系统(Log4j、Logback)中的API&#xff0c;而应依赖使用的SLF4j中的API。使用门面模式的日志框架吗&#xff0c;有利于维护和各个类的日志处理方式统一。 import org.slf4j.Logger;import org.slf4j.LoggerFactory;Logger logger LoggerFacto…

Java 回调 (Callback) 接口学习使用

文章目录Java 回调 (Callback) 接口学习使用1.什么是回调(Callback)&#xff1f;2.Java代码示例2.直接调用3.接口调用4.Lambda表达式推荐看我的InfoQ地址&#xff0c;界面排版更简洁Java 回调 (Callback) 接口学习使用 1.什么是回调(Callback)&#xff1f; 回调函数&#xff0…

常用的限流算法学习

常用的限流算法有漏桶算法和令牌桶算法&#xff0c;guava的RateLimiter使用的是令牌桶算法&#xff0c;也就是以固定的频率向桶中放入令牌&#xff0c;例如一秒钟10枚令牌&#xff0c;实际业务在每次响应请求之前都从桶中获取令牌&#xff0c;只有取到令牌的请求才会被成功响应…

基于rocketMq秒杀系统demo

基于RocketMQ设计秒杀。 要求&#xff1a; 1. 秒杀商品LagouPhone&#xff0c;数量100个。 2. 秒杀商品不能超卖。 3. 抢购链接隐藏 4. NginxRedisRocketMQTomcatMySQL 实现 接口说明&#xff1a;https://www.liuchengtu.com/swdt/#R9f978d0d00ef9be99f0…

常见压缩算法学习

文章目录无损压缩算法理论基础信息熵熵编码字典编码综合通用无损压缩算法相关常见名词说明java对几种常见算法实现Snappydeflate算法Gzip算法huffman算法Lz4算法Lzo算法使用方式无损压缩算法理论基础 信息熵 信息熵是一个数学上颇为抽象的概念&#xff0c;在这里不妨把信息熵理…

java中钩子方法 addShutdownHook 学习使用

钩子作用&#xff1a; 在线上Java程序中经常遇到进程程挂掉&#xff0c;一些状态没有正确的保存下来&#xff0c;这时候就需要在JVM关掉的时候执行一些清理现场的代码。Java中得ShutdownHook提供了比较好的方案。 JDK在1.3之后提供了Java Runtime.addShutdownHook(Thread hook)…