手表设计网站,成都网站建设博客,wordpress改回旧版编辑器,html产品介绍网页设计代码作业面试题#xff1a; Redis除了拿来做缓存#xff0c;你还见过基于Redis的什么用法#xff1f; 1.数据共享#xff0c;分布式Session 2.分布式锁 3.全局ID 4.计算器、点赞 5.位统计 6.购物车 7.轻量级消息队列#xff1a;list、stream 8.抽奖 9.点赞、签到、打卡 10.差集交集…面试题 Redis除了拿来做缓存你还见过基于Redis的什么用法 1.数据共享分布式Session 2.分布式锁 3.全局ID 4.计算器、点赞 5.位统计 6.购物车 7.轻量级消息队列list、stream 8.抽奖 9.点赞、签到、打卡 10.差集交集并集用户关注、可能认识的人推荐模型 11.热点新间、热搜排行榜 Redis做分布式锁的时候有需要注意的问题你们公司自己实现的分布式锁是否用的setnx命令实现这个是最合适的吗你如何考虑分布式锁的可重入问题如果是Redis是单点部署的会带来什么问题那你准备怎么解决单点问题呢Redis集群模式下比如主从模式CAP方面有没有什么问题呢那你简单的介绍一下Redlock吧你简历上写redisson你谈谈Redis分布式锁如何续期看门狗知道吗 分布式锁需要具备的条件和刚需
独占性
OnlyOne任何时刻只能有且仅有一个线程持有
高可用
若redis集群环境下不能因为某一个节点挂了而出现获取锁和释放锁失败的情况高并发请求下依旧性能OK好使
防死锁
杜绝死锁必须有超时控制机制或者撤销操作有个兜底终止跳出方案
不乱抢
防止张冠李戴不能私下unlock别人的锁只能自己加锁自己释放自己约的锁含着泪也要自己解
重入性
同一个节点的同一个线程如果获得锁之后它也可以再次获取这个锁 分布式锁
官网set 命令
使用set进行占锁删掉key代表锁释放。 setnx key value 注setnxexpire不安全两条命令非原子性的。
set key value [EX seconds] [PX milliseconds] [NX|XX] 重点JUC中AQS锁的规范落地参考可重入锁考虑Lua脚本Redist命令一步步实现分布式锁 案例
V1.0 版本基础案例
使用场景多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击。
1、pom.xml文件
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.atguigu.redislock/groupIdartifactIdredis_distributed_lock2/artifactIdversion1.0-SNAPSHOT/versionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.6.12/versionrelativePath/ !-- lookup parent from repository --/parentpropertiesproject.build.sourceEncodingUTF-8/project.build.sourceEncodingmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.targetlombok.version1.16.18/lombok.version/propertiesdependencies!--SpringBoot通用依赖模块--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--SpringBoot与Redis整合依赖--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId/dependency!--swagger2--dependencygroupIdio.springfox/groupIdartifactIdspringfox-swagger2/artifactIdversion2.9.2/version/dependencydependencygroupIdio.springfox/groupIdartifactIdspringfox-swagger-ui/artifactIdversion2.9.2/version/dependency!--通用基础配置boottest/lombok/hutool--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdversion${lombok.version}/versionoptionaltrue/optional/dependencydependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.8.8/version/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build/project
2、ymal文件
server.port7777spring.application.nameredis_distributed_lock
# swagger2
# http://localhost:7777/swagger-ui.html
swagger2.enabledtrue
spring.mvc.pathmatch.matching-strategyant_path_matcher# redis单机
spring.redis.database0
spring.redis.host192.168.111.185
spring.redis.port6379
spring.redis.password111111
spring.redis.lettuce.pool.max-active8
spring.redis.lettuce.pool.max-wait-1ms
spring.redis.lettuce.pool.max-idle8
spring.redis.lettuce.pool.min-idle0
3、主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication
public class RedisDistributedLockApp {public static void main(String[] args) {SpringApplication.run(RedisDistributedLockApp.class,args);}
}
4、配置类
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider;
import springfox.documentation.swagger2.annotations.EnableSwagger2;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;Configuration
EnableSwagger2
public class Swagger2Config {Value(${swagger2.enabled})private Boolean enabled;Beanpublic Docket createRestApi() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).enable(enabled).select().apis(RequestHandlerSelectors.basePackage(com.atguigu.redislock)) //你自己的package.paths(PathSelectors.any()).build();}private ApiInfo apiInfo() {return new ApiInfoBuilder().title(springboot利用swagger2构建api接口文档 \t DateTimeFormatter.ofPattern(yyyy-MM-dd).format(LocalDateTime.now())).description(springbootredis整合).version(1.0).termsOfServiceUrl(https://www.baidu.com/).build();}}import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;Configuration
public class RedisConfig {Beanpublic RedisTemplateString, Object redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {RedisTemplateString,Object redisTemplate new RedisTemplate();redisTemplate.setConnectionFactory(lettuceConnectionFactory);//设置key序列化方式stringredisTemplate.setKeySerializer(new StringRedisSerializer());//设置value的序列化方式jsonredisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.afterPropertiesSet();return redisTemplate;}
}
5、业务类
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;Service
Slf4j
public class InventoryService {Autowiredprivate StringRedisTemplate stringRedisTemplate;Value(${server.port})private String port;public String sale() {String retMessage ;try {//1 查询库存信息String result stringRedisTemplate.opsForValue().get(inventory001);//2 判断库存是否足够Integer inventoryNumber result null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber 0) {stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber));retMessage 成功卖出一个商品库存剩余: inventoryNumber;System.out.println(retMessage);}else{retMessage 商品卖完了o(╥﹏╥)o;}} finally {// 释放操作}return retMessage\t服务端口号port;}
}
import cn.hutool.core.util.IdUtil;
import com.atguigu.redislock.service.InventoryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.atomic.AtomicInteger;RestController
Api(tags redis分布式锁测试)
public class InventoryController {Autowiredprivate InventoryService inventoryService;ApiOperation(扣减库存一次卖一个)GetMapping(value /inventory/sale)public String sale() {return inventoryService.sale();}
}
丝袜哥测试地址http://localhost:7777/swagger-ui.html#/ V2.0 版本
使用 synchronized 或者 lock 进行加锁操作
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;Service
Slf4j
public class InventoryService {Autowiredprivate StringRedisTemplate stringRedisTemplate;Value(${server.port})private String port;private Lock lock new ReentrantLock();public String sale() {String retMessage ;lock.lock();try {//1 查询库存信息String result stringRedisTemplate.opsForValue().get(inventory001);//2 判断库存是否足够Integer inventoryNumber result null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber 0) {stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber));retMessage 成功卖出一个商品库存剩余: inventoryNumber;System.out.println(retMessage);}else{retMessage 商品卖完了o(╥﹏╥)o;}}finally {lock.unlock();}return retMessage\t服务端口号port;}
}
若在添加一个相同服务另一个服务为8888端口使用 nginx 轮训访问 7777 和 8888 服务此时就会出现超卖现象。lock 只针对当前服务有用锁不了其他服务。 Nginx配置负载均衡
1、常用命令 检查nginx.conf文件合法性 nginx -t -c ./conf/nginx.conf 检查nginx服务是否启动 tasklist /fi imagename eq nginx.exe 启动nginx start nginx 或 ./nginx 重新加载 nginx -s reload 停止服务 nginx -s stop 2、配置文件 服务测试
启动 7777 和 8888 服务通过Nginx访问你的Linux服务器地址lP反向代理负裁均衡可以点击看到效果一边一个默认轮询http://192.168.0.185/inventory/sale
使用 jmeter 进行压测 http请求 测试结果 异常76号商品被卖出2次出现超卖故章现象。
结论
在单机环境下可以使用synchronized或Lock来实现。
但是在分布式系统中因为竞争的线程可能不在同一个节点上同一个jvm中所以需要一个让所有进程都能访问到的锁来实现(比如redis或者zookeeper来构建)。
不同进程jvm层面的锁就不管用了那么可以利用第三方的一个组件来获取锁未获取到锁则阻塞当前想要运行的线程。 V3.1 版本
使用递归进行重试
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;Service
Slf4j
public class InventoryService {Autowiredprivate StringRedisTemplate stringRedisTemplate;Value(${server.port})private String port;private Lock lock new ReentrantLock();public String sale() {String retMessage ;String key zzyyRedisLock;String uuidValue IdUtil.simpleUUID():Thread.currentThread().getId();Boolean flag stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue);if(!flag){//暂停20毫秒后递归调用try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }sale();}else{try{//1 查询库存信息String result stringRedisTemplate.opsForValue().get(inventory001);//2 判断库存是否足够Integer inventoryNumber result null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber 0) {stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber));retMessage 成功卖出一个商品库存剩余: inventoryNumber;System.out.println(retMessage);}else{retMessage 商品卖完了o(╥﹏╥)o;}}finally {stringRedisTemplate.delete(key);}}return retMessage\t服务端口号port;}
}
结果
1、手工测试OK使用Jmeter压测5000也OK
2、递归是一种思想没错但是容号导致StackOverflowError不太推荐需进一步完善
V3.2 版本
使用多线程判断想想JUC里面的虚假唤醒用while替代if用自旋替代送归重试。
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;Service
Slf4j
public class InventoryService {Autowiredprivate StringRedisTemplate stringRedisTemplate;Value(${server.port})private String port;private Lock lock new ReentrantLock();public String sale() {String retMessage ;String key zzyyRedisLock;String uuidValue IdUtil.simpleUUID():Thread.currentThread().getId();while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)){//暂停20毫秒类似CAS自旋try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }}try {//1 查询库存信息String result stringRedisTemplate.opsForValue().get(inventory001);//2 判断库存是否足够Integer inventoryNumber result null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber 0) {stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber));retMessage 成功卖出一个商品库存剩余: inventoryNumber;System.out.println(retMessage);}else{retMessage 商品卖完了o(╥﹏╥)o;}}finally {stringRedisTemplate.delete(key);}return retMessage\t服务端口号port;}
}
问题部署了微服务的Java程序机器挂了代码层面根本没有走到finallyi这块没办法保证解锁(无过期时间该key一直存在)这个kev没有被删除需要加入一个过期时间限定key。
V4.1 版本
......
while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue)) {//暂停20毫秒进行递归重试.....try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
}
......
stringRedisTemplate.expire(key,30L,TimeUnit.SECONDS);
......
结论虽然加了过期时间但是设置key过期时间分开了不具备原子性。 V4.2 版本
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;Service
Slf4j
public class InventoryService {Autowiredprivate StringRedisTemplate stringRedisTemplate;Value(${server.port})private String port;private Lock lock new ReentrantLock();public String sale() {String retMessage ;String key zzyyRedisLock;String uuidValue IdUtil.simpleUUID():Thread.currentThread().getId();while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)){//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }}try {//1 查询库存信息String result stringRedisTemplate.opsForValue().get(inventory001);//2 判断库存是否足够Integer inventoryNumber result null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber 0) {stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber));retMessage 成功卖出一个商品库存剩余: inventoryNumber;System.out.println(retMessage);}else{retMessage 商品卖完了o(╥﹏╥)o;}}finally {stringRedisTemplate.delete(key);}return retMessage\t服务端口号port;}
}
使用Jmeter压测OK 结论加锁和过期时间设置必须同一行保证原子性。
V5.0 版本
4.2版本出现的问题实际业务处理时间如果超过了默认设晋key的过期时间怎么办张冠李戴别除了别人的锁只能自己除自己的不许动别人的 改进
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;Service
Slf4j
public class InventoryService {Autowiredprivate StringRedisTemplate stringRedisTemplate;Value(${server.port})private String port;private Lock lock new ReentrantLock();public String sale() {String retMessage ;String key zzyyRedisLock;String uuidValue IdUtil.simpleUUID():Thread.currentThread().getId();while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)) {//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }}try {//1 查询库存信息String result stringRedisTemplate.opsForValue().get(inventory001);//2 判断库存是否足够Integer inventoryNumber result null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber 0) {stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber));retMessage 成功卖出一个商品库存剩余: inventoryNumber\tuuidValue;System.out.println(retMessage);}else{retMessage 商品卖完了o(╥﹏╥)o;}}finally {// v5.0判断加锁与解锁是不是同一个客户端同一个才行自己只能删除自己的锁不误删他人的if(stringRedisTemplate.opsForValue().get(key).equalsIgnoreCase(uuidValue)){stringRedisTemplate.delete(key);}}return retMessage\t服务端口号port;}
}
V6.0 版本小厂够用
上面5.0版本出现的问题finally块的判断del删除除操作不是原子性的。
解决方案启用lua脚本编写redis分布式锁判断删除判断代码。
原理Redis调用Lua本通过eval命令保证代码执行的原子性直接用return返回脚本执行后的结果值。
Lua 脚本
官网https://redis.io/docs/manual/patterns/distributed-locks/ 脚本格式 eval luascript numkeys [key [key ]][arg [arg ...]] 案例一输出文字 案例二组合命令 案例三执行命令 案例四条件判断
使用格式如下 eval if redis.call(get,KEYS[1])ARGV[1] then return redis.call(del,KEYS[1]) else return 0 end 1 zzyyRedisLock 1111-2222-3333 解决方式
import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;Service
Slf4j
public class InventoryService {Autowiredprivate StringRedisTemplate stringRedisTemplate;Value(${server.port})private String port;private Lock lock new ReentrantLock();public String sale() {String retMessage ;String key zzyyRedisLock;String uuidValue IdUtil.simpleUUID():Thread.currentThread().getId();while(!stringRedisTemplate.opsForValue().setIfAbsent(key, uuidValue,30L,TimeUnit.SECONDS)) {//暂停毫秒try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }}try {//1 查询库存信息String result stringRedisTemplate.opsForValue().get(inventory001);//2 判断库存是否足够Integer inventoryNumber result null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber 0) {stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber));retMessage 成功卖出一个商品库存剩余: inventoryNumber\tuuidValue;System.out.println(retMessage);}else{retMessage 商品卖完了o(╥﹏╥)o;}}finally {//V6.0 将判断删除自己的合并为lua脚本保证原子性String luaScript if (redis.call(get,KEYS[1]) ARGV[1]) then return redis.call(del,KEYS[1]) else return 0 end;stringRedisTemplate.execute(new DefaultRedisScript(luaScript, Boolean.class), Arrays.asList(key), uuidValue);}return retMessage\t服务端口号port;}
}
BUG 说明 V7.0 版本可重入锁
6.0版本while判断并自旋重试获取锁setx含自然过期时间ua脚本官网除锐命令。那么问题来了如何兼顾锁的可重入性问题
一个靠谱分布式锁需要具备的条件和刚需独占性、高可用、防死锁、不乱抢、重入性
可重入锁
说明可重入锁又名递归锁是指在同一个线程在外层方法获取锁的时候再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象)不会因为之前已经获取过还没释放而阻塞。如果是1个有 synchronized 修饰的递归调用方法程序第2次进入被自己阻塞了岂不是天大的笑话出现了作茧自缚。所以Java中ReentrantLock和synchronized都是可重入锁可重入锁的一个优点是可一定程度避免死锁。 “可重入锁”这四个字分开来解释 可可以。 重再次。 入进入。 锁同步锁。 进入什么进入同步域(即同步代码快/方法或显式锁锁定的代码) 一句话一个线程中的多个流程可以获取同一把锁持有这把同步锁句以再次进入。自己可以获取自己的内部锁。 可重入锁种类
第一种隐式锁(即synchronized关键字使用的锁)默认是可重入锁。 指的是可重复可递归调用的锁在外层使用锁之后在内层仍然可以使用并且不发生死锁这样的锁就叫做可重入锁。 简单的来说就是在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时是永远可以得到锁的。 与可重入锁相反不可重入锁不可递归调用递归调用就发生死锁。 同步块
public class ReEntryLockDemo {public static void main(String[] args) {final Object objectLockA new Object();new Thread(() - {synchronized (objectLockA) {System.out.println(-----外层调用);synchronized (objectLockA) {System.out.println(-----中层调用);synchronized (objectLockA) {System.out.println(-----内层调用);}}}},a).start();}
}
同步方法
/*** 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时是永远可以得到锁的*/
public class ReEntryLockDemo {public synchronized void m1() {System.out.println(-----m1);m2();}public synchronized void m2() {System.out.println(-----m2);m3();}public synchronized void m3() {System.out.println(-----m3);}public static void main(String[] args) {ReEntryLockDemo reEntryLockDemo new ReEntryLockDemo();reEntryLockDemo.m1();}
}
实现原理 每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。当执行monitorenter时如果目标锁对象的计数器为零那么说明它没有被其他线程所持有Java虚拟机会将该锁对象的持有线程设置为当前线程并且将其计数器加1。在目标锁对象的计数器不为零的情况下如果锁对象的持有线程是当前线程那么 Java 虚拟机可以将其计数器加1否则需要等待直至持有线程释放该锁。当执行monitorexit时Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
第二种显式锁(即Lock)也有ReentrantLock这样的可重入锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时是永远可以得到锁的*/
public class ReEntryLockDemo {static Lock lock new ReentrantLock();public static void main(String[] args) {new Thread(() - {lock.lock();try {System.out.println(----外层调用lock);lock.lock();try {System.out.println(----内层调用lock);}finally {// 这里故意注释实现加锁次数和释放次数不一样// 由于加锁次数和释放次数不一样第二个线程始终无法获取到锁导致一直在等待。lock.unlock(); // 正常情况加锁几次就要解锁几次}}finally {lock.unlock();}},a).start();new Thread(() - {lock.lock();try {System.out.println(b thread----外层调用lock);}finally {lock.unlock();}},b).start();}
}
备注一般而言你lock了几次就要unlock几次。
Redis 可重入锁数据类型
使用key,key,value结构MapString,MapObject,Object hset zzyyRedisLock 29f0ee01ac77414fb8b0861271902a94:1 案例 hset key field value hset redis锁名字(zzyyRedisLock) 某个请求线程的UUIDThreadID 加锁的次数 总结setnx,只能解决有无的问题够用但是不完美hset不但解决有无还解决可重入问题。
Lua脚本
上面版本扣减库存的核心代码保证原子性 实现要求lua脚本实现过程分析 一、加锁lua脚本其实就是 lock 步骤 1、先断redis分布式锁这个key是否存在 EXISTS key 2、返回零说明不存在hset新建当前线程属于自己的锁BY UUID:ThreadID 3、返回壹说明已经有锁需进一步判断是不是当前线程自己的HEXISTS key uuid:ThreadID 命令HEXISTS zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1 3.1、返回零说明不是自己的 3.2、返回壹说明是自己的锁自增1次表示重入 命令HINCRBY key field increment 案例HINCRBY zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1 1 根据以上步骤得出lua脚本 if redis.call(exists,key) 0 then redis.call(hset,key,uuid:threadid,1) redis.call(expire,key,30) return 1 elseif redis.call(hexists,key,uuid:threadid) 1 then redis.call(hincrby,key,uuid:threadid,1) redis.call(expire,key,30) return 1 else return 0 end 以上相同部分是否可以替换处理
答案hincrby命令可否替代hset命令。自增命令没有key会创建key
优化脚本 if redis.call(exists,key) 0 or redis.call(hexists,key,uuid:threadid) 1 then redis.call(hincrby,key,uuid:threadid,1) redis.call(expire,key,30) return 1 else return 0 end 替换参数 if redis.call(exists,KEYS[1]) 0 or redis.call(hexists,KEYS[1],ARGV[1]) 1 then redis.call(hincrby,KEYS[1],ARGV[1],1) redis.call(expire,KEYS[1],ARGV[2]) return 1 else return 0 end 参数说明 测试加锁lua脚本 EVAL if redis.call(exists,KEYS[1]) 0 or redis.call(hexists,KEYS[1],ARGV[1]) 1 then redis.call(hincrby,KEYS[1],ARGV[1],1) redis.call(expire,KEYS[1],ARGV[2]) return 1 else return 0 end 1 zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1 30 HGET zzyyRedisLock 0c90d37cb6ec42268861b3d739f8b3a8:1 二、解锁Lua脚本其实就是 unlock 步骤 1、先判断redis分布式锁这个key是否存在EXISTS key 2、返回零说明根本没有锁程序块返回nil 3、不是零说明有锁且是自己的锁直接调用 HINCRBY -1 表示每次减个一解领一次直到它变为零表示可以除该锁keydel锁key 根据以上步骤得出解锁lua脚本 if redis.call(HEXISTS,lock,uuid:threadID) 0 then return nil elseif redis.call(HINCRBY,lock,uuid:threadID,-1) 0 then return redis.call(del,lock) else return 0 end 替换参数 if redis.call(HEXISTS,KEYS[1],ARGV[1]) 0 then return nil elseif redis.call(HINCRBY,KEYS[1],ARGV[1],-1) 0 then return redis.call(del,KEYS[1]) else return 0 end 调试命令 eval if redis.call(HEXISTS,KEYS[1],ARGV[1]) 0 then return nil elseif redis.call(HINCRBY,KEYS[1],ARGV[1],-1) 0 then return redis.call(del,KEYS[1]) else return 0 end 1 zzyyRedisLock 2f586ae740a94736894ab9d51880ed9d:1 测试解锁lua脚本 整合Java程序
原程序初始无锁版
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;Service
Slf4j
public class InventoryService {Autowiredprivate StringRedisTemplate stringRedisTemplate;Value(${server.port})private String port;public String sale() {String retMessage ;//1 查询库存信息String result stringRedisTemplate.opsForValue().get(inventory001);//2 判断库存是否足够Integer inventoryNumber result null ? 0 : Integer.parseInt(result);//3 扣减库存if(inventoryNumber 0) {stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber));retMessage 成功卖出一个商品库存剩余: inventoryNumber\t;System.out.println(retMessage);}else{retMessage 商品卖完了o(╥﹏╥)o;}return retMessage\t服务端口号port;}
}
使用工厂设计模式新建RedisDistributedLock类并实现UUC里面的Lock接口满足JUC里面AQS对Lock锁的接口规范定义来进行实现落地代码。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.locks.Lock;Component
public class DistributedLockFactory {Autowiredprivate StringRedisTemplate stringRedisTemplate;private String lockName;public Lock getDistributedLock(String lockType) {if(lockType null) return null;if(lockType.equalsIgnoreCase(REDIS)){lockName zzyyRedisLock;return new RedisDistributedLock(stringRedisTemplate,lockName);} else if(lockType.equalsIgnoreCase(ZOOKEEPER)){//TODO zookeeper版本的分布式锁实现return new ZookeeperDistributedLock();} else if(lockType.equalsIgnoreCase(MYSQL)){//TODO mysql版本的分布式锁实现return null;}return null;}
}
import cn.hutool.core.util.IdUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.support.collections.DefaultRedisList;
import org.springframework.stereotype.Component;import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;//Component 引入DistributedLockFactory工厂模式从工厂获得而不再从spring拿到
public class RedisDistributedLock implements Lock {private StringRedisTemplate stringRedisTemplate;private String lockName;//KEYS[1]private String uuidValue;//ARGV[1]private long expireTime;//ARGV[2]public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName) {this.stringRedisTemplate stringRedisTemplate;this.lockName lockName;this.uuidValue IdUtil.simpleUUID():Thread.currentThread().getId();//UUID:ThreadIDthis.expireTime 30L;}Overridepublic void lock(){tryLock();}Overridepublic boolean tryLock(){try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}return false;}/*** 干活的实现加锁功能实现这一个干活的就OK全盘通用* param time* param unit* return* throws InterruptedException*/Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException{if(time ! -1L){this.expireTime unit.toSeconds(time);}String script if redis.call(exists,KEYS[1]) 0 or redis.call(hexists,KEYS[1],ARGV[1]) 1 then redis.call(hincrby,KEYS[1],ARGV[1],1) redis.call(expire,KEYS[1],ARGV[2]) return 1 else return 0 end;System.out.println(script: script);System.out.println(lockName: lockName);System.out.println(uuidValue: uuidValue);System.out.println(expireTime: expireTime);while (!stringRedisTemplate.execute(new DefaultRedisScript(script,Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) {TimeUnit.MILLISECONDS.sleep(50);}return true;}/***干活的实现解锁功能*/Overridepublic void unlock() {String script if redis.call(HEXISTS,KEYS[1],ARGV[1]) 0 then return nil elseif redis.call(HINCRBY,KEYS[1],ARGV[1],-1) 0 then return redis.call(del,KEYS[1]) else return 0 end;// nil false 1 true 0 falseSystem.out.println(lockName: lockName);System.out.println(uuidValue: uuidValue);System.out.println(expireTime: expireTime);Long flag stringRedisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime));if(flag null) {throw new RuntimeException(This lock doesnt EXIST);}}//下面的redis分布式锁暂时用不到//下面的redis分布式锁暂时用不到//下面的redis分布式锁暂时用不到Overridepublic void lockInterruptibly() throws InterruptedException {}Overridepublic Condition newCondition() {return null;}
}
InventoryService使用工厂模式版 import ch.qos.logback.core.joran.conditional.ThenAction;import cn.hutool.core.util.IdUtil;import cn.hutool.core.util.StrUtil;import com.atguigu.redislock.mylock.DistributedLockFactory;import com.atguigu.redislock.mylock.RedisDistributedLock;import lombok.extern.slf4j.Slf4j;import org.omg.IOP.TAG_RMI_CUSTOM_MAX_STREAM_FORMAT;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;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.stereotype.Service;import java.util.Arrays;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;Service Slf4jpublic class InventoryService { Autowired private StringRedisTemplate stringRedisTemplate; Value(${server.port}) private String port; Autowired private DistributedLockFactory distributedLockFactory; public String sale() { String retMessage ; Lock redisLock distributedLockFactory.getDistributedLock(redis); redisLock.lock(); try { //1 查询库存信息 String result stringRedisTemplate.opsForValue().get(inventory001); //2 判断库存是否足够 Integer inventoryNumber result null ? 0 : Integer.parseInt(result); //3 扣减库存 if(inventoryNumber 0) { inventoryNumber inventoryNumber - 1; stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(inventoryNumber)); retMessage 成功卖出一个商品库存剩余: inventoryNumber\t服务端口: port; System.out.println(retMessage); return retMessage; } retMessage 商品卖完了o(╥﹏╥)o\t服务端口: port; }catch (Exception e){ e.printStackTrace(); }finally { redisLock.unlock(); } return retMessage; } } 可重入性加锁
测试代码改造InventoryService 类 import com.atguigu.redislock.mylock.DistributedLockFactory;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Service;import javax.annotation.Resource;import java.util.concurrent.locks.Lock;Service Slf4jpublic class InventoryService { Autowired private StringRedisTemplate stringRedisTemplate; Value(${server.port}) private String port; Autowired private DistributedLockFactory distributedLockFactory; public String sale() { String retMessage ; Lock redisLock distributedLockFactory.getDistributedLock(redis); redisLock.lock(); try { //1 查询库存信息 String result stringRedisTemplate.opsForValue().get(inventory001); //2 判断库存是否足够 Integer inventoryNumber result null ? 0 : Integer.parseInt(result); //3 扣减库存 if(inventoryNumber 0) { stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber)); retMessage 成功卖出一个商品库存剩余: inventoryNumber\t; System.out.println(retMessage); testReEnter(); }else{ retMessage 商品卖完了o(╥﹏╥)o; } }catch (Exception e){ e.printStackTrace(); }finally { redisLock.unlock(); } return retMessage\t服务端口号port; } private void testReEnter() { Lock redisLock distributedLockFactory.getDistributedLock(redis); redisLock.lock(); try { System.out.println(################测试可重入锁#######); }finally { redisLock.unlock(); } } } 测试结果ThreadID一致了但是UUID不OK。 改造代码 import cn.hutool.core.util.IdUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.stereotype.Component;import java.util.concurrent.locks.Lock;Componentpublic class DistributedLockFactory { Autowired private StringRedisTemplate stringRedisTemplate; private String lockName; private String uuidValue; public DistributedLockFactory() { this.uuidValue IdUtil.simpleUUID();//UUID } public Lock getDistributedLock(String lockType) { if(lockType null) return null; if(lockType.equalsIgnoreCase(REDIS)){ lockName zzyyRedisLock; return new RedisDistributedLock(stringRedisTemplate,lockName,uuidValue); } else if(lockType.equalsIgnoreCase(ZOOKEEPER)){ //TODO zookeeper版本的分布式锁实现 return new ZookeeperDistributedLock(); } else if(lockType.equalsIgnoreCase(MYSQL)){ //TODO mysql版本的分布式锁实现 return null; } return null; } } import cn.hutool.core.util.IdUtil;import lombok.SneakyThrows;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Arrays;import java.util.Timer;import java.util.TimerTask;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;public class RedisDistributedLock implements Lock { private StringRedisTemplate stringRedisTemplate; private String lockName; private String uuidValue; private long expireTime; public RedisDistributedLock(StringRedisTemplate stringRedisTemplate, String lockName,String uuidValue) { this.stringRedisTemplate stringRedisTemplate; this.lockName lockName; this.uuidValue uuidValue:Thread.currentThread().getId(); this.expireTime 30L; } Override public void lock() { this.tryLock(); } Override public boolean tryLock() { try { return this.tryLock(-1L,TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } return false; } Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { if(time ! -1L) { expireTime unit.toSeconds(time); } String script if redis.call(exists,KEYS[1]) 0 or redis.call(hexists,KEYS[1],ARGV[1]) 1 then redis.call(hincrby,KEYS[1],ARGV[1],1) redis.call(expire,KEYS[1],ARGV[2]) return 1 else return 0 end; System.out.println(lockName: lockName\tuuidValue: uuidValue); while (!stringRedisTemplate.execute(new DefaultRedisScript(script, Boolean.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime))) { try { TimeUnit.MILLISECONDS.sleep(60); } catch (InterruptedException e) { e.printStackTrace(); } } return true; } Override public void unlock() { String script if redis.call(HEXISTS,KEYS[1],ARGV[1]) 0 then return nil elseif redis.call(HINCRBY,KEYS[1],ARGV[1],-1) 0 then return redis.call(del,KEYS[1]) else return 0 end; System.out.println(lockName: lockName\tuuidValue: uuidValue); Long flag stringRedisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(lockName), uuidValue, String.valueOf(expireTime)); if(flag null) { throw new RuntimeException(没有这个锁HEXISTS查询无); } } // Override public void lockInterruptibly() throws InterruptedException { } Override public Condition newCondition() { return null; } } InventoryService类新增可重入测试方法 import cn.hutool.core.util.IdUtil;import com.atguigu.redislock.mylock.DistributedLockFactory;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import org.springframework.stereotype.Service;import java.util.Arrays;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;Service Slf4jpublic class InventoryService { Autowired private StringRedisTemplate stringRedisTemplate; Value(${server.port}) private String port; Autowired private DistributedLockFactory distributedLockFactory; public String sale() { String retMessage ; Lock redisLock distributedLockFactory.getDistributedLock(redis); redisLock.lock(); try { //1 查询库存信息 String result stringRedisTemplate.opsForValue().get(inventory001); //2 判断库存是否足够 Integer inventoryNumber result null ? 0 : Integer.parseInt(result); //3 扣减库存 if(inventoryNumber 0) { stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber)); retMessage 成功卖出一个商品库存剩余: inventoryNumber; System.out.println(retMessage); this.testReEnter(); }else{ retMessage 商品卖完了o(╥﹏╥)o; } }catch (Exception e){ e.printStackTrace(); }finally { redisLock.unlock(); } return retMessage\t服务端口号port; } private void testReEnter() { Lock redisLock distributedLockFactory.getDistributedLock(redis); redisLock.lock(); try { System.out.println(################测试可重入锁####################################); }finally { redisLock.unlock(); } } } 结果单机并发可重入性测试通过
V8.0版本自动续期
思考确保redisLock过期时间大于业务执行时间的问题Redis分布式如何续续期 什么是CAP C一致性所有的节点上的数据时刻保持同步A可用性每个请求都能接受到一个响应无论响应成功或失败P分区容错系统应该能持续提供服务即使系统内部有消息丢失分区 CA without P如果不要求P不允许分区则C强一致性和A可用性是可以保证的。但其实分区不是你想不想的问题而是始终会存在因此CA的系统更多的是允许分区后各子系统依然保持CA。CP without A如果不要求A可用相当于每个请求都需要在Server之间强一致而P分区会导致同步时间无限延长如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。AP wihtout C要高可用并允许分区则需放弃一致性。一旦分区发生节点之间可能会失去联系为了高可用每个节点只能用本地数据提供服务而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。 Redis集群是AP redis异步复制造成的锁丢失比如主节点没来的及把刚刚set进来这条数据给从节点master就挂了从机上位但从机上无该数据。Zookeeper集群是CP 故障问题 Eureka集群是AP Nacos集群是AP lua代码加钟
脚本测试 hset zzyyRedisLock 111122223333:11 3 EXPIRE zzyyRedisLock 30 ttl zzyyRedisLock 。。。。。 eval if redis.call(HEXISTS,KEYS[1],ARGV[1]) 1 then return redis.call(expire,KEYS[1],ARGV[2]) else return 0 end 1 zzyyRedisLock 111122223333:11 30 ttl zzyyRedisLock lua脚本 //自动续期 if redis.call(HEXISTS,KEYS[1],ARGV[1]) 1 then return redis.call(expire,KEYS[1],ARGV[2]) else return 0 end 自动续期 import cn.hutool.core.util.IdUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import org.springframework.data.redis.support.collections.DefaultRedisList;import org.springframework.stereotype.Component;import java.util.Arrays;import java.util.Timer;import java.util.TimerTask;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;public class RedisDistributedLock implements Lock { private StringRedisTemplate stringRedisTemplate; private String lockName;//KEYS[1] private String uuidValue;//ARGV[1] private long expireTime;//ARGV[2] public RedisDistributedLock(StringRedisTemplate stringRedisTemplate,String lockName,String uuidValue) { this.stringRedisTemplate stringRedisTemplate; this.lockName lockName; this.uuidValue uuidValue:Thread.currentThread().getId(); this.expireTime 30L; } Override public void lock() { tryLock(); } Override public boolean tryLock() { try {tryLock(-1L,TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();} return false; } /** * 干活的实现加锁功能实现这一个干活的就OK全盘通用 * param time * param unit * return * throws InterruptedException */ Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { if(time ! -1L) { this.expireTime unit.toSeconds(time); } String script if redis.call(exists,KEYS[1]) 0 or redis.call(hexists,KEYS[1],ARGV[1]) 1 then redis.call(hincrby,KEYS[1],ARGV[1],1) redis.call(expire,KEYS[1],ARGV[2]) return 1 else return 0 end; System.out.println(script: script); System.out.println(lockName: lockName); System.out.println(uuidValue: uuidValue); System.out.println(expireTime: expireTime); while (!stringRedisTemplate.execute(new DefaultRedisScript(script,Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) { TimeUnit.MILLISECONDS.sleep(50); } this.renewExpire(); return true; } /** *干活的实现解锁功能 */ Override public void unlock() { String script if redis.call(HEXISTS,KEYS[1],ARGV[1]) 0 then return nil elseif redis.call(HINCRBY,KEYS[1],ARGV[1],-1) 0 then return redis.call(del,KEYS[1]) else return 0 end; // nil false 1 true 0 false System.out.println(lockName: lockName); System.out.println(uuidValue: uuidValue); System.out.println(expireTime: expireTime); Long flag stringRedisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime)); if(flag null) { throw new RuntimeException(This lock doesnt EXIST); } } private void renewExpire() { String script if redis.call(HEXISTS,KEYS[1],ARGV[1]) 1 then return redis.call(expire,KEYS[1],ARGV[2]) else return 0 end; new Timer().schedule(new TimerTask() { Override public void run() { if (stringRedisTemplate.execute(new DefaultRedisScript(script, Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) { renewExpire(); } } },(this.expireTime * 1000)/3); } //下面的redis分布式锁暂时用不到 //下面的redis分布式锁暂时用不到 //下面的redis分布式锁暂时用不到 Override public void lockInterruptibly() throws InterruptedException { } Override public Condition newCondition() { return null; } } import cn.hutool.core.util.IdUtil;import com.atguigu.redislock.mylock.DistributedLockFactory;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import org.springframework.stereotype.Service;import java.util.Arrays;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;Service Slf4jpublic class InventoryService { Autowired private StringRedisTemplate stringRedisTemplate; Value(${server.port}) private String port; Autowired private DistributedLockFactory distributedLockFactory; public String sale() { String retMessage ; Lock redisLock distributedLockFactory.getDistributedLock(redis); redisLock.lock(); try { //1 查询库存信息 String result stringRedisTemplate.opsForValue().get(inventory001); //2 判断库存是否足够 Integer inventoryNumber result null ? 0 : Integer.parseInt(result); //3 扣减库存 if(inventoryNumber 0) { stringRedisTemplate.opsForValue().set(inventory001,String.valueOf(--inventoryNumber)); retMessage 成功卖出一个商品库存剩余: inventoryNumber; System.out.println(retMessage); //暂停几秒钟线程,为了测试自动续期 try { TimeUnit.SECONDS.sleep(120); } catch (InterruptedException e) { e.printStackTrace(); } }else{ retMessage 商品卖完了o(╥﹏╥)o; } }catch (Exception e){ e.printStackTrace(); }finally { redisLock.unlock(); } return retMessage\t服务端口号port; } private void testReEnter() { Lock redisLock distributedLockFactory.getDistributedLock(redis); redisLock.lock(); try { System.out.println(################测试可重入锁####################################); }finally { redisLock.unlock(); } } } 总结流程
1、使用 synchronized 关键字或使用jdk中Lock单机版OK上分布式死翅翘
2、nginx分布式微服务单机锁不行
3、取消单机锁上redis分布式锁setnx
3.1、只加了锁没有释放锁出异常的话可能无法释放锁必须要在代码层面finally释放锁
3.2、宕机了部署了微服务代码层面根本没有走到finally这块没办法保证解锁这个key没有被删除需要有lockKey的过期时间设定
4、为redis的分布式锁key增加过期时间此外还必须要setnx过期时间同一行
4.1、必须规定只能自己删除自己的锁你不能把别人的锁删除了防止张冠李戴1别22删3的情况
5、unlock变为lua脚本保证原子性
6、锁重入hset替代setnxlock变为Lua脚本保证
7、锁的自动续期