5.3 redis分布式锁【Java 面试第三季】
标签: 5.3 redis分布式锁【Java 面试第三季】 JavaScript博客 51CTO博客
2023-07-13 18:24:39 119浏览
redis分布式锁【Java 面试第三季】
- 前言
- 推荐
- redis分布式锁
- 概览
- 47_redis分布式锁前情说明
- 48_boot整合redis搭建超卖程序-上
- 49_boot整合redis搭建超卖程序-下
- 50_redis分布式锁01
- 51_redis分布式锁02
- 52_redis分布式锁03
- 53_redis分布式锁04
- 54_redis分布式锁05
- 55_redis分布式锁06
- 56_redis分布式锁07
- 57_redis分布式锁08
- 58_redis分布式锁09
- 59_redis分布式锁10
- 60_redis分布式锁总结回顾
- 整体代码
- 最终代码
- 最后
前言
2022/11/24
推荐
Java开发常见面试题详解
redis分布式锁
概览
Version1.0
分布式锁01
问题1:超卖
解决1:JVM层加锁 synchronized 单机可以 Version2.0
分布式锁02
问题2:测试分布式环境 synchronized 就不行了
解决2:上redis分布式锁 setnx Version3.0
redis分布式锁03
问题3:不能保证释放锁(出异常的话)
解决3:try-finally 保证释放 Version4.0
问题4:宕机导致释放不了锁
解决4:value设置过期时间 Version5.0
redis分布式锁04
问题5:加锁和过期时间不是同行,原子性操作
解决5:SetNXEX命令 Version6.0
问题6:张冠李戴,删除了别人的锁
解决6:只能自己删除自己的,不许动别人的。 Version7.0
54_redis分布式锁05
问题7:finally块的判断 + del删除操作不是原子性的
55_redis分布式锁06
解决7: 用redis事务 Version8.1
56_redis分布式锁07
解决7:用lua脚本 Version8.2
57_redis分布式锁08
问题8:确保RedisLock过期时间大于业务执行时间的问题
58_redis分布式锁09
解决8:Redisson Version9.0
59_redis分布式锁10
版本9.1 避免IllegalMonitorStateException: attempt to unlock lock,not locked by current thread by node id
47_redis分布式锁前情说明
常见的面试题:
Redis除了拿来做缓存,你还见过基于Redis的什么用法?
Redis做分布式锁的时候有需要注意的问题?
如果是Redis是单点部署的,会带来什么问题?那你准备怎么解决单点问题呢?
集群模式下,比如主从模式,有没有什么问题呢?
那你简单的介绍一下Redlock吧?你简历上写redisson,你谈谈。
Redis分布式锁如何续期?看门狗知道吗?
48_boot整合redis搭建超卖程序-上
使用场景:多个服务间 + 保证同一时刻内 + 同一用户只能有一个请求(防止关键业务出现数据冲突和并发错误)
建两个Module:boot_redis01,boot_redis02
POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>boot_redis01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot_redis01</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<!-- web+actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- SpringBoot与Redis整合依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<!-- Spring Boot AOP技术-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.4</version>
</dependency>
<!-- 一般通用基础配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.example.boot_redis01.BootRedis01Application</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Properties
server.port=1111
#2222
#=========================redis相关配香========================
#Redis数据库索引(默认方0)
spring.redis.database=0
#Redis服务器地址
spring.redis.host=127.0.0.1
#Redis服务器连接端口
spring.redis.port=6379
#Redis服务器连接密码(默认为空)
spring.redis.password=
#连接池最大连接数(使用负值表示没有限制)默认8
spring.redis.lettuce.pool.max-active=8
#连接池最大阻塞等待时间(使用负值表示没有限制)默认-1
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接默认8
spring.redis.lettuce.pool.max-idle=8
#连接池中的最小空闲连接默犬认0
spring.redis.lettuce.pool.min-idle=0
主启动类
package com.example.boot_redis01;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BootRedis01Application {
public static void main(String[] args) {
SpringApplication.run(BootRedis01Application.class, args);
}
}
配置类
package com.example.boot_redis01.config;
import java.io.Serializable;
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 {
@Bean
public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
49_boot整合redis搭建超卖程序-下
控制层
Version1.0
package com.example.boot_redis01.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping("/buy_goods")
public String buy_Goods(){
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
}
}
测试
- redis:set goods:001 100
- 浏览器:http://localhost:1111/buy_goods
boot_redis02拷贝boot_redis01
50_redis分布式锁01
JVM层面的加锁,单机版的锁
- synchronized
- ReentraLock
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds//不见不散
try {
// ... method body
} finally {
lock.unlock()
}
}
public void m2() {
if(lock.tryLock(timeout, unit)){//过时不候
try {
// ... method body
} finally {
lock.unlock()
}
}else{
// perform alternative actions
}
}
}
Version2.0
package com.example.boot_redis01.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping("/buy_goods")
public String buy_Goods(){
synchronized (this) {
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
}
}
}
51_redis分布式锁02
分布式部署后,单机锁还是出现超卖现象,需要分布式锁
下载:nginx for Windows
学习
下载后,解压并启动 Nginx
cd c:\
unzip nginx-1.18.0.zip
cd nginx-1.18.0
start nginx
查询是否启动 Nginx
C:\nginx-1.18.0>tasklist /fi "imagename eq nginx.exe"
映像名称 PID 会话名 会话# 内存使用
========================= ======== ================ =========== ============
nginx.exe 7824 Console 1 8,860 K
nginx.exe 7472 Console 1 9,200 K
06.Nginx 常用的命令
转到 Nginx 目录下
cd C:\nginx-1.18.0
启动
start nginx
查看 ngnix 版本
nginx -v
nginx version: nginx/1.18.0
关闭 ngnix
nginx -s stop
重启 ngnix
nginx -s reload
ngnix 命令更多细节
nginx -h
Nginx配置文件修改内容
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
upstream myserver{
server 127.0.0.1:1111;
server 127.0.0.1:2222;
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
# 负责用到的配置
proxy_pass http://myserver;
root html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
启动两个微服务:1111,2222,多次访问http://localhost/buy_goods,服务提供端口在1111,2222两者之间横跳
上面手点,下面高并发模拟
redis:set goods:001 100,恢复到100
用到Apache JMeter,100个线程同时访问http://localhost/buy_goods。
(下载地址:Apache JMeter - Download Apache JMeter)
【jmeter教程——从入门到熟练】
public static final String REDIS_LOCK = "redis_lock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void m(){
String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);
if(!flag) {
return "抢锁失败";
}
...//业务逻辑
stringRedisTemplate.delete(REDIS_LOCK);
}
Version3.0
package com.example.boot_redis01.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
private final Lock lock=new ReentrantLock();
@GetMapping("/buy_goods")
public String buy_Goods(){
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
if (!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
stringRedisTemplate.delete(REDIS_LOCK); //解锁
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
}
}
52_redis分布式锁03
上面Java源码分布式锁问题:出现异常的话,可能无法释放锁,必须要在代码层面finally释放锁。
解决方法:try…finally…
public static final String REDIS_LOCK = "redis_lock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void m(){
String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
try{
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);
if(!flag) {
return "抢锁失败";
}
...//业务逻辑
}finally{
stringRedisTemplate.delete(REDIS_LOCK);
}
}
不能保证释放锁(出异常的话)
package com.example.boot_redis01.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
private final Lock lock=new ReentrantLock();
@GetMapping("/buy_goods")
public String buy_Goods(){
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
try {
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
if (!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
// stringRedisTemplate.delete(REDIS_LOCK); //解锁
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
//都可以释放锁
stringRedisTemplate.delete(REDIS_LOCK); //解锁
}
}
}
另一个问题:部署了微服务jar包的机器挂了,代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,需要加入一个过期时间限定key。
public static final String REDIS_LOCK = "redis_lock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void m(){
String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
try{
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);
//设定时间
stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
if(!flag) {
return "抢锁失败";
}
...//业务逻辑
}finally{
stringRedisTemplate.delete(REDIS_LOCK);
}
}
** Version5.0**
package com.example.boot_redis01.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
private final Lock lock=new ReentrantLock();
@GetMapping("/buy_goods")
public String buy_Goods(){
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
try {
//版本3
//分布式锁
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
//版本5
//宕机也可以删,过期时间
stringRedisTemplate.expire(REDIS_LOCK,10L, TimeUnit.SECONDS);
if (!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
// stringRedisTemplate.delete(REDIS_LOCK); //解锁
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
//版本4
//正常异常都可以释放锁
stringRedisTemplate.delete(REDIS_LOCK); //解锁
}
}
}
53_redis分布式锁04
新问题:设置key+过期时间分开了,必须要合并成一行具备原子性。
解决方法:
public static final String REDIS_LOCK = "redis_lock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void m(){
String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
try{
Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法
.setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
//设定时间
//stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
if(!flag) {
return "抢锁失败";
}
...//业务逻辑
}finally{
stringRedisTemplate.delete(REDIS_LOCK);
}
}
Version6.0
package com.example.boot_redis01.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
private final Lock lock=new ReentrantLock();
@GetMapping("/buy_goods")
public String buy_Goods(){
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
try {
/*
//版本3
//分布式锁
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
//版本5
//宕机也可以删,过期时间
stringRedisTemplate.expire(REDIS_LOCK,10L, TimeUnit.SECONDS);
*/
//版本6
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);//setNX EX
if (!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
// stringRedisTemplate.delete(REDIS_LOCK); //解锁
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
//版本4
//正常异常都可以释放锁
stringRedisTemplate.delete(REDIS_LOCK); //解锁
}
}
}
另一个新问题:张冠李戴,删除了别人的锁
进程A 执行业务操作 时间过长 导致锁过期
进程B 立即来执行业务 加锁
进程A 出来之后把B锁解了
解决方法:只能自己删除自己的,不许动别人的。
public static final String REDIS_LOCK = "redis_lock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void m(){
String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
try{
Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法
.setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
//设定时间
//stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
if(!flag) {
return "抢锁失败";
}
...//业务逻辑
}finally{
if(stringRedisTemplate.opsForValue().get(REDIS_LOCK).equals(value)) {
stringRedisTemplate.delete(REDIS_LOCK);
}
}
}
Version7.0
package com.example.boot_redis01.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
private final Lock lock=new ReentrantLock();
@GetMapping("/buy_goods")
public String buy_Goods(){
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
try {
//版本6
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);//setNX EX
if (!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
// stringRedisTemplate.delete(REDIS_LOCK); //解锁
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
/*
//版本4
//正常异常都可以释放锁
stringRedisTemplate.delete(REDIS_LOCK); //解锁
*/
//版本7
//只删除自己的锁
if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
stringRedisTemplate.delete(REDIS_LOCK); //解锁
}
}
}
}
54_redis分布式锁05
finally块的判断 + del删除操作不是原子性的
用lua脚本
用redis自身的事务
事务介绍
- Redis的事条是通过MULTI,EXEC,DISCARD和WATCH这四个命令来完成。
- Redis的单个命令都是原子性的,所以这里确保事务性的对象是命令集合。
- Redis将命令集合序列化并确保处于一事务的命令集合连续且不被打断的执行。
- Redis不支持回滚的操作。
命令 |
描述 |
DISCARD |
取消事务,放弃执行事务块内的所有命令。 |
EXEC |
执行所有事务块内的命令。 |
MULTI |
标记一个事务块的开始。 |
UNWATCH |
取消 WATCH 命令对所有 key 的监视。 |
WATCH key [key …] |
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
55_redis分布式锁06
public static final String REDIS_LOCK = "redis_lock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void m(){
String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
try{
Boolean flag = stringRedisTemplate.opsForValue()//使用另一个带有设置超时操作的方法
.setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
//设定时间
//stringRedisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
if(!flag) {
return "抢锁失败";
}
...//业务逻辑
}finally{
while(true){
stringRedisTemplate.watch(REDIS_LOCK);
if(stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
stringRedisTemplate.setEnableTransactionSupport(true);
stringRedisTemplate.multi();
stringRedisTemplate.delete(REDIS_LOCK);
List<Object> list = stringRedisTemplate.exec();
if (list == null) {
continue;
}
}
stringRedisTemplate.unwatch();
break;
}
}
}
Version8.1
package com.example.boot_redis01.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
private final Lock lock=new ReentrantLock();
@GetMapping("/buy_goods")
public String buy_Goods(){
//版本2
/*
synchronized (this) {
//版本1
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
}
// 我等着觉得时间太长了,我想放弃等待
// 给我一个规定的时间内,拿不到我再放弃
*/
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
try {
/*
//版本3
//分布式锁
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
//版本5
//宕机也可以删,过期时间
stringRedisTemplate.expire(REDIS_LOCK,10L, TimeUnit.SECONDS);
*/
//版本6
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);//setNX EX
if (!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
// stringRedisTemplate.delete(REDIS_LOCK); //解锁
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
/*
//版本4
//正常异常都可以释放锁
stringRedisTemplate.delete(REDIS_LOCK); //解锁
*/
/*
//版本7
//只删除自己的锁
if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
stringRedisTemplate.delete(REDIS_LOCK); //解锁
}
*/
/**
* 不可以用Lua脚本,你还有其他想法?
* redis事务
*/
//版本8.1
while (true){
stringRedisTemplate.watch(REDIS_LOCK);
if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
stringRedisTemplate.setEnableTransactionSupport(true);
stringRedisTemplate.multi();
stringRedisTemplate.delete(REDIS_LOCK);
List<Object> list = stringRedisTemplate.exec();
if (list==null){
continue;
}
}
stringRedisTemplate.unwatch();
break;
}
}
}
}
56_redis分布式锁07
Redis调用Lua脚本通过eval命令保证代码执行的原子性
RedisUtils:
package com.example.boot_redis01.util;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @author CSDN@日星月云
* @date 2022/11/24 18:39
*/
public class RedisUtils {
private static JedisPool jedisPool;
static {
JedisPoolConfig jpc = new JedisPoolConfig();
jpc.setMaxTotal(20);
jpc.setMaxIdle(10);
jedisPool = new JedisPool(jpc);
}
public static Jedis getJedis() throws Exception{
if(jedisPool == null)
throw new NullPointerException("JedisPool is not OK.");
return jedisPool.getResource();
}
}
Version8.2
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
private final Lock lock=new ReentrantLock();
@GetMapping("/buy_goods")
public String buy_Goods() throws Exception {
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
try {
//版本6
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);//setNX EX
if (!flag){
return "抢锁失败";
}
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
// stringRedisTemplate.delete(REDIS_LOCK); //解锁
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
/*
//版本7
//只删除自己的锁
if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
stringRedisTemplate.delete(REDIS_LOCK); //解锁
}
*/
//版本8.2 Lua脚本
Jedis jedis = RedisUtils.getJedis();
String script = "if redis.call('get', KEYS[1]) == ARGV[1] "
+ "then "
+ " return redis.call('del', KEYS[1]) "
+ "else "
+ " return 0 "
+ "end";
try {
Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
if ("1".equals(o.toString())){
System.out.println("---del redis lock ok");
}else{
System.out.println("---del redis lock error");
}
}finally {
if(null!=jedis){
jedis.close();
}
}
}
}
}
57_redis分布式锁08
确保RedisLock过期时间大于业务执行时间的问题
Redis分布式锁如何续期?
集群 + CAP对比ZooKeeper 对比ZooKeeper,重点,CAP
- Redis - AP -redis异步复制造成的锁丢失,比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了。
- ZooKeeper - CP
CAP
- C:Consistency(强一致性)
- A:Availability(可用性)
-P:Partition tolerance(分区容错性)
综上所述
Redis集群环境下,我们自己写的也不OK,直接上RedLock之Redisson落地实现。
redis异步复制造成的锁丢失
比如:主节点没来的及把刚刚set进来这条数据给从节点,就挂了
58_redis分布式锁09
Redisson配置类
import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedisConfig {
@Bean
public Redisson redisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return (Redisson)Redisson.create(config);
}
}
Redisson模板
public static final String REDIS_LOCK = "REDIS_LOCK";
@Autowired
private Redisson redisson;
@GetMapping("/doSomething")
public String doSomething(){
RLock redissonLock = redisson.getLock(REDIS_LOCK);
redissonLock.lock();
try {
//doSomething
}finally {
redissonLock.unlock();
}
}
Version9.0
package com.example.boot_redis01.controller;
import com.example.boot_redis01.util.RedisUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
private final Lock lock=new ReentrantLock();
@Autowired
private Redisson redisson;
@GetMapping("/buy_goods")
public String buy_Goods() throws Exception {
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
//版本9.0
RLock redissonLock = redisson.getLock(REDIS_LOCK);
redissonLock.lock();
try {
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
//版本9.0
redissonLock.unlock();
}
}
}
59_redis分布式锁10
避免IllegalMonitorStateException: attempt to unlock lock,not locked by current thread by node id
package com.example.boot_redis02.controller;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
@Autowired
private Redisson redisson;
@GetMapping("/buy_goods")
public String buy_Goods() throws Exception {
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
//版本9.0
RLock redissonLock = redisson.getLock(REDIS_LOCK);
redissonLock.lock();
try {
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
//版本9.0
// redissonLock.unlock();
//版本9.1 避免IllegalMonitorStateException: attempt to unlock lock,not locked by current thread by node id
if (redissonLock.isLocked()){
if (redissonLock.isHeldByCurrentThread()){
redissonLock.unlock();
}
}
}
}
}
60_redis分布式锁总结回顾
synchronized单机版oK,上分布式
nginx分布式微服务单机锁不行
取消单机锁,上Redis分布式锁setnx
只加了锁,没有释放锁,出异常的话,可能无法释放锁,必须要在代码层面finally释放锁
宕机了,部署了微服务代码层面根本没有走到finally这块,没办法保证解锁,这个key没有被删除,
需要有lockKey的过期时间设定
为redis的分布式锁key,增加过期时间,此外,还必须要setnx+过期时间必须同一行
必须规定只能自己删除自己的锁,你不能把别人的锁删除了,防止张冠李戴,1删2,2删3
Redis集群环境下,我们自己写的也不oK直接上RedLock之Redisson落地实现
整体代码
package com.example.boot_redis01.controller;
import com.example.boot_redis01.util.RedisUtils;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
private final Lock lock=new ReentrantLock();
@Autowired
private Redisson redisson;
@GetMapping("/buy_goods")
public String buy_Goods() throws Exception {
//版本2
/*
synchronized (this) {
//版本1
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
}
// 我等着觉得时间太长了,我想放弃等待
// 给我一个规定的时间内,拿不到我再放弃
*/
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
//版本9.0
RLock redissonLock = redisson.getLock(REDIS_LOCK);
redissonLock.lock();
try {
/*
//版本3
//分布式锁
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);//setNX
//版本5
//宕机也可以删,过期时间
stringRedisTemplate.expire(REDIS_LOCK,10L, TimeUnit.SECONDS);
*/
//版本6
// Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS);//setNX EX
//
//
// if (!flag){
// return "抢锁失败";
// }
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
// stringRedisTemplate.delete(REDIS_LOCK); //解锁
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
//版本9.0
// redissonLock.unlock();
//版本9.1 避免IllegalMonitorStateException: attempt to unlock lock,not locked by current thread by node id
if (redissonLock.isLocked()){
if (redissonLock.isHeldByCurrentThread()){
redissonLock.unlock();
}
}
/*
//版本4
//正常异常都可以释放锁
stringRedisTemplate.delete(REDIS_LOCK); //解锁
*/
/*
//版本7
//只删除自己的锁
if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
stringRedisTemplate.delete(REDIS_LOCK); //解锁
}
*/
/**
* 不可以用Lua脚本,你还有其他想法?
* redis事务
*/
/* redis事务
//版本8.1
while (true){
stringRedisTemplate.watch(REDIS_LOCK);
if (stringRedisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)){
stringRedisTemplate.setEnableTransactionSupport(true);
stringRedisTemplate.multi();
stringRedisTemplate.delete(REDIS_LOCK);
List<Object> list = stringRedisTemplate.exec();
if (list==null){
continue;
}
}
stringRedisTemplate.unwatch();
break;
}
*/
/*
//版本8.2 Lua脚本
Jedis jedis = RedisUtils.getJedis();
String script = "if redis.call('get', KEYS[1]) == ARGV[1] "
+ "then "
+ " return redis.call('del', KEYS[1]) "
+ "else "
+ " return 0 "
+ "end";
try {
Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
if ("1".equals(o.toString())){
System.out.println("---del redis lock ok");
}else{
System.out.println("---del redis lock error");
}
}finally {
if(null!=jedis){
jedis.close();
}
}
*/
}
}
}
最终代码
package com.example.boot_redis02.controller;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* @author CSDN@日星月云
* @date 2022/11/24 14:51
*/
@RestController
public class GoodsController {
public static final String REDIS_LOCK ="atguiguLock";
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String serverPort;
@Autowired
private Redisson redisson;
@GetMapping("/buy_goods")
public String buy_Goods() throws Exception {
String value= UUID.randomUUID().toString()+Thread.currentThread().getName();
RLock redissonLock = redisson.getLock(REDIS_LOCK);
redissonLock.lock();
try {
String result = stringRedisTemplate.opsForValue().get("goods:001");// get key ====看看库存的数量够不够
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
stringRedisTemplate.opsForValue().set("goods:001", String.valueOf(realNumber));
System.out.println("成功买到商品,库存还剩下: "+ realNumber + " 件" + "\t服务提供端口" + serverPort);
return "成功买到商品,库存还剩下:" + realNumber + " 件" + "\t服务提供端口" + serverPort;
}else{
System.out.println("商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort);
}
return "商品已经售完/活动结束/调用超时,欢迎下次光临" + "\t服务提供端口" + serverPort;
} finally {
if (redissonLock.isLocked()){
if (redissonLock.isHeldByCurrentThread()){
redissonLock.unlock();
}
}
}
}
}
最后
2022-11-24 19:45:57
这篇博客能写好的原因是:站在巨人的肩膀上
这篇博客要写好的目的是:做别人的肩膀
开源:为爱发电
学习:为我而行
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论