SpringBoot2.1版本的個人應用開發框架 - 整合Redis快取

人生長恨水發表於2019-04-11

本篇作為SpringBoot2.1版本的個人開發框架 子章節,請先閱讀SpringBoot2.1版本的個人開發框架再次閱讀本篇文章

專案地址:SpringBoot2.1版本的個人應用開發框架

關於為什麼要用Redis?

專案中為什麼要用Redis快取?其實在我實習時是用到過Redis快取的,但是我只是知道用到了Redis,如何使用的,為什麼要用,這些我統統都不知道。引用網上一句話就是,我這次整合也是為了Redis而Redis了,並不是拿Redis來解決實際的問題,但是我覺得只有先學會了,才能知道在什麼情況下可以用Redis來解決問題。

為什麼要用redis?引用網上的言論

1.解決應用伺服器的cpu和記憶體壓力 2.在專案中使用 Redis,主要考慮兩個角度:效能和併發。 3.我們在碰到需要執行耗時特別久,且結果不頻繁變動的 SQL,就特別適合將執行結果放入快取。這樣,後面的請求就去快取中讀取,使得請求能夠迅速響應。 4.在大併發的情況下,所有的請求直接訪問資料庫,資料庫會出現連線異常。這個時候,就需要使用redis做一個緩衝操作,讓請求先訪問到redis,而不是直接訪問資料庫。 5.排行榜及相關問題。排行榜(leader board)按照得分進行排序。zadd命令可以直接實現這個功能,而zrevrange命令可以用來按照得分來獲取前100名的使用者,zrank可以用來獲取使用者排名,非常直接而且操作容易。 6.計數的問題,比如點贊和轉發數,通過原子遞增保持計數;getset用來重置計數器;過期屬性用來確認一個關鍵字什麼時候應該刪除。

我的學習筆記:

而且Redis可以master-slave(主從)模式進行資料備份,在分散式的系統中,可以很好保證資料的備份,Redis會自動把主資料庫(master)中的資料備份到從資料庫(slave)中,關於為什麼要用Redis這件事情上,除了本身自己專案中的原因;剩下的有很多原因都可以事先在網路上汲取知識以及經驗。

SpringBoot的快取支援

我們把快取的事情放到ywh-starter-cache這個模組中來整合,在cache中的pom.xml檔案中引入spring-boot-starter-data-redis依賴

<!-- redis依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.1.1.RELEASE</version>
</dependency>
複製程式碼

cache專案結構分為

  • config - 放配置類
  • serializer - 放序列化類
  • utils - 放工具類

原始碼級分析

為什麼使用者需要自己建立一個redis的配置類呢?

SpringBoot提供了對Redis的自動配置功能,在RedisAutoConfiguration類中預設為我們配置了客戶端連線(Lettuce和Jedis),以及資料操作模板(StringRedisTemplate和RedisTemplate),下列程式碼有一個@ConditionalOnMissingBean和@Bean的註解,@ConditionalOnMissingBean註解判斷是否執行初始化程式碼,即如果使用者已經建立了bean,則相關的初始化程式碼不再執行。這導致了預設的是redisTemplate方法會被執行。

public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}
複製程式碼

RedisTemplate這個資料操作模板類我們可以點選去看一看,在類中有一段程式碼

if (this.defaultSerializer == null) {
    this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
複製程式碼

如果預設的序列化為空則使用jdk來序列化我們的資料,而 defaultSerializer 這個私有屬性,預設為NULL,所以預設的序列方式時jdk的方式,但是這個序列化方式會把資料變得人看不懂,所以才需要建立一個Redis配置類覆蓋預設的序列化,如果我有分析的有不對的地方,望指正。

簡單的連線

分析過後,就以兩種方式來進行連線。第一種:不更改預設配置使用StringRedisTemplate和RedisTemplate

在我們寫程式碼測試之前需要在cache模組中配置application-redis.yml檔案,並且把core下的application.yml檔案中的active屬性新增redis 以逗號相隔這樣就可在執行的時候讀取application-redis.yml的內容,把cache模組下application.properties修改成application-redis.yml檔案進行配置。

spring:
  redis:
    # Redis資料庫索引(預設為0)
    database: 0
    # Redis伺服器地址
    host: 127.0.0.1
    # Redis伺服器連線埠
    port: 6379
    # Redis伺服器連線密碼(預設為空)如果沒有配置密碼就不要寫這個屬性了
    password: 123456
    #連線池
    lettuce:
      pool:
        #連線池最大連線數(使用負值表示沒有限制)
        max-active: 8
        #連線池最大阻塞等待時間(使用負值表示沒有限制)
        max-wait: 60000
        #連線池中的最大空閒連線
        max-idle: 8
        #連線池中的最小空閒連線
        min-idle: 0
    #連線超時時間(毫秒)
    timeout: 10000
複製程式碼

在SpringBoot測試類中編寫程式碼並執行新增資料。

@Autowired
private StringRedisTemplate stringRedisTemplate;

/**
 * 測試連線redis,並存入資料
 */
@Test
public void redisTest(){
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    stringRedisTemplate.opsForValue().set("abc","測試");
    stringRedisTemplate.opsForList().leftPushAll("ywh",list);

}
複製程式碼

StringRedis測試資料

我們在用StringRedisTemplate新增的資料顯示正常,也是我們人眼能讀懂的,接下來我們用RedisTemplate這個類來連線Redis新增資料看一看資料是什麼樣的,預設的序列化JdkSerializationRedisSerializer的二進位制資料序列化方式,程式碼跟上面差不多。

@Autowired
private RedisTemplate<String,Object> redisTemplate;

@Test
public void redisTest1(){
    List<String> list = new ArrayList<>();
    list.add("y");
    list.add("w");
    list.add("h");
    redisTemplate.opsForValue().set("redisTemplate","連線成功了");
    redisTemplate.opsForList().leftPushAll("redis",list);
}
複製程式碼

RedisTemplate測試資料

果然新增的資料我們人眼分辨不出來這是什麼,所以下面我們要進行覆蓋預設的配置,定製自己的序列化方式。

覆蓋預設配置進行連線

spring為我們提供了多種序列化方式,都在org.springframework.data.redis.serializer包下,常用的分別是:

  • JdkSerializationRedisSerializer
  • GenericJackson2JsonRedisSerializer
  • StringRedisSerializer
  • Jackson2JsonRedisSerializer

這四種我們只使用StringRedisSerializer來序列化Key值,value值由我們自己建立的序列化類,serializer包下建立我們自定義的FastJsonRedisSerializer類,需要實現RedisSerializer介面,實現介面中的序列化方法和反序列化方法,使用的是alibaba的Fastjson實現。

package com.ywh.cache.serializer;

/**
 * CreateTime: 2018-12-19 16:51
 * ClassName: FastJsonRedisSerializer
 * Package: com.ywh.cache.serializer
 * Describe:
 * 自定義的序列化類
 * @author YWH
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    private ObjectMapper objectMapper = new ObjectMapper();
 
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
 
    private Class<T> clazz;
 
    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }
 
    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }
 
    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
 
        return JSON.parseObject(str, clazz);
    }
    public void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }
 
    protected JavaType getJavaType(Class<?> clazz) {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}
複製程式碼

建立好自定義的序列化類後,進行覆蓋預設的配置,接下來在config包下建立RedisCacheConfig類,配置好Redis配置類以後我們再次重新執行測試類,會發現值不再是亂碼了。

package com.ywh.cache.config;
 
/**
 * CreateTime: 2018-12-18 23:34
 * ClassName: RedisCacheConfig
 * Package: com.ywh.cache.config
 * Describe:
 * Redis快取配置   @EnableCaching 開啟宣告式快取支援. 之後就可以使用 @Cacheable/@CachePut/@CacheEvict 註解快取資料.
 * @author YWH
 */
@Configuration
@EnableCaching
public class RedisCacheConfig {
 
 
    private RedisConnectionFactory redisConnectionFactory;
 
    @Autowired
    public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory){
        this.redisConnectionFactory = redisConnectionFactory;
    }
 
    /**
     * 覆蓋預設的配置
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(){
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
 
 
        // 設定value的序列化規則和key的序列化規則
        template.setKeySerializer(stringRedisSerializer);
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        template.setDefaultSerializer(fastJsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
 
    /**
     * 解決註解方式存放到redis中的值是亂碼的情況
     * @param factory 連線工廠
     * @return CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
 
        // 配置註解方式的序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration =
                config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
                        //配置註解預設的過期時間
                        .entryTtl(Duration.ofDays(1));
        // 加入白名單   https://github.com/alibaba/fastjson/wiki/enable_autotype
        ParserConfig.getGlobalInstance().addAccept("com.ywh");
        ParserConfig.getGlobalInstance().addAccept("com.baomidou");
        return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
    }
}
複製程式碼

Redis工具類

redis可以對五種型別操作,可以自己再進行擴充套件,工具類我們放在cache模組下建立utils包,建立RedisUtil工具類,類上新增@Component註解,交給Spring來管理,我們可以直接在其他的方法中用@AtuoWired獲取。

package com.ywh.cache.utils;
/**
 * CreateTime: 2018-12-19 17:45
 * ClassName: RedisUtil
 * Package: com.ywh.cache.utils
 * Describe:
 * Redis工具類
 *
 * @author YWH
 */
@Component
public class RedisUtil {
 
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
 
    //---------------------- common --------------------------
 
    /**
     * 指定快取失效時間
     * @param key key值
     * @param time 快取時間
     * @return true設定成功,time <=0 設定失敗
     */
    public void expire(String key, long time){
        if(time > 0){
            redisTemplate.expire(key,time,TimeUnit.SECONDS);
        }else{
            throw MyExceptionUtil.mxe("設定的時間不能為0或者小於0!!");
        }
    }
 
    /**
     * 判斷key是否存在
     * @param key
     * @return true 存在  false  不存在
     */
    public Boolean existsKey(String key){
        return redisTemplate.hasKey(key);
    }
 
    。。。。省略程式碼,具體程式碼可前往github檢視
 
}
複製程式碼

相關文章