1. 程式人生 > 實用技巧 >spring-boot-learning 快取之redis

spring-boot-learning 快取之redis

什麼是BSD協議:

BSD是"Berkeley Software Distribution"的縮寫,意思是"伯克利軟體發行版"。

BSD開源協議是一個給於使用者很大自由的協議。可以自由的使用,修改原始碼,也可以將修改後的程式碼作為開源或者專有軟體再發布

Redis 是基於記憶體的,所以執行速度很快,大約是關係資料庫幾倍到幾十倍的速度

為什麼需要redis

在現實中,查詢資料要遠遠多於更新資料, 一般一個正常的網站查詢和更新的比例大約是1 : 9到3:7 , 在查詢比例
較大的網站使用Redis 可以數倍地提升網站的效能。 例如, 當一個會員登入網站,我們就把其常用資料從資料庫一次性查詢出來存放在Redis 中,那麼之後大部分的查詢
只需要基於Redis 完成便可以了,這樣將很大程度上提升網站的效能。 除此之外, Redis 還提供了簡單的事務機制,通過事務機制可以有效保證在高井發的場景下資料的一致性。 Redis 在2.
6 版本之後開始增加Lua 語言的支援,這樣Redis 的運算能力就大大提高了,而且在Redis 中
Lua 語言的執行是原子性的 也就是在Redis 執行Lua 時, 不會被其他命令所打斷,這樣就能夠保證在高併發場景下的一致性

springboot中的redis

Sprin g Boot 也會為其提供stat er ,然後允許我們通過配置檔案applic atio n .properti es 進行配置,這樣就能夠以最快的速度配置並且使用Redis

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
      
        <!--        因為預設情況下springboot會依賴lettuce的redist客戶端驅動-->
        <!--        而我們實際過程中,會使用Jedis驅動,所以上面去除了依賴-->
        <!-- https://
mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>

在預設的情況下, spring-boot-starter- data-redis( 版本2.x ) 會依賴Lettuce 的Red is 客戶端驅動,

而在一般的專案中,我們會使用Jedis ,

所以在程式碼中使用了<exclusions>元素將其依賴排除了,與此同時引入了Jedis 的依賴

springboot-redis設計內容:

java中與Redis連線的驅動有很多種,用的比較多的是jdeis

spring提供一個RedisConnectionFactory介面,他可以生產一個RedisConnection介面物件

介面物件是對Redis底層介面的封裝,:

jedis驅動,那麼Spring就會根據RedisConnection介面實現類----JedisConnection去封裝原有的Jedis物件。

Spring 中是通過RedisConnection 介面操作Red is 的,而RedisConnection
則對原生的Jedis 進行封裝。要獲取RedisConnection 介面物件, 是通過RedisConnectionFactory 介面
去生成的,所以第一步要配置的便是這個工廠了,而配置這個工廠主要是配置Redis 的連線池,對於
連線池可以限定其最大連線數、超時時間等屬性

雖然我們可以使用自己的配置,去RedisConnectionFactory工廠獲取,然後使用完之後還要自己關閉,

Spring為了進一步簡化開發,提供了RedisTemplate

RedisTemplate

RedisTemplate
是一個強大的類,首先它會自動從Red i sConnectionFactory 工廠中獲取連線,然後執行對應的Red is
命令,在最後還會關閉Redis 的連線。

關於獲取關閉都已經在RedisTemplate中被封裝了,不需要我去關注redis連線和閉合的問題

前提我們需要知道:

首先需要清楚的是, Redis是一種基於字串儲存的NoSQL , 而Java 是基於對

象的語言,物件是無法儲存到Redis 中的,不過Java提供了序列化機制,只要類實現了java .io.Serializable 介面, 就代表類的物件能夠進行序列化,通過將類物件進行序列化就能夠得到二進位制字串,這樣Redis 就可以將這些類物件以字串進行儲存。

同時;Java 也可以將那些二進位制字串通過反序列化轉為物件

Spring提供了序列號器的機制,並且實現了幾個關於redis序列化器

Spring 提供了RedisSeri alizer 介面,它有兩個方法。這兩個方法:
一個是serialize , 它能把那些可以序列化的物件轉換為二進位制字串; 一個是deserialize,它能夠通過反序列化把二進位制字串轉換為Java 物件 JdkSerializationRedisSerializer 是RedisTemplate 預設的序列化器

注意:JacksonJsonRedisSerializer已經不再使用

序列化的原理如圖:

RedisTemplate中序列化器屬性

它會預設使用JdkSerial izationRedisSerializer 對物件進行序列化和反序列化,這樣子我們的key字串也會變成二進位制,

難以檢視資料

我們希望可以將Redis的Key以普通字串儲存,

提供了一個StringRedisTemplate類,這個類繼承RedisTemplate ,只是提供了字串的操作而己,

Spring對redis資料型別操作的封裝

Redis 能夠支援7 種類型的資料結構,這7 種類型是字串、雜湊、列表(連結串列) 、集合、有序

集合、基數和地理位置。為此Spring 針對每一種資料結構的操作都提供了對應的操作介面

上面的介面都可以使用ReidsTemplate得到

以通過各類的操作介面來操作不同的資料型別,

如果我們需要對某一個鍵值對做連續的操作,spring提供了對應的BoundXXXOperations 介面

原理就是,相對操作物件進行繫結,之後救可以進行多次操作了

SessionCallack 和RedisCallback介面

它們的作用是讓RedisTemplate 進行回撥,通過它們可以在同一條連線下執行多個Redis 命令

SessionCa!lback
提供了良好的封裝,對於開發者比較友好,因此在實際的開發中應該優先選擇使用它

springboot配置和使用Redis

spring配置redis

#配置redis連線池屬性
#預設是8
spring.redis.jedis.pool.max-idle= 10 
#預設是8
spring.redis.jedis.pool.max-active= 10
#預設是0
spring.redis.jedis.pool.min-idle= 5
#是-1ms就是沒限制
spring.redis.jedis.pool.max-wait= 2000
#redis伺服器屬性
spring.redis.port= 6379
spring.redis.host= 127.0.0.1
#Redis連線超時時間,單位為毫秒
spring.redis.timeout= 1000

配置了連線池和伺服器的屬性,用以連線Red is 伺服器

Spring Boot 的自動裝配機制就會讀取這些配置來生成有關Redis 的操作物件, 這裡它會自動生成

RedisConnectionFactory 、RedisTemplate 、StringRedisTemplate 等常用的Red is 物件。

RedisTemplate 會預設使用JdkSerial izati onRedisSerial izer 進行序列化鍵值,這樣便能夠儲存到Red is 伺服器中。

如果這樣, Redis伺服器存入的便是一個經過序列化後的特殊字串,我們很難去跟蹤。

Redis 只是使用字串,那麼使用其自動生成的StringRedisTemplate 即可,但是這樣就只能支援字串
了,並不能支援Java 物件的儲存。

所以!!!!

可以通過設定RedisTemplate 的序列化器來處理。

修改RedisTemplate序列化器

@SpringBootApplication
public class RedisApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class, args);
    }
    
    @Autowired
    RedisTemplate redisTemplate;

    //定義自定義後初始化方法
    @PostConstruct
    public void  init(){

    }
    //因為預設的redis序列化器是jdkSerializationRedisSerializer,java物件會被初始化為奇怪的字串
    //修改預設的序列化器為StringSerializer
    private void initRedisTemplate(){
        RedisSerializer stringserializer = redisTemplate.getStringSerializer();
        redisTemplate.setKeySerializer(stringserializer);
        redisTemplate.setHashKeySerializer(stringserializer);
    }

}

通過@Autowired 注入由Spring Boot 根據配置生成的RedisTemplate 物件,然後利用Spring  Bean 生命週期的
特性使用註解@PostConstruct 自定義後初始化方法。 在這個方法裡,把RedisTemplate中的鍵序列化器修改為StringRedisSerializer 。RedisTemplate會預設定義
一個StringRedisSerializer 對像。我們這裡直接獲取救可以了 把RedisTemplate 關於鍵和其雜湊資料型別的filed 都修改為了使用StringRedisSerializer 進行序列化,
這樣我們在Red is 伺服器上得到的鍵和雜湊的field 就都以宇符串儲存了。

操作Reids字串和雜湊資料型別

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

    @Autowired
    private StringRedisTemplate stringRedisTemplate = null;

    @RequestMapping("/done")
    public Map<String,Object> doneRedis(){
        redisTemplate.opsForValue().set("key1","value1");

        redisTemplate.opsForValue().set("int_key","1");
        //上面的兩個redisTemplate使用是jdk的序列號器,所以redis儲存時不是整數,是一個亂的字串

        stringRedisTemplate.opsForValue().set("int","1");
        //使用運算,增加1
        stringRedisTemplate.opsForValue().increment("int");
        //獲取底層的Jedis連線
        Jedis jedis = (Jedis)stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection();
        //這個減一的操作,redisTemplate是不支援的,所以我們要獲取底層Jedis連線去實現功能
        jedis.decr("int");

        //建立一個雜湊資料型別
        Map<String,String> hashm = new HashMap<>();
        hashm.put("f1","v1");
        hashm.put("f2","v2");
        //儲存雜湊資料型別,並且指定key名字為hashm
        stringRedisTemplate.opsForHash().putAll("hashm",hashm);
        //新增一個欄位
        stringRedisTemplate.opsForHash().put("hashm","f3","v3");
        //繫結雜湊操作的key,可以連續對同一個雜湊資料型別進行操作
        BoundHashOperations hashops = stringRedisTemplate.boundHashOps("hashm");
        //刪除一個欄位
        hashops.delete("f2","v2");
        //新增一個欄位
        hashops.put("f4","v4");

        Map<String,Object> map = new HashMap<>();
        map.put("done",true);
        return map;
    }

}

@Autowired 注入了Spring Boot 為我們自動初始化Red i sTemplate 和StringRedisTemplate物件。
首先是存入了一個“ keyl ”的資料,然後是“ int_key ”。但是請注意這個“ int_ key ”存入到Redis 伺服器中,
因為採用了JDK 序列化器,所以在Red is 伺服器中它不是整數, 而是一個被JDK 序列化器序列化後的二進位制字串,
是沒有辦法使用Redis 命令進行運算的 使用Strin gRedisTemplate 物件儲存了一個鍵為“
int ”的整數, 這樣就能夠運算了。因為RedisTemplate 並
不能支援底層所有的Redis 命令,所以這裡先獲取了原始的Redis 連線的Jedis 物件,用它來做減一運算。 操作雜湊資料型別, 在插入多個雜湊的field 時可以採用Map , 然後為了方便對同一個資料操作,這裡程式碼還獲取了BoundHashOperations
物件進行操作, 這樣對同一個資料操作就方便

結果:

操作列表(連結串列)

@RequestMapping("/list")
@RestController
public class RedisListController {
    @Autowired
    private RedisTemplate redisTemplate = null;

    @Autowired
    private StringRedisTemplate stringRedisTemplate = null;

    @RequestMapping("/done")
    public Map<String ,Object> testList(){
        //從左到右的順序是, v4  v3 v2 v1
        stringRedisTemplate.opsForList().leftPushAll("l1","v1","v2","v3","v4");
        //從左到右的順序是 v1 v2 v3 v4
        stringRedisTemplate.opsForList().rightPushAll("l2","v1","v2","v3","v4");

        //繫結List操作物件,
        BoundListOperations listOps = stringRedisTemplate.boundListOps("l2");
        //v1 v2 v3 v4 右邊剔除一個所以v4是沒有了
        Object re1 = listOps.rightPop();
        //檢視位置為2的元素是什麼,redis也是從0開始的。
        Object re2 = listOps.index(2);
        //v1 v2 v3,左邊加入vv ===》 vv  v1 v2 v3
        listOps.leftPush("vv");

        //獲取列表的長度
        Long size = listOps.size();
        //獲取列表指定範圍的長度
        List elements = listOps.range(0,size-2);
        Map<String,Object> map = new HashMap<>();
        map.put("success",true);
        map.put("size",size);
        map.put("listsize",elements);
        map.put("re2",re2);
        map.put("re1rightpop",re1);

        return map;

    }
}

操作是基於StringRedisTemplate 的,所以儲存到Red is 伺服器的都是字串型別

注意:

列表元素的順序問題,是從左到右還是從右到左

下標問題,在Red is 中是以0 開始的,這與Java 中的陣列類似。

redis:

操作集合

集合:

對於集合, 在Redis 中是不允許成員重複的,它在資料結構上是一個散列表的結
構,所以對於它而言是無序的,對於兩個或者以上的集合, Red is 還提供了交集、並集和差集的運算。

@RestController
@RequestMapping("/set")
public class RedisSetController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate = null;

    @RequestMapping("/done")
    public Map<String ,Object> testSet(){
        //加入連個集合型別的keyvalue
        stringRedisTemplate.opsForSet().add("s1","v1","v1","v2","v3","v4","v5");
        stringRedisTemplate.opsForSet().add("s2","v1","v3","v5","v8");
        //繫結操作集合
        BoundSetOperations setOps = stringRedisTemplate.boundSetOps("s1");
        //加入兩個元素
        setOps.add("v6","v7");
        //刪除兩個元素
        setOps.remove("v1","v2");
        //獲取所有的集合元素
        Set set1 = setOps.members();
        //獲取集合長度
        Long size = setOps.size();
        //交集
        Set inter = setOps.intersect("s2");
        //交集並新建一個集合去儲存
        setOps.intersectAndStore("s2","s1s2iner");
        
        Set diff = setOps.diff("s2");
        setOps.diffAndStore("s2","diffset");

        Set union = setOps.union("s2");

        setOps.unionAndStore("s2","unionSet");

        Map<String ,Object> map = new HashMap<>();
        map.put("set1",set1);
        map.put("inter",inter);
        map.put("diff",diff);
        map.put("union",union);
        return map;
    }
}

操作有序集合

在一些網站中,經常會有排名,如最熱門的商品或者最大的購買買家,都是常常見到的場景。對於這類排名,重新整理往往需要
及時,也涉及較大的統計,如果使用資料庫會太慢。為了支援集合的排序, Red is 還提供了有序集合( zset )。
有序集合與集合的差異並不大,它也是一種散列表儲存的方式,同時它的有序性只是靠它在資料結構中增加一個屬性--score
(分數)得以支援。

所以Spring提供了TypedTupl e 介面,它定義了兩個方法,並且S pring 還提供了其預設的實現類DefaultTypedTuple

value 是儲存有序集合的值, score 則是儲存分數, Red is 是使用分數來完成集合的排序的,這樣如果把買家作為

一個有序集合,而買家花的錢作為分數,就可以使用Red is 進行快速排序了

@RequestMapping("/zset")
@RestController
public class RedisZSetController {
    @Autowired
    private StringRedisTemplate stringRedisTemplate = null;

    @RequestMapping("/done")
    public Map<String ,Object> testZSet(){
        Set<TypedTuple<String >> typedTupleSet = new HashSet<>();
        for(int i = 1;i<=9;i++){
            double score = i*0.1;
            TypedTuple<String> typedTuple = new DefaultTypedTuple<>("value"+i,score);
            typedTupleSet.add(typedTuple);
        }

        stringRedisTemplate.opsForZSet().add("zset1",typedTupleSet);
        BoundZSetOperations zsops = stringRedisTemplate.boundZSetOps("zset1");

        zsops.add("value10",0.22);


        Set<String> setrange = zsops.range(1,6);
        //按照分數階段去獲取有序集合
        Set<String> setscore = zsops.rangeByScore(0.2,0.6);

        //定義範圍,
        RedisZSetCommands.Range range = new RedisZSetCommands.Range();
        range.gt("value3");//大於value3
//        range.gte("value3");//大於等於value3
        range.lt("value8");//小於等於


        //按照字串排序的,有序集合裡面的value的值,再上面那個範圍內進行排序
        Set<String> setLex = zsops.rangeByLex(range);
        System.out.println(setLex);//[value3, value4, value5, value6, value7]
        //刪除元素
        zsops.remove("value9","value2");


        //獲取分數,
        Double score = zsops.score("value8");
        System.out.println(score);//0.8

        //按照位置排序對指定區間取值和分數
        Set<TypedTuple<String>> rangeSet = zsops.rangeWithScores(1,6);
       rangeSet.forEach(v -> System.out.println(v.getValue()+"=="+v.getScore()));
        System.out.println("###############");
        //按照分數排序對指定區間取值和分數
        Set<TypedTuple<String>> scoreSet = zsops.rangeByScoreWithScores(0.1,0.6);
        scoreSet.forEach(v -> System.out.println(v.getValue()+"==="+v.getScore()));
        System.out.println("###############");



        //蟲小到大排序
        Set<String> reverSet = zsops.reverseRange(2,8);
        System.out.println(reverSet);//[value6, value5, value4, value3, value10, value1]


        Map<String,Object> map = new HashMap<>();
        map.put("suceess",true);
        return map;

    }
}

使用了TypedTup l e 儲存有序集合的元素,在預設的情況下,有序集合是從小到大地排序

按下標、分數和值進行排序獲取有序集合的元素

或者連同分數一起返回,有時候還可以進行從大到小的排序

在使用值排序時,我們可以使用Spring 為我們建立的Range 類,它可以定義
值的範圍,還有大於、等於、大於等於、小於等於等範圍定義,方便我們篩選對應的元素。

Redis的其他用法:

Red is 除了操作那些資料型別的功能外, j巫能支援事務、流水線、釋出訂閱和Lua i吾等功能, 這
些也是Red is 常用的功能。在高併發的場景中, 往往我們需要保證資料的一致性, 這時考慮使用Redis
事務或者利用Redis 執行Lua 的原子性來達到資料一致性的目的,

Redis的事務

Redis 是支援一定事務能力的NoSQL ,

通常的命令組合是watch...multi .. . exec,也就是要在一個Redis 連線中執行多個命令,這時我們可以考慮使用S巳ssionCallback 接口來達到這個目的

watch 命令是可以監控Redis 的一些鍵:
multi 命令是開始事務,

注意:開始事務後, 該客戶端的命令不會馬上被執行,而是存放在一個佇列裡,所以這時候去查詢什麼的,返回都是null

exec 命令的意義在於執行事務,只是它在佇列命令執行前會判斷被watch 監控的Redis 的鍵的資料是否發生過變化
( 即使賦予與之前相同的值也會被認為是變化過〉 如果它認為發生了變化,那麼Redis 就會取消事務, 否則就會執行事務, Redis 在執行事務時,要麼全部執行, 要麼全部不執行,而且不會被其他客戶端打斷

流程圖:

@RequestMapping("/transaction")
@RestController
public class RedisTransactionController {
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/done")
    public Map<String ,Object> testTransaction(){
        redisTemplate.opsForValue().set("k1","v1");

        List list = (List)redisTemplate.execute((RedisOperations operation) ->{
            operation.watch("k1"); //設定要監控的key
            operation.multi();//開啟事務,exec執行前,全部進去佇列裡面
            operation.opsForValue().set("k2","v2");
            Object value2 = operation.opsForValue().get("k2");//獲取k2的值,肯定為null,事務沒執行
            System.out.println(value2);

            operation.opsForValue().set("k3","v3");//獲取k2的值,肯定為null,事務沒執行
            Object value3 = operation.opsForValue().get("k3");
            System.out.println(value3);
            //執行佇列中的命令,如果在監控期間k1發生變化,事務不執行,
            return operation.exec();
        }) ;
        //上面事務執行則有值,不執行為null
        System.out.println(redisTemplate.opsForValue().get("k2"));

        System.out.println(list);
        Map<String,Object> map = new HashMap<>();
        map.put("suceess",true);
        return map;
    }
}

自己設定除錯進行檢視就行

這時候去修改一下key1的值:

這時候讓程式執行到尾部:

不修改的話:

redis流水線:

預設的情況下, Red is 客戶端是一條條命令傳送給Redis 伺服器的,這樣顯然效能不高。

關係資料庫中我們可以使用批量,一次性發送所有的SQL去執行。這樣效能提高了不少

對於Redis 也是可以的,這便是流水線( pipline )技術,在很多情況下並不是Redis 效能不佳,而是

網路傳輸的速度造成瓶頸,使用流水線後就可以大幅度地在需要執行很多命令時提升Redis 的效能。

可以測試一下效能,10w次讀寫功能

@RestController
@RequestMapping("/pipline")
public class RedisPiplineController {
    @Autowired
    private RedisTemplate redisTemplate;

    @RequestMapping("/done")
    public Map<String ,Object> testPipline(){

        Long startT = System.currentTimeMillis();//系統現在的時間
        List list = (List)redisTemplate.executePipelined((RedisOperations operations) ->{
            for (int i=1;i<=100000;i++){
                operations.opsForValue().set("pipline_"+i,"value_"+i);
                String v =(String) operations.opsForValue().get("pipline_"+i);
                if (i == 10000){
                    System.out.println("進入佇列,沒有執行");
                }

            }
            return null;
        });

        Long endT = System.currentTimeMillis();
        System.out.println("用時:"+(endT- startT)+"ms");

        Map<String,Object> map = new HashMap<>();
        map.put("suceess",true);
        return map;


    }

}

return null的時候就已經執行了

注意:

在執行如此多的命令時,需要考慮的另外一個問題是記憶體空間的消耗,因為對於程式而言,它最終會返回一個List 物件,
如果過多的命令執行返回的結果都儲存到這個List 中,顯然會造成記憶體消耗過大,尤其在那些高併發的網站中就很容易
造成JVM 記憶體溢位的異常, 這個時候應該考慮使用迭代的方法執行Redis 命令

與事務一樣,使用流水線的過程中, 所有的命令也只是進入佇列而沒有執行,所以執行的命
令返回值也為空

Redis的釋出與訂閱:

釋出訂閱是訊息的一種常用模式

場景:

在企業分配任務之後,可以通過郵件、簡訊或者微信
通知到相關的責任人,這就是一種典型的釋出訂閱模式。

首先是Redis 提供一個渠道,讓訊息能夠傳送到這個渠道上,而多個系統可以監聽這個渠道, 如簡訊、微信和郵件系統
都可以監昕這個渠道,當一條訊息傳送到渠道,渠道就會通知它的監昕者,這樣簡訊、微信和郵件系統就能夠得到這個渠道
給它們的訊息了,這些監聽者會根據自己的需要去處理這個訊息,於是我們就可以得到各種各樣的通知了。

11定義一個實現訊息監聽的類;

@Component
public class RedisListener implements MessageListener {

    @Override
    public void onMessage(Message message, byte[] bytes) {
        String body = new String(message.getBody());//獲取訊息體
        String topic = new String(bytes);//獲取訊息渠道
        System.out.println("來自主題為"+topic+"的訊息:  "+body);
    }
}

onMessage 方法是得到訊息後的處理方法, 其中message 引數代表Redis 傳送過來的訊息,

patten是渠道名稱,。且也onMessage方法裡列印了它們的內容。這裡因為標註了@Component 註解,所以
在S pring Boot 掃描後, 會把它自動裝配到IoC 容器中。

222建立任務池,開啟執行緒等待處理Redis訊息:

//設定任務池
@Configuration
public class ThreadPoolTaskSchedulerConfig {

    private ThreadPoolTaskScheduler threadPoolTaskScheduler = null;

    @Bean
    public ThreadPoolTaskScheduler initThreadPoolTaskScheduler(){
        if (threadPoolTaskScheduler != null){
            return threadPoolTaskScheduler;
        }
        threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
        threadPoolTaskScheduler.setPoolSize(20);
        return threadPoolTaskScheduler;
    }
}

333定義redis的監聽容器:

//設定監聽容器
@Configuration
public class RedisMessageListenerContainerConfig {
    @Autowired
    private RedisConnectionFactory connectionFactory;

    @Qualifier("initThreadPoolTaskScheduler")
    @Autowired
    private ThreadPoolTaskScheduler threadPoolTaskScheduler;

    @Autowired
    private MessageListener messageListener;

    @Bean
    public RedisMessageListenerContainer initRedisContainer(){
        RedisMessageListenerContainer listenerContainer = new RedisMessageListenerContainer();
        listenerContainer.setConnectionFactory(connectionFactory);
        listenerContainer.setTaskExecutor(threadPoolTaskScheduler);

        Topic topic = new ChannelTopic("topic1");
        listenerContainer.addMessageListener(messageListener,topic);
        return listenerContainer;
    }
}

444建立測試類,傳送訊息:

@SpringBootTest
class RedisApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void redisSendMessage() {
        int i = 0;
        while (true){
            redisTemplate.convertAndSend("topic1","你好呀"+i);
            i++;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}

5555使用Lua指令碼

Redis 提供的計算能力還是比較有限的。為了增強Redis的計算能力, Red is 在2.6 版本後提供了Lua 指令碼的支援,

執行Lua 指令碼在Redis 中還具備原子性,所以在需要保證資料一致性的高併發環境中, 我們也可以

使用Redis 的Lua 語言來保證資料的一致性

Lua 指令碼具備更加強大的運算功能, 在高併發需要保證資料一致性時lua指令碼的方案比redis自身的事務方案要好一些

Redis兩種執行Lua的方法

一種是直接傳送Lua 到Redis 伺服器去執行
一種是先把Lua 傳送給Redis, Redis 會對Lua 指令碼進行快取,然後返回一個SHA l 的32 位編碼回來,之後只
需要傳送SHAl 和相關引數給Redis便可以執行了

32位編碼:

如果Lua 指令碼很長, 那麼就需要通過網路傳遞指令碼給Redis 去執行了,而現實的情況是網路的傳遞速度往往跟不上Red is
的執行速度,所以網路就會成為Red is 執行的瓶頸。如果只是傳遞3 2位編碼和引數,那麼需要傳遞的訊息就少了許多,
這樣就可以極大地減少網路傳輸的內容,從而提高系統的效能。

為了支援Redis 的Lua 指令碼, Spring 提供了RedisScript 介面,與此同時也有一個DefaultRedisScript
實現類

原始碼如下:

Spring 會將Lua指令碼傳送到Redis 伺服器進行快取,而此時Redis 伺服器會返回一個3 2 位的SHAl 編碼,

通過getShal 方法就可以得到Redis 返回的這個編碼了; 
getResultType 方法是獲取Lua 指令碼返回的Java 型別: 
getScriptAsString 是返回指令碼的字串

@RestController
@RequestMapping("/lua")
public class RedisLuaController {
    @Autowired
    RedisTemplate redisTemplate;

    @RequestMapping("done")
    public Map<String,Object> testLua(){
      DefaultRedisScript
<String> rs = new DefaultRedisScript<>();
      //設定指令碼 rs.setScriptText(
"return 'done well'"); rs.setResultType(String.class);//設定返回值型別,不設定,不返回 RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer(); //執行Lua指令碼
String str
= (String)redisTemplate.execute(rs,stringRedisSerializer,stringRedisSerializer,null); Map<String,Object> map = new HashMap<>(); map.put("suceess",true); return map; } }

上面程式碼中Lua 只是定義了一個簡單的字串,然後就返回了,而返回型別則定義為宇

符串。這裡必須定義返回型別, 否則對於S pring 不會把指令碼執行的結果返回。

接著獲取了由RedisTemplate 自動建立的字串序列化器,而後使用RedisTemplate 的execute 方法執行了指令碼

RedisTemplate中,execute方法執行指令碼的方法以下兩種:

script 就是我們定義的RedisScript 介面物件,
keys代表Redis 的鍵,
args 是這段指令碼的引數


兩個方法最大區別是一個存在序列化器的引數, 另外一個不存在。

於不存在序列化引數的方法, Spring 將採用RedisTemplate 提供的valueSeri al i zer 序列化器對傳遞的鍵
和引數進行序列化。 這裡我們採用了第二個方法排程指令碼, 並且設定為字串序列化 器,其中第一個序列化器是鍵的序列化器, 第二個是引數序列化器,這樣鍵和引數就在字串序列化器下被序列化了。

端點測試

使用Spring快取註解操作Redis

Spring 在使用快取註解前,需要配置快取管理器,快取管理器將提供一些重要的資訊,如緩

存型別、超時時間等。Spring 可以支援多種快取的使用,因此它存在多種快取處理器, 並提供了緩

存處理器的介面CacheManager 和與之相關的類。

redis 主要使用的類RedisCacheManager為主

在Spring Boot 的starter 機制中,允許我們通過配置檔案生成快取管理器,

加粗是比較重要的

配置Redis快取管理器

快取註解+myabtis結合

spring.cache.cache-names=redisCache
spring.cache.type=redis

spring.cache.type 配置的是快取型別,為Redis , SpringBoot 會自動生成RedisCacheManager 物件,

而spring.cache . cache-names 則是配置快取名稱,多個名稱可以使用逗號分隔,以便於快取註解的引用。

了使用快取管理器,需要在S pring Boot 的配置檔案中加入驅動快取的註解@EnableCaching,
這樣就可以驅動Spring 快取機制工作了,

註解+Mybatis

11先配置我們的配置檔案,包括cache,資料來源,mybatis的配置

#註解cache
spring.cache.cache-names=redisCache
spring.cache.type=redis
###資料來源
spring.datasource.url=jdbc:mysql://localhost:3306/springboot
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=1997
###mybatis
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.quan.annotationredis.entity
#redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-wait=2000
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
#
server.port=8012
#######自定義快取器的配置,配置不多的時候,可以採用預設的快取管理器
spring.cache.redis.cache-null-values=true
spring.cache.redis.key-prefix=
spring.cache.redis.use-key-prefix=false
#設定十分鐘超時,不能從redis快取裡面獲取資料,重新整理資料
spring.cache.redis.time-to-live=600000

22建立與資料庫對應表的實體類:

import org.apache.ibatis.type.Alias;

import java.io.Serializable;
//在寫mapper.xml的時候,如果需要指定返回值的時候不用使用全限定類名
//直接使用別名user就可以了。
@Alias("user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private Long id;
    private String userName;
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", note='" + note + '\'' +
                '}';
    }
}

註解@Alias 定義了別名,因為我們在application.properties 檔案中定義了這個包作為別名
的掃描,所以它能夠被MyBatis 機制掃描,並且將“ user”作為這個類的別名載入到MyBatis 的體系

中。這個類還實現了Serial izable 介面,說明它可以進行序列化。

33操作實體類的mybatis持久層介面UserDao

@Repository
public interface UserDao {
    User getUser(Long id);
    int insertUser(User user);
    int updateUser(User user);
    int deleteUser(Long id);

    List<User> findUsers(@Param("userName") String userName,@Param("note") String note);
}

註解@Repository ,它是Spring 用來標識DAO 層的註解,在MyBatis 體系中則是使
用註解@Mapper 來標識,這裡無論使用哪種註解都是允許的,只是我偏愛@Repository 而己,將來我
們可以定義掃描來使得這個介面被掃描為S pring 的Bean 裝配到IoC 容器中

44建立持久層介面對應的mapper對映檔案:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.quan.annotationredis.dao.UserDao">
    <select id="getUser" resultType="user" parameterType="long">
    select id,user_name as userName ,note from user where id = #{id}
  </select>

    <insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
        insert into user (user_name,note) values(#{userName},#{note})
    </insert>

    <update id="updateUser" >
        update user
        <set>
            <if test="userName != null">user_name =#{userName},</if>
            <if test="note != note>"> note = #{note},</if>
        </set>
        where id = #{id}
    </update>

    <select id="findUsers" resultType="user">
        select id ,user_name as userName ,note from user
        <where>
            <if test="userName != null">and user_name =#{userName}</if>
            <if test="note != note>">and  note = #{note},</if>
        </where>
    </select>

    <delete id="deleteUser" parameterType="long">
        delete  from  user where  id = #{id}
    </delete>
</mapper>

注意檔案的位置和配置檔案當中的位置應該一致:

        src/main/resources/mapper/UserDaoMapper.xml

屬性useGeneratedK.eys 設定為位ue ,代表將通過資料庫生成主鍵,而將keyPrope 即設定為POJO 的id 屬性,

My Bati s 就會將資料庫生成的主鍵回填到POJO的id 屬性中。

55建立service層介面:

public interface UserService {
    User getUser(Long id);
    int insertUser(User user);
    User updateUser(Long id ,String userName);
    int deleteUser(Long id);

    List<User> findUsers( String userName, String note);
}

66介面對應的實現類:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserDao userDao = null;

    @Override
    @Transactional
    @Cacheable(value = "redisCache",key = "'redis_user_'+#id")
    public User getUser(Long id) {
        return userDao.getUser(id);
    }

    @Override
    @Transactional
    @CachePut(value = "redisCache",key = "'redis_user_'+#result.id")
    public int insertUser(User user) {
        return userDao.insertUser(user);
    }

    @Override
    @Transactional
    @CachePut(value = "redisCache",condition = "#result != 'null'",key = "'redis_user_'+#id")
    public User updateUser(Long id ,String userName) {
        User user =  this.getUser(id);//自呼叫
        if (user == null){
            return null;
        }
        user.setUserName(userName);//更新快取,
        userDao.updateUser(user);
        return user;
    }

    //移除快取
    @Override
    @Transactional
    @CacheEvict(value = "redisCache",key = "'redis_user_'+#id",beforeInvocation = false)
    public int deleteUser(Long id) {
        return userDao.deleteUser(id);
    }


    @Override
    public List<User> findUsers(String userName, String note) {
        return userDao.findUsers(userName,note);
    }
}

上面加入了快取註解,

@CachePut 表示將方法結果返回存放到快取中。

@Cacheable 表示先從快取中通過定義的鍵查詢,如果可以查詢到資料,則返回,否則執行該方法,返回資料,
        並且將返回結果儲存到快取中。 @CacheEvict 通過定義的鍵移除快取,它有一個Boolean 型別的配置項beforelnvocation ,表示在方法之前
          或者之後移除快取。因為其預設值為也lfalse ,所以預設為方法之後將快取移除

快取中都配置了value =”redisCache”,因為我們在S pring Boot 中配置了對應的快取名稱為“ redisCache ”,

這樣它就能夠引用到對應的快取了

redis獲取大概在1ms裡面

資料庫查詢3ms

自定義以個快取管理器;

不改變spring'boot自帶的快取管理器:

通過配置修改:

#######自定義快取器的配置,配置不多的時候,可以採用預設的快取管理器
spring.cache.redis.cache-null-values=true
spring.cache.redis.key-prefix=
spring.cache.redis.use-key-prefix=false
#設定十分鐘超時,不能從redis快取裡面獲取資料,重新整理資料
spring.cache.redis.time-to-live=600000

通過程式碼:

//    自定義快取管理器:
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean(name = "redisCacheManager")
    public RedisCacheManager initRedisCacheManager(){
        //枷鎖的寫入器
        RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory);
        //啟動redis快取的預設設定
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        //設定JDK預設序列化器
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer( new
                JdkSerializationRedisSerializer()));
        //不使用字首
        config = config.disableKeyPrefix();
        //有效時間
        config = config.entryTtl(Duration.ofMinutes(10));
        RedisCacheManager redisCacheManager = new RedisCacheManager(writer,config);
        return redisCacheManager;
    }

}