Redis分布式锁实现方式及超时问题解决

作者:知识追寻者 时间:2023-08-24 23:28:34 

一 前言

redis在分布式应用十分广泛,本篇文章也是互联网面试的重点内容,读者至少需要知道为什么需要分布式锁,分布式锁的实现原理,分布式锁的应用场景,在使用分布式锁时遇到哪些问题?你是如何解决的,如果读者能掌握以上问题,那么关于这道面试题,你也就基本过关了;

二 分布式锁的产生背景

分布式锁对应的是多个应用,每个应用中都可能会处理相同的数据,如果多个应用对用一个操作进行了重复操作,就会出现数据不一致,数据重复问题,于是分布式锁应用而生,通常你可以理解为多线程中的synchronized

三 分布式锁的应用场景

多台机器都能执行某个任务,如果限制任务每次只能被一台机器执行,不能重复执行,就可以用分布式锁来做标记秒杀场景,要求并发量很高,那么同一件商品只能被一个用户抢到,就可以使用分布式锁实现比较敏感的数据比如金额修改,同一时间只能有一个人操作,如果2个人同时修改金额,一个加一个减金额,为了防止同时操作造成数据不一致,就可以使用分布式锁实现

四 分布式锁的实现

4.1 分布式锁的实现方式

  • 基于数据库实现分布式锁

  • 基于缓存(redis,memcached,tair)实现分布式锁

  • 基于Zookeeper实现分布式锁

4.2 分布式锁使用原理

每个应用对敏感数据进行操作时都需要向获取一个锁,持有锁的应用才能对数据进行操作,保证在同一时间内只有一台应用能对数据进行操作;

4.3 分布式锁实现过程

基本实现思路:

redis分布式实现是基于 命令setnx key value , 其意指 若该键不存在则创建键,这就保证了redis中只有一个该键,故应用谁先获得该键,谁就拿到了锁的权限;然后业务逻辑执行完毕则需要使用 del key 删除键,表示释放锁;

出现了问题:

如果一台业务逻辑执行完毕,程序出现异常,则锁会一直存在,没有得到释放,其它应用就会无法获得锁,此时就会造成死锁问题;

改进方式:

拿到锁之后,给锁加上一个过期时间,也就是 expire key seconds 指令;此时避免了死锁问题,但是由于业务逻辑执行的时间不同,过期的时间设置也是一个问题,故通常分布式锁不能应用于业务逻辑执行较长的程序;

出现问题:

由于redis 每条指令都是原子性操作,但由于setnx 和 expire 是2 条指令,如果在执行setnx后程序出现问题expire指令未得到执行就会造成死锁问题;

解决问题:

redis2.8版本之后引入了指令 set key value [EX seconds] [PX milliseconds] [NX|XX] ,该指令可以同时执行 setnx 和 expire ,于是解决了死锁问题;

参数列表解释

  • EX seconds: 设定过期时间,单位为秒

  • PX milliseconds: 设定过期时间,单位为毫秒

  • NX: key不存在时设置值

  • XX: key存在时设置值

使用jedis客户端实现分布式锁方式


public boolean lock(Jedis jedis,String key,String val,int expireTime){
 String lock = jedis.set(key, val, "NX", "PX",
     expireTime);
 return "OK".equals(lock);
}

关于未获得锁的解决思路:

可以直接抛出异常让客户重试

可以使用延迟队列

五 分布式锁的超时问题

问题:

如果在加锁和释放锁之间,业务逻辑执行时间太长,导致超出了锁的超时限制,就会出现锁过期问题;换句话说,就是第一台应用执行了业务,导致锁过期;第二台应用此时可以获得锁,进行执行业务,此时第一台应用释放了锁,第二台应用在执行业务的时第三台应用获得了锁执行业务,导致在执行过程中,会有2台应用在同时执行业务逻辑;

解决思路:

在释放锁的时候出现了问题,即每台应用都可以释放锁,这会造成1应用的锁释放了2应用锁的问题,换句话说,很多人手中持有的钥匙是通用的,都可以开同一个门;为了避免这个问题,就是1 应用只能释放1应用上的锁,2应用只能释放2应用上的锁,则需要对释放锁进行身份校验;由于上锁的时候key是唯一,但value可以不同,所以可以根据value进行身份的唯一标识,随机数就是一个很好的选择 :

String value = UUID.randomUUID().toString();

由于考虑到匹配到value校验和del不是同一个操作,故需要使用Lua脚本实现多条指令的原子性执行;

jedis释放锁实现方式:


public void unlock(Jedis jedis,String key,String value) {
   String script_command = "if redis.call('get',KEYS[1]) == ARGV[1] then " +
       "return redis.call('del',KEYS[1]) else return 0 end";
   // 解锁
   jedis.eval(script_command, Collections.singletonList(key), Collections.singletonList(value));

}

来源:https://www.cnblogs.com/zszxz/p/12488852.html

标签:Redis,分布,锁,超时
0
投稿

猜你喜欢

  • Mybatis-Plus 全局配置无效的解决方案

    2022-06-29 12:46:02
  • 深入探究Spring底层核心原理

    2023-03-05 08:32:16
  • android同时控制EditText输入字符个数和禁止特殊字符输入的方法

    2021-08-01 14:16:46
  • Java 客户端操作 FastDFS 实现文件上传下载替换删除功能

    2022-06-01 15:01:38
  • Java实现月饼的制作、下单和售卖功能

    2023-03-06 18:26:24
  • Mybatis-Plus sum聚合函数及按日期查询并求和的方式详解

    2022-07-09 12:58:07
  • 基于java集合中的一些易混淆的知识点(详解)

    2023-08-29 03:06:26
  • C#实现泛型List分组输出元素的方法

    2022-03-10 07:33:15
  • 使用JPA自定义VO类型转换(EntityUtils工具类)

    2023-08-26 14:56:17
  • Java中ArrayList在foreach里remove的问题详析

    2022-08-04 02:30:40
  • Java 17的一些新特性介绍

    2022-04-11 15:33:15
  • MyBatis的SQL执行结果和客户端执行结果不一致问题排查

    2022-12-30 20:37:22
  • C#中的值传递和引用传递详细解析

    2022-03-20 20:11:02
  • Flutter实现底部导航栏创建详解

    2023-09-29 10:01:04
  • 使用 Lambda 取代 Android 中的匿名类

    2023-11-16 14:01:04
  • Android中获取网页表单中的数据实现思路及代码

    2021-07-04 19:51:18
  • Java打印出所有的水仙花数的实现代码

    2023-03-06 17:24:22
  • Winform实现调用asp.net数据接口实例

    2021-12-16 09:12:48
  • 详解在Spring中如何自动创建代理

    2023-11-15 15:28:52
  • Android7.0 工具类:DiffUtil详解

    2023-02-24 01:43:46
  • asp之家 软件编程 m.aspxhome.com