前言 最近在做智能家居平臺,考慮到家居的控制需要快速的響應於是打算使用redis緩存。一方面減少資料庫壓力另一方面又能提高響應速度。項目中使用的技術棧基本上都是大家熟悉的springboot全家桶,在springboot2.x以後操作redis的客戶端推薦使用lettuce(生菜)取代jedis。 ...
前言
最近在做智能家居平臺,考慮到家居的控制需要快速的響應於是打算使用redis緩存。一方面減少資料庫壓力另一方面又能提高響應速度。項目中使用的技術棧基本上都是大家熟悉的springboot全家桶,在springboot2.x以後操作redis的客戶端推薦使用lettuce(生菜)取代jedis。
jedis的劣勢主要在於直連redis,又無法做到彈性收縮。
一、配置文件
application.yml文件中的內容
spring: application: name: simple-lettuce cache: type: redis redis: # 緩存超時時間ms time-to-live: 60000 # 是否緩存空值 cache-null-values: true redis: host: 127.0.0.1 port: 6379 password: 123456 # 連接超時時間(毫秒) timeout: 60000 # Redis預設情況下有16個分片,這裡配置具體使用的分片,預設是0 database: 1 # spring2.x redis client 採用了lettuce(生菜),放棄使用jedis lettuce: # 關閉超時時間 shutdown-timeout: 30000 pool: # 連接池最大連接數(使用負值表示沒有限制) 預設 8 max-active: 30 # 連接池最大阻塞等待時間(使用負值表示沒有限制) 預設 -1 max-wait: -1 # 連接池中的最大空閑連接 預設 8 max-idle: 8 # 連接池中的最小空閑連接 預設 0 min-idle: 0
說明:
- spring.cache.type: redis
已經表明使用項目採用redis做為緩存方式。
- spring.cache.redis.cache-null-values: true
表示是否緩存空值,一般情況下是允許的。因為這涉及到緩存的三大問題:緩存穿透、緩存雪崩、緩存擊穿。
如果設置false即不允許緩存空值,這樣會導致很多請求資料庫沒有的數據時,不會緩存到redis導致每次都會請求到資料庫。這種情況即:緩存穿透。
具體想初步瞭解這些概念可以參考文章:緩存三大問題及解決方案!
二、config配置類
@Configuration @EnableCaching public class RedisTemplateConfig extends CachingConfigurerSupport { private static Map<String, RedisCacheConfiguration> cacheMap = Maps.newHashMap(); @Bean(name = "stringRedisTemplate") @ConditionalOnMissingBean(name = "stringRedisTemplate") //表示:如果容器已經有redisTemplate bean就不再註入 public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory); } @Bean(name = "redisTemplate") @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) { System.out.println("RedisTemplateConfig.RedisTemplate"); RedisTemplate<String, Object> template = new RedisTemplate<>(); // key的序列化採用StringRedisSerializer template.setKeySerializer(keySerializer()); template.setHashKeySerializer(keySerializer()); // value值的序列化採用fastJsonRedisSerializer template.setValueSerializer(valueSerializer()); //使用fastjson序列化 template.setHashValueSerializer(valueSerializer()); //使用fastjson序列化 template.setConnectionFactory(lettuceConnectionFactory); return template; } /** * 添加自定義緩存異常處理 * 當緩存讀寫異常時,忽略異常 * 參考:https://blog.csdn.net/sz85850597/article/details/89301331 */ @Override public CacheErrorHandler errorHandler() { return new IgnoreCacheErrorHandler(); } @SuppressWarnings("Duplicates") @Bean @Primary//當有多個管理器的時候,必須使用該註解在一個管理器上註釋:表示該管理器為預設的管理器 public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) { // 預設配置 RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(keyPair()) .serializeValuesWith(valuePair()) .entryTtl(Duration.ofSeconds(DEFAULT_TTL_SECS)) //設置過期時間 .disableCachingNullValues(); // 其它配置 for(MyCaches cache : MyCaches.values()) { cacheMap.put(cache.name(), RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(keyPair()) .serializeValuesWith(valuePair()) .entryTtl(cache.getTtl()) // .disableCachingNullValues() // 表示不允許緩存空值 .disableKeyPrefix() // 不使用預設首碼 // .prefixKeysWith("mytest") // 添加自定義首碼 ); } /** 遍歷MyCaches添加緩存配置*/ RedisCacheManager cacheManager = RedisCacheManager.builder( RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory) ) .cacheDefaults(defaultCacheConfig) .withInitialCacheConfigurations(cacheMap) .transactionAware() .build(); ParserConfig.getGlobalInstance().addAccept("mypackage.db.entity."); return cacheManager; } /** * key序列化方式 * @return */ private RedisSerializationContext.SerializationPair<String> keyPair() { RedisSerializationContext.SerializationPair<String> keyPair = RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()); return keyPair; } private RedisSerializer<String> keySerializer() { return new StringRedisSerializer(); } /** * value序列化方式 * @return */ private RedisSerializationContext.SerializationPair<Object> valuePair() { RedisSerializationContext.SerializationPair<Object> valuePair = RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()); return valuePair; } /** * 使用fastjson序列化 * @return */ private RedisSerializer<Object> valueSerializer() { MyFastJsonRedisSerializer<Object> fastJsonRedisSerializer = new MyFastJsonRedisSerializer<>(Object.class); return fastJsonRedisSerializer; } @Getter private enum MyCaches { defaultCache(Duration.ofDays(1)), MyCaches(Duration.ofMinutes(10)); MyCaches(Duration ttl) { this.ttl = ttl; } /** 失效時間 */ private Duration ttl = Duration.ofHours(1); } }
說明
1. 類上的註解@EnableCaching
表明開啟緩存功能。
2. extends CachingConfigurerSupport
這個類就很豐富了,其實如果沒有什麼特別操作也可以不用繼承這個類。
這個類可以支持動態選擇緩存方式,比如項目中不止一種緩存方案,有可能有ehcache那麼可以自定義在什麼情況下使用redis使用情況下使用ehcache。還有一些有關異常的處理。我也不是很懂具體可以參考:
springboot(25)自定義緩存讀寫機制CachingConfigurerSupport3. StringRedisTemplate和RedisTemplate的使用
(1)兩者的主要差別是:如果你只想緩存簡單的字元串選擇StringRedisTemplate是一個明智的舉措。如果想使用redis緩存一些對象數據肯定是要選擇RedisTemplate。 (2)RedisTemplate需要註意一點就是要怎麼選擇序列化工具。預設使用jdk的序列化緩存數據後即value值是無法直接閱讀的而存的二進位數據。 通常我們會選擇jackson或者fastjson來序列化對象,把對象轉換成json格式。兩者序列化對象後都會在頭部加上一個對象類路徑如:@type com.mypackage.entity.User。這個也算是一種安全策略。 比如使用fastjosn就會在cacheManager中指定序列化對象的包所在位置白名單:ParserConfig.getGlobalInstance().addAccept("mypackage.db.entity."); fastjson官方說明:https://github.com/alibaba/fastjson/wiki/enable_autotype (3)還有需要註意如果value是string類型。RedisTemplate會在字元串外圍再加一對雙引號,如""abc""。如果使用RedisTemplate讀取則能得到abc,但是我在項目使用Jedis讀取就成了"abc"這就導致這些字元串無法被反序列化。 (4)StringRedisTemplate和RedisTemplate兩者數據是相互隔離的,如果使用StringRedisTemplate存入的數據使用RedisTemplate是無法讀取、刪除的。三、緩存註解使用
@Cacheable 使用在查詢方法上
@CachePut 使用在更新、保存方法上
@CacheEvict 使用在刪除方法上
需要註意的是@Cacheable、@CachePut方法一定要有返回被緩存對象。因為註解使用的AOP切麵如果沒有返回值表示緩存對象為空值。
@CacheConfig註解在類上,可以選擇使用哪個緩存、緩存管理器、Key生成器
好了以上就是最近在項目中的一些知識點總結,如果以後使用緩存有新的體會我會同步更新的。