三站一体网站公司,查询网 网站查询,英文成品网站模板下载,做一个页面多少钱目录
一、定义与原理
基于Redis的分布式锁
获取锁
释放锁
锁误删问题#xff1a;因为key值一样,将别人的锁删掉了
锁误判问题二#xff1a;判断锁和释放锁不是原子性的 Lua脚本 分布式锁#xff1a;满足分布式系统或集群模式下多进程可见并且互斥的锁
分布式锁的优点…目录
一、定义与原理
基于Redis的分布式锁
获取锁
释放锁
锁误删问题因为key值一样,将别人的锁删掉了
锁误判问题二判断锁和释放锁不是原子性的 Lua脚本 分布式锁满足分布式系统或集群模式下多进程可见并且互斥的锁
分布式锁的优点
多进程可见高可用安全性高性能互斥 知识点补充
多进程Multiprocessing是操作系统中的一个重要概念它指的是在操作系统中同时运行多个程序实例的能力。这些程序实例即进程Process是系统进行资源分配和调度的一个独立单元是程序在计算机上的一次执行活动。每个进程都有自己独立的内存空间和系统资源它们之间通过特定的机制如管道、消息队列、共享内存等进行通信和数据交换。 多线程Multithreading是指从软件或者硬件上实现多个线程并发执行的技术。在计算机编程中多线程是一种强大的工具它允许程序能够同时执行多个任务从而提高程序的执行效率和响应性。以下是对多线程的详细解释
一、定义与原理
定义多线程是并行化的一种形式通过拆分工作以便同时进行处理。在程序中这些独立运行的程序片段被称作“线程”Thread利用它编程的概念就叫作“多线程处理”。原理多线程的并发执行机制原理是将一个处理器划分为若干个短的时间片每个时间片依次轮流地执行处理各个线程由于时间片非常短相对于一个应用程序来说就好像是处理器在为自己单独服务一样从而达到多个应用程序或线程在同时进行的效果。 基于Redis的分布式锁
实现分布式锁时有两个要实现的基本方法
获取锁
互斥确保只能有一个线程获取锁非阻塞尝试一次成功返回true失败返回falseset lock thread ex 10 nx获取锁时存入线程标示(可以用UUID标示UUIDThreadId)
细节如果第一步 setnx lock thread
第二部 expire lock 10
那么如果redis在第一步执行完第二步执行前宕机了就有造成死锁的概率
释放锁
手动释放超时释放获取锁时加一个超时时间DEL KEY在释放锁时先获取锁中的线程标示判断是否与当前的线程标示一致
一致释放锁不一致不释放 锁误删问题因为key值一样,将别人的锁删掉了
问题线程1业务阻塞redis锁超时已经释放线程2拿到锁但线程1去手动释放锁时因为key一样把线程2的锁给释放了导致线程3拿到锁同时就有两个线程拿到了锁。
解决线程在手动释放锁的时候判断一下这是不是自己上的锁key的value值设为标示UUIDThreadId 、 ID_PREFIX 是一个静态的UUID前缀这意味着它对于类来说是唯一的但在多个JVM实例之间不是唯一的。不过在这个上下文中它主要用于与线程ID结合以形成一个更长的、理论上唯一的标识符。Thread.currentThread().getId() 获取当前线程的ID。这个ID在JVM内部是唯一的但跨JVM则不是。
线程id是JVM在创建线程时赋值的idid是自增的不同JVM的线程id可能相同
在这里锁对应的值不相同因为值的前缀有UUID不同的JVM不同的线程进来都会创建锁对象上锁每个锁对象在上锁时都会生成一个随机的UUID作为value的前缀所以key对应的值不同id标示不同就只能释放自己的锁。 未改进代码
public class SimpleRedisLock implements ILock{private String name;// 锁名称private final String KEY_PREFIXlock:;private final String ID_PREFIX UUID.randomUUID().toString(true)-;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name name;this.stringRedisTemplate stringRedisTemplate;}Overridepublic boolean tryLock(long timeoutSec) {// 给线程id加个uuid前缀避免在不同jvm下线程id相同,导致值相同String threadId ID_PREFIXThread.currentThread().getId();Boolean result stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId , timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(result);}Overridepublic void unLock() {String threadId ID_PREFIXThread.currentThread().getId();String id stringRedisTemplate.opsForValue().get(KEY_PREFIX name);if (threadId.equals(id)){stringRedisTemplate.delete(KEY_PREFIXname);}}
} 锁误判问题二判断锁和释放锁不是原子性的
在释放锁时线程1已经判断完了要去释放锁但JVM垃圾回收时遇到了阻塞时间过久后锁超时释放线程2拿到锁开始执行业务这时线程1恢复正常因为已经判断过锁是我上的了它还会去释放锁锁的key值都是 -lock:业务用户id它找到线程2上的锁并释放线程3就可以来拿到锁开始执行业务 Lua脚本
保证判断锁和释放锁原子性措施
Redis提供了Lua脚本功能在一个脚本中编写多条Redis命令确保多条命令执行时的原子性。Lua是一种编程语言
Redis提供的调用函数语法如下
redis.cail(命令名称,key其它参数...) 带参数的Lua语法 改进后的上锁和解锁
public class SimpleRedisLock implements ILock{private String name;// 锁名称private StringRedisTemplate stringRedisTemplate;private static final String KEY_PREFIXlock:;// 不同jvm来加载这个类时创建的uuid前缀不同但这个uuid是静态的无论对象创建多少次都是唯一确定的private static final String ID_PREFIX UUID.randomUUID().toString(true)-;private static final DefaultRedisScriptLong UNLOCK_SCRIPT;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name name;this.stringRedisTemplate stringRedisTemplate;}static {UNLOCK_SCRIPT new DefaultRedisScript();UNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.lua));UNLOCK_SCRIPT.setResultType(Long.class);}Overridepublic boolean tryLock(long timeoutSec) {// 给线程id加个uuid前缀避免在不同jvm下线程id相同,导致值相同String threadId ID_PREFIXThread.currentThread().getId();Boolean result stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId , timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(result);}Overridepublic void unLock() {// 执行lua脚本来确保判断id标示和执行释放锁的命令的原子性stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX name),ID_PREFIXThread.currentThread().getId());}
}