青铜峡网站建设推广,最新新闻热点头条,快速制作网站,软文有哪几种类型文章目录简介设计分布式锁应该考虑的东⻄基于Redis实现分布式锁总结解决解锁的原子性代码实现遗留⼀个问题简介 分布式锁核⼼知识介绍和注意事项 背景 就是保证同⼀时间只有⼀个客户端可以对共享资源进⾏操作 案例 优惠券领劵限制张数、商品库存超卖 核⼼ 为了防⽌分布式系统中…
文章目录简介设计分布式锁应该考虑的东⻄基于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 (key30s 综合伪代码 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;ifsetnxkey1 1{expire(key,30,TimeUnit.MILLISECONDS)try {//做对应的业务逻辑//查询⽤户是否已经领券//如果没有则扣减库存//新增领劵记录} finally {delkey}}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_66String 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 RedisTemplateString,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 使⽤特别简单