緩存
什麼是緩存?
在高并發下,為了提高通路的性能,需要将資料庫中 一些經常展現和不會頻繁變更的資料,存放在存取速率更快的記憶體中。這樣可以
- 降低資料的擷取時間,帶來更好的體驗
- 減輕資料庫的壓力
緩存适用于讀多寫少的場合,查詢時緩存命中率很低、寫操作很頻繁等場景不适宜用緩存。
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 政策,從三個次元:
- 命中:應用程式從cache中取資料,取到後傳回。執行圖中1,2步
- 失效:應用程式先從cache取資料,沒有得到,則從資料庫中取資料,成功後,放到緩存中。執行圖中1,2,3,4,1,2步
- 更新:先把資料存到資料庫中,成功後,再讓緩存失效。執行圖中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
,配置了該資料通路對象中傳回的内容将存儲于名為companies的緩存對象中,我們也可以不使用該注解,直接通過@CacheConfig(cacheNames = "companies")
自己配置緩存集的名字來定義。@Cacheable
-
: 聲明Spring在調用方法之前,首先應該在緩存中查找方法的傳回值。如果這個值能夠找到,就會傳回存儲的值,否則的話,這個方法就會被調用,傳回值會放在緩存之中。該注解主要有下面幾個參數:@Cacheable
-
、value
:兩個等同的參數(cacheNames為Spring4新增,作為value的别名),用于指定緩存存儲的集合名。由于Spring4中新增了cacheNames
,是以在Spring3中原本必須有的value屬性,也成為非必需項了@CacheConfig
-
:緩存對象存儲在Map集合中的key值,非必需,預設按照函數的所有參數組合作為key值,若自己配置需使用SpEL表達式,比如:@Cacheable(key = "#p0"):使用函數第一個參數作為緩存的key值,更多關于SpEL表達式的詳細内容可參考官方文檔key
-
:緩存對象的條件,非必需,也需使用SpEL表達式,隻有滿足表達式條件的内容才會被緩存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示隻有當第一個參數的長度小于3的時候才會被緩存,若做此配置上面的AAA使用者就不會被緩存,讀者可自行實驗嘗試。condition
-
:另外一個緩存條件參數,非必需,需使用SpEL表達式。它不同于condition參數的地方在于它的判斷時機,該條件是在函數被調用之後才做判斷的,是以它可以通過對result進行判斷。unless
-
:用于指定key生成器,非必需。若需要指定一個自定義的key生成器,我們需要去實作org.springframework.cache.interceptor.KeyGenerator接口,并使用該參數來指定。需要注意的是:該參數與key是互斥的keyGenerator
-
:用于指定使用哪個緩存管理器,非必需。隻有當有多個時才需要使用cacheManager
-
:用于指定使用那個緩存解析器,非必需。需通過org.springframework.cache.interceptor.CacheResolver接口來實作自己的緩存解析器,并用該參數指定。cacheResolver
-
除了這裡用到的兩個注解之外,還有下面幾個核心注解:
-
: 表明Spring應該将方法的傳回值放到緩存中,在方法的調用前并不會檢查緩存,方法始終都會被調用。它的參數與@@CachePut
類似,具體功能可參考上面對Cacheable
參數的解析。@Cacheable
-
:配置于函數上,通常用在删除方法上,用來從緩存中移除相應資料。除了同@CacheEvict
一樣的參數之外,它還有下面兩個參數:@Cacheable
-
:非必需,預設為false。當為true時,會移除所有資料allEntries
-
:非必需,預設為false,會在調用方法之後移除資料。當為true時,會在調用方法之前移除資料。beforeInvocation
-
緩存與資料庫一緻性
- 資料庫處理要求強一緻實時性的資料,例如金融資料、交易資料。
- Redis處理不要求強一緻實時性的資料,例如網站最熱貼排行榜。
也就是說根據你的業務需求,設定你的過期時間,容許redis有一些不一緻。
注意:
- 緩存java對象時必須實作Serilaizable接口,因為Spring會将對象先序列化之後再存入到Redis中。
- 緩存方法的
最好使用方法名,避免不同的方法的 @Cacheable 值一緻,然後再配以以上緩存政策。@Cacheable
- 在我将這個
放置在SSM的dao層和service層時,redis緩存可以正常運作,但是當我将@Cacheable
放在action層上時就會有NPE。@Cacheable
-
沒有配置名字,改為@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.
參考文檔:
- Redis 緩存 + Spring 的內建示例
- Spring Boot 使用Redis緩存
- MySQL緩存--伺服器緩存query cache