• 【redis】ssm项目整合redis,redis注解式缓存及应用场景,redis的击穿、穿透、雪崩的解决方案


    目录

    一、整合redis

    1、介绍

    1.1、redis(Remote Dictionary Server)

    1.2、MySQL

    1.3、区别

    2、整合

    2.1、配置

    2.2、文件配置

    2.3、key的生成规则方法

    2.4、注意

    二、redis注解式缓存

    1、@Cacheable注解

    2、@CachePut注解

    3、@CacheEvict注解

    4、应用场景

    三、redis击穿穿透雪崩

    1、击穿(Cache Miss)

    2、穿透(Cache Penetration)

    3、雪崩(Cache Avalanche)


    一、整合redis

    1、介绍

    1.1、redis(Remote Dictionary Server)

    • Redis是一种基于内存的键值存储系统,它将数据存储在内存中,因此读写速度非常快。
    • Redis支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,这使得Redis适用于各种应用场景,如缓存、消息队列、计数器等。
    • Redis具有高可用性和可扩展性,支持主从复制和分片,以实现数据的备份和负载均衡。
    • Redis的持久化方式有RDB(快照)和AOF(日志追加),可以将数据持久化到磁盘,保证数据的安全性。

    1.2、MySQL

    • MySQL是一种关系型数据库管理系统,使用标准的SQL语言进行数据操作。
    • MySQL将数据存储在磁盘上,因此相对于Redis来说,读写速度较慢。
    • MySQL支持事务处理和复杂的查询,适用于需要处理结构化数据的应用,如网站、电子商务等。
    • MySQL具有较高的稳定性和成熟度,支持ACID特性(原子性、一致性、隔离性、持久性),可以保证数据的完整性和一致性。

    1.3、区别

    1. redis是nosql数据库

    2. MySQL是sql数据库

    更一步的理解:

    • 存储方式:Redis将数据存储在内存中,而MySQL将数据存储在磁盘中。
    • 数据结构:Redis支持多种数据结构,MySQL使用表格和关系进行数据存储。
    • 读写性能:由于Redis使用内存存储,读写速度较快,而MySQL较慢。
    • 功能特性:Redis适用于缓存和实时数据处理,MySQL适用于结构化数据的存储和复杂查询。
    • ACID特性:MySQL支持事务处理和ACID特性,而Redis在默认情况下不支持事务处理。
    • 持久化方式:Redis可以将数据持久化到磁盘,MySQL具有多种持久化方式,如日志文件和复制。

    2、整合

    2.1、配置

    创建ssm的项目,在配置文件的pom文件添加

    1. <redis.version>2.9.0redis.version>
    2. <redis.spring.version>1.7.1.RELEASEredis.spring.version>
    3. <dependency>
    4. <groupId>redis.clientsgroupId>
    5. <artifactId>jedisartifactId>
    6. <version>${redis.version}version>
    7. dependency>
    8. <dependency>
    9. <groupId>org.springframework.datagroupId>
    10. <artifactId>spring-data-redisartifactId>
    11. <version>${redis.spring.version}version>
    12. dependency>

    2.2、文件配置

    编写一个配置文件redis.properties,在我们的resources包里面。

    redis.hostName:对应的IP地址
    redis.port:对应的端口号
    redis.password:对应的redis连接的密码

    1. redis.hostName=localhsot
    2. redis.port=6379
    3. redis.password=123456
    4. redis.timeout=10000
    5. redis.maxIdle=300
    6. redis.maxTotal=1000
    7. redis.maxWaitMillis=1000
    8. redis.minEvictableIdleTimeMillis=300000
    9. redis.numTestsPerEvictionRun=1024
    10. redis.timeBetweenEvictionRunsMillis=30000
    11. redis.testOnBorrow=true
    12. redis.testWhileIdle=true
    13. redis.expiration=3600

    spring-redis.xml里面包含了

    1. 添加注册
    2. redis的连接池配置:对应的value值是redis.properties里面的配置
    3. redis的的连接工厂:这里就用到了连接池的配置redis。
    4. 配置序列化:里面有string、json、hash的序列化器
    5. 配置key的生成
    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xmlns:cache="http://www.springframework.org/schema/cache"
    6. xsi:schemaLocation="http://www.springframework.org/schema/beans
    7. http://www.springframework.org/schema/beans/spring-beans.xsd
    8. http://www.springframework.org/schema/context
    9. http://www.springframework.org/schema/context/spring-context.xsd
    10. http://www.springframework.org/schema/cache
    11. http://www.springframework.org/schema/cache/spring-cache.xsd">
    12. <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    13. <property name="maxIdle" value="${redis.maxIdle}"/>
    14. <property name="maxTotal" value="${redis.maxTotal}"/>
    15. <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
    16. <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
    17. <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
    18. <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
    19. <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    20. <property name="testWhileIdle" value="${redis.testWhileIdle}"/>
    21. bean>
    22. <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
    23. destroy-method="destroy">
    24. <property name="poolConfig" ref="poolConfig"/>
    25. <property name="hostName" value="${redis.hostName}"/>
    26. <property name="port" value="${redis.port}"/>
    27. <property name="password" value="${redis.password}"/>
    28. <property name="timeout" value="${redis.timeout}"/>
    29. bean>
    30. <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    31. <property name="connectionFactory" ref="connectionFactory"/>
    32. <property name="keySerializer">
    33. <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    34. property>
    35. <property name="valueSerializer">
    36. <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
    37. property>
    38. <property name="hashKeySerializer">
    39. <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    40. property>
    41. <property name="hashValueSerializer">
    42. <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
    43. property>
    44. <property name="enableTransactionSupport" value="true"/>
    45. bean>
    46. <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
    47. <constructor-arg name="redisOperations" ref="redisTemplate"/>
    48. <property name="defaultExpiration" value="${redis.expiration}"/>
    49. <property name="usePrefix" value="true"/>
    50. <property name="cachePrefix">
    51. <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
    52. <constructor-arg index="0" value="-cache-"/>
    53. bean>
    54. property>
    55. bean>
    56. <bean id="cacheKeyGenerator" class="com.tgq.ssm.redis.CacheKeyGenerator">bean>
    57. <cache:annotation-driven cache-manager="redisCacheManager" key-generator="cacheKeyGenerator"/>
    58. beans>

    redis.propertiesjdbc.properties在与Spring做整合时会发生冲突;所以引入配置文件的地方要放到applicationContext.xml

    applicationContext.xml配置如下

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    5. xmlns:aop="http://www.springframework.org/schema/aop"
    6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    7. <bean id="propertyConfigurer"
    8. class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    9. <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
    10. <property name="ignoreResourceNotFound" value="true" />
    11. <property name="locations">
    12. <list>
    13. <value>classpath:jdbc.propertiesvalue>
    14. <value>classpath:redis.propertiesvalue>
    15. list>
    16. property>
    17. bean>
    18. <import resource="applicationContext-mybatis.xml">import>
    19. <import resource="spring-redis.xml">import>
    20. <import resource="applicationContext-shiro.xml">import>
    21. beans>

    2.3、key的生成规则方法

    spring-redis.xml的最后的配置就说明了键的配置规则。

    1. package com.tgq.ssm.redis;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.cache.interceptor.KeyGenerator;
    4. import org.springframework.util.ClassUtils;
    5. import java.lang.reflect.Array;
    6. import java.lang.reflect.Method;
    7. @Slf4j
    8. public class CacheKeyGenerator implements KeyGenerator {
    9. // custom cache key
    10. public static final int NO_PARAM_KEY = 0;
    11. public static final int NULL_PARAM_KEY = 53;
    12. @Override
    13. public Object generate(Object target, Method method, Object... params) {
    14. StringBuilder key = new StringBuilder();
    15. key.append(target.getClass().getSimpleName()).append(".").append(method.getName()).append(":");
    16. if (params.length == 0) {
    17. key.append(NO_PARAM_KEY);
    18. } else {
    19. int count = 0;
    20. for (Object param : params) {
    21. if (0 != count) {//参数之间用,进行分隔
    22. key.append(',');
    23. }
    24. if (param == null) {
    25. key.append(NULL_PARAM_KEY);
    26. } else if (ClassUtils.isPrimitiveArray(param.getClass())) {
    27. int length = Array.getLength(param);
    28. for (int i = 0; i < length; i++) {
    29. key.append(Array.get(param, i));
    30. key.append(',');
    31. }
    32. } else if (ClassUtils.isPrimitiveOrWrapper(param.getClass()) || param instanceof String) {
    33. key.append(param);
    34. } else {//Java一定要重写hashCode和eqauls
    35. key.append(param.hashCode());
    36. }
    37. count++;
    38. }
    39. }
    40. String finalKey = key.toString();
    41. // IEDA要安装lombok插件
    42. log.debug("using cache key={}", finalKey);
    43. return finalKey;
    44. }
    45. }

    2.4、注意

    1. applicationContext.xml中注册多个.properties结尾配置,那么不能在spring-*.xml添加注册。
    2. resources的配置必须要涵盖读取.properties结尾的文件。
    3. redisTemplate的使用可以参考jdbcTemplate、amqpTemplate、rabbitMQTemplate等。

    二、redis注解式缓存

    1、@Cacheable注解


            配置在方法或类上,作用:本方法执行后,先去缓存看有没有数据,如果没有,从数据库中查找出来,给缓存中存一份,返回结果,
            下次本方法执行,在缓存未过期情况下,先在缓存中查找,有的话直接返回,没有的话从数据库查找。

    value缓存位置的一段名称,不能为空
    key缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL
    condition触发条件,满足条件就加入缓存,默认为空,表示全部都加入缓存,支持SpEL

    • @Cacheable测试

    但我们调用查询的方法而没有用@Cacheable注解的时候我们可以看到我们查询出来的结果是两个,而且查询了数据库两次。

    当我们用到@Cacheable注解的时候我们只查询了数据库一次,如果再次查询就不会出现sql语句,而且已经缓存到了redis里面。

    测试结果:redis中有数据,则访问redis;如果没有数据,则访问MySQL;

    • 改变原有的key生成规则

    改变原有的规则之后我们再次运行,我们的redis的键就不一样了。

    condition:当某个值大于或者小于某个值才进行缓存。

    2、@CachePut注解

            类似于更新操作,即每次不管缓存中有没有结果,都从数据库查找结果,并将结果更新到缓存,并返回结果。

    value:缓存的名称,在 spring 配置文件中定义,必须指定至少一个。
    key:缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。
    condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存。

    当我们使用它测试出来的结果则是:只存不取

    • @Cacheable与@CachePut区别

    @Cacheable 注解表示方法的返回值可以被缓存,下次访问相同的方法时,会直接从缓存中获取结果,而不会再执行方法内部的逻辑。该注解标记在方法上,指定了缓存的名称(也可以使用默认的缓存名称),也可以通过参数指定缓存的Key。如果缓存中已有相应的Key存在,则会直接返回缓存中的值;若缓存中不存在对应的Key,则会执行方法,并将方法的返回值添加到缓存中。

    @CachePut 注解表示无条件地执行方法内部的逻辑,并将方法的返回值添加到缓存中。该注解也标记在方法上,通常用于更新缓存的操作。它与 @Cacheable 注解的不同之处在于,每次调用带有 @CachePut 注解的方法时,都会执行方法内部的逻辑,并将结果添加到缓存中,覆盖原有的缓存值。

    @CachePut 注解常用于需要更新缓存中数据的场景,可以保证每次调用方法时都会执行方法内部的逻辑,并更新缓存的结果。

    Cacheable:会在redis中存储数据,同时也会读取数据。

    CachePut:会在redis中写数据,不会读取数据。

    3、@CacheEvict注解

            用来清除用在本方法或者类上的缓存数据(用在哪里清除哪里)

    value:缓存位置的一段名称,不能为空
    key:缓存的key,默认为空,表示使用方法的参数类型及参数值作为key,支持SpEL
    condition:触发条件,满足条件就加入缓存,默认为空,表示全部都加入缓存,支持SpEL
    allEntries:true表示清除value中的全部缓存,默认为false

    测试结果:可以配置删除指定缓存数据,也可以删除符合规则的所有缓存数据;

    4、应用场景

    Redis注解式缓存是一种通过使用注解来简化缓存操作的方法,它可以方便地在方法调用前后自动进行缓存的读取和写入。以下是Redis注解式缓存的常见应用场景:

    1. 方法结果缓存:对于一些计算成本较高的方法,可以使用注解将其结果缓存起来,避免重复计算。当下次调用相同的方法时,可以直接从缓存中获取结果,提高性能并减轻服务器负载。

    2. 数据查询缓存:对于频繁读取的数据查询操作,可以使用注解将查询结果缓存起来。这样可以避免反复查询数据库,提高系统的响应速度。

    3. 限流和熔断:通过使用注解实现限流,可以控制对某些资源或接口的并发请求量,避免系统过载。同时,可以设置特定的缓存时间,当资源或接口不可用时,返回缓存中的数据,实现熔断降级。

    4. 防止缓存穿透:通过使用注解和布隆过滤器等技术,可以在查询之前拦截请求,并且判断缓存中是否存在对应的数据。如果不存在,则直接返回空值,避免对数据库的无效查询。

    5. 异步刷新缓存:通过使用注解和异步任务,在数据更新时可以同时异步地更新相应的缓存,保持缓存和数据库的一致性。

    三、redis击穿穿透雪崩

            redis的简单的作用:能够极大的减轻MySQL的访问压力

            大部分的解决方案需要根据具体的场景和需求选择合适的解决方案。同时,合理的缓存策略、数据预热和监控是缓解这些问题的关键,以确保系统的稳定性和可靠性。

    1、击穿(Cache Miss)

    什么事缓存击穿?

            缓存击穿就是在处于集中式高并发访问的情况下,当某个热点 key 在失效的瞬间,大量的请求在缓存中获取不到。瞬间击穿了缓存,所有请求直接打到数据库,就像是在一道屏障上击穿了一个洞。

    高并发量的同时key失效,导致请求直接到达数据库;

    • 解决方案之一:设置锁
      • 使用互斥锁(Mutex Lock)或分布式锁(Distributed Lock)来防止并发请求同时访问数据库。
        • 获取 Redis 锁,如果没有获取到,则回到任务队列继续排队
        • 获取到锁,从数据库拉取数据并放入缓存中
        • 释放锁,其他请求从缓存中拿到数据
    • 另一种方式:限流
      • 在缓存中设置短暂的过期时间,并使用异步更新机制,即在缓存失效时,只有一个请求重新加载数据到缓存中,其他请求等待缓存数据更新完成后再读取。
      • 请求redis之前做流量削峰

    2、穿透(Cache Penetration)

    很多请求都在访问数据库一定不存在的数据,造成请求将缓存和数据库都穿透的情况。 

    解决方案如下:

    • 规则排除:
      1. 使用布隆过滤器(Bloom Filter)等数据结构来过滤不存在的请求,在查询缓存之前先进行判断,避免对数据库进行不必要的查询。
      2. 或者可以增加一些参数检验。例如数据库数据 id 一般都是递增的,如果请求 id = -10 这种参数,势必绕过Redis。避免这种情况,可以对用户真实性检验等操作。
    • 缓存空值(Null Cache):
      1. 即将空结果缓存一段时间,以避免频繁查询不存在的数据。
      2. 可以理解为当缓存穿透时,redis存入一个类似null的值,下次访问则直接缓存返回空,当数据库中存在该数据的值则需要把redis存在的null值清除并载入新值,此方案不能解决频繁随机不规则的key请求。

    3、雪崩(Cache Avalanche)

     雪崩和击穿类似,不同的是击穿是一个热点 Key 某时刻失效,而雪崩是大量的热点 Key 在一瞬间失效 。

    方案如下:

    • 设置合适的缓存失效时间,可以使用随机值或添加一定的时间偏移量,避免多个键同时失效,导致数据库负载过大。
    • 使用多级缓存架构,将缓存分为多个层次,如本地缓存、分布式缓存等,提高缓存命中率和可用性。
    • 实时监控缓存健康状态,当缓存出现异常时,采取相应的措施,如自动降级、限流等,保护后端系统。

    更快捷的解决方案是:给不同的热点key设置不同的缓存策略

  • 相关阅读:
    Redis经典面试题总结
    python调用接口脚本
    vscode代码拼写错误检测插件
    SpringBoot之spring-boot-devtools热部署
    STM32-遥感数据处理
    教你如何使用接口根据关键词取视频列表
    Python机器学习实战-特征重要性分析方法(9):卡方检验(附源码和实现效果)
    Spring Boot - devtools 热部署
    OpenSea PHP开发包
    SpringBoot 项目实战 ~ 9.数据缓存
  • 原文地址:https://blog.csdn.net/weixin_74383330/article/details/134248681