1.Spring是如何集成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>
2.高级封装
3.Redis配置
#Redis服务器地址
spring.redis.host=192.168.11.84
#Redis连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database=0
#Redis服务器的连接密码默认为空
spring.redis.password=
#连接超时时间
spring.redis.timeout=30000
#连接池最大的连接数(使用负值表示没有限制)默认为8
spring.redis.lettuce.pool.max-active=8
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=1
#连接池中最大空闲等待时间,3s没有活干的时候直接驱逐该链接
spring.redis.lettuce.pool.time-between-eviction-runs=3000
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
4.StringRedisTemplate
String
@SpringBootTest
class StringTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name="redisTemplate")
RedisTemplate<String,Student> redisTemplate;
@Test
public void test3() {
Student student = Student.builder().id(1).name("海洋").build();
redisTemplate.opsForValue().set("student.1",student);
// stringRedisTemplate.opsForValue().set("Student."+student.getId(), JSONUtil.toJsonStr(student));
Student student1 = redisTemplate.opsForValue().get("student.1");
System.out.println(student1);
}
@Test //设置过期时间
public void test(){
// stringRedisTemplate.opsForValue().set("海洋","帅呆了",30, TimeUnit.SECONDS);
stringRedisTemplate.opsForValue().set("海洋","帅呆了",Duration.ofSeconds(30));
String s = stringRedisTemplate.opsForValue().get("海洋");
System.out.println(s);
}
@Test //setnx(锁的竞争)
public void test1() {
Boolean haiyang = stringRedisTemplate.opsForValue().setIfAbsent("haiyang", "88");
}
@Test
public void test2() {
Long haiyang = stringRedisTemplate.opsForValue().increment("haiyang");
Long haiyang1 = stringRedisTemplate.opsForValue().increment("haiyang", 20);
Long haiyang2 = stringRedisTemplate.opsForValue().decrement("haiyang", 50);
}
@Test
public void test4() {
stringRedisTemplate.opsForValue().append("haiyang","酷");
}
}
Hash
package com.by;
import com.by.model.Product;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
@SpringBootTest
class HashTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name="redisTemplate")
RedisTemplate<String, Product> redisTemplate;
@Test
public void test(){
redisTemplate.opsForHash().put("手机","name","小米");
redisTemplate.opsForHash().put("手机","age","6个月");
Product product = Product.builder().id(1).name("手机").build();
redisTemplate.opsForHash().put("手机","小米品牌手机",product);
}
@Test
public void test1(){
Object o = redisTemplate.opsForHash().get("手机", "name");
System.out.println(o);
}
@Test
public void test2(){
Boolean aBoolean = redisTemplate.opsForHash().hasKey("手机", "name");
Map<Object, Object> entries = redisTemplate.opsForHash().entries("手机");//key和value同时获取
}
@Test
public void test3(){
Set<Object> objects = redisTemplate.opsForHash().keys("手机");
}
@Test
public void test4(){
List<Object> values = redisTemplate.opsForHash().values("手机");
}
@Test
public void test5(){
Product product1 = Product.builder().id(1).name("小米手机").build();
Product product2 = Product.builder().id(1).name("华为手机").build();
redisTemplate.opsForHash().put("黑名单",String.valueOf(1),product1);
redisTemplate.opsForHash().put("黑名单",String.valueOf(2),product2);
}
}
List
@SpringBootTest
class ListTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name="redisTemplate")
RedisTemplate<String, Product> redisTemplate;
@Test
public void test(){
Product oppo1 = Product.builder().id(1).name("OPPO").build();
Product oppo2 = Product.builder().id(2).name("OPPOX").build();
redisTemplate.opsForList().leftPushAll("OPPO手机",oppo1,oppo2);
}
@Test
public void test1(){
Product oppo3 = Product.builder().id(3).name("OPPOB").build();
redisTemplate.opsForList().leftPush("OPPO手机",oppo3);
}
@Test
public void test2(){
redisTemplate.opsForList().leftPop("OPPO手机");
}
@Test
public void test3(){
redisTemplate.opsForList().rightPop("OPPO手机");
}
@Test
public void test4(){
Product product = redisTemplate.opsForList().index("OPPO手机", 0);
System.out.println(product);
}
@Test
public void test5(){
Long size = redisTemplate.opsForList().size("OPPO手机");
System.out.println(size);
}
@Test
void test6() {
// 如果一些原生命令,spring 没有给我们封装,redisTemplate.execute(new RedisCallback)
while (true){
System.out.println("开始一轮监听");
List<byte[]> rawResults = redisTemplate.execute(new RedisCallback<List<byte[]>>() {
@Override
public List<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
return connection.bRPop(10,"OPPO手机".getBytes());
}
});
if(ObjUtil.isNotEmpty(rawResults)){
byte[] rawKey = rawResults.get(0);
byte[] rawValue = rawResults.get(1);
Product product = (Product)redisTemplate.getValueSerializer().deserialize(rawValue);
System.out.println(product);
}
}
}
}
Set
package com.by;
import cn.hutool.core.util.ObjUtil;
import com.by.model.Product;
import com.by.model.Student;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import javax.annotation.Resource;
import java.util.List;
import java.util.Set;
@SpringBootTest
class SetTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name="redisTemplate")
SetOperations<String, Student> setOperations;
@Test//取差集
void test(){
stringRedisTemplate.opsForSet().add("海洋","语文","数学","英语","品德");
stringRedisTemplate.opsForSet().add("洋洋","语文","数学","英语","美术");
Set<String> difference = stringRedisTemplate.opsForSet().difference("海洋", "甜甜");
}
@Test//取交集
void test1(){
stringRedisTemplate.opsForSet().add("海洋","语文","数学","英语","品德");
stringRedisTemplate.opsForSet().add("洋洋","语文","数学","英语","美术");
Set<String> intersect = stringRedisTemplate.opsForSet().intersect("海洋", "甜甜");
}
@Test//取交集
void test2(){
stringRedisTemplate.opsForSet().add("海洋","语文","数学","英语","品德");
stringRedisTemplate.opsForSet().add("洋洋","语文","数学","英语","美术");
Set<String> union = stringRedisTemplate.opsForSet().union("海洋", "甜甜");
}
}
Zset
@SpringBootTest
class ZSetTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name="redisTemplate")
SetOperations<String, Student> setOperations;
@Test
void test(){
stringRedisTemplate.opsForZSet().add("海洋","语文",80);
stringRedisTemplate.opsForZSet().add("海洋","英语",60);
stringRedisTemplate.opsForZSet().add("海洋","数学",70);
Long aLong = stringRedisTemplate.opsForZSet().size("海洋");
Long aLong1 = stringRedisTemplate.opsForZSet().removeRangeByScore("海洋", 60, 100);
}
@Test
void test1(){
stringRedisTemplate.opsForZSet().add("海洋","语文",80);
stringRedisTemplate.opsForZSet().add("海洋","英语",60);
stringRedisTemplate.opsForZSet().add("海洋","数学",70);
Set<String> range1 = stringRedisTemplate.opsForZSet().range("海洋", 0,-1);
Set<ZSetOperations.TypedTuple<String>> tuples = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("海洋", 60, 100);
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores("海洋", 50, 100);//正序排列
}
@Test //模拟新闻点击量,排名
void test2(){
String key ="product.hot";
ArrayList<Integer> productId = CollUtil.newArrayList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);//使用hutool中的工具,获取先的数组
ExecutorService executorService = Executors.newFixedThreadPool(100);//jdk自带的线程池
for (int i = 1; i <=100; i++) {
executorService.submit(()->{
int c = RandomUtil.randomInt(1, 11);//RandomUtil.randomInt 获得指定范围内的随机数,例如我们想产生一个[10, 100)的随机数
System.out.println("访问了"+c);
stringRedisTemplate.opsForZSet().incrementScore(key,String.valueOf(c),1);//每次访问,数据加一
});
}
//因为是异步的,避免冲突
ThreadUtil.safeSleep(5000);
Set<String> strings = stringRedisTemplate.opsForZSet().reverseRange(key, 0, -1);
}
}
BitMap
@SpringBootTest
class BitMapTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name="redisTemplate")
RedisTemplate<String, Student> redisTemplate;
private String key = "sing.2024.haiyang";
@Test //签到
void test(){
Boolean b = stringRedisTemplate.opsForValue().setBit(key, 10, true);//设置某一天的偏移量,表示第10天的偏移量为1
b = stringRedisTemplate.opsForValue().setBit(key, 30, true);
b = stringRedisTemplate.opsForValue().setBit(key, 56, true);
RedisCallback<Long> redisCallback = new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
return connection.bitCount(key.getBytes());
}
};
Long execute = redisTemplate.execute(redisCallback);
}
@Test //车展,统计总的人数和这三天都来的人数
void test1() {
String key1 = "2024.3.28";
String key2 = "2024.3.29";
String key3 = "2024.3.30";
int yangyang = 10, tiantian = 20, tangtang = 40;
//第一天
stringRedisTemplate.opsForValue().setBit(key1, yangyang, true);
stringRedisTemplate.opsForValue().setBit(key1, tangtang, true);
//第二天
stringRedisTemplate.opsForValue().setBit(key2, yangyang, true);
stringRedisTemplate.opsForValue().setBit(key2, tiantian, true);
stringRedisTemplate.opsForValue().setBit(key2, tangtang, true);
//第三天
stringRedisTemplate.opsForValue().setBit(key3, yangyang, true);
stringRedisTemplate.opsForValue().setBit(key3, tangtang, true);
//Q1统计一共来了多少人
String q1 = "q1";
RedisCallback<Long> redisCallback = connection -> {
return connection.bitOp(RedisStringCommands.BitOperation.OR, q1.getBytes(), key1.getBytes(), key2.getBytes(), key3.getBytes());
};
//将三天的key合并值放到q1的key中
Long aLong = redisTemplate.execute(redisCallback);
RedisCallback<Long> redisCallback2 = connection -> {
return connection.bitCount(q1.getBytes());
};
//求q1里面1的总和
Long execute = redisTemplate.execute(redisCallback2);
//Q2:统计每天都来的人数
String q2="q2";
redisCallback = connection -> {
return connection.bitOp(RedisStringCommands.BitOperation.AND, q2.getBytes(), key1.getBytes(), key2.getBytes(), key3.getBytes());
};
//将三天的key合并值放到q1的key中
Long aLong1 = redisTemplate.execute(redisCallback);
redisCallback2 = connection -> {
return connection.bitCount(q2.getBytes());
};
//求q1里面1的总和
execute = redisTemplate.execute(redisCallback2);
}
}
5.RedisTemplate
5.1乱码的问题
JdkSerializationRedisSerializer serializer = new JdkSerializationRedisSerializer();
byte[] serialize = serializer.serialize("user#01");
System.out.println(new String(serialize));
5.2自定义序列化工具(RedisTemplate)配置类
@Configuration
public class RedisConfig {
@Bean //主动注册了一个名字为redisTemplate的bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
//启用默认类型推理,将类型信息作为属性写入json
//就是把类型的全类名写入JSON
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(),ObjectMapper.DefaultTyping.NON_FINAL);
jackson.setObjectMapper(mapper);
template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(jackson);
template.setHashKeySerializer(RedisSerializer.string());
template.setHashValueSerializer(jackson);
return template;
}
}
SetNX(分布式锁)
@SpringBootTest
class SetNXTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name="redisTemplate")
ValueOperations<String, String> valueOperations;
@Test
void test(){
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <=5; i++) {
executorService.execute(()->{
//某一个工人
String ioId="IO"+ RandomUtil.randomInt(1,1000);
while (true){
Boolean b = valueOperations.setIfAbsent("lock.product.1", ioId + " : " + DateUtil.now());
if (b){
System.out.println(Thread.currentThread().getId()+"获得了分布式锁======");
//执行业务
ThreadUtil.safeSleep(3000);
//执行业务成功后
stringRedisTemplate.delete("lock.product.1");
System.out.println(Thread.currentThread().getId()+"释放了分布式锁++++++++");
break;
}else {
System.out.println(Thread.currentThread().getId()+"没有获得分布式锁-------------");
ThreadUtil.safeSleep(3000);
}
}
});
}
ThreadUtil.safeSleep(100000);
}
LuaTest
在Redis中使用lua脚本,主要是其能够使多条redis语句具有原子性,在处理订单模块具有重要作用
-
参数说明:
KEYS[]
数组用于在脚本中引用传入的键名。ARGV[]
数组用于传递额外的参数(非键名)给脚本。redis.call()
函数在脚本内部调用Redis命令。
@SpringBootTest
class LuaTests {
@Autowired
StringRedisTemplate stringRedisTemplate;
@Resource(name="redisTemplate")
RedisTemplate<String, Student> redisTemplate;
@Test
void test(){
String lua = "return redis.call('set',KEYS[1],ARGV[1])";
RedisScript<String> redisScript = RedisScript.of(lua, String.class);
stringRedisTemplate.execute(redisScript, CollUtil.newArrayList("a"),"b100");
}
@Test
void test1(){
for (int i = 1; i <= 5; i++) {
stringRedisTemplate.opsForValue().set("product."+i,String.valueOf(i));
}
}
@Test //一次扣减一个库存商品
void test2(){
StringBuilder sb = new StringBuilder();
sb.append( " local key = KEYS[1] " );//你要扣减的key,例如:product.1
sb.append( " local qty = ARGV[1] " );//你要剪得的数量
sb.append( "local redis_qty = redis.call('get',key) " );//查询redis里面存储的数量
sb.append( " if tonumber(redis_qty) >= tonumber(qty) then" ); //库存量与需求量进行对比,tonumber作用是转成数字
sb.append( " redis.call('decrby',key,qty) " ); //对redis数据库进行减操作
sb.append( " return -1 " ); //满足条件,返回-1
sb.append( " else " );
sb.append( " return tonumber(redis_qty) " );//如果不满足,返回库存数量
sb.append( " end " );
RedisScript<Long> redisScript = RedisScript.of(sb.toString(), Long.class);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 5; i++) {
executorService.execute(()->{
int qty = RandomUtil.randomInt(1,6);
Long aLong = stringRedisTemplate.execute(redisScript, CollUtil.newArrayList("product.5"), String.valueOf(qty));
if (aLong == -1L) {
System.out.println(Thread.currentThread().getId() + " 扣减成功,扣减了-> "+ qty);
} else {
System.out.println(Thread.currentThread().getId() + "扣减失败,需求量是:"+qty+",剩余库存量:"+aLong);
}
});
ThreadUtil.safeSleep(3000);//因为线程是异步的,所以要睡眠一定时间
}
}
@Test //一次扣减多个库存
void test3(){
StringBuilder sb = new StringBuilder();
sb.append( " local table = {} " );//所有不满足的商品都存到table
sb.append( " local values = redis.call('mget', unpack(KEYS)) " );//查询所有的key所含有的value,[product.1,product.2]=>product.1,product.2
sb.append( " for i = 1, #KEYS do " );//循环,#KEYS代表KEYS的值
sb.append( " if tonumber(ARGV[i]) > tonumber(values[i]) then " );//如果需求量大于库存量
sb.append( " table[#table + 1] = KEYS[i] .. '=' .. values[i] " );
sb.append( " end " );
sb.append( " end " );
sb.append( " if #table > 0 then " );//如果有不满足的商品,返回该需求
sb.append( " return table " );
sb.append( " end " );
sb.append( " for i=1 ,#KEYS do " );//如果满足,进行循环扣除
sb.append( " redis.call('decrby',KEYS[i],ARGV[i]) " );
sb.append( " end " );
sb.append( " return{} " );
RedisScript<List> redisScript = RedisScript.of(sb.toString(), List.class);
List<StockProduct> stockProducts =new ArrayList<>();
stockProducts.add(new StockProduct(5,1));
stockProducts.add(new StockProduct(4,2));
List<String> keys = stockProducts.stream().map(it -> "product." + it.getId()).collect(Collectors.toList());
Object[] qtys = stockProducts.stream().map(it -> it.getQty()+"").toArray();
List<String> list = stringRedisTemplate.execute(redisScript, keys, qtys);
if (list.isEmpty()){
System.out.println("库存冻结成功");
}else {
for(String key_qty : list){
String[] split = key_qty.split("=");
System.out.println(split[0]+"库存不足,剩余库存量"+split[1]);
}
}
ThreadUtil.safeSleep(3000);
}
}
private static final String redisInventory = "product.inventory.";
static StringBuilder sb = new StringBuilder();
static StringBuilder sb2 = new StringBuilder();
//冻结库存
static {
// 创建 lua 脚本
sb.append(" local table = {} "); // 你要扣减的 key:product.1,定义一个局部变量table,用来存储库存扣减失败的商品及其当前的库存量
sb.append(" local values = redis.call('mget', unpack(KEYS) )"); // [product.1,product.2] => product.1 product.2,使用 redis的mget命令的命令获取所有传入的KEYS参数对应的值
//unpack函数将KEYS数组转换为mget命令的多个参数
sb.append(" for i = 1, #KEYS do ");//开始一个循环,遍历KEYS中的每一个商品键
sb.append(" if tonumber(ARGV[i]) > tonumber(values[i]) then ");//检查传入的减少数量ARGV[i]是否大于当前库存values[i]
sb.append(" table[#table + 1] = KEYS[i] .. '=' .. values[i] "); // product.1=23,如果要减少的数量大于库存,则将商品键和其当前库存值加入table格式为:商品键=库存值
sb.append(" end ");
sb.append(" end ");
sb.append(" if #table > 0 then ");//判断table中是否有元素,即是否有库存扣减失败的情况
sb.append(" return table ");//如果有失败的情况,返回包含失败的信息
sb.append(" end ");
sb.append(" for i = 1 , #KEYS do ");//如果没有扣减失败,开始一个新的循环来减少每个商品的实际库存
sb.append(" redis.call('decrby',KEYS[i],ARGV[i]) ");//使用decrby命令减少每个商品的库存,ARGV[i扣减的库存值
sb.append(" end ");
sb.append(" return {} ");
}
//解冻
static {
sb2.append("for i = 1, #KEYS do ")
.append(" redis.call('incrby', KEYS[i], ARGV[i]) ")
.append("end ")
.append("return 'ok' ");
}
public String decrease(List<DecreaseRequest> decreaseRequest) {
RedisScript<List> luaScript = RedisScript.of(sb.toString(), List.class);
// 查询传入的商品,如果没有就初始化,加入 redis 中
for (DecreaseRequest request : decreaseRequest) {
Integer productId = request.getProductId();
if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(redisInventory + productId))) {
//从数据库中查询当前商品的具体信息
Integer canSaleQty = inventoryDao.queryCanSaleQty(productId);
//将库存信息写入 redis
valueOperations.set(redisInventory + productId, canSaleQty);
}
}
// 构建 Redis 中键名的列表,格式为"redisInventory" + 商品 id
List<String> keys = decreaseRequest.stream().map(it -> redisInventory + it.getProductId()).collect(Collectors.toList());
// 将需要减少的库存数量转换为字符串数组
Object[] qtys = decreaseRequest.stream().map(it -> it.getCount() + "").toArray();
// 使用 Lua 脚本执行库存减少操作,返回执行结果的列表
List<String> list = stringRedisTemplate.execute(luaScript, keys, qtys);
// 检查执行结果,若为空则表示所有产品库存冻结成功
if (ObjUtil.isEmpty(list)) {
decreaseRequest.forEach(it -> {
log.debug("订单:{}冻结库存成功,冻结商品——> ID:{},名称:{},数量为:{}", it.getOrderId(), it.getProductId(), it.getProductName(), it.getCount());
});
return "ok";
}
// 在控制台打印库存不足的产品信息
Long orderId = decreaseRequest.get(0).getOrderId();
for (String key_qty : list) {
String[] split = key_qty.split("=");
String[] split1 = split[0].split("\\.");
decreaseRequest.forEach(it -> {
Integer productId = it.getProductId();
if (ObjUtil.equals(Integer.parseInt(split1[2]), productId)) {
log.debug("订单:{} 冻结库存失败,商品:{} 库存不足,id:{},剩余库存量:{},请求冻结数量为:{}", orderId, it.getProductName(), productId, split[1], it.getCount());
}
});
}
return list.toString();
}
/**
* 取消订单并回补库存
*
* @param decreaseRequests 订单中的商品信息及每件商品需要回补的数量。每个订单项包含商品 ID 和回补数量。
* 这个列表中的每个元素表示一个需要回补库存的商品。
*/
public String increase(List<DecreaseRequest> decreaseRequests) {
// 将构建的 Lua 脚本字符串封装为 RedisScript 对象,指定脚本返回值的类型为 String。
RedisScript<String> luaScript = RedisScript.of(sb2.toString(), String.class);
// 根据订单中的商品信息构建回补库存操作所需的 Redis 键(商品 ID)和值(回补数量)。
List<String> keys = decreaseRequests.stream()
.map(it -> redisInventory + it.getProductId())
.collect(Collectors.toList());
List<String> quantities = decreaseRequests.stream()
.map(DecreaseRequest::getCount)
.map(Object::toString)
.collect(Collectors.toList());
String[] values = ArrayUtil.toArray(quantities, String.class);
// 执行 Lua 脚本,回补库存。keys 是库存键列表,quantities 是对应每个键(商品)的回补数量。
String result = stringRedisTemplate.execute(luaScript, keys, values);
// 根据执行结果,记录日志。如果执行成功,则为每个回补的商品记录成功信息;如果执行失败,则记录失败原因。
if ("ok".equals(result)) {
decreaseRequests.forEach(it -> {
log.debug("订单 {} 回补库存成功,商品 ID:{},商品名称:{},回补数量为:{}", it.getOrderId(), it.getProductId(), it.getProductName(), it.getCount());
});
return result;
} else {
log.error("库存回补失败");
}
return "error";
}
为什么不使用decyby,decrby具有弊端,以下通过demo进行演示
@Test
void test(){
String key ="product.1";
valueOperations.set(key,5);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <=5; i++) {
executorService.execute(()->{
Integer redis_qty = valueOperations.get(key);
if (redis_qty>=1){
//满足条件,数量减一,但是可能被打断
valueOperations.set(key,redis_qty-1);
System.out.println("线程:"+Thread.currentThread().getId() +"执行成功,数量减一");
}else {
System.out.println("线程:"+Thread.currentThread().getId() +"执行失败");
}
});
}
ThreadUtil.safeSleep(3000);
}
可以看出,定义了product.1的数量为5,线程数为5,正常结果应该为0,但是结果缺为4,这是因为,线程的执行速度非常快,多条线程执行时,库里面显示的都是5,才会造成这种原因。
@Test
void test2(){
String key ="product.1";
valueOperations.set(key,5);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <=8; i++) {
executorService.execute(()->{
Integer redis_qty = valueOperations.get(key);
if (redis_qty>=0){
//满足条件,数量减一,但是可能被打断
Long redis_qty2 = valueOperations.decrement(key);
System.out.println("线程:"+Thread.currentThread().getId() +"执行成功,数量减一");
}else {
System.out.println("线程:"+Thread.currentThread().getId() +"执行失败");
}
});
}
ThreadUtil.safeSleep(3000);
}
decrby具有的弊端为,使商品多买,出现负数。