Redis 萬字入門教程

Java陈序员發表於2024-10-16

0. 前言

文章已經收錄到 GitHub 個人部落格專案,歡迎 Star:

https://github.com/chenyl8848/chenyl8848.github.io

或者訪問網站,進行線上瀏覽:

https://chenyl8848.github.io/

1. NoSQL

1.1 NoSQL 介紹

NoSQL(Not Only SQL ),意即不僅僅是 SQL,泛指非關係型的資料庫。NoSQL 這個技術門類,早期就有人提出,發展至2009年趨勢越發高漲。

隨著網際網路網站的興起,傳統的關聯式資料庫在應付動態網站,特別是超大規模和高併發的純動態網站已經顯得力不從心,暴露了很多難以克服的問題。如商城網站中對商品資料頻繁查詢、對熱搜商品的排行統計、訂單超時問題、以及微信朋友圈(音訊,影片)儲存等相關使用傳統的關係型資料庫實現就顯得非常複雜,雖然能實現相應功能但是在效能上卻不是那麼樂觀。NoSQL 這個技術門類的出現,更好的解決了這些問題,它告訴了世界不僅僅是 SQL.

關注微信公眾號:【Java陳序員】,獲取開源專案分享、AI副業分享、超200本經典計算機電子書籍等。

1.2 NoSQL 四大分類

  • 鍵值(Key-Value)儲存資料庫
1. 說明
  這一類資料庫主要會使用到一個雜湊表,這個表中有一個特定的鍵和一個指標指向特定的資料。

2. 特點
  Key/Value 模型對於 IT 系統來說的優勢在於簡單、易部署。  
  但是如果 DBA 只對部分值進行查詢或更新的時候,Key/Value 就顯得效率低下了。

3. 相關產品
  Tokyo Cabinet/Tyrant
  Redis
  SSDB
  Voldemort 
  Oracle BDB
  • 列儲存資料庫
1. 說明
  這部分資料庫通常是用來應對分散式儲存的海量資料。

2. 特點
  鍵仍然存在,但是它們的特點是指向了多個列。這些列是由列家族來安排的。

3. 相關產品
  Cassandra、HBase、Riak
  • 文件型資料庫
1. 說明
  文件型資料庫的靈感是來自於 Lotus Notes 辦公軟體的,而且它同第一種鍵值儲存相類似該型別的資料模型是版本化的文件,半結構化的文件以特定的格式儲存,比如 JSON.文件型資料庫可以看作是鍵值資料庫的升級版,允許之間巢狀鍵值,而且文件型資料庫比鍵值資料庫的查詢效率更高。

2. 特點
  以文件形式儲存

3. 相關產品
  MongoDB、CouchDB,國內也有文件型資料庫 SequoiaDB,已經開源。
  • 圖形(Graph)資料庫

  • 說明
    圖形結構的資料庫同其他行列以及剛性結構的 SQL 資料庫不同,它是使用靈活的圖形模型,並且能夠擴充套件到多個伺服器上。
    NoSQL 資料庫沒有標準的查詢語言(SQL),因此進行資料庫查詢需要制定資料模型。許多 NoSQL 資料庫都有 REST 式的資料介面或者查詢 API.
  • 特點
  • 相關產品
    Neo4J、InfoGrid、 Infinite Graph

1.3 NoSQL 應用場景

  • 資料模型比較簡單
  • 需要靈活性更強的IT系統
  • 對資料庫效能要求較高
  • 不需要高度的資料一致性

2. Redis

2.1 Redis 介紹

Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker.

Redis 是一個遵循 BSD 開源的基於記憶體資料儲存庫,可用於作為資料庫、快取 、訊息中介軟體。

總結: Redis 是一個記憶體型的資料庫

2.2 Redis 特點

  • Redis 是一個高效能 Key/Value 記憶體型資料庫
  • Redis 支援豐富的資料型別
  • Redis 支援持久化
  • Redis 單執行緒、單程序

2.3 Redis 安裝

0. 準備環境
  - vmware15.x+
  - centos7.x+

1. 下載 Redis 原始碼包
  https://redis.io/

2. 下載完整原始碼包
  redis-4.0.10.tar.gz

3. 將下載的 Redis 安裝包上傳到 Linux 中

4. 解壓縮檔案
  tar -zxvf redis-4.0.10.tar.gz
  ll

5. 安裝 gcc  
  yum install -y gcc

6. 進入解壓縮目錄執行如下命令
  make MALLOC=libc

7. 編譯完成後執行如下命令
  make install PREFIX=/usr/redis

8. 進入 /usr/redis 目錄啟動 Redis 服務 
  ./redis-server

9. Redis 服務埠預設是 6379

10. 進入 bin 目錄執行客戶端連線操作
  ./redis-cli –p 6379

11.  連線成功出現上面介面連線成功

3. Redis 資料庫相關指令

3.1 資料庫操作指令

1. Redis 庫說明
  使用 Redis 的預設配置啟動 Redis 服務後,預設會存在 16 個庫,編號從 0-15
  可以使用 select 庫的編號 來選擇一個 Redis 的庫

2. Redis 操作庫的指令
  清空當前的庫 FLUSHDB
  清空全部的庫 FLUSHALL

3. Redis 客戶端顯示中文
  ./redis-cli -p 7000 --raw

3.2 Key 操作指令

1. DEL 指令
  語法:DEL key [key ...] 
  作用:刪除給定的一個或多個 key,不存在的 key 會被忽略
  可用版本:>= 1.0.0
  返回值:被刪除 key 的數量 

2. EXISTS 指令
  語法:EXISTS key
  作用:檢查給定 key 是否存在
  可用版本:>= 1.0.0
  返回值:若 key 存在,返回 1,否則返回 0

3. EXPIRE
  語法:EXPIRE key seconds
  作用:為給定 key 設定生存時間,當 key 過期時(生存時間為0 ),它會被自動刪除。
  可用版本: >= 1.0.0
  時間複雜度: O(1)
  返回值:設定成功返回 1

4. KEYS
  語法:KEYS pattern
  作用:查詢所有符合給定模式 pattern 的 key
  語法:
      KEYS * 匹配資料庫中所有 key
      KEYS h?llo 匹配 hello、hallo 和 hxllo 等
      KEYS h*llo 匹配 hllo 和 heeeeello 等
      KEYS h[ae]llo 匹配 hello 和 hallo,但不匹配 hillo,特殊符號用 "\" 隔開
  可用版本:>= 1.0.0
  返回值:符合給定模式的 key 列表

5. MOVE
  語法:MOVE key db
  作用:將當前資料庫的 key 移動到給定的資料庫 db 當中
  可用版本:>= 1.0.0
  返回值:移動成功返回 1, 失敗則返回 0

6. PEXPIRE
  語法:PEXPIRE key milliseconds
  作用:這個命令和 EXPIRE 命令的作用類似,但是它以毫秒為單位設定 key 的生存時間,而不像 EXPIRE 命令以秒為單位
  可用版本:>= 2.6.0
  時間複雜度:O(1)
  返回值:設定成功,返回 1,key 不存在或設定失敗,返回 0

7. PEXPIREAT
  語法:PEXPIREAT key milliseconds-timestamp
  作用:這個命令和 EXPIREAT 命令類似,但它以毫秒為單位設定 key 的過期 Unix 時間戳,而不是像 EXPIREAT 以秒為單位
  可用版本:>= 2.6.0
  返回值:如果生存時間設定成功,返回 1,當 key 不存在或沒辦法設定生存時間時,返回 0

8. TTL
  語法:TTL key
  作用:以秒為單位,返回給定 key 的剩餘生存時間(TTL, time to live)
  可用版本:>= 1.0.0
  返回值:
      當 key 不存在時,返回 -2 
      當 key 存在但沒有設定剩餘生存時間時,返回 -1 
      否則,以秒為單位,返回 key 的剩餘生存時間
  注意:在 Redis2.8 以前,當 key 不存在,或者 key 沒有設定剩餘生存時間時,命令都返回-1 

9. PTTL
  語法:PTTL key
  作用:這個命令類似於 TTL 命令,但它以毫秒為單位返回 key 的剩餘生存時間,而不是像 TTL 命令以秒為單位
  可用版本:>= 2.6.0
  返回值:
    當 key 不存在時,返回 -2
    當 key 存在但沒有設定剩餘生存時間時,返回 -1
  否則,以毫秒為單位,返回 key 的剩餘生存時間
  注意:在 Redis2.8 以前,當 key 不存在,或者 key 沒有設定剩餘生存時間時,命令都返回 -1

10. RANDOMKEY
  語法:RANDOMKEY
  作用:從當前資料庫中隨機返回(不刪除) 一個 key
  可用版本:>= 1.0.0
  返回值:當資料庫不為空時,返回一個 key,當資料庫為空時,返回 nil

11. RENAME
  語法:RENAME key newkey
  作用:將 key 改名為 newkey,當 key 和 newkey 相同,或者 key 不存在時,返回一個錯誤;當 newkey 已經存在時,RENAME 命令將覆蓋舊值
  可用版本:>= 1.0.0
  返回值:改名成功時提示 OK,失敗時候返回一個錯誤

12. TYPE
  語法:TYPE key
  作用:返回 key 所儲存的值的型別
  可用版本:>= 1.0.0
  返回值:
    none (key 不存在)
    string (字串)
    list (列表)
    set (集合)
    zset (有序集)
    hash (雜湊表)

3.3 String 型別

  1. 記憶體儲存模型

  1. 常用操作命令
命令說明
set設定一個key/value
get根據key獲得對應的value
mset一次設定多個key value
mget一次獲得多個key的value
getset獲得原始key的值,同時設定新值
strlen獲得對應key儲存value的長度
append為對應key的value追加內容
getrange 索引0開始擷取value的內容
setex設定一個key存活的有效期(秒)
psetex設定一個key存活的有效期(毫秒)
setnx存在不做任何操作,不存在新增
msetnx原子操作(只要有一個存在不做任何操作)可以同時設定多個key,只有有一個存在都不儲存
decr進行數值型別的-1操作
decrby根據提供的資料進行減法操作
Incr進行數值型別的+1操作
incrby根據提供的資料進行加法操作
Incrbyfloat根據提供的資料加入浮點數

3.4 List 型別

List 型別相當於 Java 中 List 集合,元素有序且可以重複

  1. 記憶體儲存模型

  1. 常用操作指令
命令說明
lpush將某個值加入到一個 key 列表頭部
lpushx同 lpush,但是必須要保證這個 key 存在
rpush將某個值加入到一個 key 列表末尾
rpushx同 rpush,但是必須要保證這個 key 存在
lpop返回和移除列表左邊的第一個元素
rpop返回和移除列表右邊的第一個元素
lrange獲取某一個下標區間內的元素
llen獲取列表元素個數
lset設定某一個指定索引的值(索引必須存在)
lindex獲取某一個指定索引位置的元素
lrem刪除重複元素
ltrim保留列表中特定區間內的元素
linsert在某一個元素之前,之後插入新元素

3.5 Set 型別

Set 型別相當於 Java 中的 Set 集合,元素無序且不可以重複

  1. 記憶體儲存模型

  1. 常用命令
命令說明
sadd為集合新增元素
smembers顯示集合中所有元素 無序
scard返回集合中元素的個數
spop隨機返回一個元素 並將元素在集合中刪除
smove從一個集合中向另一個集合移動元素 必須是同一種型別
srem從集合中刪除一個元素
sismember判斷一個集合中是否含有這個元素
srandmember隨機返回元素
sdiff去掉第一個集合中其它集合含有的相同元素
sinter求交集
sunion求和集

3.6 ZSet 型別

ZSet 型別是可排序的 Set 集合,但不可重複

  1. 記憶體模型

  1. 常用命令
命令說明
zadd新增一個有序集合元素
zcard返回集合的元素個數
zrange 升序 zrevrange 降序返回一個範圍內的元素
zrangebyscore按照分數查詢一個範圍內的元素
zrank返回排名
zrevrank倒序排名
zscore顯示某一個元素的分數
zrem移除某一個元素
zincrby給某個特定元素加分

3.7 Hash 型別

Hash 型別中的 Value 是一個 Map 結構。

  1. 記憶體模型

  1. 常用命令
命令說明
hset設定一個 key/value 對
hget獲得一個 key 對應的 value
hgetall獲得所有的 key/value 對
hdel刪除某一個 key/value 對
hexists判斷一個 key 是否存在
hkeys獲得所有的 key
hvals獲得所有的 value
hmset設定多個 key/value
hmget獲得多個 key的value
hsetnx設定一個不存在的 key 的值
hincrby為 value 進行加法運算
hincrbyfloat為 value 加入浮點值

4. Redis 持久化機制

client redis[記憶體] --> 記憶體資料 - 資料持久化 --> 磁碟

Redis 官方提供了兩種不同的持久化方法來將資料儲存到硬碟裡面分別是:

  • 快照(Snapshot)
  • AOF (Append Only File) 只追加日誌檔案

4.1 快照(Snapshot)

  1. 特點

這種方式可以將某一時刻的所有資料都寫入硬碟中,當然這也是 Redis 的預設開啟持久化方式,儲存的檔案是以 .rdb 形式結尾的檔案因此這種方式也稱之為 RDB 方式

  1. 快照生成方式
  • 客戶端方式:BGSAVESAVE 指令
  • 伺服器配置自動觸發
1. 客戶端方式之 BGSAVE
  客戶端可以使用 BGSAVE 命令來建立一個快照,當接收到客戶端的 BGSAVE 命令時,Redis 會呼叫 fork 來建立一個子程序,然後子程序負責將快照寫入磁碟中,而父程序則繼續處理命令請求。
    
    名詞解釋: fork 當一個程序建立子程序的時候,底層的作業系統會建立該程序的一個副本,在類 Unix 系統中建立子程序的操作會進行最佳化:在剛開始的時候,父子程序共享相同記憶體,直到父程序或子程序對記憶體進行了寫之後,對被寫入的記憶體的共享才會結束服務。

2. 客戶端方式之SAVE
  客戶端還可以使用 SAVE 命令來建立一個快照,接收到 SAVE 命令的 Redis 伺服器在快照建立完畢之前將不再響應任何其他的命令。

注意:SAVE 命令並不常用,使用 SAVE 命令在快照建立完畢之前,Redis 處於阻塞狀態,無法對外服務
3. 伺服器配置方式之滿足配置自動觸發
  如果使用者在 redis.conf 中設定了 save 配置選項,Redis 會在 save 選項條件滿足之後自動觸發一次 BGSAVE 命令,如果設定多個 save 配置選項,當任意一個 save 配置選項條件滿足,Redis也會觸發一次 BGSAVE 命令。

4. 伺服器接收客戶端 shutdown 指令
  當 Redis 透過 shutdown 指令接收到關閉伺服器的請求時,會執行一個 save 命令,阻塞所有的客戶端,不再執行客戶端執行傳送的任何命令,並且在 save 命令執行完畢之後關閉伺服器。
  1. 配置生成快照名稱和位置
1. 修改生成快照名稱
  dbfilename dump.rdb

2. 修改生成位置
  dir ./

4.2 AOF 只追加日誌檔案

  1. 特點

這種方式可以將所有客戶端執行的寫命令記錄到日誌檔案中,AOF 持久化會將被執行的寫命令寫到 AOF 的檔案末尾,以此來記錄資料發生的變化,因此只要 Redis 從頭到尾執行一次AOF檔案所包含的所有寫命令,就可以恢復AOF檔案的記錄的資料集。

  1. 開啟 AOF 持久化

在 Redis 的預設配置中AOF持久化機制是沒有開啟的,需要在配置中開啟。

1. 開啟AOF持久化
  修改 appendonly yes 開啟持久化
  修改 appendfilename "appendonly.aof" 指定生成檔名稱

  1. 日誌追加頻率
1. always 【謹慎使用】
  說明:每個 Redis 寫命令都要同步寫入硬碟,嚴重降低 Redis 速度
  解釋:如果使用者使用了 always 選項,那麼每個 Redis 寫命令都會被寫入硬碟,從而將發生系統崩潰時出現的資料丟失減到最少;遺憾的是,因為這種同步策略需要對硬碟進行大量的寫入操作,所以 Redis 處理命令的速度會受到硬碟效能的限制
  注意:轉盤式硬碟在這種頻率下200左右個命令/s;固態硬碟(SSD) 幾百萬個命令/s
  警告:使用 SSD 使用者請謹慎使用 always 選項,這種模式不斷寫入少量資料的做法有可能會引發嚴重的寫入放大問題,導致將固態硬碟的壽命從原來的幾年降低為幾個月

2. everysec 【推薦】
  說明:每秒執行一次同步顯式的將多個寫命令同步到磁碟
  解釋:為了兼顧資料安全和寫入效能,使用者可以考慮使用 everysec 選項,讓 Redis 每秒一次的頻率對 AOF 檔案進行同步;Redis 每秒同步一次 AOF 檔案時效能和不使用任何持久化特性時的效能相差無幾,而透過每秒同步一次 AOF 檔案,Redis 可以保證,即使系統崩潰,使用者最多丟失一秒之內產生的資料

3. no    【不推薦】
  說明:由作業系統決定何時同步 
  解釋:最後使用 no 選項,將完全有作業系統決定什麼時候同步 AOF 日誌檔案,這個選項不會對 Redis 效能帶來影響但是系統崩潰時,會丟失不定數量的資料,另外如果使用者硬碟處理寫入操作不夠快的話,當緩衝區被等待寫入硬碟資料填滿時,Redis 會處於阻塞狀態,並導致 Redis 的處理命令請求的速度變慢
  1. 修改同步頻率
1. 修改日誌同步頻率
  修改 appendfsync everysec|always|no 指定

4.3 AOF 檔案的重寫

  1. AOF 帶來的問題

AOF 的方式也同時帶來了另一個問題:持久化檔案會變的越來越大

例如我們呼叫 incr test 命令 100 次,檔案中必須儲存全部的100條命令,其實有99條都是多餘的。因為要恢復資料庫的狀態其實檔案中儲存一條 set test 100 就夠了。為了壓縮 AOF 的持久化檔案 Redis 提供了 AOF 重寫(ReWriter)機制。

  1. AOF 重寫

用來在一定程度上減小 AOF 檔案的體積。

  1. 觸發重寫方式
1. 客戶端方式觸發重寫
  執行 BGREWRITEAOF 命令,不會阻塞 Redis 的服務

2. 伺服器配置方式自動觸發
  配置 redis.conf 中的 auto-aof-rewrite-percentage 選項
  如果設定 auto-aof-rewrite-percentage 值為 100 和 auto-aof-rewrite-min-size 64mb,並且啟用的 AOF 持久化時,那麼當 AOF 檔案體積大於64M,並且 AOF 檔案的體積比上一次重寫之後體積大了至少一倍(100%)時,會自動觸發,如果重寫過於頻繁,使用者可以考慮將 auto-aof-rewrite-percentage 設定為更大

  1. 重寫原理
注意:重寫 AOF 檔案的操作,並沒有讀取舊的 AOF 檔案,而是將整個記憶體中的資料庫內容用命令的方式重寫了一個新的 AOF 檔案,替換原有的檔案這點和快照有點類似
重寫流程:
  1. Redis 呼叫 fork,現在有父子兩個程序,子程序根據記憶體中的資料庫快照,往臨時檔案中寫入重建資料庫狀態的命令
  2. 父程序繼續處理 Client 請求,除了把寫命令寫入到原來的 AOF 檔案中,同時把收到的寫命令快取起來,這樣就能保證如果子程序重寫失敗的話並不會出問題
  3. 當子程序把快照內容寫入已命令方式寫到臨時檔案中後,子程序發訊號通知父程序,然後父程序把快取的寫命令也寫入到臨時檔案
  4. 現在父程序可以使用臨時檔案替換老的 AOF 檔案,並重新命名,後面收到的寫命令也開始往新的 AOF 檔案中追加

4.4 持久化總結

兩種持久化方案既可以同時使用,又可以單獨使用,在某種情況下也可以都不使用,具體使用那種持久化方案取決於使用者的資料和應用決定。

無論使用 AOF 還是快照機制持久化,將資料持久化到硬碟都是有必要的,除了持久化外,使用者還應該對持久化的檔案進行備份(最好備份在多個不同地方)。


5. Java 操作 Redis

5.1 環境準備

  1. 引入依賴
<!--引入jedis連線依賴-->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.9.0</version>
</dependency>
  1. 建立 Jedis 物件
 public static void main(String[] args) {
   //1.建立jedis物件
   Jedis jedis = new Jedis("192.168.40.4", 6379);//1.redis服務必須關閉防火牆  2.redis服務必須開啟遠端連線
   jedis.select(0);//選擇操作的庫預設0號庫
   //2.執行相關操作
   //....
   //3.釋放資源
   jedis.close();
 }

5.2 操作 Key 相關API

private Jedis jedis;
    @Before
    public void before(){
        this.jedis = new Jedis("192.168.202.205", 7000);
    }
    @After
    public void after(){
        jedis.close();
    }

    //測試key相關
    @Test
    public void testKeys(){
        //刪除一個key
        jedis.del("name");
        //刪除多個key
        jedis.del("name","age");

        //判斷一個key是否存在exits
        Boolean name = jedis.exists("name");
        System.out.println(name);

        //設定一個key超時時間 expire pexpire
        Long age = jedis.expire("age", 100);
        System.out.println(age);

        //獲取一個key超時時間 ttl
        Long age1 = jedis.ttl("newage");
        System.out.println(age1);

        //隨機獲取一個key
        String s = jedis.randomKey();

        //修改key名稱
        jedis.rename("age","newage");

        //檢視可以對應值的型別
        String name1 = jedis.type("name");
        System.out.println(name1);
        String maps = jedis.type("maps");
        System.out.println(maps);
    }

5.3 操作 String 相關API

//測試String相關
    @Test
    public void testString(){
        //set
        jedis.set("name","小陳");
        //get
        String s = jedis.get("name");
        System.out.println(s);
        //mset
        jedis.mset("content","好人","address","海淀區");
        //mget
        List<String> mget = jedis.mget("name", "content", "address");
        mget.forEach(v-> System.out.println("v = " + v));
        //getset
        String set = jedis.getSet("name", "小明");
        System.out.println(set);

        //............
    }

5.4 操作 List 相關 API

//測試List相關
    @Test
    public void testList(){

        //lpush
        jedis.lpush("names1","張三","王五","趙柳","win7");

        //rpush
        jedis.rpush("names1","xiaomingming");

        //lrange

        List<String> names1 = jedis.lrange("names1", 0, -1);
        names1.forEach(name-> System.out.println("name = " + name));

        //lpop rpop
        String names11 = jedis.lpop("names1");
        System.out.println(names11);

        //llen
        jedis.linsert("lists", BinaryClient.LIST_POSITION.BEFORE,"xiaohei","xiaobai");

          //........

    }

5.5 操作 Set 的相關 API

//測試SET相關
@Test
public void testSet(){

  //sadd
  jedis.sadd("names","zhangsan","lisi");

  //smembers
  jedis.smembers("names");

  //sismember
  jedis.sismember("names","xiaochen");

  //...
}

5.6 操作 ZSet 相關 API

//測試ZSET相關
@Test
public void testZset(){

  //zadd
  jedis.zadd("names",10,"張三");

  //zrange
  jedis.zrange("names",0,-1);

  //zcard
  jedis.zcard("names");

  //zrangeByScore
  jedis.zrangeByScore("names","0","100",0,5);

  //..

}

5.7 操作 Hash 相關 API

//測試HASH相關
@Test
public void testHash(){
  //hset
  jedis.hset("maps","name","zhangsan");
  //hget
  jedis.hget("maps","name");
  //hgetall
  jedis.hgetAll("mps");
  //hkeys
  jedis.hkeys("maps");
  //hvals
  jedis.hvals("maps");
  //....
}


6. SpringBoot 整合 Redis

Spring Boot Data(資料) Redis 中提供 RedisTemplate和StringRedisTemplate,其中 StringRedisTemplateRedisTemplate 的子類,兩個方法基本一致,不同之處主要體現在操作的資料型別不同,RedisTemplate 中的兩個泛型都是 Object,意味著儲存的 key 和 value 都可以是一個物件,而 StringRedisTemplate 的兩個泛型都是 String,意味著 StringRedisTemplate 的 key 和 value 都只能是字串。

注意:使用 RedisTemplate 預設是將物件序列化到 Redis 中,所以放入的物件必須實現物件序列化介面

6.1 環境準備

  1. 引入依賴
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 配置 application.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0

6.2 使用 StringRedisTemplateRedisTemplate

@Autowired
private StringRedisTemplate stringRedisTemplate;  //對字串支援比較友好,不能儲存物件
@Autowired
private RedisTemplate redisTemplate;  //儲存物件

@Test
public void testRedisTemplate(){
    System.out.println(redisTemplate);
    //設定redistemplate值使用物件序列化策略
    redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());//指定值使用物件序列化
    //redisTemplate.opsForValue().set("user",new User("21","小黑",23,new Date()));
    User user = (User) redisTemplate.opsForValue().get("user");
    System.out.println(user);
//      Set keys = redisTemplate.keys("*");
//      keys.forEach(key -> System.out.println(key));
    /*Object name = redisTemplate.opsForValue().get("name");
    System.out.println(name);*/

    //Object xiaohei = redisTemplate.opsForValue().get("xiaohei");
    //System.out.println(xiaohei);
    /*redisTemplate.opsForValue().set("name","xxxx");
    Object name = redisTemplate.opsForValue().get("name");
    System.out.println(name);*/
    /*redisTemplate.opsForList().leftPushAll("lists","xxxx","1111");
    List lists = redisTemplate.opsForList().range("lists", 0, -1);
    lists.forEach(list-> System.out.println(list));*/
}


//key的繫結操作 如果日後對某一個key的操作及其頻繁,可以將這個key繫結到對應redistemplate中,日後基於繫結操作都是操作這個key
//boundValueOps 用來對String值繫結key
//boundListOps 用來對List值繫結key
//boundSetOps 用來對Set值繫結key
//boundZsetOps 用來對Zset值繫結key
//boundHashOps 用來對Hash值繫結key

@Test
public void testBoundKey(){
    BoundValueOperations<String, String> nameValueOperations = stringRedisTemplate.boundValueOps("name");
    nameValueOperations.set("1");
    //yuew
    nameValueOperations.set("2");
    String s = nameValueOperations.get();
    System.out.println(s);

}


//hash相關操作 opsForHash
@Test
public void testHash(){
    stringRedisTemplate.opsForHash().put("maps","name","小黑");
    Object o = stringRedisTemplate.opsForHash().get("maps", "name");
    System.out.println(o);
}

//zset相關操作 opsForZSet
@Test
public void testZSet(){
    stringRedisTemplate.opsForZSet().add("zsets","小黑",10);
    Set<String> zsets = stringRedisTemplate.opsForZSet().range("zsets", 0, -1);
    zsets.forEach(value-> System.out.println(value));
}

//set相關操作 opsForSet
@Test
public void testSet(){
    stringRedisTemplate.opsForSet().add("sets","xiaosan","xiaosi","xiaowu");
    Set<String> sets = stringRedisTemplate.opsForSet().members("sets");
    sets.forEach(value-> System.out.println(value));
}

//list相關的操作opsForList
@Test
public void testList(){
    // stringRedisTemplate.opsForList().leftPushAll("lists","張三","李四","王五");
    List<String> lists = stringRedisTemplate.opsForList().range("lists", 0, -1);
    lists.forEach(key -> System.out.println(key));
}


//String相關的操作 opsForValue
@Test
public void testString(){
    //stringRedisTemplate.opsForValue().set("166","好同學");
    String s = stringRedisTemplate.opsForValue().get("166");
    System.out.println(s);
    Long size = stringRedisTemplate.opsForValue().size("166");
    System.out.println(size);
}


//key相關的操作
@Test
public void test(){
    Set<String> keys = stringRedisTemplate.keys("*");//檢視所有key
    Boolean name = stringRedisTemplate.hasKey("name");//判斷某個key是否存在
    stringRedisTemplate.delete("age");//根據指定key刪除
    stringRedisTemplate.rename("","");//修改key的名稱
    stringRedisTemplate.expire("key",10, TimeUnit.HOURS);
    //設定key超時時間 引數1:設定key名 引數2:時間 引數3:時間的單位
    stringRedisTemplate.move("",1);//移動key
}

7. Redis 主從複製

7.1 主從複製介紹

主從複製架構僅僅用來解決資料的冗餘備份,從節點僅僅用來同步資料,無法解決 Master 節點出現故障的自動故障轉移。

架構圖

7.2 搭建主從複製

1. 準備 3 臺機器並修改配置
  - master
    port 6379
    bind 0.0.0.0
    
  - slave1
    port 6380
    bind 0.0.0.0
    slaveof masterip masterport

  - slave2
    port 6381
    bind 0.0.0.0
    slaveof masterip masterport

2. 啟動 3 臺機器進行測試
  cd /usr/redis/bin
  ./redis-server /root/master/redis.conf
  ./redis-server /root/slave1/redis.conf
  ./redis-server /root/slave2/redis.conf

8. Redis Sentinel 哨兵機制

8.1 Sentinel 哨兵機制介紹

Sentinel(哨兵)是 Redis 的高可用性解決方案:由一個或多個 Sentinel 例項組成的 Sentinel 系統可以監視任意多個主伺服器,以及這些主伺服器屬下的所有從伺服器,並在被監視的主伺服器進入下線狀態時,自動將下線主伺服器屬下的某個從伺服器升級為新的主伺服器。

簡單的說哨兵機制就是帶有自動故障轉移功能的主從架構但是無法解決單節點併發壓力、單節點記憶體和磁碟物理上限的問題

架構原理

8.2 搭建哨兵架構

1. 在主節點上建立哨兵配置
  在 Master 對應 redis.conf 同目錄下新建 sentinel.conf 檔案,名字絕對不能錯

2. 配置哨兵,在 sentinel.conf 檔案中填入內容:
  sentinel monitor 被監控資料庫名字(自己起名字) ip port 1

3. 啟動哨兵模式進行測試
  redis-sentinel  /root/sentinel/sentinel.conf
    
  說明:這個後面的數字 2,是指當有兩個及以上的 sentinel 服務檢測到 Master 當機,才會去執行主從切換的功能

8.3 透過 SpringBoot 操作哨兵

修改 application.properties 配置檔案:

# redis sentinel 配置
# master書寫是使用哨兵監聽的那個名稱
spring.redis.sentinel.master=mymaster
# 連線的不再是一個具體redis主機,書寫的是多個哨兵節點
spring.redis.sentinel.nodes=192.168.202.206:26379

注意:如果連線過程中出現如下錯誤 RedisConnectionException: DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2)

解決方案:在哨兵的配置檔案中加入 bind 0.0.0.0 開啟遠端連線許可權

9. Redis 叢集

9.1 叢集介紹

Redis 在 3.0 後開始支援 Cluster 模式,目前 Redis 的叢集支援節點的自動發現,支援 slave-master 選舉和容錯,支援線上分片(sharding shard)等特性。reshard

叢集架構圖

叢集細節

  • 所有的 Redis 節點彼此互聯(PING-PONG 機制),內部使用二進位制協議最佳化傳輸速度和頻寬
  • 節點的 fail 是透過叢集中超過半數的節點檢測失效時才生效
  • 客戶端與 Redis 節點直連,不需要中間 proxy 層,客戶端不需要連線叢集所有節點,連線叢集中任何一個可用節點即可
  • redis-cluster 把所有的物理節點對映到[0-16383] slot 上,Cluster 負責維護 node<->slot<->value

9.2 叢集搭建

判斷一個是叢集中的節點是否可用,是叢集中的所用主節點選舉過程,如果半數以上的節點認為當前節點掛掉,那麼當前節點就是掛掉了,所以搭建 Redis 叢集時建議節點數最好為奇數,搭建叢集至少需要三個主節點,三個從節點,至少需要6個節點

1. 準備環境安裝ruby以及redis叢集依賴
  yum install -y ruby rubygems
  gem install redis-xxx.gem

2. 在一臺機器建立7個目錄

3. 每個目錄複製一份配置檔案
  cp redis-4.0.10/redis.conf 7000/
  cp redis-4.0.10/redis.conf 7001/
  cp redis-4.0.10/redis.conf 7002/
  cp redis-4.0.10/redis.conf 7003/
  cp redis-4.0.10/redis.conf 7004/
  cp redis-4.0.10/redis.conf 7005/
  cp redis-4.0.10/redis.conf 7006/

4. 修改不同目錄配置檔案
  - port     6379 .....                         //修改埠
  - bind  0.0.0.0                            //開啟遠端連線
  - cluster-enabled  yes                          //開啟叢集模式
  - cluster-config-file  nodes-port.conf //叢集節點配置檔案
  - cluster-node-timeout  5000             //叢集節點超時時間
  - appendonly  yes                          //開啟AOF持久化

5. 指定不同目錄配置檔案啟動七個節點
  ./redis-server  /root/7000/redis.conf
  ./redis-server  /root/7001/redis.conf
  ./redis-server  /root/7002/redis.conf
  ./redis-server  /root/7003/redis.conf
  ./redis-server  /root/7004/redis.conf
  ./redis-server  /root/7005/redis.conf
  ./redis-server  /root/7006/redis.conf

6. 檢視程序
  ps aux|grep redis

  1. 建立叢集
1. 複製叢集操作指令碼到 bin 目錄中
  cp /root/redis-4.0.10/src/redis-trib.rb .

2. 建立叢集
  ./redis-trib.rb create --replicas 1 192.168.202.205:7000 192.168.202.205:7001 192.168.202.205:7002 192.168.202.205:7003 192.168.202.205:7004 192.168.202.205:7005

3. 叢集建立成功出現如下提示

  1. 檢視叢集狀態
1. 檢視叢集狀態 check [原始叢集中任意節點] [無]
  ./redis-trib.rb check 192.168.202.205:7000

2. 叢集節點狀態說明
  - 主節點 
    主節點存在hash slots,且主節點的hash slots 沒有交叉
    主節點不能刪除
    一個主節點可以有多個從節點
    主節點當機時多個副本之間自動選舉主節點

  - 從節點
    從節點沒有hash slots
    從節點可以刪除
    從節點不負責資料的寫,只負責資料的同步
  1. 新增主節點
1. 新增主節點 add-node [新加入節點] [原始叢集中任意節點]
  ./redis-trib.rb  add-node 192.168.1.158:7006  192.168.1.158:7005
  注意:
    1.該節點必須以叢集模式啟動
    2.預設情況下該節點就是以master節點形式新增
  1. 新增從節點
1. 新增從節點 add-node --slave [新加入節點] [叢集中任意節點]
  ./redis-trib.rb  add-node --slave 192.168.1.158:7006 192.168.1.158:7000
  注意:當新增副本節點時沒有指定主節點,redis會隨機給副本節點較少的主節點新增當前副本節點
    
2. 為確定的master節點新增主節點 add-node --slave --master-id master節點id [新加入節點] [叢集任意節點]
  ./redis-trib.rb  add-node --slave --master-id 3c3a0c74aae0b56170ccb03a76b60cfe7dc1912e 127.0.0.1:7006  127.0.0.1:7000
  1. 刪除副本節點
1. 刪除節點 del-node [叢集中任意節點] [刪除節點id]
  ./redis-trib.rb  del-node 127.0.0.1:7002 0ca3f102ecf0c888fc7a7ce43a13e9be9f6d3dd1
  注意:被刪除的節點必須是從節點或沒有被分配 hash slots 的節點
  1. 叢集線上分片
1. 線上分片 reshard [叢集中任意節點] [無]
  ./redis-trib.rb  reshard  192.168.1.158:7000

10. Redis 實現分散式 Session 管理

10.1 管理機制

Redis 的 Session 管理是利用 Spring 提供的 Session 管理解決方案,將一個應用 Session 交給 Redis 儲存,整個應用中所有 Session 的請求都會去 Redis 中獲取對應的 Session 資料

10.2 開發 Session 管理

  1. 引入依賴
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
  1. 開發 Session 管理配置類
@Configuration
@EnableRedisHttpSession
public class RedisSessionManager {
   
}
  1. 測試