spring-boot整合redis操作

201 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

spring-boot整合redis操作

1. 引入redis的starter

pom.xml

        <!-- 引入redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>
2. 配置redis

application.yml

spring:
  redis:
    host: 192.168.31.125
    password:
    port: 6379

3. 引入stringRedisTemplate
    @Autowired
    StringRedisTemplate stringRedisTemplate;
	...
    @Test
    public void testRedis(){
        //保存
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        ops.set("hello","world_" + UUID.randomUUID().toString());

        //查询
        String hello = ops.get("hello");
        System.out.println(hello);

    }

运行测试:

2021-11-22 15:40:40.198  INFO 13260 --- [           main] c.a.n.client.config.impl.ClientWorker    : [fixed-127.0.0.1_8848-561fc56a-09b9-4827-afff-dc46de5469ed] [subscribe] mybatis.yml+dev+561fc56a-09b9-4827-afff-dc46de5469ed
2021-11-22 15:40:40.198  INFO 13260 --- [           main] c.a.nacos.client.config.impl.CacheData   : [fixed-127.0.0.1_8848-561fc56a-09b9-4827-afff-dc46de5469ed] [add-listener] ok, tenant=561fc56a-09b9-4827-afff-dc46de5469ed, dataId=mybatis.yml, group=dev, cnt=1
2021-11-22 15:40:41.043  INFO 13260 --- [           main] io.lettuce.core.EpollProvider            : Starting without optional epoll library
2021-11-22 15:40:41.046  INFO 13260 --- [           main] io.lettuce.core.KqueueProvider           : Starting without optional kqueue library
world_b96c7f9c-3dbc-45ae-85f1-0bd47a9f512c

客户端登录检查: 在这里插入图片描述

在这里插入图片描述

模拟一段真实逻辑:

只做测试试用,实际使用需要加入适当空处理(防止缓存穿透,一直查库)过期时间时间随机(防止缓存同时失效,批量业务查库雪崩) 加锁(防止缓存击穿 某一时刻高并发查库)

    public Map<String, List<Catelog2Vo>>  getCatalogJson() {
        //给缓存存放json 拿出的json字符串需要逆转为对象类型【序列化与反序列化】

        //1.从缓存取出的json
        String redisString = stringRedisTemplate.opsForValue().get(RedisConstant.INDEX_CATALOGJSON);
        if(StringUtils.isEmpty(redisString)){
            System.out.println("经过查数据库...");
            //2.缓存中没有 查询数据库
            Map<String, List<Catelog2Vo>> catlogJsonFromDb = getCatalogJsonFromDb();
            //3. 数据库查询结构 转成json保存缓存
            String s  = JSON.toJSONString(catlogJsonFromDb);
            stringRedisTemplate.opsForValue().set(RedisConstant.INDEX_CATALOGJSON,s,1, TimeUnit.DAYS);
            return catlogJsonFromDb;
        }
        //2. - 直接把缓存取出结果 逆转指定类型
        Map<String, List<Catelog2Vo>> result = JSON.parseObject(redisString, new TypeReference< Map<String, List<Catelog2Vo>>>(){});
        return  result;

    }


public Map<String, List<Catelog2Vo>>  getCatalogJsonFromDb() {
//数据库逻辑
	return res;
}

结果展示: 在这里插入图片描述

首次调用数据库 存储redis消耗了6283ms 后续的直接redis获取数据消耗之多在百毫秒级别。 对性能有明显的提升


拓展

记录一个谷粒学院大神解决问题的过程:

springboot整合redis后,做压力测试或者长时间线上运行后产生:堆外内存溢出 OutOfDirectMemoryError

分析:

springboot2.0以后默认使用的lettuce作为操作redis的客户端。它使用netty进行网络通信。

leetuce的bug导致netty对外内存溢出。 大神翻阅代码后找出 netty的底层自己会统计内存容量使用量,netty会根据开销减去内存剩余量。推测netty客户端某处操作时,没有及时释放内存触发异常。

大神提出两个方案解决:

1升级lettuce客户端 2.更换使用jedis

大神操作是更换jedis(截至目前lettuce没有好的解决方案)

原先的pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>

修改(剔除lettuce 增加jedis):

 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.1.8.RELEASE</version>
        </dependency>

大神的补充讲解:

redisTemplate:

lettuce 、jedis两者都是操作redis底层客户端,封装了redis的一些api.redisTemplate是对这两者的在此封装。

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
...
...    

redisTemplate有基于这两者的连接工厂类,所以这两者可以替换。

  • 缓存穿透
  • 代码现象:redis每次都不命中,每次都触发查询数据库 一个redis的key经常性问题

  • 缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数 据库也无此记录,我们没有将这次查询的 null 写入缓存,这将导致这个不存在的数据每次 请求都要到存储层去查询,失去了缓存的意义。高并发时,可能 DB瞬间压力增大而崩溃,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。

  • 解决: null结果缓存、并且设置短暂过期时间。

  • 缓存雪崩
  • 代码现象:某时刻,所以逻辑都查询数据库 很多key瞬时问题

  • 缓存雪崩:我们设置缓存时key采用了相通的过期时间,导致存在某一时刻同时失效,请求全部转发到了DB上,DB瞬时压力过载雪崩。

  • 解决:原有的失效时间基础上,增加按一个随机值

  • 缓存击穿
  • 代码现象:某时刻,某个逻辑的缓存失效 碰上高并发去查数据库 一个key瞬时问题

  • 缓存雪崩:设置了过期时间的key,这个key在某些时间段被高并发的访问 是一种非常热点数据。当key失效时,恰好为高并发求情进来,落到DB层处理数据。

  • 解决:加锁 大量并发只让一个去查, 其他请求等候。查到后,释放锁。其他人获取到锁,先查缓存,就会有数据不用去DB.