Redis分布式锁
Redis分布式锁
为了防止分布式系统中的多个进程之间相互干扰,我们需要一种分布式协调技术来对这些进程进行调度。而这个分布式协调技术的核心就是来实现这个分布式锁。
库存超卖
构建Redis分布式锁基础环境
maven依赖
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.13.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.chinda</groupId>
<artifactId>redis-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-01</name>
<description>Redis project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</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>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.2.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
资源配置
server.port=1111
# redis数据库索引(默认0)
spring.redis.database=0
# redis服务器地址
spring.redis.host=192.168.2.5
# 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.mix-idle=0
Redis配置
package com.chinda.redis.config;
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;
import java.io.Serializable;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@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;
}
}
库存业务代码编写
不考虑并发情况1.0版本
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
}
}
此时,在不考虑并发情况下,业务代码是没有任何问题的。若存在并发,此时就会出现超卖现象。
考虑并发,不考虑分布式情况2.0版本
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
synchronized (this) {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
}
}
}
此时会根据业务场景选择使用synchronized
还是ReentrantLock
。选择synchronized
是一直阻塞状态,直到一线程释放锁下一线程才会获取锁。选择ReentrantLock
可以使用boolean tryLock(long timeout, TimeUnit unit)
方法设置超时时间,当锁超时时,会将锁自动释放,这时候会存在删错锁的情况,下文会出现Redis分布式锁类似的情况。
考虑分布式情况3.0版本
此时需要将上述环境部署多份。需要做负载均衡,简单架构图模型。
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
public static final String REDIS_LOCK = "lock";
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
String value = IdUtil.fastUUID() + Thread.currentThread().getName();
Boolean absent = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);
if (!absent) {
System.out.println("抢锁失败");
return "抢锁失败";
}
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
redisTemplate.delete(REDIS_LOCK);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
}
}
此时当业务异常,或者程序执行其他分支,会导致Redis锁没有被删除,后续线程就会永远的阻塞了。
考虑lock/unlock配对情况4.0版本
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
public static final String REDIS_LOCK = "lock";
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
String value = IdUtil.fastUUID() + Thread.currentThread().getName();
Boolean absent = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);
if (!absent) {
System.out.println("抢锁失败");
return "抢锁失败";
}
try {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
} finally {
redisTemplate.delete(REDIS_LOCK);
}
}
}
若在加锁以后,解锁之前程序突然宕机,这时锁永远不会被解除。
考虑锁超时5.0版本
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
public static final String REDIS_LOCK = "lock";
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
String value = IdUtil.fastUUID() + Thread.currentThread().getName();
Boolean absent = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value);
redisTemplate.expire(REDIS_LOCK, 10L, TimeUnit.SECONDS);
if (!absent) {
System.out.println("抢锁失败");
return "抢锁失败";
}
try {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
} finally {
redisTemplate.delete(REDIS_LOCK);
}
}
}
此时加锁与设置超时时间非原子操作,所以会引起严重的并发问题。
考虑加锁与设置超时原子操作6.0版本
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
public static final String REDIS_LOCK = "lock";
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
String value = IdUtil.fastUUID() + Thread.currentThread().getName();
Boolean absent = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
if (!absent) {
System.out.println("抢锁失败");
return "抢锁失败";
}
try {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
} finally {
redisTemplate.delete(REDIS_LOCK);
}
}
}
若业务代码执行超过设置的超时时间, 当业务代码执行完删除锁时会删除其他线程的锁。
考虑删错锁7.0版本
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
public static final String REDIS_LOCK = "lock";
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
String value = IdUtil.fastUUID() + Thread.currentThread().getName();
Boolean absent = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
if (!absent) {
System.out.println("抢锁失败");
return "抢锁失败";
}
try {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
} finally {
if (redisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)) {
redisTemplate.delete(REDIS_LOCK);
}
}
}
}
此时判断和删除锁非原子操作。
考虑删除与判断为原子操作8.0版本(Redis事务方式)
package com.chinda.redis.controller;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
public static final String REDIS_LOCK = "lock";
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
String value = IdUtil.fastUUID() + Thread.currentThread().getName();
Boolean absent = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
if (!absent) {
System.out.println("抢锁失败");
return "抢锁失败";
}
try {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
} finally {
while (true) {
redisTemplate.watch(REDIS_LOCK);
if (redisTemplate.opsForValue().get(REDIS_LOCK).equalsIgnoreCase(value)) {
// 开启redis事务
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.multi();
redisTemplate.delete(REDIS_LOCK);
List<Object> list = redisTemplate.exec();
if (CollUtil.isEmpty(list)) {
continue;
}
}
redisTemplate.unwatch();
break;
}
}
}
}
考虑删除与判断为原子操作8.1版本(LUA脚本)--推荐
封装简单的Jedis工具
package com.chinda.redis.util;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @author Wang Chinda
* @date 2021/4/14
*/
public class RedisUtils {
private static JedisPool jedisPool;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(20);
jedisPoolConfig.setMinIdle(10);
jedisPool = new JedisPool(jedisPoolConfig, "192.168.1.5", 6379);
}
public static Jedis getJedis() throws Exception {
if (null != jedisPool) {
return jedisPool.getResource();
}
throw new Exception("Jedis Pool is not ok");
}
}
修改业务代码
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.IdUtil;
import com.chinda.redis.util.RedisUtils;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
public static final String REDIS_LOCK = "lock";
@Autowired
private StringRedisTemplate redisTemplate;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
String value = IdUtil.fastUUID() + Thread.currentThread().getName();
Boolean absent = redisTemplate.opsForValue().setIfAbsent(REDIS_LOCK, value, 10L, TimeUnit.SECONDS);
if (!absent) {
System.out.println("抢锁失败");
return "抢锁失败";
}
try {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
} finally {
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 obj = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
if ("1".equals(obj.toString())) {
System.out.println("-----delete redis lock ok");
} else {
System.out.println("-----delete redis lock fail");
}
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
}
此时可满足大部分场景,但是锁超时自动续期问题没有解决。Redis集群模式下,当锁注册到master节点,但是master节点还没来及的同步到slave节点时,master节点宕机,导致锁丢失问题。
Redis集群与锁超时自动续期9.0版本
添加Redisson配置
package com.chinda.redis.config;
import org.redisson.Redisson;
import org.redisson.config.Config;
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;
import java.io.Serializable;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@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;
}
@Bean
public Redisson redisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.2.5:6379").setDatabase(0);
return (Redisson) Redisson.create(config);
}
}
编写业务代码
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
public static final String REDIS_LOCK = "lock";
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private Redisson redisson;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
RLock lock = redisson.getLock(REDIS_LOCK);
lock.lock();
try {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
} finally {
lock.unlock();
}
}
}
在超高并发下会出现异常, 创建锁的线程与解锁的当前线程不是同一个的问题。
考虑超高并发9.1版本
package com.chinda.redis.controller;
import cn.hutool.core.convert.Convert;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Wang Chinda
* @date 2021/4/13
*/
@RestController
@RequestMapping("/product")
public class ProductController {
public static final String REDIS_LOCK = "lock";
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private Redisson redisson;
@Value("${server.port}")
private String serverPort;
@GetMapping
public String getTestStr() {
RLock lock = redisson.getLock(REDIS_LOCK);
lock.lock();
try {
Integer result = Convert.toInt(redisTemplate.opsForValue().get("product:001"));
if (result == null) {
System.out.println("商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort);
return "商品已经售完/活动结束/调用超时, 欢迎下次光临 " + "\t 服务提供端口: " + serverPort;
}
int realNumber = result - 1;
redisTemplate.opsForValue().set("product:001", Convert.toStr(realNumber));
System.out.println("成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort);
return "成功买到商品, 库存还剩下: " + realNumber + "件 \t 服务提供端口" + serverPort;
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}