Spring---缓存数据-Redis

目录

0. 简介

0.1 缓存收益和场景

0.2 缓存更新策略

1. 概述

2. Spring Cache

2.1 Spring与缓存实现进行集成

2.1.1 启用对缓存的支持

2.1.2 缓存管理器

2.1.3 为方法添加注解以支持缓存

3. 使用Redis缓存

3.1 添加依赖

3.2 配置连接池

3.3 Redis连接工厂 

3.4 配置RedisTemplate       

3.5 key 和 value 的序列化器

3.6 启用对缓存的支持

3.7 配置缓存管理器

3.8 RedisTemplate API 


0. 简介

0.1 缓存收益和场景

缓存能够有效地加速应用的读写速度,同时也可以降低后端负载,对日常应用的开发至关重要。

下图左侧为客户端直接调用存储层的架构,右侧为比较典型的缓存层+存储层架构

 缓存比较常用的选型,缓存层选用Redis,存储层选用MySQL。

缓存加入后带来的收益和成本。

       收益

  1. 加速读写:因为缓存通常都是全内存的,而存储层通常读写性能不够强悍(例如MySQL),通过缓存的使用可以有效地加速读写,优化用户体验。
  2. 降低后端负载:帮助后端减少访问量和复杂计算(例如很复杂的SQL语句),在很大程度降低了后端的负载

       成本

  1. 数据不一致性:缓存层和存储层的数据存在着一定时间窗口的不一致性,时间窗口跟更新策略有关。
  2. 代码维护成本:加入缓存后,需要同时处理缓存层和存储层的逻辑,增大了开发者维护代码的成本。
  3. 运维成本:以Redis Cluster为例,加入后无形中增加了运维成本。

缓存的使用场景基本包含如下两种:

  1. 开销大的复杂计算:以MySQL为例子,一些复杂的操作或者计算(例如大量联表操作、一些分组计算),如果不加缓存,不但无法满足高并发量,同时也会给MySQL带来巨大的负担。
  2. 加速请求响应:即使查询单条后端数据足够快(例如select*from tablewhere id=),那么依然可以使用缓存,以Redis为例子,每秒可以完成数万次读写,并且提供的批量操作可以优化整个IO链的响应时间。

0.2 缓存更新策略

缓存中的数据会和数据源中的真实数据有一段时间窗口的不一致,需要利用某些策略进行更新,下面会介绍几种主要的缓存更新策略。

  • LRU/LFU/FIFO算法剔除

剔除算法通常用于缓存使用量超过了预设的最大值时候,如何对现有的数据进行剔除。例如Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的剔除策略。

  • 超时剔除

通过给缓存数据设置过期时间,在过期时间后自动删除,例如Redis提供的expire命令。如果业务可以容忍一段时间内,缓存层数据和存储层数据不一致,那么可以为其设置过期时间。在数据过期后,再从真实数据源获取数据,重新放到缓存并设置过期时间。例如一个视频的描述信息,可以容忍几分钟内数据不一致,但是涉及交易方面的业务,后果可想而知。

  • 主动更新

应用方对于数据的一致性要求高,需要在真实数据更新后,立即更新缓存数据。例如可以利用消息系统或者其他方式通知缓存更新。

建议:

  1. 低一致性业务建议配置最大内存和淘汰策略的方式使用。
  2. 高一致性业务可以结合使用超时剔除和主动更新,这样即使主动更新出了问题,也能保证数据过期时间后删除脏数据;

1. 概述

     缓存(Caching)可以存储经常会用到的信息,这样每次需要的时候,这些信息都是立即可用的。

     常用的缓存数据库:

  • Redis   使用内存存储(in-memory)的非关系数据库,字符串、列表、集合、散列表、有序集合,每种数据类型都有自己的专属命令。另外还有批量操作(bulk operation)和不完全(partial)的事务支持 、发布与订阅、主从复制(master/slave replication)、持久化、脚本(存储过程,stored procedure)。 效率比ehcache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。
  • memcached   使用内存存储的键值缓存,键值之间的映射、创建命令、读取命令、更新命令、删除命令以及其他几个命令。为提升性能而设的多线程服务器。memcache在客户端中实现分布式缓存,通过分布式算法指定目标数据的节点。
  • ehcache    纯java实现,缓存在内存中,可持久化到硬盘,效率高于memcache;但是缓存共享麻烦,集群分布式应用不方便。如果是单个应用或者对缓存访问要求很高的应用,用ehcache。ehcache也有缓存共享方案,不过是通过RMI或者Jgroup多播方式进行广播缓存通知更新,缓存共享复杂,维护不方便;简单的共享可以,但是涉及到缓存恢复,大数据缓存,则不合适。

2. Spring Cache

    Spring 缓存的实现是通过创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)。根据所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值

2.1 Spring与缓存实现进行集成

Spring 能都与多个流行的缓存实现进行集成,实现步骤如下:

2.1.1 启用对缓存的支持

  • Java 配置 注解驱动缓存

使用注解:@EnableCaching

  • XML 声明的缓存

----<cache:annotation-driven>

2.1.2 缓存管理器

缓存管理器是Spring缓存抽象的核心,它能够与多个流行的缓存实现进行集成。Spring3.1配置了五个缓存管理器实现:

  • SimpleCacheManager
  • NoOpCacheManager
  • ConcurrentMapCacheManager(它的缓存存储是基于内存的,所以它的生命周期是与应用关联的,对于生产级别的大型企业级应用程序,这可能并不是理想的选择)
  • CompositeCacheManager(系统同时使用多个缓存管理器集成)
  • EhCacheCacheManager

除了核心的Spring框架,Spring Data又提供了两个缓存管理器:

  • RedisCacheManager--Spring Data Redis提供
  • GemfireCacheManager

2.1.3 为方法添加注解以支持缓存

Spring 提供的四个注解来声明缓存规则:

  1. @Cacheable会条件性地触发对方法的调用,这取决于缓存中是不是已经有了所需要的值。
  2. @CachePut采用了一种更为直接的流程。带有@CachePut注解的方法始终都会被调用,而且它的返回值也会放到缓存中。

@Cacheable 和 @CachePut 一些共有的属性:

  • value:value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。主要作用是给 cache 取个名称!个人觉得,主要是为了在Spring 容器中可以更好的管理缓存对象。
  • condition
    • 调用方法前:如果SPEL表达式的值为false的话,将不会去走缓存。
    • 调用方法后:如果SPEL表达式的值为false的话,将不会将返回值放在缓存中。。
  • unless
    • 调用方法前:不进行判断。
    • 调用方法后:如果SPEL表达式的值是true的话,将不会将返回值放在缓存中。
  • key:指定的key的值就是我们要保存到缓存数据库中的key的值,但是这个key的指定有自己的一套方式,如下:

另外,需要说明的是 #result 不能用在@Cacheable 上是因为 #result 表示的是 方法调用的返回值。但是@Cacheable 可能不走方法,可能不会有返回值。所以就不太适用了。但是!但是!#result 可以用在@Cacheable的 unless 属性上,因为 unless 是在方法返回的时候再判断的,所以一定会有返回值!

3. @CacheEvict 的一些属性,指定了哪些缓存数目应该被移除: 

@CacheEvict 主要用在要移除一个数据条目的时候,同时移除这个数据条目在缓存中的 key 和 value。

4. @Cacheing

@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关注解,其中拥有属性:cacheable、put、evict。

@Caching(cacheable=@Cacheable("users"),evict={@CacheEvict("cache2"),@CacheEvict(value="cache3",allEntries=true)})

3. 使用Redis缓存

 Spring Data Redis包含了多个模板实现,用来完成Redis数据库的数据存取功能。

 <!-- 配置连接池 -->
       <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
           <property name="maxIdle" value="50"/>
           <property name="maxTotal" value="100"/>
           <property name="maxWaitMillis" value="20000"/>
       </bean>
                  
       <!-- 配置连接工厂 -->
       <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
           <property name="hostName" value="localhost"/>
           <property name="port" value="6379"/>
           <property name="poolConfig" ref="poolConfig"/>
       </bean>
    
    <!-- 键值序列化器 -->
       <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
       <bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>
  
    <!-- 配置redisTemplate -->
       <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
           <property name="connectionFactory" ref="connectionFactory"/>
           <!-- 设置默认的序列化器为字符串序列化 -->
           <property name="defaultSerializer" ref="stringRedisSerializer"/>
           <property name="keySerializer" ref="stringRedisSerializer"/>
           <property name="valueSerializer" ref="jdkSerializationRedisSerializer"/>
           <property name="hashKeySerializer" ref="stringRedisSerializer"/>
           <property name="hashValueSerializer" ref="jdkSerializationRedisSerializer"/>
       </bean>
       
       <!-- 使用注解驱动,其中属性cache-manager默认值为cacheManager -->
       <cache:annotation-driven cache-manager="redisCacheManager"/>
       
       <!-- 定义缓存管理器 -->
       <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
           <!-- 通过构造方法注入redisTemplate -->
           <constructor-arg index="0" ref="redisTemplate"/>
           <!-- 定义默认超时时间 -->
           <property name="defaultExpiration" value="600"/>
           <!-- 缓存管理器名称 -->
           <property name="cacheNames">
               <list>
                   <value>redisCacheManager</value>
               </list>
           </property>
       </bean>

3.1 添加依赖

在Spring中使用Redis需要jedis.jar和spring-data-redis.jar

    <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-redis</artifactId>
      <version>1.8.7.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.9.0</version>
    </dependency>

3.2 配置连接池

配置了Redis数据库连接池的最大闲置连接数、最大连接数和最长等待时间;

3.3 Redis连接工厂 

redis连接工厂会生成到Redis数据库服务器的连接。配置了Redis数据库的主机和端口号,同时引入了之前配置的连接池;

有四种Redis连接工厂:

  1. JedisConnetionFactory
  2. JredisConnectionFactory
  3. LettuceConnectionFactory
  4. SrpConnectionFactory

3.4 配置RedisTemplate       

redisTemplate封装了对redis的操作,要操作redis,则肯定要引入上面配置好的连接工厂和序列化器;

  • Spring Data Redis提供了两个模版
  1. RedisTemplate
  2. StringRedisTemplate

RedisTemplate 支持 Object类型 的key 和 Object类型 的value。而StringRedisTemplate则采用更简单粗暴的方式,只支持 String类型 的key 和String类型 的value。 

  • 需要注入Redis连接工厂bean;
  • 需要注入序列化器bean

3.5 key 和 value 的序列化器

什么是序列化器呢?当某个条目保存到Redis key-value存储的时候,key和value都会使用Redis的序列化器进行序列化。由于Redis只能提供基于字符串型的操作,而在java中是以类和对象为主,序列化器的作用是将java对象和Redis字符串相互转换,使得可以将java对象序列化为字符串存入Redis,同时也可以取出Redis序列化过的字符串转换成java对象

Spring Data Redis提供了多个这样的序列化器,包括:

  • GenericToStringSerializer:使用Spring转换服务进行序列化;
  • JacksonJsonRedisSerializer:使用Jackson 1,将对象序列化为JSON;
  • Jackson2JsonRedisSerializer:使用Jackson 2,将对象序列化为JSON;
  • JdkSerializationRedisSerializer:使用Java序列化;
  • OxmSerializer:使用Spring O/X映射的编排器和解排器(marshaler和unmarshaler)实现序列化用于XML序列化;
  • StringRedisSerializer:序列化String类型的key和value。

RedisTemplate会使用JdkSerializationRedisSerializer,这意味着key和value都会通过Java进行序列化。StringRedisTemplate默认会使用StringRedisSerializer。

RedisTemplate 的 key 和 value 经过序列化存储可能会让你觉得奇怪,类似如下:

这是正常的,因为使用的是 java 的序列化,如果想要看起来比较舒服的,可以用 StringRedisSerializer 序列成字符串的样子。

3.6 启用对缓存的支持

注解驱动的缓存:<cache:annotation-driven>会启动注解驱动的缓存,会创建一个切面(aspect),并触发Spring缓存注解的切点(pointcut)。根据所使用的注解以及缓存的状态,切面会从缓存中获取数据,将数据添加到缓存中,或者从缓存中移除某个值。

3.7 配置缓存管理器

有了对redis的操作,接下来就是要将spring缓存机制和redis进行结合(即配置缓存管理器),缓存管理器中我们注入了redisTemplate,同时设置了超时时间和管理器的名称。

3.8 RedisTemplate API 

Spring Data Redis 提供了一套API 友好的操作Redis,如下:

猜你喜欢

转载自blog.csdn.net/zhangpower1993/article/details/88917736