SpringBoot使用Redis

1.Spring是如何集成Redis的?

Spring Data 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语句具有原子性,在处理订单模块具有重要作用

  1. 参数说明:

    • 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具有的弊端为,使商品多买,出现负数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值