Redis 入門篇

捞月亮的小北發表於2024-04-30

1. 初始 Redis

1.1 認識NoSQL

NoSql可以翻譯做Not Only Sql(不僅僅是SQL),或者是No Sql(非Sql的)資料庫。是相對於傳統關係型資料庫而言,有很大差異的一種特殊的資料庫​,因此也稱之為非關係型資料庫

1.1.1 結構化與非結構化

image

1.1.2 關係型與非關係型的差異

  • 儲存方式

    • 關係型資料庫基於磁碟進行儲存,會有大量的磁碟IO,對效能有一定影響
    • 非關係型資料庫,他們的操作更多的是依賴於記憶體來操作,記憶體的讀寫速度會非常快,效能自然會好一些
  • 擴充套件性

    • 關係型資料庫叢集模式一般是主從,主從資料一致,起到資料備份的作用,稱為垂直擴充套件。
    • 非關係型資料庫可以將資料拆分,儲存在不同機器上,可以儲存海量資料,解決記憶體大小有限的問題。稱為水平擴充套件。
    • 關係型資料庫因為表之間存在關聯關係,如果做水平擴充套件會給資料查詢帶來很多麻煩

1.2 認識 Redis

Redis誕生於2009年全稱是Remote Dictionary Server 遠端詞典伺服器,是一個基於記憶體的鍵值型NoSQL資料庫。

特徵

  • 鍵值(key-value)型,value支援多種不同資料結構,功能豐富
  • 單執行緒,每個命令具備原子性
  • 低延遲,速度快(基於記憶體.IO多路複用.良好的編碼)。
  • 支援資料持久化
  • 支援主從叢集.分片叢集
  • 支援多語言客戶端

作者:Antirez

Redis的官方網站地址:https://redis.io/

1.3 安裝Redis

Redis的安裝步驟

2. Redis 的常見命令

2.1 Redis 資料結構介紹

Redis是一個key-value的資料庫,key一般是String型別,不過value的型別多種多樣:

image

貼心小建議:命令不要死記,學會查詢就好啦

Redis為了方便我們學習,將操作不同資料型別的命令也做了分組,在官網( https://redis.io/commands )可以檢視到不同的命令:

當然我們也可以透過Help命令來幫助我們去檢視命令

image

2.2 Redis 通用命令

通用指令是部分資料型別的,都可以使用的指令,常見的有:

  • KEYS:檢視符合模板的所有key
  • DEL:刪除一個指定的key
  • EXISTS:判斷key是否存在
  • EXPIRE:給一個key設定有效期,有效期到期時該key會被自動刪除
  • TTL:檢視一個KEY的剩餘有效期

透過help [command] 可以檢視一個命令的具體用法,例如:

image

例項程式碼如下:

  • KEYS
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379>

# 查詢以a開頭的key
127.0.0.1:6379> keys a*
1) "age"
127.0.0.1:6379>

貼心小提示:在生產環境下,不推薦使用keys 命令,因為這個命令在key過多的情況下,效率不高

  • DEL
127.0.0.1:6379> help del

  DEL key [key ...]
  summary: Delete a key
  since: 1.0.0
  group: generic

127.0.0.1:6379> del name #刪除單個
(integer) 1  #成功刪除1個

127.0.0.1:6379> keys *
1) "age"

127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 #批次新增資料
OK

127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
4) "age"

127.0.0.1:6379> del k1 k2 k3 k4
(integer) 3   #此處返回的是成功刪除的key,由於redis中只有k1,k2,k3 所以只成功刪除3個,最終返回
127.0.0.1:6379>

127.0.0.1:6379> keys * #再查詢全部的key
1) "age"	#只剩下一個了
127.0.0.1:6379>

貼心小提示:同學們在複製程式碼的時候,只需要複製對應的命令哦~

  • EXISTS
127.0.0.1:6379> help EXISTS

  EXISTS key [key ...]
  summary: Determine if a key exists
  since: 1.0.0
  group: generic

127.0.0.1:6379> exists age
(integer) 1

127.0.0.1:6379> exists name
(integer) 0
  • EXPIRE

貼心小提示:記憶體非常寶貴,對於一些資料,我們應當給他一些過期時間,當過期時間到了之後,他就會自動被刪除~

127.0.0.1:6379> expire age 10
(integer) 1

127.0.0.1:6379> ttl age
(integer) 8

127.0.0.1:6379> ttl age
(integer) 6

127.0.0.1:6379> ttl age
(integer) -2

127.0.0.1:6379> ttl age
(integer) -2  #當這個key過期了,那麼此時查詢出來就是-2 

127.0.0.1:6379> keys *
(empty list or set)

127.0.0.1:6379> set age 10 #如果沒有設定過期時間
OK

127.0.0.1:6379> ttl age
(integer) -1  # ttl的返回值就是-1

2.3 Redis命令-String命令

String型別,也就是字串型別,是Redis中最簡單的儲存型別。

其value是字串,不過根據字串的格式不同,又可以分為3類:

  • string:普通字串
  • int:整數型別,可以做自增.自減操作
  • float:浮點型別,可以做自增.自減操作

String的常見命令有:

  • SET:新增或者修改已經存在的一個String型別的鍵值對
  • GET:根據key獲取String型別的value
  • MSET:批次新增多個String型別的鍵值對
  • MGET:根據多個key獲取多個String型別的value
  • INCR:讓一個整型的key自增1
  • INCRBY:讓一個整型的key自增並指定步長,例如:incrby num 2 讓num值自增2
  • INCRBYFLOAT:讓一個浮點型別的數字自增並指定步長
  • SETNX:新增一個String型別的鍵值對,前提是這個key不存在,否則不執行
  • SETEX:新增一個String型別的鍵值對,並且指定有效期

貼心小提示:以上命令除了INCRBYFLOAT 都是常用命令

  • SET 和GET: 如果key不存在則是新增,如果存在則是修改

    image

  • MSET和MGET : 可以批次增加 , 檢視

    image

  • INCR和INCRBY和DECY

    image

  • SETNX : 新增一個String型別的鍵值對,前提是這個key不存在,否則不執行

    image

  • SETEX : 新增一個String型別的鍵值對,並且指定有效期

    image

2.4 Redis命令-Key的層級結構

Redis沒有類似MySQL中的Table的概念​,我們該如何區分不同型別的key呢?

例如,需要儲存使用者.商品資訊到redis,有一個使用者id是1,有一個商品id恰好也是1,此時如果使用id作為key,那就會衝突了,該怎麼辦?

我們可以透過給key新增字首加以區分,不過這個字首不是隨便加的,有一定的規範:

Redis的key允許有多個單詞形成層級結構,多個單詞之間用':'隔開,格式如下:

專案名:業務名:id

這個格式並非固定,也可以根據自己的需求來刪除或新增詞條。

例如我們的專案名稱叫 heima,有user和product兩種不同型別的資料,我們可以這樣定義key:

  • user相關的key:heima:user:1
  • product相關的key:heima:product:1

如果Value是一個Java物件,例如一個User物件,則可以將物件序列化為JSON字串後儲存:

KEY VALUE
heima:user:1
heima:product:1

一旦我們向redis採用這樣的方式儲存,那麼在視覺化介面中,redis會以層級結構來進行儲存,形成類似於這樣的結構,更加方便Redis獲取資料

image

2.5 Redis命令-Hash命令

Hash型別,也叫雜湊,其value是一個無序字典,類似於Java中的HashMap結構。

String結構是將物件序列化為JSON字串後儲存,當需要修改物件某個欄位時很不方便:

image

Hash結構可以將物件中的每個欄位獨立儲存,可以針對單個欄位做CRUD:

image

Hash型別的常見命令

  • HSET key field value:新增或者修改hash型別key的field的值
  • HGET key field:獲取一個hash型別key的field的值
  • HMSET:批次新增多個hash型別key的field的值
  • HMGET:批次獲取多個hash型別key的field的值
  • HGETALL:獲取一個hash型別的key中的所有的field和value
  • HKEYS:獲取一個hash型別的key中的所有的field
  • HINCRBY:讓一個hash型別key的欄位值自增並指定步長
  • HSETNX:新增一個hash型別的key的field值,前提是這個field不存在,否則不執行

貼心小提示:雜湊結構也是我們以後實際開發中常用的命令喲

  • HSET和HGET
127.0.0.1:6379> HSET heima:user:3 name Lucy//大key是 heima:user:3 小key是name,小value是Lucy
(integer) 1
127.0.0.1:6379> HSET heima:user:3 age 21// 如果操作不存在的資料,則是新增
(integer) 1
127.0.0.1:6379> HSET heima:user:3 age 17 //如果操作存在的資料,則是修改
(integer) 0
127.0.0.1:6379> HGET heima:user:3 name 
"Lucy"
127.0.0.1:6379> HGET heima:user:3 age
"17"
  • HMSET和HMGET
127.0.0.1:6379> HMSET heima:user:4 name HanMeiMei
OK
127.0.0.1:6379> HMSET heima:user:4 name LiLei age 20 sex man
OK
127.0.0.1:6379> HMGET heima:user:4 name age sex
1) "LiLei"
2) "20"
3) "man"
  • HGETALL
127.0.0.1:6379> HGETALL heima:user:4
1) "name"
2) "LiLei"
3) "age"
4) "20"
5) "sex"
6) "man"
  • HKEYS和HVALS
127.0.0.1:6379> HKEYS heima:user:4
1) "name"
2) "age"
3) "sex"
127.0.0.1:6379> HVALS heima:user:4
1) "LiLei"
2) "20"
3) "man"
  • HINCRBY
127.0.0.1:6379> HINCRBY  heima:user:4 age 2
(integer) 22
127.0.0.1:6379> HVALS heima:user:4
1) "LiLei"
2) "22"
3) "man"
127.0.0.1:6379> HINCRBY  heima:user:4 age -2
(integer) 20
  • HSETNX
127.0.0.1:6379> HSETNX heima:user4 sex woman
(integer) 1
127.0.0.1:6379> HGETALL heima:user:3
1) "name"
2) "Lucy"
3) "age"
4) "17"
127.0.0.1:6379> HSETNX heima:user:3 sex woman
(integer) 1
127.0.0.1:6379> HGETALL heima:user:3
1) "name"
2) "Lucy"
3) "age"
4) "17"
5) "sex"
6) "woman"

2.6 Redis命令-List命令

redis中的List型別與Java中的LinkedList類似,可以看做是一個雙向連結串列結構。既可以支援正向檢索和也可以支援反向檢索。

特徵也與LinkedList類似:

  • 有序
  • 元素可以重複
  • 插入和刪除快
  • 查詢速度一般

常用來儲存一個有序資料,例如:朋友圈點贊列表,評論列表等。

List的常見命令有:

  • LPUSH key element ... :向列表左側插入一個或多個元素
  • LPOP key:移除並返回列表左側的第一個元素,沒有則返回nil
  • RPUSH key element ... :向列表右側插入一個或多個元素
  • RPOP key:移除並返回列表右側的第一個元素
  • LRANGE key star end:返回一段角標範圍內的所有元素
  • BLPOP和BRPOP:與LPOP和RPOP類似,只不過在沒有元素時等待指定時間,而不是直接返回nil

image

  • LPUSH和RPUSH
127.0.0.1:6379> LPUSH users 1 2 3
(integer) 3
127.0.0.1:6379> RPUSH users 4 5 6
(integer) 6
  • LPOP和RPOP
127.0.0.1:6379> LPOP users
"3"
127.0.0.1:6379> RPOP users
"6"
  • LRANGE
127.0.0.1:6379> LRANGE users 1 2
1) "1"
2) "4"

2.7 Redis命令-Set命令

Redis的Set結構與Java中的HashSet類似,可以看做是一個value為null的HashMap。因為也是一個hash表,因此具備與HashSet類似的特徵:

  • 無序
  • 元素不可重複
  • 查詢快
  • 支援交集.並集.差集等功能

Set型別的常見命令

  • SADD key member ... :向set中新增一個或多個元素
  • SREM key member ... : 移除set中的指定元素
  • SCARD key: 返回set中元素的個數
  • SISMEMBER key member:判斷一個元素是否存在於set中
  • SMEMBERS:獲取set中的所有元素
  • SINTER key1 key2 ... :求key1與key2的交集
  • SDIFF key1 key2 ... :求key1與key2的差集
  • SUNION key1 key2 ..:求key1和key2的並集

例如兩個集合:s1和s2:

求交集:SINTER s1 s2

求s1與s2的不同:SDIFF s1 s2

具體命令

127.0.0.1:6379> sadd s1 a b c
(integer) 3
127.0.0.1:6379> smembers s1
1) "c"
2) "b"
3) "a"
127.0.0.1:6379> srem s1 a
(integer) 1
  
127.0.0.1:6379> SISMEMBER s1 a
(integer) 0
  
127.0.0.1:6379> SISMEMBER s1 b
(integer) 1
  
127.0.0.1:6379> SCARD s1
(integer) 2

案例

  • 將下列資料用Redis的Set集合來儲存:
  • 張三的好友有:李四.王五.趙六
  • 李四的好友有:王五.麻子.二狗
  • 利用Set的命令實現下列功能:
  • 計算張三的好友有幾人
  • 計算張三和李四有哪些共同好友
  • 查詢哪些人是張三的好友卻不是李四的好友
  • 查詢張三和李四的好友總共有哪些人
  • 判斷李四是否是張三的好友
  • 判斷張三是否是李四的好友
  • 將李四從張三的好友列表中移除
127.0.0.1:6379> SADD zs lisi wangwu zhaoliu
(integer) 3
  
127.0.0.1:6379> SADD ls wangwu mazi ergou
(integer) 3
  
127.0.0.1:6379> SCARD zs
(integer) 3
  
127.0.0.1:6379> SINTER zs ls
1) "wangwu"
  
127.0.0.1:6379> SDIFF zs ls
1) "zhaoliu"
2) "lisi"
  
127.0.0.1:6379> SUNION zs ls
1) "wangwu"
2) "zhaoliu"
3) "lisi"
4) "mazi"
5) "ergou"
  
127.0.0.1:6379> SISMEMBER zs lisi
(integer) 1
  
127.0.0.1:6379> SISMEMBER ls zhangsan
(integer) 0
  
127.0.0.1:6379> SREM zs lisi
(integer) 1
  
127.0.0.1:6379> SMEMBERS zs
1) "zhaoliu"
2) "wangwu"

2.8 Redis命令-SortedSet型別

Redis的SortedSet是一個可排序的set集合,與Java中的TreeSet有些類似,但底層資料結構卻差別很大。SortedSet中的每一個元素都帶有一個score屬性,可以基於score屬性對元素排序,底層的實現是一個跳錶(SkipList)加 hash表。

SortedSet具備下列特性:

  • 可排序
  • 元素不重複
  • 查詢速度快

因為SortedSet的可排序特性,經常被用來實現排行榜這樣的功能。

SortedSet的常見命令有:

  • ZADD key score member:新增一個或多個元素到sorted set ,如果已經存在則更新其score值
  • ZREM key member:刪除sorted set中的一個指定元素
  • ZSCORE key member : 獲取sorted set中的指定元素的score值
  • ZRANK key member:獲取sorted set 中的指定元素的排名
  • ZCARD key:獲取sorted set中的元素個數
  • ZCOUNT key min max:統計score值在給定範圍內的所有元素的個數
  • ZINCRBY key increment member:讓sorted set中的指定元素自增,步長為指定的increment值
  • ZRANGE key min max:按照score排序後,獲取指定排名範圍內的元素
  • ZRANGEBYSCORE key min max:按照score排序後,獲取指定score範圍內的元素
  • ZDIFF.ZINTER.ZUNION:求差集.交集.並集

注意:所有的排名預設都是升序,如果要降序則在命令的Z後面新增REV即可,例如:

  • 升序獲取sorted set 中的指定元素的排名:ZRANK key member
  • 降序獲取sorted set 中的指定元素的排名:ZREVRANK key memeber

3. Redis的Java客戶端-Jedis

在Redis官網中提供了各種語言的客戶端,地址:https://redis.io/docs/clients/

image

其中Java客戶端也包含很多:

image

標記為❤的就是推薦使用的java客戶端,包括:

  • Jedis和Lettuce:這兩個主要是提供了Redis命令對應的API,方便我們操作Redis,而SpringDataRedis又對這兩種做了抽象和封裝,因此我們後期會直接以SpringDataRedis來學習。
  • Redisson:是在Redis基礎上實現了分散式的可伸縮的java資料結構,例如Map.Queue等,而且支援跨程序的同步機制:Lock.Semaphore等待,比較適合用來實現特殊的功能需求。

3.1 快速入門

  1. 建立Maven工程

  2. 引入依賴

    <!--jedis-->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.7.0</version>
    </dependency>
    <!--單元測試-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.7.0</version>
        <scope>test</scope>
    </dependency>
    
  3. 建立連線

        private Jedis jedis;
    
        @BeforeEach
        void setUp() {
            // 1. 建立連線
    //        jedis = new Jedis("192.168.146.129" , 6379);
            jedis = JedisConnectionFactory.getJedis();
    
            // 2. 設定密碼
    //        jedis.auth("north");
    
            // 3. 選擇庫
            jedis.select(0);
        }
    
  4. 測試

     @Test
        public void testSet() {
            // 存資料
            String result = jedis.set("name", "陳平安");
            System.out.println("result = " + result);
    
            // 取資料
            String name = jedis.get("name");
            System.out.println("name = " + name);
        }
    
        @Test
        public void testHash() {
            Long hset = jedis.hset("user:1", "name", "石昊");
            Long hset1 = jedis.hset("user:1", "age", "19");
    
            Map<String, String> hgetAll = jedis.hgetAll("user:1");
            System.out.println("hgetAll = " + hgetAll);
        }
    
  5. 釋放資源

    @AfterEach
    void tearDown() {
        if (jedis != null) {
            jedis.close();
        }
    }
    

3.2 Jedis連線池

Jedis本身是執行緒不安全的,並且頻繁的建立和銷燬連線會有效能損耗,因此我們推薦大家使用Jedis連線池代替Jedis的直連方式

有關池化思想,並不僅僅是這裡會使用,很多地方都有,比如說我們的資料庫連線池,比如我們tomcat中的執行緒池,這些都是池化思想​的體現。

3.2.1 建立Jedis的連線池

public class JedisConnectionFactory {
    private static final JedisPool jedisPool;

    static {
        // 配置連線池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(8);
        poolConfig.setMaxIdle(8);
        poolConfig.setMinIdle(0);
        poolConfig.setMaxWaitMillis(1000);
        // 建立連線池物件
        jedisPool = new JedisPool(poolConfig , "192.168.146.129" , 6379 , 1000 , "north");
    }

    public static Jedis getJedis() {
        return jedisPool.getResource();
    }
}

程式碼說明:

  • 1) JedisConnectionFacotry:工廠設計模式是實際開發中非常常用的一種設計模式,我們可以使用工廠,去降低代的耦合,比如Spring中的Bean的建立,就用到了工廠設計模式
  • 2)靜態程式碼塊:隨著類的載入而載入,確保只能執行一次,我們在載入當前工廠類的時候,就可以執行static的操作完成對 連線池的初始化
  • 3)最後提供返回連線池中連線的方法.

對於這段程式碼的理解:

這段程式碼是一個用於連線 Redis 資料庫的 Java 類 JedisConnectionFactory​。它使用了 Jedis 庫,這是一個流行的 Redis 客戶端庫,用於在 Java 程式中與 Redis 伺服器進行通訊。

讓我解釋一下這段程式碼的主要部分:

  1. public class JedisConnectionFactory​: 這是一個公共類的定義,類名為 JedisConnectionFactory​,用於建立 Redis 連線工廠。

  2. private static final JedisPool jedisPool;​: 這是一個私有的靜態常量,用於儲存連線到 Redis 伺服器的連線池例項。連線池是一種管理和維護與 Redis 伺服器連線的機制,可以提高連線的複用和效能。

  3. static { ... }​: 這是一個靜態程式碼塊,在類被載入時執行。在這個程式碼塊中,你可以看到配置了一個 Jedis 連線池。以下是對配置選項的解釋:

    • JedisPoolConfig poolConfig = new JedisPoolConfig();​: 建立一個 Jedis 連線池的配置物件。
    • poolConfig.setMaxTotal(8);​: 設定連線池中最大連線數為 8。
    • poolConfig.setMaxIdle(8);​: 設定連線池中最大空閒連線數為 8。
    • poolConfig.setMinIdle(0);​: 設定連線池中最小空閒連線數為 0。
    • poolConfig.setMaxWaitMillis(1000);​: 設定從連線池獲取連線的最大等待時間為 1000 毫秒。

    然後,使用這些配置項建立一個 Jedis 連線池例項:

    • jedisPool = new JedisPool(poolConfig, "192.168.146.129", 6379, 1000, "north");

      • poolConfig​: 使用之前配置的連線池配置物件。
      • "192.168.146.129"​: Redis 伺服器的 IP 地址。
      • 6379​: Redis 伺服器的埠號。
      • 1000​: 連線超時時間(毫秒)。
      • "north"​: 連線到 Redis 伺服器的密碼(如果有密碼保護)。
  4. public static Jedis getJedis()​: 這是一個公共靜態方法,用於從連線池中獲取一個可用的 Redis 連線(Jedis 物件)。透過呼叫 jedisPool.getResource()​ 來獲取連線。

這段程式碼的作用是建立一個連線到 Redis 資料庫的連線池,以便在應用程式中可以輕鬆地獲取和管理與 Redis 伺服器的連線。透過呼叫 getJedis()​ 方法,你可以獲取一個可用的 Redis 連線並開始執行對 Redis 的操作。要確保在使用完連線後,將其釋放回連線池以供其他地方使用。

3.2.2 改造原始程式碼

程式碼說明:

1.在我們完成了使用工廠設計模式來完成程式碼的編寫之後,我們在獲得連線時,就可以透過工廠來獲得。

,而不用直接去new物件,降低耦合,並且使用的還是連線池物件。

2.當我們使用了連線池後,當我們關閉連線其實並不是關閉,而是將Jedis還回連線池的。

    @BeforeEach
    void setUp(){
        //建立連線
        /*jedis = new Jedis("127.0.0.1",6379);*/
        jedis = JedisConnectionFacotry.getJedis();
         //選擇庫
        jedis.select(0);
    }

   @AfterEach
    void tearDown() {
        if (jedis != null) {
            jedis.close();
        }
    }

4. Redis的Java客戶端-SpringDataRedis

SpringData是Spring中資料操作的模組,包含對各種資料庫的整合,其中對Redis的整合模組就叫做SpringDataRedis,官網地址:https://spring.io/projects/spring-data-redis

  • 提供了對不同Redis客戶端的整合(Lettuce和Jedis)
  • 提供了RedisTemplate統一API來操作Redis
  • 支援Redis的釋出訂閱模型
  • 支援Redis哨兵和Redis叢集
  • 支援基於Lettuce的響應式程式設計
  • 支援基於JDK.JSON.字串.Spring物件的資料序列化及反序列化
  • 支援基於Redis的JDKCollection實現

SpringDataRedis中提供了RedisTemplate工具類,其中封裝了各種對Redis的操作。並且將不同資料型別的操作API封裝到了不同的型別中:

image

4.1 快速入門

  1. 匯入座標

     <!--redis依賴-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <!--common-pool-->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-pool2</artifactId>
            </dependency>
            <!--Jackson依賴-->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
            </dependency>
    
  2. 配置檔案

    spring:
      redis:
        host: 192.168.150.101
        port: 6379
        password: 123321
        lettuce:
          pool:
            max-active: 8  #最大連線
            max-idle: 8   #最大空閒連線
            min-idle: 0   #最小空閒連線
            max-wait: 100ms #連線等待時間
    
  3. test

        @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        void testString() {
            // 寫入一條String資料
            redisTemplate.opsForValue().set("name" , "徐鳳年");
            // 取出資料
            Object name = redisTemplate.opsForValue().get("name");
            System.out.println("name = " + name);
    
        }
    
        @Test
        void testSaveUser() {
            // 寫入資料
            redisTemplate.opsForValue().set("user:100" , new User("李七夜" , 19));
            // 獲取資料
            User o = (User) redisTemplate.opsForValue().get("user:100");
            System.out.println("o = " + o);
        }
    

貼心小提示:SpringDataJpa使用起來非常簡單,記住如下幾個步驟即可

SpringDataRedis的使用步驟:

  • 引入spring-boot-starter-data-redis依賴
  • 在application.yml配置Redis資訊
  • 注入RedisTemplate

4.2 資料序列化器

RedisTemplate可以接收任意Object作為值寫入Redis:

只不過寫入前會把Object序列化為位元組形式,預設是採用JDK序列化,得到的結果是這樣的:

缺點:

  • 可讀性差
  • 記憶體佔用較大

我們可以自定義RedisTemplate的序列化方式,程式碼如下:

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        // 建立RedisTemplate物件
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 設定連線工廠
        template.setConnectionFactory(connectionFactory);
        // 建立JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        // 設定Key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 設定Value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        // 返回
        return template;
    }
}

這裡採用了JSON序列化來代替預設的JDK序列化方式。最終結果如圖

{
  "@class": "com.north.pojo.User",
  "name": "李七夜",
  "age": 19
}

整體可讀性有了很大提升,並且能將Java物件自動的序列化為JSON字串,並且查詢時能自動把JSON反序列化為Java物件。不過,其中記錄了序列化時對應的class名稱,目的是為了查詢時實現自動反序列化。這會帶來額外的記憶體開銷。

4.3 StringRedisTemplate

儘管JSON的序列化方式可以滿足我們的需求,但依然存在一些問題,如下:

{
  "@class": "com.north.pojo.User",
  "name": "李七夜",
  "age": 19
}

為了在反序列化時知道物件的型別,JSON序列化器會將類的class型別寫入json結果中,存入Redis,會帶來額外的記憶體開銷​。

為了減少記憶體的消耗,我們可以採用手動序列化的方式​,換句話說,就是不借助預設的序列化器,而是我們自己來控制序列化的動作​,同時,我們只採用String的序列化器,這樣,在儲存value時,我們就不需要在記憶體中就不用多儲存資料,從而節約我們的記憶體空間

image

這種用法比較普遍,因此SpringDataRedis就提供了RedisTemplate的子類:StringRedisTemplate,它的key和value的序列化方式預設就是String方式。

手動序列化實現:

    @Bean
    public RedisTemplate<String , Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 建立RedisTemplate 物件
        RedisTemplate<String , Object> redisTemplate = new RedisTemplate<>();

        // 設定連線工廠
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        // 設定序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        // key 和 hashKey 採用String序列化
        redisTemplate.setKeySerializer(jsonRedisSerializer);
        redisTemplate.setHashKeySerializer(jsonRedisSerializer);

        // value 和 hashValue 採用JSON序列化
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);

        // 返回
        return redisTemplate;
    }

此時我們再來看一看儲存的資料,小夥伴們就會發現那個class資料已經不在了,節約了我們的空間~

{
  "name": "徐鳳年",
  "age": 22
}

測試類

@SpringBootTest
class RedisApplicationTests {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // JSON 工具
    private static final ObjectMapper mapper = new ObjectMapper();

    @Test
    public void testStringTemplate() throws JsonProcessingException {
        // 準備資料
        User user = new User("徐鳳年" , 22);

        // 手動序列化
        String json = mapper.writeValueAsString(user);

        // 寫入一條資料到redis
        stringRedisTemplate.opsForValue().set("user:200" , json);

        // 讀取資料
        String result = stringRedisTemplate.opsForValue().get("user:200");
        User user1 = mapper.readValue(result, User.class);
        System.out.println("user1 = " + user1);
    }

}

最後小總結:

RedisTemplate的兩種序列化實踐方案:

  • 方案一:

    • 自定義RedisTemplate
    • 修改RedisTemplate的序列化器為GenericJackson2JsonRedisSerializer
  • 方案二:

    • 使用StringRedisTemplate
    • 寫入Redis時,手動把物件序列化為JSON
    • 讀取Redis時,手動把讀取到的JSON反序列化為物件

4.4 Hash結構操作

@SpringBootTest
class RedisStringTests {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Test
    void testHash() {
        stringRedisTemplate.opsForHash().put("user:400", "name", "虎哥");
        stringRedisTemplate.opsForHash().put("user:400", "age", "21");

        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries("user:400");
        System.out.println("entries = " + entries);
    }
}

我的部落格即將同步至騰訊雲開發者社群,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=3vtuwevgbfms4