SpringBoot2.x—代碼實現(xiàn)多級緩存

Redis作為分布式緩存,性能是比較好的。但服務器會嚴重依賴Redis服務,且每次請求都會查詢Redis獲取緩存內(nèi)容。

實現(xiàn)多級緩存時,有兩個措施可以保證本地緩存和Redis緩存數(shù)據(jù)一致性:

  1. 主動措施:Redis實現(xiàn)訂閱發(fā)布模型,即修改Redis內(nèi)容后,集群所有服務器均要收到通知,來修改本地緩存。
  2. 被動措施:本地緩存設置失效時間,緩存失效后主動查詢Redis獲取數(shù)據(jù)。

1. Redis的訂閱發(fā)布

加入依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--guava依賴-->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.0.1-jre</version>
</dependency>

訂閱者配置:

Redis使用list結構實現(xiàn)訂閱發(fā)布。訂閱者可以在不同的項目,但是訂閱者和發(fā)布者必須使用同一個Redis庫。

@Slf4j
@Configuration
public class RedisContainerConfig {

    @Bean
    RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                                            MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        //設置監(jiān)聽的隊列,可以設置多個
        container.addMessageListener(listenerAdapter, new PatternTopic("topic:service_xx:module"));
        container.addMessageListener(listenerAdapter, new PatternTopic("topic:service_xx"));
        return container;
    }

    //FuseInterceptor是一個普通的Bean對象,他的作用是處理監(jiān)聽到的內(nèi)容
    @Bean
    MessageListenerAdapter listenerAdapter(FuseInterceptor receiver) {
        return new MessageListenerAdapter(new MessageListener() {
            public void onMessage(Message message, byte[] pattern) {
                try {
                    //監(jiān)聽的隊列信息
                    String type = new String(message.getChannel(), StandardCharsets.UTF_8.name());
                    //發(fā)送的內(nèi)容信息
                    String m = new String(message.getBody(), StandardCharsets.UTF_8.name());
                    log.info("redis監(jiān)聽到[{}]的消息[{}]",type,m);
                    receiver.refreshCache(type, m);
                } catch (UnsupportedEncodingException e) {

                    e.printStackTrace();
                }
            }
        });
    }
}

代碼中使用了guava Cache實現(xiàn)本地緩存(并設置失效時間)。當監(jiān)聽到Redis修改時,主動修改本地緩存。緩存失效后,也會去修改本地緩存。

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

@Component
@Slf4j
public class FuseInterceptor implements HandlerInterceptor {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //設置超時時間。guava的Cache
    public static Cache<String, Object> moduleCache = CacheBuilder.
            newBuilder().
            //寫入緩存后,10s后過期
            expireAfterWrite(10, TimeUnit.SECONDS).
            build();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ExecutionException {
           //先在本地緩存中獲取內(nèi)容,若獲取不到,直接查詢Redis并放入本地緩存(線程安全)
          String moduleStatus = (String) moduleCache.get("topic:service_xx:module", () -> {
           //若本地緩存獲取不到,那么去Redis查詢數(shù)據(jù)并放入緩存。
            String value = stringRedisTemplate.opsForValue().get("service_xx:module");
            return StringUtils.isBlank(value) ? "close": value;
          });
        //攔截器處理。
        return true;
    }

    /**
     * 訂閱發(fā)布獲取到最新的Redis內(nèi)容
     *
     * @param type 監(jiān)聽隊列的名
     * @param message 監(jiān)聽到的信息
     */
    public void refreshCache(String type, String message) {
        if ("topic:service_xx".equals(type)) {
            //若是這個隊列的消息,那么將其轉(zhuǎn)換為Set<String>結構,并放入緩存中
            Set<String> patterns = JSONObject.parseObject(message,
                    new TypeReference<LinkedHashSet<String>>() {
                    }.getType());
            moduleCache.put(type, patterns);
        } else {
            moduleCache.put(type, message);
        }
    }
}

發(fā)布者配置:

//發(fā)布消息
StringRedisTemplate.convertAndSend("topic:service_xx:module","發(fā)送的消息");
//將消息存儲到Redis中,以便本地緩存失效后查詢Redis緩存
StringRedisTemplate.opsForValue().set("service_xx:module","發(fā)送的消息");

推薦閱讀

SpringBoot2.x—SpringCache(5)使用多級緩存
SpringBoot2.x—SpringCache(6)緩存注意事項

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容