写点什么

微服务架构中的缓存技术:SpringBoot 中缓存自动配置原理详解并整合 Redis

发布于: 2021 年 04 月 21 日
微服务架构中的缓存技术:SpringBoot中缓存自动配置原理详解并整合Redis

JSR107

Java Caching 定义 5 个核心接口:CachingProvider,CacheManager,Cache,Entry,Expiry


  • CachingProvider:定义了创建,配置,获取,管理和控制多个 CacheManager.一个应用可以在运行期间访问多个 CachingProvider

  • CacheManager:定义了创建,配置,获取,管理和控制多个唯一命名的 Cache,这些 Cache 存在于 CacheManager 的上下文中,一个 CacheManager 仅被一个 CacheProvider 所拥有

  • Cache:是一个类似 Map 的数据结构并临时存储以 key 为索引的值,一个 Cache 仅被一个 CacheManager 所拥有

  • Entry:是一个存储在 Cache 中的 key-value 键值对

  • Expiry:每一个存储在 Cache 中的条目有一个定义的有效期,一旦超过这个时间,条目为过期状态.一旦过期,条目将不可访问,更新和删除.缓存有效期可以通过 ExpiryPolicy 设置


<dependency>  <groupId>javax.cache</groupId>  <artifactId>cache-api</artifactId></dependency>
复制代码

搭建缓存基本环境

1.导入数据库文件,创建,创建表2.创建javabean封装数据3.整合MyBatis操作数据库  1.配置数据源信息  2.使用Mybatis,创建数据库操作语句
复制代码

缓存的使用

  • 步骤


1.开启基于注解的缓存     @EnableCaching2.标注缓存注解    
复制代码


  • 缓存重要概念

  • Cache: 缓存接口,定义缓存操作.实现有:RedisCache,EhCacheCache,ConcurrentMapCache

  • CacheManager: 缓存管理器,管理各种缓存组件

  • keyGenerator: 缓存数据时 key 生成策略

  • serialize: 缓存数据时 value 序列化策略

  • 缓存注解

  • @Cacheable: 主要针对方法配置,根据方法的请求参数对结果进行缓存:以后再要相同的数据,直接从缓存中获取,不再调用方法


  属性:    cacheNames/value:指定缓存组件的名字,将方法的返回结果放在缓存中,是数组的方式,可以指定多个缓存(CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字)    key:缓存数据使用的key,可以用key指定键值,默认使用方法参数的值(SpEL:methodName(当前被调用的方法名),                                    method(当前被调用的方法),                                    target(当前被调用的目标对象),                                    targetClass(当前被调用的目标对象类),                                    args(当前被调用的方法参数列表),                                    caches(当前方法调用使用的缓存列表),                                    argument name(方法参数的名字-#参数),                                    result(方法执行后的结果返回值))    keyGenerator:key的生成器,可以指定key的生成器组件.     cacheManager:指定缓存管理器,     cacheResolver:指定缓存解析器,和缓存管理器一样     condition:指定符合条件的情况下才进行缓存     unless:否定缓存-当unless指定的条件为true,方法的返回值就不会缓存.可以获取到结果进行判断     sysnc:是否使用异步模式,不支持unless属性      key和keyGenerator只要出现一个    cacheManager和cacheResolver只要出现一个
复制代码


  • @CacheEvict: 清空缓存

  • @CachePut: 保证方法被调用,结果被缓存,更新缓存数据

  • @EnableCaching: 开启基于注解的缓存

缓存的工作原理

默认使用的是 ConcurrentMapCache 组件中的 CacheManager 进行缓存的,将数据保存在 ConcurrentMap<object,object>中

自动配置类

org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
复制代码

缓存配置类

org.springframework.boot.autoconfigure.cache.GenericCacheConfigurationorg.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration  (JSR107)org.springframework.boot.autoconfigure.cache.EhCacheCacheConfigurationorg.springframework.boot.autoconfigure.cache.HazelcastCacheConfigurationorg.springframework.boot.autoconfigure.cache.InfinispanCacheConfigurationorg.springframework.boot.autoconfigure.cache.CouchbaseCacheConfigurationorg.springframework.boot.autoconfigure.cache.RedisCacheConfigurationorg.springframework.boot.autoconfigure.cache.CaffeineCacheConfigurationorg.springframework.boot.autoconfigure.cache.SimpleCacheConfigurationorg.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
复制代码


  • 默认 SimpleCacheConfiguration 生效:

1.给容器中注册 CacheManager:ConcurrentMapCacheManager

2.ConcurrentMapCacheManager 可以获取和创建 ConcurrentMapCache 类型的缓存组件 3.ConcurrentMapCache 的作用是将数据保存在 ConcurrentMap 中

缓存的运行流程

  • @Cacheable

  • 方法运行之前,CacheManager 先获取相应的缓存,按照 CacheNames 指定的名字获取,,第一次获取缓存如果没有 Cache 组件会自动创建缓存组件

  • 在 Cache 中使用 key 查找缓存内容,key 是通过 KeyGenerator 生成的,默认就是方法的参数


        SimpleKeyGenerator生成key的策略:        1.如果没有参数: key=new SimpleKey()        2.如果有一个参数: key=参数的值        3.如果有多个参数: key=new SimpleKey(params)    
复制代码


- 如果没有查到缓存内容就会调用目标方法- 将目标方法返回的结果,放进缓存中
复制代码


@Cacheable 标注的方法执行之前先检查缓存中是否有缓存数据,默认按照参数值作为 key 去查询缓存,如果没有就运行方法,并将结果放入缓存中


  • 流程核心:

  • 使用 CacheManager(ConcurrentMapCacheManager)按照 CacheNames 指定的名字获取 Cache(ConcurrentMapCache)组件

  • key 是使用 keyGenerator 生成的,默认使用 SimpleKeyGenerator 生成 key


  • @CachePut 既调用方法,又更新缓存数据(对数据库修改操作,会将结果保存在缓存中)

  • 运行流程:

  • 先调用目标方法

  • 再将方法的结果放到缓存中


   在运行过程中,执行完@CachePut的方法,查询的结果还是之前的数据 :   - 因为在没有指定key值的情况下,key值默认是方法的参数.   - 当方法的参数值不同时,Cache根据key查询缓存数据,key值对应的数据并没有更新.   - 如果需要同步更新缓存数据,需要指定为相同的key值,缓存中的数据才会更新
- 在@Cacheable中不能像@CachePut中使用result属性, - 因为在@Cacheable是先判断注解,此时没有result值,而在@CachePut是先执行方法
复制代码




  • @CacheEvict 清空缓存:当数据库中数据被删除时,执行删除缓存中的数据

  • 通过 key 指定要清楚的缓存数据

  • allEntries: 是否清除这个缓存中所有数据

  • beforeInvocation: 是否在方法执行之前清除缓存中的数据




  • @Caching

  • 定义复杂的缓存规则

  • 如果在定义的复杂缓存规则中有 @CachePut 或则 @CacheEvict(beforInvocation=true) ,这个注解就是在目标方法执行后执行


  @Caching(            cacheable = {                    @Cacheable(value = "emp",key = "#emp.lastName"),                    @Cacheable(value = "emp",key = "#emp.id")            },            put = {                    @CachePut(value = "emp",key = "#emp.lastName"),                    @CachePut(value = "emp",key = "#emp.id")            },            evict = {                    @CacheEvict(value = "emp",key = "#emp.lastName"),                    @CacheEvict(value = "emp",key = "#emp.id")            }    )
复制代码




  • @CacheConfig

  • 配置缓存中的所有的公共配置

缓存中间件 Redis

  • Redis: Redis 是一个开源的内存中的数据结构存储系统,可以用作数据库,缓存消息中间件

  • 操作工具:Redis Desktop Manager

Redis 配置

  • 下载Redis-Windows版本

  • 启动 Redis 服务端:

  • 直接双击 redis-server.exe 文件

  • 或者在命令行中输入 start redis-server.exe

  • 启动 Redis 客户端:

  • 启动 Redis 服务端完成之后

  • 直接双击 redis-cli.exe 文件

  • 或者在命令行输入 start redis-cli.exe

  • 设置 Redis 密码:

  • 命令方式:

  • 获取旧密码 : config get requirepass

  • 设置新密码 : config set requirepass

  • config 文件方式:

  • 打开配置文件 redis.windows.conf

  • 修改配置文件中的 reqiurepass

整合 Redis 缓存

  • 在 pom.xml 中引入 redis 依赖


<dependency>       <groupId>org.springframework.boot</groupId>       <artifactId>spring-boot-starter-data-redis</artifactId></dependency>
复制代码


  • 配置 redis,在 application.properties 中配置 redis


spring.redis.host=192.168.32.242
复制代码


  • RedisTemplate:(操作 k-v 都是对象)


  @Bean    @ConditionalOnMissingBean(        name = {"redisTemplate"}    )    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {        RedisTemplate<Object, Object> template = new RedisTemplate();        template.setConnectionFactory(redisConnectionFactory);        return template;    }
复制代码


  • 保存对象时,默认使用的是 JDK 的序列化机制,将序列化后的数据保存到 redis 中

  • 为了增强 Redis 数据库中的数据可读性:

  • 将对象数据以==json==方式保存:

  • 将对象转化为 json

  • 配置 redisTemplate json 序列化规则,使用 Jackson2JsonRedisSerializer 进行数据的序列化


  @Configuration  public class MyRedisConfig {    @Bean    public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory){        RedisTemplate<Object,Employee> redisTemplate=new RedisTemplate<Object,Employee>();        redisTemplate.setConnectionFactory(redisConnectionFactory);        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);        redisTemplate.setDefaultSerializer(serializer);        return redisTemplate;    }}
复制代码


Redis常见的数据类型:  String-字符串  List-列表  Set-集合  Hash-散列  ZSet-有序集合
redisTemplate.opsForValue()--String(字符串)redisTemplate.opsForList()--List(列表)redisTemplate.opsForSet()--Set(集合)redisTemplate.opsForHash()--Hash(散列)redisTemplate.opsForZSet()--ZSet(有序集合)
复制代码


  • StringRedisTemplate(操作 k-v 都是字符串)在 RedisAutoConfiguration 中:


@Bean    @ConditionalOnMissingBean    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {        StringRedisTemplate template = new StringRedisTemplate();        template.setConnectionFactory(redisConnectionFactory);        return template;    }
复制代码


在 StringRedisTemplate 中:


public class StringRedisTemplate extends RedisTemplate<String, String> {    public StringRedisTemplate() {        this.setKeySerializer(RedisSerializer.string());        this.setValueSerializer(RedisSerializer.string());        this.setHashKeySerializer(RedisSerializer.string());        this.setHashValueSerializer(RedisSerializer.string());    }
public StringRedisTemplate(RedisConnectionFactory connectionFactory) { this(); this.setConnectionFactory(connectionFactory); this.afterPropertiesSet(); }
protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) { return new DefaultStringRedisConnection(connection); }}
复制代码


Redis常见的数据类型:  String-字符串  List-列表  Set-集合  Hash-散列  ZSet-有序集合
stringRedisTemplate.opsForValue()--String(字符串)stringRedisTemplate.opsForList()--List(列表)stringRedisTemplate.opsForSet()--Set(集合)stringRedisTemplate.opsForHash()--Hash(散列)stringRedisTemplate.opsForZSet()--ZSet(有序集合)
复制代码

Redis 服务治理

  • Redis 默认是惰性删除:

  • 过期的 Redis 的不会删除

  • 获取 key 时会检查 key 是否有效,如果无效才会删除

  • Redis 的惰性删除机制会造成 Redis 缓存中保存很多过期的 key. 可以使用定时删除过期的 key 的策略解决

缓存雪崩

  • 缓存雪崩: 同一时刻 redis key 全部失效

  • 解决方案:Redis 的过期时间设置为随机

缓存击穿

  • 缓存击穿: 同一时刻缓存中没有数据,请求转向数据库

  • 解决方案:

  • 第一次请求如果没有数据,直接返回空

  • 然后发送一个消息队列进行数据库同步

  • 第二次请求再从缓存中获取数据

自定义 CacheManager

  • CacheManagerCustomizers: 定制缓存规则

  • 可以在方法中使用缓存管理器获取缓存,调用操作 API 对缓存中的数据进行操作

发布于: 2021 年 04 月 21 日阅读数: 51
用户头像

一位攻城狮的自我修养 2021.04.06 加入

分享技术干货,面试题和攻城狮故事。 你的关注支持是我持续进步的最大动力! https://github.com/ChovaVea

评论

发布
暂无评论
微服务架构中的缓存技术:SpringBoot中缓存自动配置原理详解并整合Redis