天天看點

spring使用redis做緩存

緩存

什麼是緩存?

在高并發下,為了提高通路的性能,需要将資料庫中 一些經常展現和不會頻繁變更的資料,存放在存取速率更快的記憶體中。這樣可以

  1. 降低資料的擷取時間,帶來更好的體驗
  2. 減輕資料庫的壓力

緩存适用于讀多寫少的場合,查詢時緩存命中率很低、寫操作很頻繁等場景不适宜用緩存。

MySQL有自己的查詢緩存,為什麼還要使用 Redis 等緩存應用?

  • 當隻有一台 MySQL伺服器時,可以将緩存放置在本地。這樣當有相同的 SQL 查詢到達時,可以直接從緩存中取到查詢結果,不需要進行 SQL 的解析和執行。MySQL 提供了伺服器層面的緩存支援。
  • 如果有多台 MySQL 伺服器,請求會随機分發給多台中的一台,我們無法保證相同的請求會到達同一台伺服器,本地緩存命中率較低。是以基于本機的緩存就沒有什麼意義,此時采用的政策應該是将查詢結果緩存在 Redis 或者 Memcache 中。

而Redis是一個高性能的 key-value 記憶體資料庫,恰恰可以作為緩存使用。

GitHub 位址:https://github.com/antirez/redis 。Github 是這麼描述的:

Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, HyperLogLogs, Bitmaps.

但是mysql自己本身有查詢緩存,memcached也是一個優秀的記憶體資料庫,為什麼一定要選擇redis

緩存更新

檢視緩存更新的套路,緩存更新的模式有四種:

  • Cache aside
  • Read through
  • Write through
  • Write behind cachin。

這裡我們使用的是 Cache Aside 政策,從三個次元:

  1. 命中:應用程式從cache中取資料,取到後傳回。執行圖中1,2步
  2. 失效:應用程式先從cache取資料,沒有得到,則從資料庫中取資料,成功後,放到緩存中。執行圖中1,2,3,4,1,2步
  3. 更新:先把資料存到資料庫中,成功後,再讓緩存失效。執行圖中1,2步
spring使用redis做緩存

spring配置redis緩存

接下來講解一下spring的配置。

依賴配置

pom.xml中添加

<!-- redis cache-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.8.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.7.2.RELEASE</version>
</dependency>
           

RedisConfig

現在我們使用的是java config 配置,是以需要将本RedisConfig放在可以被

<context:component-scan base-package=""/>

掃描的包下。

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;

/**
 * @author 李文浩
 * @version 2017/11/5.
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory redisConnectionFactory = new JedisConnectionFactory();
        // Defaults
        redisConnectionFactory.setHostName("127.0.0.1");
        redisConnectionFactory.setPort(6379);
        return redisConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.afterPropertiesSet();
        setSerializer(redisTemplate);
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        // 設定緩存過期時間,秒
        rcm.setDefaultExpiration(600);
        return rcm;
    }


    private void setSerializer(RedisTemplate<String, String> template) {
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(jackson2JsonRedisSerializer);
    }


    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuilder sb = new StringBuilder();
                sb.append(target.getClass().getName());
                sb.append(":" + method.getName());
                for (Object obj : params) {
                    sb.append(":" + null == obj ? "null" : obj.toString());
                }
                return sb.toString();
            }
        };
    }
}
           

如果我們不配置重寫

keyGenerator()

方法的話,預設的key生成政策是

Cacheable.java

/**
 * Spring Expression Language (SpEL) expression for computing the key dynamically.
 * <p>Default is {@code ""}, meaning all method parameters are considered as a key,
 * unless a custom {@link #keyGenerator} has been configured.
 * <p>The SpEL expression evaluates against a dedicated context that provides the
 * following meta-data:
 * <ul>
 * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
 * references to the {@link java.lang.reflect.Method method}, target object, and
 * affected cache(s) respectively.</li>
 * <li>Shortcuts for the method name ({@code #root.methodName}) and target class
 * ({@code #root.targetClass}) are also available.
 * <li>Method arguments can be accessed by index. For instance the second argument
 * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
 * can also be accessed by name if that information is available.</li>
 * </ul>
 */
String key() default "";
           

也就是把所有的方法參數作為一個key,但是這可能會重複。

緩存注解

  • @CacheConfig

    :主要用于配置該類中會用到的一些共用的緩存配置。在這裡

    @CacheConfig(cacheNames = "companies")

    ,配置了該資料通路對象中傳回的内容将存儲于名為companies的緩存對象中,我們也可以不使用該注解,直接通過

    @Cacheable

    自己配置緩存集的名字來定義。
  • @Cacheable

    : 聲明Spring在調用方法之前,首先應該在緩存中查找方法的傳回值。如果這個值能夠找到,就會傳回存儲的值,否則的話,這個方法就會被調用,傳回值會放在緩存之中。該注解主要有下面幾個參數:
    • value

      cacheNames

      :兩個等同的參數(cacheNames為Spring4新增,作為value的别名),用于指定緩存存儲的集合名。由于Spring4中新增了

      @CacheConfig

      ,是以在Spring3中原本必須有的value屬性,也成為非必需項了
    • key

      :緩存對象存儲在Map集合中的key值,非必需,預設按照函數的所有參數組合作為key值,若自己配置需使用SpEL表達式,比如:@Cacheable(key = "#p0"):使用函數第一個參數作為緩存的key值,更多關于SpEL表達式的詳細内容可參考官方文檔
    • condition

      :緩存對象的條件,非必需,也需使用SpEL表達式,隻有滿足表達式條件的内容才會被緩存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示隻有當第一個參數的長度小于3的時候才會被緩存,若做此配置上面的AAA使用者就不會被緩存,讀者可自行實驗嘗試。
    • unless

      :另外一個緩存條件參數,非必需,需使用SpEL表達式。它不同于condition參數的地方在于它的判斷時機,該條件是在函數被調用之後才做判斷的,是以它可以通過對result進行判斷。
    • keyGenerator

      :用于指定key生成器,非必需。若需要指定一個自定義的key生成器,我們需要去實作org.springframework.cache.interceptor.KeyGenerator接口,并使用該參數來指定。需要注意的是:該參數與key是互斥的
    • cacheManager

      :用于指定使用哪個緩存管理器,非必需。隻有當有多個時才需要使用
    • cacheResolver

      :用于指定使用那個緩存解析器,非必需。需通過org.springframework.cache.interceptor.CacheResolver接口來實作自己的緩存解析器,并用該參數指定。

除了這裡用到的兩個注解之外,還有下面幾個核心注解:

  • @CachePut

    : 表明Spring應該将方法的傳回值放到緩存中,在方法的調用前并不會檢查緩存,方法始終都會被調用。它的參數與@

    Cacheable

    類似,具體功能可參考上面對

    @Cacheable

    參數的解析。
  • @CacheEvict

    :配置于函數上,通常用在删除方法上,用來從緩存中移除相應資料。除了同

    @Cacheable

    一樣的參數之外,它還有下面兩個參數:
    • allEntries

      :非必需,預設為false。當為true時,會移除所有資料
    • beforeInvocation

      :非必需,預設為false,會在調用方法之後移除資料。當為true時,會在調用方法之前移除資料。

緩存與資料庫一緻性

  • 資料庫處理要求強一緻實時性的資料,例如金融資料、交易資料。
  • Redis處理不要求強一緻實時性的資料,例如網站最熱貼排行榜。

也就是說根據你的業務需求,設定你的過期時間,容許redis有一些不一緻。

注意:

  1. 緩存java對象時必須實作Serilaizable接口,因為Spring會将對象先序列化之後再存入到Redis中。
  2. 緩存方法的

    @Cacheable

    最好使用方法名,避免不同的方法的 @Cacheable 值一緻,然後再配以以上緩存政策。
  3. 在我将這個

    @Cacheable

    放置在SSM的dao層和service層時,redis緩存可以正常運作,但是當我将

    @Cacheable

    放在action層上時就會有NPE。
  4. @Cacheable

    沒有配置名字,改為

    @Cacheable("值")

    ,否則會出現如下錯誤。
    java.lang.IllegalStateException: No cache could be resolved for 'Builder[public abstract studio.jikewang.entity.TeacherClass studio.jikewang.dao.TeacherClassDao.getTeacherClass(int)] caches=[] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'' using resolver 'org.springframework.cache.interceptor.SimpleCacheResolver@4f8d471b'. At least one cache should be provided per cache operation.
               

參考文檔:

  1. Redis 緩存 + Spring 的內建示例
  2. Spring Boot 使用Redis緩存
  3. MySQL緩存--伺服器緩存query cache