SpringBoot使用Redis

1.Spring是如何集成Redis的?

Spring Data Redis

引入依赖

XML 复制代码
 <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配置

XML 复制代码
#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

java 复制代码
@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

java 复制代码
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

java 复制代码
@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

java 复制代码
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

java 复制代码
@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

java 复制代码
@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乱码的问题

java 复制代码
 JdkSerializationRedisSerializer  serializer = new JdkSerializationRedisSerializer();
        byte[] serialize = serializer.serialize("user#01");
        System.out.println(new String(serialize));

5.2自定义序列化工具(RedisTemplate)配置类

java 复制代码
@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(分布式锁)

java 复制代码
@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命令。
java 复制代码
@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);
    }
}

为什么不使用decyby,decrby具有弊端,以下通过demo进行演示

java 复制代码
 @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,才会造成这种原因。

java 复制代码
@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具有的弊端为,使商品多买,出现负数。

相关推荐
李慕婉学姐3 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆5 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin5 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20055 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉6 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国6 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882486 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈6 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_997 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹7 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理