• SSM整合redis及redis的注解式开发和解决Redis缓存问题


    一.SSM整合Redis

    1.pom配置

    用于解决运行时没有将数据库配置信息jdbc.properites加载到target文件中

    1. <resource>
    2. <directory>src/main/resourcesdirectory>
    3. <includes>
    4. <include>*.propertiesinclude>
    5. <include>*.xmlinclude>
    6. includes>
    7. resource>

    2.配置spring-redis.xml

    2.1 注册redis.properties

    1. redis.hostName=localhost
    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

    2.2 配置redis连接池

    1. <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
    2. <property name="maxIdle" value="${redis.maxIdle}"/>
    3. <property name="maxTotal" value="${redis.maxTotal}"/>
    4. <property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
    5. <property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
    6. <property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
    7. <property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
    8. <property name="testOnBorrow" value="${redis.testOnBorrow}"/>
    9. <property name="testWhileIdle" value="${redis.testWhileIdle}"/>
    10. bean>

    2.3 配置连接工厂

    1. <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
    2. destroy-method="destroy">
    3. <property name="poolConfig" ref="poolConfig"/>
    4. <property name="hostName" value="${redis.hostName}"/>
    5. <property name="port" value="${redis.port}"/>
    6. <property name="password" value="${redis.password}"/>
    7. <property name="timeout" value="${redis.timeout}"/>
    8. bean>

    2.4 配置序列化器

    1. <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    2. <property name="connectionFactory" ref="connectionFactory"/>
    3. <property name="keySerializer">
    4. <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    5. property>
    6. <property name="valueSerializer">
    7. <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
    8. property>
    9. <property name="hashKeySerializer">
    10. <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    11. property>
    12. <property name="hashValueSerializer">
    13. <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
    14. property>
    15. <property name="enableTransactionSupport" value="true"/>
    16. bean>

    2.5 配置缓存管理器

    1. <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
    2. <constructor-arg name="redisOperations" ref="redisTemplate"/>
    3. <property name="defaultExpiration" value="${redis.expiration}"/>
    4. <property name="usePrefix" value="true"/>
    5. <property name="cachePrefix">
    6. <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix">
    7. <constructor-arg index="0" value="-cache-"/>
    8. bean>
    9. property>
    10. bean>

    2.6 配置redis的key生成策略

    Spring框架中,缓存键生成器用于生成缓存中存储的键,以便于识别和检索缓存数据

    1. package com.YU.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. }

    生成逻辑:

    1. 如果方法没有参数,缓存键被设置为类名 + 方法名 + ":0"(NO_PARAM_KEY)。
    2. 如果方法有参数,缓存键由类名 + 方法名 + ":" + 参数的组合构成。参数之间用逗号分隔。具体的参数值会被加入到缓存键中,如果参数为null,则用NULL_PARAM_KEY(53)表示;如果参数是基本数据类型、包装类或字符串,则直接将参数值加入缓存键;如果参数是其他对象类型,则将其hashCode()的返回值加入缓存键。

    这样生成的缓存键具有类似以下的形式:

    com.example.SomeClass.someMethod:parameter1,parameter2,53,parameter4

    这个缓存键生成器的实现允许在方法参数不同的情况下生成不同的缓存键,以便于确保缓存的正确性

    3.Springcontext.xml中添加spring-redis.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>

     注意点:

    1.当spring上下文中的注册信息文件出现两个及以上,不能在单独的配置文件信息中进行配置,要全部整合到spring上下文的配置文件中进行配置

    2.当有*.properties文件需要编译时要到pom文件中进行配置

    二.Redis的注解式开发

    1.简介

    1. 简化代码:注解式开发可以显著减少与Redis相关的样板代码。通过使用注解,你可以在方法上直接标记缓存操作,而不必在每个方法中手动编写缓存逻辑。

    2. 提高开发效率:通过使用注解,开发者可以更容易地实现缓存功能,减少了手动处理缓存的复杂性。这使得开发者可以专注于业务逻辑的实现,而不必花费过多时间在缓存的管理和维护上。

    3. 降低错误风险:手动管理缓存可能会导致错误,例如忘记在适当的时机清除缓存,或者在更新数据时没有及时更新缓存。使用注解可以减少这些潜在的错误,提高了代码的可靠性。

    4. 提升性能:通过缓存常用数据,可以显著提升应用程序的性能和响应速度。注解式开发使得缓存的使用变得更加便捷,可以更灵活地选择何时以及如何使用缓存。

    5. 减少重复劳动:在传统的手动缓存管理中,你可能会在每个方法中都编写类似的缓存逻辑。通过使用注解,可以将缓存逻辑抽象到通用的注解中,从而减少了重复的劳动。

    6. 提升可维护性:通过使用注解,缓存的管理变得更加集中和清晰。开发者可以在方法上直接看到哪些操作会涉及到缓存,从而使得代码更易于理解和维护。

    总的来说,使用Redis的注解式开发可以简化缓存管理,提高开发效率,降低错误风险,提升应用程序的性能,减少重复劳动,同时也提升了代码的可维护性

    2.实际开发应用

    2.1 测试方法

    1. @CachePut(value = "xx",key = "'cid:'+#cid",condition = "#cid > 6")
    2. Clazz selectByPrimaryKey(Integer cid);

    测试类

    1. @Test
    2. public void test1(){
    3. System.out.println(clazzBiz.selectByPrimaryKey(10));
    4. System.out.println(clazzBiz.selectByPrimaryKey(10));
    5. }

     测试结果

    结论:当我们使用redis注解式开发时,第一次执行会查询我们的关系型数据库,但是在执行完成后会加入我们的缓存中,在第二次进行查询时,会查询我们的缓存,也就是查询我们的redis,这样做大大提高了我们查询的效率,而且还可以减轻我们数据库的压力 

    2.2 @Cacheable和Cacheput的区别

    (1)@Cacheable

    @Cacheable 注解用于标记一个方法的结果应该被缓存,以便下次相同的方法调用可以直接返回缓存的结果,而不必再次执行方法体。它有以下主要属性:

    • value:指定缓存的名称,通常用于区分不同的缓存。

    • key:用于指定缓存的键值,可以是 SpEL 表达式,根据方法的参数动态生成缓存的键。

    • condition:指定一个 SpEL 表达式,如果为 true,则进行缓存,否则不进行缓存。

    • unless:与 condition 相反,如果为 true,则不进行缓存。

     

    (2)@CachePut:

    @CachePut 注解用于标记一个方法的结果应该被存储到缓存中,通常在创建或更新操作后使用。与 @Cacheable 不同,它不检查缓存中是否已存在相同的键,而是强制将方法的结果存储到缓存中。它的主要属性包括:

    • value:指定缓存的名称。
    • key:用于指定缓存的键值,通常也可以使用 SpEL 表达式。
    • condition:指定一个 SpEL 表达式,如果为 true,则进行缓存,否则不进行缓存。
    • unless:与 condition 相反,如果为 true,则不进行缓存。

    小结:

    • @Cacheable 用于从缓存中获取数据,如果数据已存在于缓存中,将直接返回缓存数据,否则执行方法体并将结果存储到缓存中。
    • @CachePut 用于强制将方法的结果存储到缓存中,无论之前是否存在相同的缓存键。
    • 两者都可以使用 value 属性来指定缓存的名称,以及 key 属性来指定缓存的键。条件判断可以使用 condition 和 unless 属性来控制是否执行缓存操作。

    这些注解通常与缓存管理器(如EhCache、Redis等)一起使用,以配置和管理应用程序中的缓存

     

    2.3 @CacheEvict

    主要作用:在方法执行之后从缓存中移除特定的条目,以保持缓存的一致性。下面是 @CacheEvict 的一些主要特点和用法:

    1. 清除缓存条目:

      • @CacheEvict 主要用于清除指定缓存中的某个或某些缓存条目。
    2. 注解属性:

      • value:用于指定要清除的缓存的名称。
      • key:用于指定要清除的缓存条目的键,支持使用 SpEL 表达式动态生成。
      • allEntries:如果设置为 true,表示清除指定缓存中的所有条目。
      • beforeInvocation:指定是否在方法执行之前执行缓存清除操作。默认情况下,在方法执行之后清除缓存,但如果设置为 true,将在方法执行之前清除缓存。
    3. 条件清除:

      • 可以使用 condition 和 unless 属性来进行条件清除,类似于 @Cacheable 和 @CachePut
    1. @CacheEvict(value = "myCache", key = "#userId")
    2. public void deleteUserFromCache(String userId) {
    3. }

    三.解决Redis缓存问题

    Redis的击穿、穿透、雪崩是三种与缓存相关的常见问题,它们通常被统称为"缓存问题三兄弟"

    1.缓存查询

    要理解缓存击穿,首先要理解redis的缓存查询

     

    Redis 缓存查询是指在使用 Redis 作为缓存时,应用程序首先尝试从 Redis 缓存中获取数据,如果缓存中存在数据,那么可以直接返回,从而减轻后端数据库的负担。这是缓存的正常使用场景,它可以大大提高应用程序的性能和响应时间。

    常见的 Redis 缓存查询流程如下:

    1. 应用程序检查 Redis 缓存中是否存在所需数据。
    2. 如果数据存在,应用程序直接从 Redis 中获取并返回数据。
    3. 如果数据不存在,应用程序从后端数据库中获取数据,并将其存储到 Redis 缓存中,以供后续查询使用。

    2.Redis击穿

    Redis 缓存击穿是指在大并发情况下,有大量的请求同时查询某个不存在于缓存中的数据,导致这些请求都穿透 Redis 缓存,直接访问后端数据库。这会导致数据库负载急剧增加,降低了性能,甚至可能导致数据库宕机。

    主要原因是某个热点数据失效,然后有大量请求同时尝试获取相同的数据,而这个数据不在缓存中。解决 Redis 缓存击穿问题的方法包括:

    • 使用互斥锁:在缓存失效时,使用互斥锁来阻止多个线程同时访问后端数据库,只有一个线程去加载数据,其他线程等待加载完成后再从缓存中获取。
    • 设置短暂的缓存过期时间:即使数据失效,也不至于在短时间内导致大量请求穿透缓存。
    • 预热缓存:在启动应用程序时,加载一些热门数据到缓存中,以减少冷启动时的击穿问题。

    小结:

    Redis 缓存查询是一种有效的性能优化手段,但在高并发情况下可能会出现缓存击穿问题。为了解决击穿问题,可以采取锁、合理的缓存设置以及预热等策略,以保护后端数据库免受不必要的负担

    3.Redis穿透

    3.1 简介

    Redis 缓存穿透是指恶意或异常请求,通常是查询一个不存在于缓存中的键,导致大量请求穿透 Redis 缓存,直接访问后端数据存储系统(通常是数据库)。这种情况会增加后端数据库的负担,降低性能,甚至导致数据库崩溃

    1. 恶意请求:攻击者故意发送不存在于缓存中的键来进行恶意攻击,例如通过构造恶意的查询参数。

    2. 异常查询:当应用程序中没有足够的保护来处理异常情况时,可能会导致查询非常频繁的不存在的键,例如使用无效的用户标识。

    3.2 解决措施

    1. 缓存空对象或哨兵值:当一个请求查询到不存在的数据时,可以将一个特殊的哨兵值(如null)存储在缓存中,以表示该键确实在数据存储中不存在。这样,在下一次请求相同键时,可以立即从缓存中获取哨兵值而不访问数据库,从而减轻数据库负担。

    2. 使用布隆过滤器:布隆过滤器是一种数据结构,可以用于快速确定某个数据是否存在于缓存中。如果布隆过滤器显示数据不存在,可以直接拒绝查询,而不访问数据库或缓存。这可以有效减少缓存穿透。

    3. 合理设置缓存失效时间:设置合适的缓存失效时间,避免长时间持有无效的缓存数据。这样可以防止在失效期间频繁访问数据库。

    4. 限制请求频率:在应用程序层面,可以实施请求频率限制,以防止频繁的请求击穿缓存。

    小结:

    防止 Redis 缓存穿透需要综合考虑多种策略,包括设置合理的缓存策略、使用哨兵值或布隆过滤器,以及在应用程序中增加异常查询的保护

    4.Redis雪崩

    4.1 简介

    Redis 雪崩是指在某个时间点,大量缓存键同时失效,导致大量请求直接访问后端数据存储系统,从而对系统性能和可用性造成严重影响的情况

    1. 大规模缓存失效:当大量的缓存键在相同时间内失效,通常是因为这些键设置了相同的失效时间,或者是由于系统维护操作(如 Redis 重启)导致的。这会导致大量请求同时涌入后端数据存储系统。

    2. 热门数据集:如果系统中有一组特别热门的缓存数据,当这些缓存键同时失效并被请求时,会导致大量请求竞争获取相同的数据,增加了后端存储的负载

    4.2 解决措施

    1. 合理设置缓存失效时间:避免将所有缓存键设置相同的失效时间。通过将失效时间分散,可以减少大规模失效的概率。

    2. 使用随机失效时间:将缓存键的失效时间设置为一个随机值,而不是固定的时间,以避免同时失效。

    3. 持久化备份:定期将缓存数据持久化到磁盘,以防止 Redis 重启时丢失所有数据。

    4. 热点数据预热:对于热门的数据集,可以在合适的时机手动刷新缓存,而不是等待缓存失效。

    5. 分布式缓存:使用多台 Redis 服务器来分担负载,以降低单点故障的风险。

    6. 请求限流:在应用程序层面,实施请求限流措施,以避免过多的请求同时涌入 Redis。

    7. 监控和报警:建立监控系统,实时监控 Redis 的性能和状态,以及缓存键的失效情况,及时发现问题并采取措施应对

    小结:

    总之,防止 Redis 雪崩需要综合考虑多种策略,包括设置合理的失效时间、持久化备份、数据预热、请求限流以及监控系统

  • 相关阅读:
    C#的值类型和引用类型
    gitbook使用
    Postgresql-xl全局快照与GTM代码走读(支线)
    微服务边界
    【深度学习】基于卷积神经网络(tensorflow)的人脸识别项目(三)
    Element UI库 之 el-upload 图片上传组件多次上传或重新上传失效bug
    网课搜题接口 微信公众号一步授权即可
    实施主品牌进化战略(一):确立主品牌进化架构
    物联网AI MicroPython学习之语法 SPI串行外设通信
    在Xamarin.Android项目中调用自己写的java jar包
  • 原文地址:https://blog.csdn.net/weixin_73320743/article/details/134259680