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的配置。

依賴配置

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在調用方法之前,首先應該在緩存中查找方法的返回值。如果這個值能夠找到,就會返回存儲的值,否則的話,這個方法就會被調用,返回值會放在緩存之中。該注解主要有下面幾個參數:
    • valuecacheNames:兩個等同的參數(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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM