0


springboot使用redis

1. 连接redis

默认有三种方式连接redis.

第一种:jedis—传统的项目–ssm

第二种:lettuce:---->刚出现没有多久就被springboot整合进来。

第三种:springboot连接redis

1.1 jedis操作redis服务器

(1)引入jedis依赖

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.1</version>
</dependency>

(2)编写相关的代码

@Testpublicvoidtest01(){//        Jedis(String host, int port)Jedis jedis =newJedis("192.168.80.128",6379);//        该类包含很多对redis操作的方法,这些方法和原来我们使用的命令一样Set<String> keys = jedis.keys("*");System.out.println(keys);//        对string数据类型操作
        jedis.set("k1","坤");String k1 = jedis.get("k1");System.out.println("k1: "+ k1);
        jedis.setex("k2",16,"有很多");String k2 = jedis.get("k2");System.out.println("k2: "+ k2);//        对hash数据类型操作Map<String,String> map =newHashMap<>();
        map.put("name","座右铭");
        map.put("age","18");
        map.put("hobby","优惠点");
        jedis.hset("k3",map);Map<String,String> k3 = jedis.hgetAll("k3");System.out.println(k3);//        对list类型数据操作
        jedis.lpush("k4","座右铭","优惠点","老油条","屏幕膜","万美元");List<String> k4 = jedis.lrange("k4",0,-1);System.out.println(k4);

        jedis.close();}

每次使用jedis对象时 都需要自己创建,当使用完后,需要关闭该对象。===>jedis中也存在连接池.

1.2 jedis连接池的使用

@TestpublicvoidtestPool(){//连接池的配置信息JedisPoolConfig config=newJedisPoolConfig();
        config.setMaxTotal(10);//最多的连接个数
        config.setMaxIdle(10);//最多空闲的连接个数
        config.setMinIdle(2);//最小的空闲个数
        config.setTestOnBorrow(true);//在获取连接对象时是否验证该连接对象的连通性//创建连接池对象JedisPool jedisPool=newJedisPool(config,"192.168.80.128",6379);Jedis jedis = jedisPool.getResource();//清空当前库
        jedis.flushDB();String set = jedis.set("k1","v1");String k1 = jedis.get("k1");System.out.println(k1);

        jedis.close();//归还连接池}

1.3 测试jedis使用和不使用连接池的效率

@Testpublicvoidtest03(){//连接池的配置信息JedisPoolConfig config=newJedisPoolConfig();
        config.setMaxTotal(100);//最多的连接个数
        config.setMaxIdle(10);//最多空闲的连接个数
        config.setMinIdle(2);//最小的空闲个数
        config.setTestOnBorrow(true);//在获取连接对象时是否验证该连接对象的连通性//创建连接池对象JedisPool jedisPool=newJedisPool(config,"192.168.80.128",6379);long start =System.currentTimeMillis();for(int i=0;i<10000;i++){Jedis jedis = jedisPool.getResource();String ping = jedis.ping();
            jedis.close();}long end =System.currentTimeMillis();System.out.println("耗时:"+(end-start));}@Testpublicvoidtest02(){long start =System.currentTimeMillis();//Jedis(String host, int port)for(int i=0;i<10000;i++){Jedis jedis=newJedis("192.168.80.128",6379);String ping = jedis.ping();
            jedis.close();}long end =System.currentTimeMillis();System.out.println("耗时:"+(end-start));}

1.4 springboot整合redis

springboot在整合redis时提高两个模板类,StringRedisTemplate和RedisTemplate.以后对redis的操作都在该模板类中。StringRedisTemplate是RedisTemplate的子类。

<!--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>

修改配置文件

#redis的配置信息
spring.data.redis.host=192.168.80.128
spring.data.redis.port=6379
#最多获取数
spring.data.redis.lettuce.pool.max-active=8
spring.data.redis.lettuce.pool.max-wait=-1ms
spring.data.redis.lettuce.pool.max-idle=8
spring.data.redis.lettuce.pool.min-idle=0

测试:

packagecom.zym;importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.data.redis.core.*;importjava.util.*;@SpringBootTestclassSpringbootRedisApplicationTests{//因为springboot整合redis时会把StringRedisTemplate创建并交于spring容器管理@AutowiredprivateStringRedisTemplate redisTemplate;@TestvoidcontextLoads(){//关于key的操作System.out.println(redisTemplate.keys("*"));//获取所有的key//是否存在指定的keyBoolean k1 = redisTemplate.hasKey("k1");System.out.println("是否存在指定的key:"+ k1);//    删除指定的keyBoolean k11 = redisTemplate.delete("k1");System.out.println("删除是否成功:"+ k11);}@Testvoidtest1(){//操作字符串===StringRedisTemplate会把对每一种数据的操作单独封装成一个类。//ValueOperations专门操作字符串ValueOperations<String,String> forValue = redisTemplate.opsForValue();//set 向指定key中设置value
        forValue.set("k1","你好");//get 获取指定key的值String k12 = forValue.get("k1");System.out.println("k12=="+ k12);//mset 同时设置一个或多个 key-value 对Map<String,String> map =newHashMap<>();
        map.put("k1","座右铭");
        map.put("k2","有很多");
        map.put("k3","另一条");
        map.put("k4","屏幕膜");
        forValue.multiSet(map);//mget 获取所有(一个或多个)给定 key 的值List<String> list =newArrayList<>();
        list.add("k1");
        list.add("k2");
        list.add("k3");
        list.add("k4");List<String> stringList = forValue.multiGet(list);System.out.println(stringList);//setnxBoolean aBoolean = forValue.setIfAbsent("k2","你也好");System.out.println("是否存入成功:"+ aBoolean);//incr decrLong k3 = forValue.increment("k3",20);System.out.println("k3:"+ k3);Long decrement = forValue.decrement("k3",20);System.out.println("k3:"+ decrement);}@Testvoidtest2(){//可以hash操作//hsetHashOperations<String,Object,Object> forHash = redisTemplate.opsForHash();
        forHash.put("k1","name","平家祥");
        forHash.put("k1","age","16");
        forHash.put("k1","sex","男");Map<String,String> map =newHashMap<>();
        map.put("name","胡萌");
        map.put("age","17");
        map.put("sex","女");
        forHash.putAll("k2", map);//hgetSystem.out.println(forHash.get("k1","name"));//hgetallMap<Object,Object> k1 = forHash.entries("k1");System.out.println(k1);//hkeysSet<Object> k11 = forHash.keys("k1");System.out.println(k11);//hvalsSystem.out.println(forHash.values("k1"));//flushall
        forHash.notifyAll();}@Testvoidtest3(){//可以list操作ListOperations<String,String> forList = redisTemplate.opsForList();//lpush
        forList.leftPushAll("k1","name","age","sex");//lrangeList<String> k1 = forList.range("k1",0,-1);System.out.println(k1);//lpopString leftPop = forList.leftPop("k1");System.out.println(leftPop);}@Testvoidtest4(){//可以set操作SetOperations<String,String> forSet = redisTemplate.opsForSet();//sadd
        forSet.add("k1","z","y","m","l");//smembersSet<String> k1 = forSet.members("k1");System.out.println(k1);//SRANDMEMBERString randomMember = forSet.randomMember("k1");System.out.println(randomMember);//    sinterSet<String> intersect = forSet.intersect("k1","k2");System.out.println(intersect);}@Testvoidtest5(){//可以zset操作ZSetOperations<String,String> forZSet = redisTemplate.opsForZSet();//zadd
        forZSet.add("k1","y",99);//zrangeSet<String> k1 = forZSet.range("k1",0,-1);System.out.println(k1);//ZREVRANKSet<String> k11 = forZSet.reverseRange("k1",0,-1);System.out.println(k11);}}

如果每次使用都人为指定序列化方式,统一设置redisTemplate的序列化

packagecom.zym.springbootredis.config;importcom.fasterxml.jackson.annotation.JsonAutoDetect;importcom.fasterxml.jackson.annotation.PropertyAccessor;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.RedisSerializer;importorg.springframework.data.redis.serializer.StringRedisSerializer;@ConfigurationpublicclassRedisConfig{@SuppressWarnings("all")@Bean//该方法的返回对象交于spring容器管理publicRedisTemplate<String,Object>redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object> template =newRedisTemplate<>();RedisSerializer<String> redisSerializer =newStringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =newJackson2JsonRedisSerializer(Object.class);ObjectMapper om =newObjectMapper();
        om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);//key序列化方式
        template.setKeySerializer(redisSerializer);//value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);//value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);//field序列化  key field  value
        template.setHashKeySerializer(redisSerializer);return template;}}

上面的连接都是连接的单机版的redis,真实项目它们的redis都是集群模式.

1.5 springboot连接集群

server.port=8888

spring.datasource.url=jdbc:mysql://192.168.1.51:3306/qy163
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

#redis的配置信息
#spring.redis.host=192.168.80.128
#spring.redis.port=6379
#最多获取数
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

# 设置redis重定向的次数---根据主节点的个数
spring.redis.cluster.max-redirects=3

spring.redis.cluster.nodes=192.168.80.128:7001,192.168.80.128:7002,192.168.80.128:7003,192.168.80.128:7004,192.168.80.128:7005,192.168.80.128:7006

2. redis的应用场景

2.1 redis可以作为缓存

(1) 缓存的原理

(2)缓存的作用:

减少访问数据库的频率。–提高系统的性能。

(3)什么样的数据适合放入缓存

  1. 查询频率高的
  2. 修改频率低的
  3. 数据安全性要求低的。

(4)如何使用缓存

<?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.3.12.RELEASE</version><relativePath/><!-- lookup parent from repository --></parent><groupId>com.zym</groupId><artifactId>demo111</artifactId><version>0.0.1-SNAPSHOT</version><name>demo111</name><description>demo111</description><properties><java.version>8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

配置文件

server.port=8888

spring.datasource.url=jdbc:mysql:///qy163
spring.datasource.password=123456
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

#redis的配置信息
spring.redis.host=192.168.80.128
spring.redis.port=6379
#最多获取数
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

servie

@ServicepublicclassStudentServiceImplimplementsStudentService{@ResourceprivateStudentMapper studentMapper;@ResourceprivateRedisTemplate<String,Object> redisTemplate;@OverridepublicList<Student>findAll(){ValueOperations<String,Object> forValue = redisTemplate.opsForValue();Object student = forValue.get("students");if(student !=null){return(List<Student>) student;}List<Student> students = studentMapper.selectList(null);if(students !=null){
            forValue.set("students", students);}return students;}@OverridepublicStudentgetById(Integer id){ValueOperations<String,Object> forValue = redisTemplate.opsForValue();Object o = forValue.get("student"+ id);if(o !=null){if(o instanceofNullObject){returnnull;}return(Student) o;}Student student = studentMapper.selectById(id);if(student !=null){
            forValue.set("student"+ id, student);}else{
            forValue.set("student"+ id,newNullObject(),5,TimeUnit.MINUTES);}return student;}publicIntegerinsert(Student student){
        redisTemplate.delete("student"+ student.getId());return studentMapper.insert(student);}publicIntegerupdate(Student student){
        redisTemplate.delete("student"+ student.getId());return studentMapper.updateById(student);}publicIntegerdelete(Integer id){
        redisTemplate.delete("student"+ id);return studentMapper.deleteById(id);}}

当执行增删改操纵时必须保证缓存和数据库数据一致性。—删除缓存

3. redis使用分布式锁

(1)通过使用jmeter压测工具测试
在这里插入图片描述

同一个库存数被多个线程卖,线程安全问题。—思考:之间出现线程安全问题时如何解决。

可以使用锁解决:----synchronized和Lock锁

packagecom.ykq.service;importcom.ykq.dao.StockDao;importcom.ykq.entity.Stock;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.Lock;importjava.util.concurrent.locks.ReentrantLock;/**
 * @program: redis-lock-qy145
 * @description:
 * @author: 闫克起2
 * @create: 2022-03-01 11:13
 **/@ServicepublicclassStockService_lock_syn{@AutowiredprivateStockDao stockDao;publicstaticObject o=newObject();Lock lock=newReentrantLock();public/*synchronized*/StringjianStock(Integer pid){try{
            lock.lock();//加锁//1. 查询指定的商品库存Stock stock = stockDao.selectById(pid);if(stock.getNum()>0){//2.库存减1
                stock.setNum(stock.getNum()-1);
                stockDao.updateById(stock);System.out.println("库存剩余数量:"+ stock.getNum());return"减库存成功";}else{System.out.println("库存不足");return"库存减失败";}}finally{
            lock.unlock();//释放锁}}}

上面的synchronized或Lock锁是否适合集群模式|分布式系统。不适合、因为synchronized都是基于JVM的本地锁。

在这里插入图片描述

需要在idea中跑项目的集群
在这里插入图片描述
在这里插入图片描述

配置nginx
在这里插入图片描述

启动nginx
在这里插入图片描述

jmeter压测
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

两台集群出现重卖问题。

3.2 使用redis来解决分布式锁

在这里插入图片描述

@AutowiredprivateStringRedisTemplate redisTemplate;/**
     * 2.0
     * @param pid
     * @return
     */publicStringjianStock(Integer pid){//占锁ValueOperations<String,String> forValue = redisTemplate.opsForValue();//占锁失败while(!forValue.setIfAbsent("product::"+ pid,"",30,TimeUnit.SECONDS)){try{Thread.sleep(20);}catch(InterruptedException e){
                e.printStackTrace();}}//占锁成功try{//1. 查询指定的商品库存Stock stock = stockDao.selectById(pid);if(stock.getNum()>0){//2.库存减1
                stock.setNum(stock.getNum()-1);
                stockDao.updateById(stock);System.out.println("库存剩余数量:"+ stock.getNum());return"减库存成功";}else{System.out.println("库存不足");return"库存减失败";}}finally{//释放锁资源
            redisTemplate.delete("product::"+pid);}}

如果你的业务代码的执行时间超过30s,当前线程删除的是其他线程的锁资源。 --watchDog机制

每个10s检测当前线程是否还持有所资源,如果持有则为当前线程延迟。—可以自己设置watchDog机制,—第三方Redission完美的解决分布式锁。

3.3 redisson完美解决redis超时问题

在这里插入图片描述

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.4</version></dependency>

(2)main函数

@Bean//创建redisson交于spring容器来管理publicRedissonClientredisson(){Config config =newConfig();
        config.useSingleServer().setAddress("redis://192.168.80.128:6379");RedissonClient redisson =Redisson.create(config);return redisson;}

(3)使用

@AutowiredprivateRedissonClient redisson;/**
     * 3.0
     * @param pid
     * @return
     */publicStringjianStock(Integer pid){RLock lock = redisson.getLock("product::"+ pid);try{
            lock.lock(30,TimeUnit.SECONDS);//加锁: 如果程序执行是出现一次//1. 查询指定的商品库存Stock stock = stockDao.selectById(pid);if(stock.getNum()>0){//2.库存减1
                stock.setNum(stock.getNum()-1);
                stockDao.updateById(stock);System.out.println("库存剩余数量:"+ stock.getNum());return"减库存成功";}else{System.out.println("库存不足");return"库存减失败";}}finally{
            lock.unlock();}}

4. redis面试题

一、Redis是什么?

redis是使用C语言编写的一个高速缓存数据库,它以key-value形式存储数据,而且它支持的数据类型非常丰富。

二、Redis都有哪些使用场景?

  1. 热点数据的缓存
  2. 计数器
  3. 排行榜。
  4. 实现分布式锁
  5. 使用session的共享–后面项目时使用

三、Redis支持的数据类型有哪些?

  1. String 2. Hash 3.List 4.Set 5.ZSet(sorted set)

四、Redis为什么是单线程的?

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了

五、Redis真的是单线程的吗?

并不是真的单线程,比如:RDB—Bgsave时,创建一个子线程

六、Redis持久化有几种方式?

RDB(快照):save,bgsave,配置文件自动

AOF:将每一条命令写入AOF文件中

七、什么是缓存穿透?怎么解决?

  1. 查询的数据在数据库中不存在,缓存中也不存在,这时有可能有人恶意访问这种数据。这些请求都会访问数据库,从而出现数据库压力过大。

情景:(1) 比如id不合法— ​ (2)确实数据库中不存在。

解决: 1. 在controller加校验
2. 我们可以在缓存中存入一个空对象,但是对象的过期时间不要太长,一般不会
超过5分钟。
3. 可以使用布隆过滤器。
布隆过滤器

八、怎么保证缓存和数据库数据的一致性?

  1. 设置合理的过期时间
  2. 当执行增删改时需要删除缓存数据

九、Redis,什么是缓存雪崩?怎么解决?

缓存雪崩:就是在某一时刻出现大量数据过期,而这时就有大量的请求访问该数据,这种现象叫做缓存雪崩。

什么情况下会出现大量数据过期:

  1. 项目刚刚上线
  2. redis服务器宕机
  3. 缓存数据真实过期

解决方案:

  1. 上线前预热数据。
  2. 集群
  3. 设置过期时间时要分散设置。

十、Redis怎么实现分布式锁?

使用redis中的setnx命令–占锁,当业务代码执行完毕后释放锁资源,而释放锁的命令是del。

十一、 redis在实现分布式锁式有什么缺陷

超时问题: 业务代码执行时间超过锁时间。使用:watchDog机制。 我们使用第三方:redisson

十二、Redis 淘汰策略有哪些?
在这里插入图片描述


本文转载自: https://blog.csdn.net/weixin_73310020/article/details/130394340
版权归原作者 资源码 所有, 如有侵权,请联系我们删除。

“springboot使用redis”的评论:

还没有评论