Spring boot 中緩存的使用

1.內存緩存的使用

1.1 自定義HashMap實現

如題所說,自己實現key-value的map,使用定時器管理緩存的清空之類的, 比較麻煩,不建議使用

1.2 使用現有的內存緩存框架

spring引入緩存框架是非常容易的, 詳細使用方式可以參考Spring Boot 緩存配置

在之前做的項目中使用的是 caffenie 框架, 該框架爲 google guava 框架內緩存模塊的升級版本, 提高了性能, 增加了更多的功能, 使用方式可以參考Caffeine 緩存框架

簡單介紹一下使用方式

1.2.1 通用配置方式

在application.yml文件中增加配置

  cache:
    type: caffeine
    caffeine:
      spec: maximumSize=%d,expireAfterWrite=%ds

在SpringBoot啓動類中增加註解

@EnableCaching
@SpringBootApplication
class Application {
    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            SpringApplication.run(Application::class.java, *args)
        }
    }
}

在需要被緩存的方法上增加註解

/* 對方法增加緩存 */
@Override
@Cacheable
public List<ConfigAllSite> listConfigAllSite() {
    return configMapper.listConfigAllSite();
}

/* 對類中的所有方法增加緩存 */
@Component
@CacheConfig(cacheNames = CacheNames.ConfigRefer)
@Cacheable
public class ConfigRepositoryImpl implements ConfigRepository {
//````
}

其中CacheConfig指定了該緩存塊的名稱,使用其他註解進行更新和清除操作時會用到

1.3 使用redis

內存緩存框架有一定的侷限性, 包括分佈式緩存以及大數據量緩存的恢復和重建等

redis很好的解決了這些問題, 而且性能也並沒有差太多, 比數據庫快許多

使用redis的方式

1.3.1 添加依賴

spring-data-redis是spring爲redis操作開發的一套框架, 對於redis的簡單使用引用這個包就十分方便了

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在application.yml中聲明

// 主從模式, 配置host,port,password
spring:
  redis:
    host: xxx
    password: xxx
    port: xxx

// cluster模式, 配置集羣所有節點host, port
spring:
  redis:
    cluster: 
      nodes: 127.0.0.1:6379,127.0.0.1:6380...
    password: xxx
    port: xxx

我們可能引入一些序列化工具, 因爲spring 默認使用 jvmserialiser 來序列化對象, 存入redis的對象必須繼承 serialer 接口, 非常不方便,
spring 自帶 jackson, 我們可以使用jackson, 也可以使用 alibaba 的 fastjson

使用 jackson

@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        redisTemplate.setDefaultSerializer(serializer);
        return redisTemplate;
    }

}

使用 fastjson (文檔), 首先需要引用依賴

<!--https://github.com/alibaba/fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.38</version>
</dependency>

然後配置, 基本同上

@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        GenericFastJsonRedisSerializer serializer = new GenericFastJsonRedisSerializer();
        redisTemplate.setDefaultSerializer(serializer);
        return redisTemplate;
    }

}

fastjson其他使用方法可以參考文檔

2 使用多個緩存時發生衝突的問題

有時可能我們的工程既需要使用redis, 又因爲對速度的需求需要使用內存緩存, 這時就需要增加配置來適配多個manager的問題.

一般情況下, 引用了 spring-data-redis 模塊後, 在不進行額外配置的情況下spring會默認使用redis的cachemanager作爲全局的cachemanager. 也就是@Cacheable相關注解也會使用redis作爲管理器, 這是與我們的需求違背的.

首先還是需要增加依賴和相關配置, 如前幾點所說, 這裏不做敘述

接下來需要增加一個新的 cachemManager

@Configuration
@EnableCaching
public class SimpleCaffeineCacheManager {

    // CacheManager bean name
    public static final String CACHE_MANAGER_BEAN_NAME = "caffeineCacheManager";

    // 最多存儲的緩存條數
    private static final int DEFAULT_MAX_SIZE = 20000;
    // 默認過期時間
    private static final int DEFAULT_TTL_SECONDS = 300;

    @Bean
    @Qualifier(CACHE_MANAGER_BEAN_NAME)
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();

        String spec = String.format("maximumSize=%d,expireAfterWrite=%ds", DEFAULT_MAX_SIZE, DEFAULT_TTL_SECONDS);
        CaffeineSpec caffeineSpec = CaffeineSpec.parse(spec);
        manager.setCaffeineSpec(caffeineSpec);

        return manager;
    }

}

然後在其他地方使用@Cacaheable相關主解時, 需要指定CacheManager

@CacheConfig(cacheNames = CacheNames.ConfigRefer, cacheManager = SimpleCaffeineCacheManager.CACHE_MANAGER_BEAN_NAME)
@Cacheable
public class ConfigRepositoryImpl implements ConfigRepository {
//...
}

這樣就解決了多個CacheManager的衝突問題

備註

今天使用時候碰到了個bug, 使用@Cacheable標註類, 在類的方法中使用mybatis的mapper的查詢結果作爲返回值, 會出現說返回值是單個無法轉換爲list的bug.

查詢了一下發現是由於key的生成策略導致的:

默認的key是通過KeyGenerator生成的,其默認策略如下:
1. 如果方法沒有參數,則使用0作爲key;
2. 如果只有一個參數的話則使用該參數作爲key;
3. 如果參數多於一個則使用所有參數的hashcode作爲key;

所以添加了一個keygenerator來避免這個問題

用 Cachemanager 繼承 CachingConfigurerSupport 類, 並實現keygenerator方法

public class SimpleCaffeineCacheManager extends CachingConfigurerSupport {
    //...

    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, objects) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getSimpleName() + " :");
            sb.append(method.getName() + "=-=");
            for (Object object : objects) {
                if (object == null) sb.append("|-|-|");
                else sb.append(object.toString());
            }
            return sb.toString();
        };
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章