6. SpringBoot整合Redis

6. SpringBoot整合Redis

Redis是NoSQL類型的數據庫,我們也常稱爲內存型數據庫類型.在SpringBoot中使用Redis非常簡單.

6.1 添加包依賴

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

添加依賴後其實就直接可以使用redis了(SpringBoot提供了默認配置,也可以自己配置)

6.2 配置

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=1000
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=5
spring.redis.lettuce.pool.max-wait=1000

6.3 編寫Controller測試

@Controller
@RequestMapping("/redis")
public class RedisController {
    
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/set")
    @ResponseBody
    public String redisStringSet(String key, String value) {
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set(key, value);
        return "OK";
    }
}

當執行了上面的代碼後,使用Redis客戶端可查看到redis中多出了一個記錄.但是該記錄是編碼後的,不方面我們查看.這就需要我們進行相應的配置了.讓redis直接以字符串的方式顯示我們插入的內容.

@Configuration
public class RedisTemplateConfig {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 自定義的string序列化器和fastjson序列化器
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // jackson 序列化器
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // fastjson 序列化器
//        GenericFastJsonRedisSerializer jsonRedisSerializer = new GenericFastJsonRedisSerializer();
        // kv 序列化
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        // hash 序列化
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

RedisTemplate是我們操作數據庫的類,Spring也提供了StringRedisTemplate和HashKeyRedisTemplate分別用於處理內容序列化顯示的問題。添加完上面的配置代碼後,重新執行redis插入,就可以看到redis顯示了真實的字符串內容。

Redis提供7種基本數據類型,可通過RedisTemplate提供的方法執行。

/獲取地理位置操作接口
redisTemplate.opsForGeo();
/獲取散列操作接口
redisTemplate.opsForHash();
//獲取基數操作接口
redisTemplate.opsForHyperLogLog();
/獲取列表操作接口
redisTemplate.opsForlist();
/獲取集合操作接口
redisTemplate.opsForset();
//獲取字符串操作接口
redisTemplate.opsForvalue();
//獲取有序集合操作接口
redisTemplate.psForzset();

一個Redis連接中只執行一個redis命令。Redis也提供了方法讓我們可以在一個連接中執行多個命令。就是SessionCallback和RedisCallback接口。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RllYlt4s-1578452712845)(C:\Users\mxlei\AppData\Roaming\Typora\typora-user-images\image-20200107151716183.png)]

Redis的各種數據類型的使用這裏不做介紹。

6.4 Redis高級用法

6.4.1 redis事務

 @RequestMapping("/transaction")
    @ResponseBody
    public List redisTransaction() {
        //key1設置爲字符串類型
        redisTemplate.opsForValue().set("key1", "value1");
        //Redis事務
        List list = (List) redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                //設置要監控的KEY
                operations.watch("key1");
                //開啓事務,在exec命令執行之前,全部都知識加入隊列
                operations.multi();
                operations.opsForValue().set("key2", "value2");
//                operations.opsForValue().increment("key1",1); //①
                //取得的值爲null,因爲redis只是把命令加入隊列
                Object value2 = operations.opsForValue().get("key2");
                System.out.println("取得的值爲null,因爲redis只是把命令加入隊列 value2 = " + value2);
                operations.opsForValue().set("key3", "value3");
                Object value3 = operations.opsForValue().get("key3");
                System.out.println("取得的值爲null,因爲redis只是把命令加入隊列 value3 = " + value3);
                //執行exec命令,將判斷key1是否在監控後被修改過,如果是則不執行事務,否則就執行事務
                return operations.exec();  //②
            }
        });
        System.out.println(list);
        return list;
    }

  爲了揭示 Redis事務的特性,我們對這段代碼做以下兩種測試先在 Redis客戶端清空key2和key3兩個鍵的數據,然後在②處設置斷點,在調試的環境下讓請求達到斷點,此時在 Redis上修改keyl的值,然後再跳過斷點,在請求完成後在 Redis上查詢key2和key3值,可以發現key2、key3返回的值都爲空(ni),因爲程序中先使得 Redis的 watch命令監控了keyl的值,而後的 multi讓之後的命令進入隊列,而在exec方法運行前我們修改了keyl,根據 Redis事務的規則,它在exec方法後會探測keyl是否被修改過,如果沒有則會執行事務,否則就取消事務,所以key2和key3沒有被保存到Redis服務器中迷續把key2和key3兩個值清空,把①處的註釋取消,讓代碼可以運行,因爲keyl是一個字符串,所以這裏的代碼是對字符串加一,這顯然是不能運算的。同樣地,我們運行這段代碼後,可以看到服務器拋出了異常,然後我們去 Redis服務器查詢key2和key3,可以看到它們已經有了值。注意,這就是 Redis事務和數據庫事務的不一樣,對於 Redis事務是先讓命令進入隊列,所以一開始它並沒有檢測這個加一命令是否能夠成功,只有在exec命令執行的時候,才能發現錯誤,對於出錯的命令 Redis只是報出錯誤,而錯誤後面的命令依舊被執行,所以key2和key3都存在數據,這就是 Redis事務的特點,也是使用Reds事務需要特別注意的地方。爲了克服這個問題,一般我們要在執行 Redis事務前,嚴格地檢查數據,以避免這樣的情況發生。

6.4.2 Redis流水線

  在默認的情況下, Redis客戶端是一條條命令發送給 Redis服務器的,這樣顯然性能不高。在關係數據庫中我們可以使用批量,也就是隻有需要執行SQL時,才一次性地發送所有的SQL去執行,這樣性能就提高了許多。對於 Redis也是可以的,這便是流水線( pipline)技術,在很多情況下並不是 Redis性能不佳,而是網絡傳輸的速度造成瓶頸,使用流水線後就可以大幅度地在需要執行很多命令時提升 Redis的性能。

  下面使用Redis流水線技術測試10萬次讀寫功能

  @RequestMapping("/pipeline")
    @ResponseBody
    public Long pipeline(){
        long start = System.currentTimeMillis();
        List list = (List)redisTemplate.executePipelined(new SessionCallback() {
            @Override
            public Object execute(RedisOperations redisOperations) throws DataAccessException {
                for(int i=0;i<=100000;i++){
                    redisOperations.opsForValue().set("pipeline_"+i,"value_"+i);
                    String value = (String) redisOperations.opsForValue().get("pipeline_"+i);
                    if(i == 100000){
                        System.out.println("命令只是進入隊列,所有值爲空 = "+value);
                    }
                }
                return null;
            }
        });
        long end = System.currentTimeMillis();
        long cost = end - start;
        System.out.println("耗時:"+cost+" 毫秒");
        return cost;
    }

  爲了測試性能,這裏記錄了開始執行時間和結束執行時間,並且打出了耗時。在我的測試中,這10萬次讀寫基本在300600ms,大約平均值在400500ms,也就是不到s就能執行10萬次讀和寫命令,這個速度還是十分快的。在使用非流水線的情況下,我的測試大約每秒只能執行2萬~3萬條命令,可見使用流水線後可以提升大約10倍的速度,它十分適合大數據量的執行。

6.4.3 Redis發佈訂閱

  發佈訂閱是消息的一種常用模式。例如,在企業分配任務之後,可以通過郵件、短信或者微信通知到相關的責任人,這就是一種典型的發佈訂閱模式。首先是 Redis提供一個渠道,讓消息能夠發送到這個渠道上,而多個系統可以監聽這個渠道,如短信、微信和郵件系統都可以監聽這個渠道,當一條消息發送到渠道,渠道就會通知它的監聽者,這樣短信、微信和郵件系統就能夠得到這個渠道給它們的消息了,這些監聽者會根據自己的需要去處理這個消息,於是我們就可以得到各種各樣的通知了。

UTOOLS1578447716093.png

爲了接收 Redis渠道發送過來的消息,我們先定義一個消息監聽器( Messagelistener)

@Configuration
public class RedisMessageListenerConfig {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisConnectionFactory connectionFactory;
    @Autowired
    private RedisMessageListener redisMessageListener;
    private ThreadPoolTaskExecutor taskExecutor;

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(){
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setTaskExecutor(threadPoolTaskExecutor());
        Topic topic = new ChannelTopic("topic1");
        container.addMessageListener(redisMessageListener, topic);
        return container;
    }

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
        if(taskExecutor != null){
            return taskExecutor;
        }
        taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5);
        return taskExecutor;
    }
}

這裏 RedisTemplate和 RedisConnection Factory對象都是 Spring Boot自動創建的,所以這裏只是把它們注入進來,只需要使用@ Autowired註解即可。然後定義了一個任務池,並設置了任務池大小爲20,這樣它將可以運行線程,並進行阻塞,等待 Redis消息的傳入。接着再定義了一個 Redis消息監聽的容器 RedisMessageListenerContainer,並且往容器設置了 Redis連接工廠和指定運行消息的線程池,定義了接收“ topicI”渠道的消息,這樣系統就可以監聽 Redis關於“ topic1”渠道的消息了。啓用 Spring Boot項目後,在 Redis的客戶端輸入命令:

publish topic msg

在 Spring中,我們也可以使用 Redis Template來發送消息,例如:

redisTemplate.convertAndSend(channel, message);

其中, channel代表渠道, message代表消息,這樣就能夠得到 Redis發送過來的消息了。

6.4.4 使用Lua腳本

Redis中有很多的命令,但是嚴格來說 Redis提供的計算能力還是比較有限的。爲了增強 Redis的計算能力, Redis在2.6版本後提供了Lua腳本的支持,而且執行Lua腳本在 Redis中還具備原子性,所以在需要保證數據一致性的高併發環境中,我們也可以使用 Redis的Lua語言來保證數據的致性,且Lua腳本具備更加強大的運算功能,在高併發需要保證數據一致性時,Lua腳本方案比使用Redis自身提供的事務要更好一些。

在 Redis中有兩種運行Lua的方法,一種是直接發送Lua到 Redis服務器去執行,另一種是先把Lua發送給 Redis, Redis會對Lua腳本進行緩存,然後返回一個SHA1的32位編碼回來,之後只需要發送SHA1和相關參數給 Redis便可以執行了。這裏需要解釋的是爲什麼會存在通過32位編碼執行的方法。如果Lua腳本很長,那麼就需要通過網絡傳遞腳本給 Redis去執行了,而現實的情況是網絡的傳遞速度往往跟不上 Redis的執行速度,所以網絡就會成爲 Redis執行的瓶頸。如果只是傳遞32位編碼和參數,那麼需要傳遞的消息就少了許多,這樣就可以極大地減少網絡傳輸的內容,從而提供系統性能。

爲了支持 Redis的Lua腳本, Spring提供了 Redis Script接口,與此同時也有一個 DefaultRedis Script
實現類。讓我們先來看看 Redis script接口的源碼

public interface RedisScript<T> {
    //獲取腳本的SHA1
    String getSha1();
	//獲取腳本的返回值類型
    @Nullable
    Class<T> getResultType();
	//獲取腳本的字符串內容
    String getScriptAsString();
}

這裏 Spring會將Lua腳本發送到 Redis服務器進行緩存,而此時 Redis服務器會返回一個32位的SHA編碼,這時候通過 geiSha方法就可以得到 Redis返回的這個編碼了; getResult Type方法是獲取Lua腳本返回的Java類型; getScriptAs String是返回腳本的字符串,以便我們觀看腳本。下面我們採用 Redis Script接口執行一個十分簡單的Lua腳本,這個腳本只是簡單地返回一個字符串

UTOOLS1578449579189.png

這裏的代碼,首先Lua只是定義了一個簡單的字符串,然後就返回了,而返回類型則定義爲字符串。這裏必須定義返回類型,否則對於 Spring不會把腳本執行的結果返回。接着獲取了由RedisTemplate自動創建的字符串序列化器,而後使用 Redis Template的 execute方法執行了腳本。在Redis Template中, execute方法執行腳本的方法有兩種,其定義如下:

UTOOLS1578449643469.png

6.5 使用Spring緩存註解操作Redis

發佈了123 篇原創文章 · 獲贊 201 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章