文章目录

  • 简介
  • 设计分布式锁应该考虑的东⻄
  • 基于Redis实现分布式锁
  • 总结
  • 解决解锁的原子性
  • 代码实现
  • 遗留⼀个问题

简介

分布式锁核⼼知识介绍和注意事项

背景

就是保证同⼀时间只有⼀个客户端可以对共享资源进⾏操作

案例

优惠券领劵限制张数、商品库存超卖

核⼼

为了防⽌分布式系统中的多个进程之间相互⼲扰,我们需要⼀种分布式协调技术来对这些进程进⾏调度利⽤互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题避免共享资源并发操作导致数据问题

加锁

本地锁:synchronize、lock等,锁在当前进程内,集群部署下依旧存在问题
分布式锁:redis、zookeeper等实现,虽然还是锁,但是多个进程共⽤的锁标记,可以⽤Redis、Zookeeper、Mysql等都可以

设计分布式锁应该考虑的东⻄

1.排他性 --在分布式应⽤集群中,同⼀个⽅法在同⼀时间只能被⼀台机器上的⼀个线程执⾏
2.容错性 --分布式锁⼀定能得到释放,⽐如客户端奔溃或者⽹络中断
3.满⾜可重⼊、⾼性能、⾼可⽤
4.注意分布式锁的开销、锁粒度

基于Redis实现分布式锁

实现分布式锁 可以⽤ Redis、Zookeeper、Mysql数据库这⼏种 , 性能最好的是Redis

分布式锁离不开 key - value 设置
key 是锁的唯⼀标识,⼀般按业务来决定命名,⽐如想要给⼀种优惠券活动加锁,key 命名为 “coupon:id” 。value就可以使⽤固定值,⽐如设置成1

加锁 SETNX key value
解锁 del (key)
配置锁超时 expire (key,30s)

综合伪代码

1.setnx 的含义就是 SET if Not Exists,有两个参数setnx(key, value),该⽅法是原⼦性操作
2.如果 key 不存在,则设置当前 key 成功,返回 1
3.如果当前 key 已经存在,则设置当前 key 失败,返回 0
4.得到锁的线程执⾏完任务,需要释放锁,以便其他线程可以进⼊,调⽤ del(key)
5.客户端奔溃或者⽹络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在⼀定时间后⾃动释放

methodA(){String key = "coupon_66";if(setnx(key,1) == 1){expire(key,30,TimeUnit.MILLISECONDS)try {//做对应的业务逻辑//查询⽤户是否已经领券//如果没有则扣减库存//新增领劵记录} finally {del(key)}}else{//睡眠100毫秒,然后⾃旋调⽤本⽅法methodA()}
}

存在什么问题?

多个命令之间不是原⼦性操作,如setnx和expire之间,如果setnx成功,但是expire失败,且宕机了,则这个资源就是死锁业务超时,存在其他线程勿删,key 30秒过期,假如线程A执⾏很慢超过30秒,则key就被释放了,其他线程B就得到了锁,这个时候线程A执⾏完成,⽽B还没执⾏完成,结果就是线程A删除了线程B加的锁

问题解决

使⽤原⼦命令:设置和配置过期时间 setnx / setex
如: set key 1 ex 30 nx
java⾥⾯redisTemplate.opsForValue().setIfAbsent(“seckill_1”,“success”,30,TimeUnit.MILLISECONDS)
可以在 del 释放锁之前做⼀个判断,验证当前的锁是不是⾃⼰加的锁, 那 value 应该是存当前线程的标识或者uuid
String key = "coupon_66"String value = Thread.currentThread().getId()
进⼀步细化误删当线程A获取到正常值时,返回带代码中判断期间锁过期了,线程B刚好重新设置了新值,线程A那边有判断value是⾃⼰的标识,然后调⽤del⽅法,结果就是删除了新设置的线程B的值核⼼还是判断和删除命令不是原⼦性操作导致

总结

加锁+配置过期时间:保证原⼦性操作
解锁: 防⽌误删除、也要保证原⼦性操作

解决解锁的原子性

前⾯说了redis做分布式锁存在的问题核⼼是保证多个指令原⼦性,加锁使⽤setnx setex 可以保证原⼦性,那解锁使⽤ 判断和删除怎么保证原⼦性

多个命令的原⼦性:采⽤ lua脚本+redis, 由于【判断和删除】是lua脚本执⾏,所以要么全成功,要么全失败

//获取lock的值和传递的值⼀样,调⽤删除操作返回1,否则返回0
String script = "if redis.call('get',KEYS[1])== ARGV[1] then returnredis.call('del',KEYS[1]) else return 0 end";
//Arrays.asList(lockKey)是key列表,uuid是参数
Integer result = redisTemplate.execute(newDefaultRedisScript<>(script, Integer.class),Arrays.asList(lockKey), uuid);

代码实现

import net.xdclass.xdclassredis.util.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.time.Duration;
import java.util.Arrays;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/api/v1/coupon")
public class CouponController {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@GetMapping("add")public JsonData saveCoupon(@RequestParam(value = "coupon_id",required = true) int couponId){//防止其他线程误删String uuid = UUID.randomUUID().toString();String lockKey = "lock:coupon:"+couponId;lock(couponId,uuid,lockKey);return JsonData.buildSuccess();}private void lock(int couponId,String uuid,String lockKey){//lua脚本String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey,uuid,Duration.ofSeconds(30));System.out.println(uuid+"加锁状态:"+nativeLock);if(nativeLock){//加锁成功try{//TODO 做相关业务逻辑TimeUnit.SECONDS.sleep(10L);} catch (InterruptedException e) {} finally {//解锁Long result = redisTemplate.execute( new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockKey),uuid);System.out.println("解锁状态:"+result);}}else {//自旋操作try {System.out.println("加锁失败,睡眠5秒 进行自旋");TimeUnit.MILLISECONDS.sleep(5000);} catch (InterruptedException e) { }//睡眠一会再尝试获取锁lock(couponId,uuid,lockKey);}}}

遗留⼀个问题

锁的过期时间,如何实现锁的⾃动续期或者避免业务执⾏时间过⻓,锁过期了?

1.原⽣⽅式的话,⼀般把锁的过期时间设置久⼀点,⽐如10分钟时间
原⽣代码+redis实现分布式锁使⽤⽐较复杂,且有些锁续期问题更难处理

2.框架 官⽅推荐⽅式:https://redis.io/topics/distlock 使⽤特别简单

分布式锁之Redis6+Lua脚本实现原生分布式锁相关推荐

  1. Redis悲观锁、乐观锁和调用Lua脚本的优缺点

    悲观锁使用了数据库的锁机制,可以消除数据不一致性,对于开发者而言会十分简单,但是,使用悲观锁后,数据库的性能有所下降,因为大量的线程都会被阻塞,而且需要有大量的恢复过程,需要进一步改变算法以提高系统的 ...

  2. 【面试 分布式锁详细解析】续命 自旋锁 看门狗 重入锁,加锁 续命 解锁 核心源码,lua脚本解析,具体代码和lua脚本如何实现

    Redisson实现分布式锁原理 自己实现锁续命 在 controller 里开一个 线程 (可以为 守护线程) 每10秒,判断一个 这个 UUID是否存在,如果 存在,重置为 30秒. 如果不存在, ...

  3. 基于 Redis + Lua 脚本实现分布式锁,确保操作的原子性

    为了保证数据的争用安全,通常要采用锁机制控制. 如果是单应用部署,直接通过synchronized关键字修改方法,就能解决,但是如果是分布式的部署 该方法就不能解决这个问题啦,此时就引出了一个分布式锁 ...

  4. Redis Lua脚本的详细介绍以及使用入门

    Redis Lua脚本的详细介绍以及使用入门. 文章目录 Redis Lua脚本的引入 开源软件的可扩展性 Redis的扩展性脚本 Redis Lua脚本的基本使用 通过EVAL命令执行Lua脚本 通 ...

  5. OceanBase,走入原生分布式数据库的无人区

    数据智能产业创新服务媒体 --聚焦数智 · 改变商业 在数字化时代,数据量呈现指数级增长,尤其是视频.图像.语音等数据急速积累.数据库,在整个数据价值体系中,承担着基石的作用.海啸一般的数据正在向我们 ...

  6. 【2020尚硅谷Java大厂面试题第三季 04】Redis 9种数据类型使用场景,分布式锁演变步骤,lua脚本,redis事务,Redisson,Redis内存占用,删除策略,内存淘汰策略,手写LRU

    1.安装redis6.0.8 2023 02 02 为:redis-7.0.8.tar.gz 2.redis传统五大数据类型的落地应用 3.知道分布式锁吗?有哪些实现方案?你谈谈对redis分布式锁的 ...

  7. 【Redis Lua 脚本 可重入分布式锁】

    文章目录 前言 一.最简单的版本:setnx key value 获取锁成功 获取锁失败 释放锁 缺点 二.升级版本:set key value [ex seconds] [nx] 获取锁成功 获取锁 ...

  8. Redis分布式锁—SETNX+Lua脚本实现篇

    前言 平时的工作中,由于生产环境中的项目是需要部署在多台服务器中的,所以经常会面临解决分布式场景下数据一致性的问题,那么就需要引入分布式锁来解决这一问题. 针对分布式锁的实现,目前比较常用的就如下几种 ...

  9. 使用redis分布式锁+lua脚本实现分布式定时任务控制demo

    2019独角兽企业重金招聘Python工程师标准>>> 分布式系统经常要遇到定时任务执行的问题,不能重复执行,但很多时候又不能统一到一个微服务里面,因为这样就失去了微服务的意义.由于 ...

最新文章

  1. MRTG—网络监控工具
  2. Python中的变量以及赋值语句
  3. 图灵近期新书精彩不断,让你应接不暇!
  4. Shell脚本之反引号【``】和 $()
  5. 【Android NDK 开发】JNI 引用 ( 全局引用 | NewGlobalRef | DeleteGlobalRef )
  6. 字符串html在线互转,将string 的字符串转换为HTML的两种方法
  7. php删除禁用函数,百度云平台封禁的PHP函数列表 PHP禁用函数列表(转)
  8. OpenMP入门教程(二)reduce sum
  9. 【iOS-Cocos2d游戏开发】系列(总结了多篇文章,可以好好学习
  10. MySql中添加用户/删除用户
  11. express 随笔
  12. java面试题11 牛客:如下语句通过算术运算和逻辑运算之后i和 j的结果是
  13. 现代软件工程讲义 6 用户调研
  14. PHP key() 函数
  15. Linux Unix shell 编程指南学习笔记(第五部分)
  16. 备份容灾相关概念总结
  17. 2021-04-26 Matlab遗传算法工具箱的使用及实例(线性规划)
  18. matlab srgb,matlab – 将Photoshop sRGB复制到LAB转换
  19. T1292:宠物小精灵之收服
  20. 【机器学习】算法 之 决策树

热门文章

  1. php给html传值,PHP传值到不同页面的三种常见方式及php和html之间传值问题_PHP
  2. mysql-5.7.10-winx64 MySQL服务无法启动,服务没有报告任何错误的解决办法
  3. P6378 [PA2010] Riddle 2-sat + 前缀和优化建图
  4. Codeforces Round #615 (Div. 3) A-F
  5. 牛客网 【每日一题】7月27日题目精讲—乌龟棋
  6. AtCoder4515 [AGC030F] Permutation and Minimum(dp)
  7. 动态区间第k小:树状数组套权值线段树
  8. 2021牛客OI赛前集训营-提高组(第五场)C-第K排列【dp】
  9. [2020.10.25NOIP模拟赛]序列【Splay】
  10. jzoj6800-NOIP2020.9.19模拟spongebob【枚举】