spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
username: root
password: 123456
redis:
database: 2
host: 127.0.0.1
port: 6379
password: 123456
timeout: 10000
lettuce:
pool:
max-active: 1000
max-wait: -1
max-idle: 10
min-idle: 0
org.springframework.boot
spring-boot-starter-data-redis
org.apache.commons
commons-pool2
2.6.0
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//配置序列化方式
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper obm = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和publi
obm.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
obm.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(obm);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key 采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash
template.setHashKeySerializer(stringRedisSerializer);
//value
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
/**
* 模拟商品秒杀场景
* 使用乐观锁处理并发
*/
@PassToken
@GetMapping("/goodsKill")
public Boolean goodsKill() {
// 开启事务支持,在同一个 Connection 中执行命令
redisTemplate.setEnableTransactionSupport(true);
// 模拟一千人进行秒杀
int userId = new Random().nextInt(1000);
String userKey = "MS:USER";
// 默认10件商品
String goodsKey = "MS:GOODS";
// 监听商品key
redisTemplate.watch(goodsKey);
// 判断商品是否上架
Integer quantity = (Integer) redisTemplate.opsForValue().get(goodsKey);
if (null == quantity) {
System.out.println("商品还没上架,不能秒杀");
return false;
}
if (0 >= quantity) {
System.out.println("商品已经秒杀完");
return false;
}
// 用户已经秒杀
if (redisTemplate.opsForSet().isMember(userKey, userId)) {
System.out.println("用户" + userId + ":已经参与秒杀");
return false;
}
// 开启事务
redisTemplate.multi();
// 减库存
redisTemplate.opsForValue().decrement(goodsKey);
// 添加秒杀用户
redisTemplate.opsForSet().add(userKey, userId);
// 提交事务
List<Object> results = redisTemplate.exec();
if (null == results || results.size() == 0) {
System.out.println("并发秒杀,失败了");
return false;
}
System.out.println("用户" + userId + ":秒杀成功");
return true;
}
local userId = KEYS[1];
local userKey = "MS:USER";
local goodsKey = "MS:GOODS";
local userExists = redis.call("sismember", userKey, userId);
if tonumber(userExists) == 1 then
return 2;
end
local num = redis.call("get", goodsKey);
if tonumber(num) <= 0 then
return 0;
else
redis.call("decr", goodsKey);
redis.call("sadd", userKey, userId);
end
return 1;
/**
* 使用Lua脚本解决库存遗留问题
*
* @return
*/
@PassToken
@GetMapping("/luaGoodsKill")
public boolean luaGoodsKill() {
// resource/lua/GoodsKill.lua
DefaultRedisScript<Long> redisScript = new DefaultRedisScript();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/GoodsKill.lua")));
redisScript.setResultType(Long.class);
// 模拟一千人进行秒杀
Integer userId = new Random().nextInt(1000);
// redisScript,key列表,arg(可多个),参考redis eval 命令
Long result = (Long) redisTemplate.execute(redisScript, Arrays.asList(userId.toString()));
if (0L == result) {
System.out.println("商品已经秒杀完");
} else if (1L == result) {
System.out.println("用户" + userId + ":秒杀成功");
} else if (2L == result) {
System.out.println("用户" + userId + ":已经参与秒杀");
} else {
System.out.println("秒杀异常!!");
}
return true;
}
PS: spring-boot-starter-data-redis 提供的返回类型里面不支持 Integer,使用Long代替。
package org.springframework.data.redis.connection;
import java.util.List;
import org.springframework.lang.Nullable;
public enum ReturnType {
BOOLEAN,
INTEGER,
MULTI,
STATUS,
VALUE;
private ReturnType() {
}
public static ReturnType fromJavaType(@Nullable Class<?> javaType) {
if (javaType == null) {
return STATUS;
} else if (javaType.isAssignableFrom(List.class)) {
return MULTI;
} else if (javaType.isAssignableFrom(Boolean.class)) {
return BOOLEAN;
} else {
return javaType.isAssignableFrom(Long.class) ? INTEGER : VALUE;
}
}
}