<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Boot 緩存應(yīng)用實踐

          共 27218字,需瀏覽 55分鐘

           ·

          2020-09-24 10:08

          點擊上方藍(lán)色“程序猿DD”,選擇“設(shè)為星標(biāo)”

          回復(fù)“資源”獲取獨家整理的學(xué)習(xí)資料!

          來源:cnblogs.com/jeffwongishandsome

          緩存是最直接有效提升系統(tǒng)性能的手段之一。個人認(rèn)為用好用對緩存是優(yōu)秀程序員的必備基本素質(zhì)。本文結(jié)合實際開發(fā)經(jīng)驗,從簡單概念原理和代碼入手,一步一步搭建一個簡單的二級緩存系統(tǒng)。

          一、通用緩存接口

          1、緩存基礎(chǔ)算法

          • FIFO(First In First Out),先進先出,和OS里的FIFO思路相同,如果一個數(shù)據(jù)最先進入緩存中,當(dāng)緩存滿的時候,應(yīng)當(dāng)把最先進入緩存的數(shù)據(jù)給移除掉。
          • LFU(Least Frequently Used),最不經(jīng)常使用,如果一個數(shù)據(jù)在最近一段時間內(nèi)使用次數(shù)很少,那么在將來一段時間內(nèi)被使用的可能性也很小。
          • LRU(Least Recently Used),最近最少使用,如果一個數(shù)據(jù)在最近一段時間沒有被訪問到,那么在將來它被訪問的可能性也很小。也就是說,當(dāng)限定的空間已存滿數(shù)據(jù)時,應(yīng)當(dāng)把最久沒有被訪問到的數(shù)據(jù)移除。

          2、接口定義

          簡單定義緩存接口,大致可以抽象如下:
          package com.power.demo.cache.contract;

          import?java.util.function.Function;

          /**
          ?* 緩存提供者接口
          ?**/

          public?interface?CacheProviderService {

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????extends
          ?Object> T?get(String?key);

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????**/

          ????extends?Object> T?get(String?key,?Function<String, T>?function);

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param funcParm function函數(shù)的調(diào)用參數(shù)
          ?????**/

          ????extends?Object, M?extends?Object> T?get(String?key,?Function?function,?M?funcParm);

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????extends?Object> T?get(String?key,?Function<String, T>?function,?Long?expireTime);

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param funcParm function函數(shù)的調(diào)用參數(shù)
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????extends?Object, M?extends?Object> T?get(String?key,?Function?function,?M?funcParm,?Long?expireTime);

          ????/**
          ?????* 設(shè)置緩存鍵值
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param obj 緩存值 不可為空
          ?????**/

          ????extends?Object>?void?set(String?key, T obj);

          ????/**
          ?????* 設(shè)置緩存鍵值
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param obj 緩存值 不可為空
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????extends?Object>?void?set(String?key, T obj, Long expireTime);

          ????/**
          ?????* 移除緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????void?remove(String?key);

          ????/**
          ?????* 是否存在緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????boolean?contains(String?key);
          }
          注意,這里列出的只是常見緩存功能接口,一些在特殊場景下用到的統(tǒng)計類的接口、分布式鎖、自增(減)等功能不在討論范圍之內(nèi)。
          Get相關(guān)方法,注意多個參數(shù)的情況,緩存接口里面?zhèn)魅说腇unction,這是Java8提供的函數(shù)式接口,雖然支持的入?yún)€數(shù)有限(這里你會非常懷念.NET下的Func委托),但是僅對Java這個語言來說,這真是一個重大的進步^_^。
          接口定義好了,下面就要實現(xiàn)緩存提供者程序了。按照存儲類型的不同,本文簡單實現(xiàn)最常用的兩種緩存提供者:本地緩存和分布式緩存

          二、本地緩存

          本地緩存,也就是JVM級別的緩存(本地緩存可以認(rèn)為是直接在進程內(nèi)通信調(diào)用,而分布式緩存則需要通過網(wǎng)絡(luò)進行跨進程通信調(diào)用),一般有很多種實現(xiàn)方式,比如直接使用Hashtable、ConcurrentHashMap等天生線程安全的集合作為緩存容器,或者使用一些成熟的開源組件,如EhCache、Guava Cache等。本文選擇上手簡單的Guava緩存。

          1、什么是Guava

          Guava,簡單來說就是一個開發(fā)類庫,且是一個非常豐富強大的開發(fā)工具包,號稱可以讓使用Java語言更令人愉悅,主要包括基本工具類庫和接口、緩存、發(fā)布訂閱風(fēng)格的事件總線等。在實際開發(fā)中,我用的最多的是集合、緩存和常用類型幫助類,很多人都對這個類庫稱贊有加。

          2、添加依賴

          <dependency>
          ????<groupId>com.google.guavagroupId>

          ????<artifactId>guavaartifactId>
          dependency>

          3、實現(xiàn)接口

          /*
          ?* 本地緩存提供者服務(wù) (Guava Cache)
          ?* */

          @Configuration
          @ComponentScan(basePackages = AppConst.BASE_PACKAGE_NAME)
          @Qualifier("localCacheService")
          public?class?LocalCacheProviderImpl?implements?CacheProviderService {

          ????private?static?Map<String, Cache<String,?Object>> _cacheMap = Maps.newConcurrentMap();

          ????static?{

          ????????Cache<String,?Object> cacheContainer = CacheBuilder.newBuilder()
          ????????????????.maximumSize(AppConst.CACHE_MAXIMUM_SIZE)
          ????????????????.expireAfterWrite(AppConst.CACHE_MINUTE, TimeUnit.MILLISECONDS)//最后一次寫入后的一段時間移出
          ????????????????//.expireAfterAccess(AppConst.CACHE_MINUTE, TimeUnit.MILLISECONDS) //最后一次訪問后的一段時間移出
          ????????????????.recordStats()//開啟統(tǒng)計功能
          ????????????????.build();

          ????????_cacheMap.put(String.valueOf(AppConst.CACHE_MINUTE), cacheContainer);
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????public?extends
          ?Object> T?get(String?key) {
          ????????T obj =?get(key,?null,?null, AppConst.CACHE_MINUTE);

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????**/

          ????public?extends?Object> T?get(String?key,?Function<String, T>?function)?{
          ????????T obj =?get(key,?function,?key,?AppConst.CACHE_MINUTE);

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param funcParm function函數(shù)的調(diào)用參數(shù)
          ?????**/

          ????public?extends?Object, M?extends?Object> T?get(String?key,?Function?function,?M?funcParm)?{
          ????????T obj =?get(key,?function,?funcParm,?AppConst.CACHE_MINUTE);

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????public?extends?Object> T?get(String?key,?Function<String, T>?function,?Long?expireTime)?{
          ????????T obj =?get(key,?function,?key,?expireTime);

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param funcParm function函數(shù)的調(diào)用參數(shù)
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????public?extends?Object, M?extends?Object> T?get(String?key,?Function?function,?M?funcParm,?Long?expireTime)?{
          ????????T obj =?null;
          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return?obj;
          ????????}

          ????????expireTime = getExpireTime(expireTime);

          ????????Cache<String,?Object> cacheContainer = getCacheContainer(expireTime);

          ????????try?{
          ????????????if?(function?==?null)?{
          ????????????????obj = (T) cacheContainer.getIfPresent(key);
          ????????????}?else?{
          ????????????????final Long cachedTime = expireTime;
          ????????????????obj = (T) cacheContainer.get(key, () -> {
          ????????????????????T retObj =?function.apply(funcParm);
          ????????????????????return?retObj;
          ????????????????});
          ????????????}
          ????????}?catch?(Exception e) {
          ????????????e.printStackTrace();
          ????????}

          ????????return?obj;
          ????}

          ????/**
          ?????* 設(shè)置緩存鍵值 直接向緩存中插入值,這會直接覆蓋掉給定鍵之前映射的值
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param obj 緩存值 不可為空
          ?????**/

          ????public?extends?Object>?void?set(String?key, T obj) {

          ????????set(key, obj, AppConst.CACHE_MINUTE);
          ????}

          ????/**
          ?????* 設(shè)置緩存鍵值 直接向緩存中插入值,這會直接覆蓋掉給定鍵之前映射的值
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param obj 緩存值 不可為空
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????public?extends?Object>?void?set(String?key, T obj, Long expireTime) {
          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return;
          ????????}

          ????????if?(obj ==?null) {
          ????????????return;
          ????????}

          ????????expireTime = getExpireTime(expireTime);

          ????????Cache<String,?Object> cacheContainer = getCacheContainer(expireTime);

          ????????cacheContainer.put(key, obj);
          ????}

          ????/**
          ?????* 移除緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????public?void?remove(String?key) {
          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return;
          ????????}

          ????????long expireTime = getExpireTime(AppConst.CACHE_MINUTE);

          ????????Cache<String,?Object> cacheContainer = getCacheContainer(expireTime);

          ????????cacheContainer.invalidate(key);
          ????}

          ????/**
          ?????* 是否存在緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????public?boolean?contains(String?key) {
          ????????boolean?exists =?false;
          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return?exists;
          ????????}

          ????????Object?obj =?get(key);

          ????????if?(obj !=?null) {
          ????????????exists =?true;
          ????????}

          ????????return?exists;
          ????}

          ????private?static?Lock lock =?new?ReentrantLock();

          ????private?Cache<String,?Object> getCacheContainer(Long expireTime) {

          ????????Cache<String,?Object> cacheContainer =?null;
          ????????if?(expireTime ==?null) {
          ????????????return?cacheContainer;
          ????????}

          ????????String?mapKey =?String.valueOf(expireTime);

          ????????if?(_cacheMap.containsKey(mapKey) ==?true) {
          ????????????cacheContainer = _cacheMap.get(mapKey);
          ????????????return?cacheContainer;
          ????????}

          ????????try?{
          ????????????lock.lock();
          ????????????cacheContainer = CacheBuilder.newBuilder()
          ????????????????????.maximumSize(AppConst.CACHE_MAXIMUM_SIZE)
          ????????????????????.expireAfterWrite(expireTime, TimeUnit.MILLISECONDS)//最后一次寫入后的一段時間移出
          ????????????????????//.expireAfterAccess(AppConst.CACHE_MINUTE, TimeUnit.MILLISECONDS) //最后一次訪問后的一段時間移出
          ????????????????????.recordStats()//開啟統(tǒng)計功能
          ????????????????????.build();

          ????????????_cacheMap.put(mapKey, cacheContainer);

          ????????}?finally?{
          ????????????lock.unlock();
          ????????}

          ????????return?cacheContainer;
          ????}

          ????/**
          ?????* 獲取過期時間 單位:毫秒
          ?????*
          ?????* @param expireTime 傳人的過期時間 單位毫秒 如小于1分鐘,默認(rèn)為10分鐘
          ?????**/

          ????private?Long getExpireTime(Long expireTime) {
          ????????Long result = expireTime;
          ????????if?(expireTime ==?null?|| expireTime < AppConst.CACHE_MINUTE /?10) {
          ????????????result = AppConst.CACHE_MINUTE;
          ????????}

          ????????return?result;
          ????}
          }

          4、注意事項

          Guava Cache初始化容器時,支持緩存過期策略,類似FIFO、LRU和LFU等算法。
          • expireAfterWrite:最后一次寫入后的一段時間移出。
          • expireAfterAccess:最后一次訪問后的一段時間移出。
          Guava Cache對緩存過期時間的設(shè)置實在不夠友好。常見的應(yīng)用場景,比如,有些幾乎不變的基礎(chǔ)數(shù)據(jù)緩存1天,有些熱點數(shù)據(jù)緩存2小時,有些會話數(shù)據(jù)緩存5分鐘等等。
          通常我們認(rèn)為設(shè)置緩存的時候帶上緩存的過期時間是非常容易的,而且只要一個緩存容器實例即可,比如.NET下的ObjectCache、System.Runtime.Cache等等。
          但是Guava Cache不是這個實現(xiàn)思路,如果緩存的過期時間不同,Guava的CacheBuilder要初始化多份Cache實例。
          好在我在實現(xiàn)的時候注意到了這個問題,并且提供了解決方案,可以看到getCacheContainer這個函數(shù),根據(jù)過期時長做緩存實例判斷,就算不同過期時間的多實例緩存也是完全沒有問題的。

          三、分布式緩存

          分布式緩存產(chǎn)品非常多,本文使用應(yīng)用普遍的Redis,在Spring Boot應(yīng)用中使用Redis非常簡單。

          1、什么是Redis

          Redis是一款開源(BSD許可)的、用C語言寫成的高性能的鍵-值存儲(key-value store)。它常被稱作是一款數(shù)據(jù)結(jié)構(gòu)服務(wù)器(data structure server)。它可以被用作緩存、消息中間件和數(shù)據(jù)庫,在很多應(yīng)用中,經(jīng)常看到有人選擇使用Redis做緩存,實現(xiàn)分布式鎖和分布式Session等。作為緩存系統(tǒng)時,和經(jīng)典的KV結(jié)構(gòu)的Memcached非常相似,但又有很多不同。
          Redis支持豐富的數(shù)據(jù)類型。Redis的鍵值可以包括字符串(strings)類型,同時它還包括哈希(hashes)、列表(lists)、集合(sets)和有序集合(sorted sets)等數(shù)據(jù)類型。對于這些數(shù)據(jù)類型,你可以執(zhí)行原子操作。例如:對字符串進行附加操作(append);遞增哈希中的值;向列表中增加元素;計算集合的交集、并集與差集等。
          Redis的數(shù)據(jù)類型:
          Keys:非二進制安全的字符類型( not binary-safe strings ),由于key不是binary safe的字符串,所以像“my key”和“mykey\n”這樣包含空格和換行的key是不允許的。
          Values:Strings、Hash、Lists、 Sets、 Sorted sets。考慮到Redis單線程操作模式,Value的粒度不應(yīng)該過大,緩存的值越大,越容易造成阻塞和排隊。
          為了獲得優(yōu)異的性能,Redis采用了內(nèi)存中(in-memory)數(shù)據(jù)集(dataset)的方式。同時,Redis支持?jǐn)?shù)據(jù)的持久化,你可以每隔一段時間將數(shù)據(jù)集轉(zhuǎn)存到磁盤上(snapshot),或者在日志尾部追加每一條操作命令(append only file,aof)。
          Redis同樣支持主從復(fù)制(master-slave replication),并且具有非常快速的非阻塞首次同步( non-blocking first synchronization)、網(wǎng)絡(luò)斷開自動重連等功能。
          同時Redis還具有其它一些特性,其中包括簡單的事物支持、發(fā)布訂閱 ( pub/sub)、管道(pipeline)和虛擬內(nèi)存(vm)等 。

          2、添加依賴

          <dependency>
          ?????<groupId>org.springframework.bootgroupId>

          ?????<artifactId>spring-boot-starter-data-redisartifactId>
          dependency>

          3、配置Redis

          在application.properties配置文件中,配置Redis常用參數(shù):
          ## Redis緩存相關(guān)配置
          #Redis數(shù)據(jù)庫索引(默認(rèn)為0)
          spring.redis.database=0
          #Redis服務(wù)器地址
          spring.redis.host=127.0.0.1
          #Redis服務(wù)器端口
          spring.redis.port=6379
          #Redis服務(wù)器密碼(默認(rèn)為空)
          spring.redis.password=123321
          #Redis連接超時時間 默認(rèn):5分鐘(單位:毫秒)
          spring.redis.timeout=300000ms
          #Redis連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
          spring.redis.jedis.pool.max-active=512
          #Redis連接池中的最小空閑連接
          spring.redis.jedis.pool.min-idle=0
          #Redis連接池中的最大空閑連接
          spring.redis.jedis.pool.max-idle=8
          #Redis連接池最大阻塞等待時間(使用負(fù)值表示沒有限制)
          spring.redis.jedis.pool.max-wait=-1ms
          redisproperties
          常見的需要注意的是最大連接數(shù)(spring.redis.jedis.pool.max-active )和超時時間(spring.redis.jedis.pool.max-wait)。Redis在生產(chǎn)環(huán)境中出現(xiàn)故障的頻率經(jīng)常和這兩個參數(shù)息息相關(guān)。
          接著定義一個繼承自CachingConfigurerSupport(請注意cacheManager和keyGenerator這兩個方法在子類的實現(xiàn))的RedisConfig類:
          /**
          ?* Redis緩存配置類
          ?*/

          @Configuration
          @EnableCaching
          public?class?RedisConfig?extends?CachingConfigurerSupport {

          ????@Bean
          ????public?CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
          ????????return?RedisCacheManager.create(connectionFactory);
          ????}

          ????@Bean
          ????public?RedisTemplate<String,?Object> redisTemplate(RedisConnectionFactory factory) {
          ????????RedisTemplate<String,?Object> template =?new?RedisTemplate<>();

          ????????//Jedis的Key和Value的序列化器默認(rèn)值是JdkSerializationRedisSerializer
          ????????//經(jīng)實驗,JdkSerializationRedisSerializer通過RedisDesktopManager看到的鍵值對不能正常解析

          ????????//設(shè)置key的序列化器
          ????????template.setKeySerializer(new?StringRedisSerializer());

          ????????////設(shè)置value的序列化器 默認(rèn)值是JdkSerializationRedisSerializer
          ????????//使用Jackson序列化器的問題是,復(fù)雜對象可能序列化失敗,比如JodaTime的DateTime類型

          ????????// //使用Jackson2,將對象序列化為JSON
          ????????// Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
          ????????// //json轉(zhuǎn)對象類,不設(shè)置默認(rèn)的會將json轉(zhuǎn)成hashmap
          ????????// ObjectMapper om = new ObjectMapper();
          ????????// om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
          ????????// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
          ????????// jackson2JsonRedisSerializer.setObjectMapper(om);
          ????????// template.setValueSerializer(jackson2JsonRedisSerializer);

          ????????//將redis連接工廠設(shè)置到模板類中
          ????????template.setConnectionFactory(factory);

          ????????return?template;
          ????}

          // //自定義緩存key生成策略
          // @Bean
          // public KeyGenerator keyGenerator() {
          // return new KeyGenerator() {
          // @Override
          // public Object generate(Object target, java.lang.reflect.Method method, Object... params) {
          // StringBuffer sb = new StringBuffer();
          // sb.append(target.getClass().getName());
          // sb.append(method.getName());
          // for (Object obj : params) {
          // if (obj == null) {
          // continue;
          // }
          // sb.append(obj.toString());
          // }
          // return sb.toString();
          // }
          // };
          // }
          }
          在RedisConfig這個類上加上@EnableCaching這個注解,這個注解會被Spring發(fā)現(xiàn),并且會創(chuàng)建一個切面(aspect) 并觸發(fā)Spring緩存注解的切點(pointcut)。據(jù)所使用的注解以及緩存的狀態(tài),這個切面會從緩存中獲取數(shù)據(jù),將數(shù)據(jù)添加到緩存之中或者從緩存中移除某個值。
          cacheManager方法,申明一個緩存管理器(CacheManager)的bean,作用就是@EnableCaching這個切面在新增緩存或者刪除緩存的時候會調(diào)用這個緩存管理器的方法。keyGenerator方法,可以根據(jù)需求自定義緩存key生成策略。
          而redisTemplate方法,則主要是設(shè)置Redis模板類,比如鍵和值的序列化器(從這里可以看出,Redis的鍵值對必須可序列化)、redis連接工廠等。
          RedisTemplate支持的序列化器主要有如下幾種:
          • JdkSerializationRedisSerializer:使用Java序列化;
          • StringRedisSerializer:序列化String類型的key和value;
          • GenericToStringSerializer:使用Spring轉(zhuǎn)換服務(wù)進行序列化;
          • JacksonJsonRedisSerializer:使用Jackson 1,將對象序列化為JSON;
          • Jackson2JsonRedisSerializer:使用Jackson 2,將對象序列化為JSON;
          • OxmSerializer:使用Spring O/X映射的編排器和解排器(marshaler和unmarshaler)實現(xiàn)序列化,用于XML序列化;
          注意:RedisTemplate的鍵和值序列化器,默認(rèn)情況下都是JdkSerializationRedisSerializer,它們都可以自定義設(shè)置序列化器。
          推薦將字符串鍵使用StringRedisSerializer序列化器,因為運維的時候好排查問題,JDK序列化器的也能識別,但是可讀性稍差(是因為緩存服務(wù)器沒有JRE嗎?),見如下效果:
          而值序列化器則要復(fù)雜的多,很多人推薦使用Jackson2JsonRedisSerializer序列化器,但是實際開發(fā)過程中,經(jīng)常有人碰到反序列化錯誤,經(jīng)過排查多數(shù)都和Jackson2JsonRedisSerializer這個序列化器有關(guān)。

          4、實現(xiàn)接口

          使用RedisTemplate,在Spring Boot中調(diào)用Redis接口比直接調(diào)用Jedis簡單多了。
          @Configuration
          @ComponentScan(basePackages = AppConst.BASE_PACKAGE_NAME)
          @Qualifier("redisCacheService")
          public?class?RedisCacheProviderImpl?implements?CacheProviderService {

          ????@Resource
          ????private?RedisTemplateObject
          > redisTemplate;

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????public?extends?Object> T?get(String?key) {
          ????????T obj =?get(key,?null,?null, AppConst.CACHE_MINUTE);

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????**/

          ????public?extends?Object> T?get(String?key,?Function<String, T>?function)?{
          ????????T obj =?get(key,?function,?key,?AppConst.CACHE_MINUTE);

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param funcParm function函數(shù)的調(diào)用參數(shù)
          ?????**/

          ????public?extends?Object, M?extends?Object> T?get(String?key,?Function?function,?M?funcParm)?{
          ????????T obj =?get(key,?function,?funcParm,?AppConst.CACHE_MINUTE);

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????public?extends?Object> T?get(String?key,?Function<String, T>?function,?Long?expireTime)?{
          ????????T obj =?get(key,?function,?key,?expireTime);

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param funcParm function函數(shù)的調(diào)用參數(shù)
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????public?extends?Object, M?extends?Object> T?get(String?key,?Function?function,?M?funcParm,?Long?expireTime)?{
          ????????T obj =?null;
          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return?obj;
          ????????}

          ????????expireTime = getExpireTime(expireTime);

          ????????try?{

          ????????????ValueOperationsObject> operations = redisTemplate.opsForValue();
          ????????????obj = (T) operations.get(key);
          ????????????if?(function?!=?null?&&?obj?==?null)?{
          ????????????????obj =?function.apply(funcParm);
          ????????????????if?(obj !=?null) {
          ????????????????????set(key, obj, expireTime);//設(shè)置緩存信息
          ????????????????}
          ????????????}
          ????????}?catch?(Exception e) {
          ????????????e.printStackTrace();
          ????????}

          ????????return?obj;
          ????}

          ????/**
          ?????* 設(shè)置緩存鍵值 直接向緩存中插入值,這會直接覆蓋掉給定鍵之前映射的值
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param obj 緩存值 不可為空
          ?????**/

          ????public?extends?Object>?void?set(String?key, T obj) {

          ????????set(key, obj, AppConst.CACHE_MINUTE);
          ????}

          ????/**
          ?????* 設(shè)置緩存鍵值 直接向緩存中插入值,這會直接覆蓋掉給定鍵之前映射的值
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param obj 緩存值 不可為空
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????public?extends?Object>?void?set(String?key, T obj, Long expireTime) {
          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return;
          ????????}

          ????????if?(obj ==?null) {
          ????????????return;
          ????????}

          ????????expireTime = getExpireTime(expireTime);

          ????????ValueOperationsObject> operations = redisTemplate.opsForValue();

          ????????operations.set(key, obj);

          ????????redisTemplate.expire(key, expireTime, TimeUnit.MILLISECONDS);
          ????}

          ????/**
          ?????* 移除緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????public?void?remove(String?key) {
          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return;
          ????????}

          ????????redisTemplate.delete(key);
          ????}

          ????/**
          ?????* 是否存在緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????public?boolean?contains(String?key) {
          ????????boolean?exists =?false;
          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return?exists;
          ????????}

          ????????Object?obj =?get(key);

          ????????if?(obj !=?null) {
          ????????????exists =?true;
          ????????}

          ????????return?exists;
          ????}

          ????/**
          ?????* 獲取過期時間 單位:毫秒
          ?????*
          ?????* @param expireTime 傳人的過期時間 單位毫秒 如小于1分鐘,默認(rèn)為10分鐘
          ?????**/

          ????private?Long getExpireTime(Long expireTime) {
          ????????Long result = expireTime;
          ????????if?(expireTime ==?null?|| expireTime < AppConst.CACHE_MINUTE /?10) {
          ????????????result = AppConst.CACHE_MINUTE;
          ????????}

          ????????return?result;
          ????}
          }
          注意:很多教程里都講到通過注解的方式(@Cacheable,@CachePut、@CacheEvict和@Caching)實現(xiàn)數(shù)據(jù)緩存,根據(jù)實踐,我個人是不推崇這種使用方式的。

          四、緩存“及時”過期問題

          這個也是開發(fā)和運維過程中非常經(jīng)典的問題。
          有些公司寫緩存客戶端的時候,會給每個團隊分別定義一個Area,但是這個只能做到緩存鍵的分布區(qū)分,不能保證緩存“實時”有效的過期。
          多年以前我寫過一篇結(jié)合實際情況的文章,也就是加上緩存版本,請猛擊這里 ,算是提供了一種相對有效的方案,不過高并發(fā)站點要慎重,防止發(fā)生雪崩效應(yīng)。
          Redis還有一些其他常見問題,比如:Redis的字符串類型Key和Value都有限制,且都是不能超過512M,請猛擊這里。還有最大連接數(shù)和超時時間設(shè)置等問題,本文就不再一一列舉了。

          五、二級緩存

          在配置文件中,加上緩存提供者開關(guān):
          ##是否啟用本地緩存
          spring.power.isuselocalcache=1
          ##是否啟用Redis緩存
          spring.power.isuserediscache=1
          緩存提供者程序都實現(xiàn)好了,我們會再包裝一個調(diào)用外觀類PowerCacheBuilder,加上緩存版本控制,可以輕松自如地控制和切換緩存,code talks:
          /*
          ?* 支持多緩存提供程序多級緩存的緩存幫助類
          ?* */

          @Configuration
          @ComponentScan(basePackages = AppConst.BASE_PACKAGE_NAME)
          public?class?PowerCacheBuilder {

          ????@Autowired
          ????@Qualifier("localCacheService")
          ????private?CacheProviderService localCacheService;

          ????@Autowired
          ????@Qualifier("redisCacheService")
          ????private?CacheProviderService redisCacheService;

          ????private?static?List _listCacheProvider = Lists.newArrayList();

          ????private?static?final Lock providerLock =?new?ReentrantLock();

          ????/**
          ?????* 初始化緩存提供者 默認(rèn)優(yōu)先級:先本地緩存,后分布式緩存
          ?????**/

          ????private?List getCacheProviders() {

          ????????if?(_listCacheProvider.size() >?0) {
          ????????????return?_listCacheProvider;
          ????????}

          ????????//線程安全
          ????????try?{
          ????????????providerLock.tryLock(1000, TimeUnit.MILLISECONDS);

          ????????????if?(_listCacheProvider.size() >?0) {
          ????????????????return?_listCacheProvider;
          ????????????}

          ????????????String?isUseCache = ConfigUtil.getConfigVal(AppField.IS_USE_LOCAL_CACHE);

          ????????????CacheProviderService cacheProviderService =?null;

          ????????????//啟用本地緩存
          ????????????if?("1".equalsIgnoreCase(isUseCache)) {
          ????????????????_listCacheProvider.add(localCacheService);
          ????????????}

          ????????????isUseCache = ConfigUtil.getConfigVal(AppField.IS_USE_REDIS_CACHE);

          ????????????//啟用Redis緩存
          ????????????if?("1".equalsIgnoreCase(isUseCache)) {
          ????????????????_listCacheProvider.add(redisCacheService);

          ????????????????resetCacheVersion();//設(shè)置分布式緩存版本號
          ????????????}

          ????????????PowerLogger.info("初始化緩存提供者成功,共有"?+ _listCacheProvider.size() +?"個");
          ????????}?catch?(Exception e) {
          ????????????e.printStackTrace();

          ????????????_listCacheProvider = Lists.newArrayList();

          ????????????PowerLogger.error("初始化緩存提供者發(fā)生異常:{}", e);
          ????????}?finally?{
          ????????????providerLock.unlock();
          ????????}

          ????????return?_listCacheProvider;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????public?extends
          ?Object> T?get(String?key) {
          ????????T obj =?null;

          ????????//key = generateVerKey(key);//構(gòu)造帶版本的緩存鍵

          ????????for?(CacheProviderService provider : getCacheProviders()) {

          ????????????obj = provider.get(key);

          ????????????if?(obj !=?null) {
          ????????????????return?obj;
          ????????????}
          ????????}

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????**/

          ????public?extends?Object> T?get(String?key,?Function<String, T>?function)?{
          ????????T obj =?null;

          ????????for?(CacheProviderService provider : getCacheProviders()) {

          ????????????if?(obj ==?null) {
          ????????????????obj = provider.get(key,?function);
          ????????????}?else?if?(function?!=?null?&&?obj?!=?null)?{//查詢并設(shè)置其他緩存提供者程序緩存
          ????????????????provider.get(key,?function);
          ????????????}

          ????????????//如果callable函數(shù)為空 而緩存對象不為空 及時跳出循環(huán)并返回
          ????????????if?(function?==?null?&&?obj?!=?null)?{
          ????????????????return?obj;
          ????????????}

          ????????}

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param funcParm function函數(shù)的調(diào)用參數(shù)
          ?????**/

          ????public?extends?Object, M?extends?Object> T?get(String?key,?Function?function,?M?funcParm)?{
          ????????T obj =?null;

          ????????for?(CacheProviderService provider : getCacheProviders()) {

          ????????????if?(obj ==?null) {
          ????????????????obj = provider.get(key,?function,?funcParm);
          ????????????}?else?if?(function?!=?null?&&?obj?!=?null)?{//查詢并設(shè)置其他緩存提供者程序緩存
          ????????????????provider.get(key,?function,?funcParm);
          ????????????}

          ????????????//如果callable函數(shù)為空 而緩存對象不為空 及時跳出循環(huán)并返回
          ????????????if?(function?==?null?&&?obj?!=?null)?{
          ????????????????return?obj;
          ????????????}
          ????????}

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????public?extends?Object> T?get(String?key,?Function<String, T>?function,?long?expireTime)?{
          ????????T obj =?null;

          ????????for?(CacheProviderService provider : getCacheProviders()) {

          ????????????if?(obj ==?null) {
          ????????????????obj = provider.get(key,?function,?expireTime);
          ????????????}?else?if?(function?!=?null?&&?obj?!=?null)?{//查詢并設(shè)置其他緩存提供者程序緩存
          ????????????????provider.get(key,?function,?expireTime);
          ????????????}

          ????????????//如果callable函數(shù)為空 而緩存對象不為空 及時跳出循環(huán)并返回
          ????????????if?(function?==?null?&&?obj?!=?null)?{
          ????????????????return?obj;
          ????????????}
          ????????}

          ????????return?obj;
          ????}

          ????/**
          ?????* 查詢緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param function 如沒有緩存,調(diào)用該callable函數(shù)返回對象 可為空
          ?????* @param funcParm function函數(shù)的調(diào)用參數(shù)
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????public?extends?Object, M?extends?Object> T?get(String?key,?Function?function,?M?funcParm,?long?expireTime)?{
          ????????T obj =?null;

          ????????for?(CacheProviderService provider : getCacheProviders()) {

          ????????????if?(obj ==?null) {
          ????????????????obj = provider.get(key,?function,?funcParm,?expireTime);
          ????????????}?else?if?(function?!=?null?&&?obj?!=?null)?{//查詢并設(shè)置其他緩存提供者程序緩存
          ????????????????provider.get(key,?function,?funcParm,?expireTime);
          ????????????}

          ????????????//如果callable函數(shù)為空 而緩存對象不為空 及時跳出循環(huán)并返回
          ????????????if?(function?==?null?&&?obj?!=?null)?{
          ????????????????return?obj;
          ????????????}
          ????????}

          ????????return?obj;
          ????}

          ????/**
          ?????* 設(shè)置緩存鍵值 直接向緩存中插入或覆蓋值
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param obj 緩存值 不可為空
          ?????**/

          ????public?extends?Object>?void?set(String?key, T obj) {

          ????????//key = generateVerKey(key);//構(gòu)造帶版本的緩存鍵

          ????????for?(CacheProviderService provider : getCacheProviders()) {

          ????????????provider.set(key, obj);

          ????????}
          ????}

          ????/**
          ?????* 設(shè)置緩存鍵值 直接向緩存中插入或覆蓋值
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????* @param obj 緩存值 不可為空
          ?????* @param expireTime 過期時間(單位:毫秒) 可為空
          ?????**/

          ????public?extends?Object>?void?set(String?key, T obj, Long expireTime) {

          ????????//key = generateVerKey(key);//構(gòu)造帶版本的緩存鍵

          ????????for?(CacheProviderService provider : getCacheProviders()) {

          ????????????provider.set(key, obj, expireTime);

          ????????}
          ????}

          ????/**
          ?????* 移除緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????public?void?remove(String?key) {

          ????????//key = generateVerKey(key);//構(gòu)造帶版本的緩存鍵

          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return;
          ????????}

          ????????for?(CacheProviderService provider : getCacheProviders()) {

          ????????????provider.remove(key);

          ????????}
          ????}

          ????/**
          ?????* 是否存在緩存
          ?????*
          ?????* @param key 緩存鍵 不可為空
          ?????**/

          ????public?boolean?contains(String?key) {
          ????????boolean?exists =?false;

          ????????//key = generateVerKey(key);//構(gòu)造帶版本的緩存鍵

          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return?exists;
          ????????}

          ????????Object?obj =?get(key);

          ????????if?(obj !=?null) {
          ????????????exists =?true;
          ????????}

          ????????return?exists;
          ????}

          ????/**
          ?????* 獲取分布式緩存版本號
          ?????**/

          ????public?String?getCacheVersion() {
          ????????String?version =?"";
          ????????boolean?isUseCache = checkUseRedisCache();

          ????????//未啟用Redis緩存
          ????????if?(isUseCache ==?false) {
          ????????????return?version;
          ????????}

          ????????version = redisCacheService.get(AppConst.CACHE_VERSION_KEY);

          ????????return?version;
          ????}

          ????/**
          ?????* 重置分布式緩存版本 如果啟用分布式緩存,設(shè)置緩存版本
          ?????**/

          ????public?String?resetCacheVersion() {
          ????????String?version =?"";
          ????????boolean?isUseCache = checkUseRedisCache();

          ????????//未啟用Redis緩存
          ????????if?(isUseCache ==?false) {
          ????????????return?version;
          ????????}

          ????????//設(shè)置緩存版本
          ????????version =?String.valueOf(Math.abs(UUID.randomUUID().hashCode()));
          ????????redisCacheService.set(AppConst.CACHE_VERSION_KEY, version);

          ????????return?version;
          ????}

          ????/**
          ?????* 如果啟用分布式緩存,獲取緩存版本,重置查詢的緩存key,可以實現(xiàn)相對實時的緩存過期控制
          ?????*


          ?????* 如沒有啟用分布式緩存,緩存key不做修改,直接返回
          ?????**/
          ????public?String?generateVerKey(String?key) {

          ????????String?result = key;
          ????????if?(StringUtils.isEmpty(key) ==?true) {
          ????????????return?result;
          ????????}

          ????????boolean?isUseCache = checkUseRedisCache();

          ????????//沒有啟用分布式緩存,緩存key不做修改,直接返回
          ????????if?(isUseCache ==?false) {
          ????????????return?result;
          ????????}

          ????????String?version = redisCacheService.get(AppConst.CACHE_VERSION_KEY);
          ????????if?(StringUtils.isEmpty(version) ==?true) {
          ????????????return?result;
          ????????}

          ????????result =?String.format("%s_%s", result, version);

          ????????return?result;
          ????}

          ????/**
          ?????* 驗證是否啟用分布式緩存
          ?????**/

          ????private?boolean?checkUseRedisCache() {
          ????????boolean?isUseCache =?false;
          ????????String?strIsUseCache = ConfigUtil.getConfigVal(AppField.IS_USE_REDIS_CACHE);

          ????????isUseCache =?"1".equalsIgnoreCase(strIsUseCache);

          ????????return?isUseCache;
          ????}
          }

          單元測試如下:
          @Test
          public?void?testCacheVerson() throws Exception?
          {

          ????????String version = cacheBuilder.getCacheVersion();
          ????????System.out.println(String.format("當(dāng)前緩存版本:%s", version));

          ????????String cacheKey = cacheBuilder.generateVerKey("goods778899");

          ????????GoodsVO goodsVO =?new?GoodsVO();
          ????????goodsVO.setGoodsId(UUID.randomUUID().toString());
          ????????goodsVO.setCreateTime(new?Date());
          ????????goodsVO.setCreateDate(new?DateTime(new?Date()));
          ????????goodsVO.setGoodsType(1024);
          ????????goodsVO.setGoodsCode("123456789");
          ????????goodsVO.setGoodsName("我的測試商品");

          ????????cacheBuilder.set(cacheKey, goodsVO);

          ????????GoodsVO goodsVO1 = cacheBuilder.get(cacheKey);

          ????????Assert.assertNotNull(goodsVO1);

          ????????version = cacheBuilder.resetCacheVersion();
          ????????System.out.println(String.format("重置后的緩存版本:%s", version));


          ????????cacheKey = cacheBuilder.generateVerKey("goods112233");

          ????????cacheBuilder.set(cacheKey, goodsVO);

          ????????GoodsVO goodsVO2 = cacheBuilder.get(cacheKey);

          ????????Assert.assertNotNull(goodsVO2);

          ????????Assert.assertTrue("兩個緩存對象的主鍵相同", goodsVO1.getGoodsId().equals(goodsVO2.getGoodsId()));
          ????}


          一個滿足基本功能的多級緩存系統(tǒng)就好了。
          在Spring Boot應(yīng)用中使用緩存則非常簡潔,選擇調(diào)用上面包裝好的緩存接口即可。
          String?cacheKey = _cacheBuilder.generateVerKey("com.power.demo.apiservice.impl.getgoodsbyid."?+ request.getGoodsId());

          GoodsVO?goodsVO = _cacheBuilder.get(cacheKey, _goodsService::getGoodsByGoodsId, request.getGoodsId());
          到這里Spring Boot業(yè)務(wù)系統(tǒng)開發(fā)中最常用到的ORM,緩存和隊列三板斧就介紹完了。
          在開發(fā)的過程中你會發(fā)現(xiàn),Java真的是非常非常中規(guī)中矩的語言,你需要不斷折騰并熟悉常見的開源中間件和工具,開源的輪子實在是太豐富,多嘗試幾個,實踐出真知。
          預(yù)告一下,后面的文章我將繼續(xù)學(xué)習(xí)分享介紹常用中間件和工具,如定時任務(wù),MongoDB,ES,分布式文件系統(tǒng)以及各種實用工具。總之Java下工具鏈非常完備,我們要給自己更多動力。

          參考:

          http://ifeve.com/google-guava/
          http://www.cnblogs.com/luochengqiuse/p/4640932.html



          往期推薦

          贈書:一本書帶你吃透Nginx應(yīng)用與運維

          超全的 Linux Shell 文本處理工具集錦,快收藏

          今年2月的微盟“刪庫”主角被判 6 年有期徒刑

          MySQL中的InnoDB是怎么解決幻讀的?

          Redis為什么變慢了?常見延遲問題定位與分析

          為什么 GROUP BY 之后不能直接引用原表中的列?

          Java開發(fā)中Websocket的技術(shù)選型參考


          我們在星球聊了很多深度話題,你不來看看?

          我的星球是否適合你?

          點擊閱讀原文看看我們都聊過啥?

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久亚洲一区女同性恋中文字幕 | 超碰人人妻 | 性爱视频在线无码播放 | 亚洲精品水蜜桃 | 天天撸夜夜操 |