# Redis 如有錯誤歡迎糾正,小弟正在學習中,透過此文章你可以獲取的知識有 - 基本概念與用途 - 數據結構與優點 - Redis 的環境安裝 - 主要數據結構操作 - **String**:如何新增、刪除、查詢、設置過期時間。 - **List**:如何插入、讀取、刪除元素,以及保留指定範圍的元素。 - **Set**:如何新增、查詢、刪除集合中的元素,以及進行交集、聯集、差集操作。 - **SortedSet**:如何使用 ZADD 新增元素,使用 ZRANGE 查詢,使用 ZSCORE 查看分數,使用 ZRANK 和 ZREVRANK 查詢排名。 - **Hash**:如何新增、查詢、刪除哈希中的鍵值對,以及查看哈希的所有鍵值對和長度。 - **訂閱發布模式**:如何使用 SUBSCRIBE 和 PUBLISH 進行消息的訂閱和發布。 ## 介紹 Redis(全名 Remote Dictionary Server),為NoSQL資料庫,基於記憶體儲存系統,常見運用場景有 - DB數據緩存 - MQ消息隊列 在早期大多企業使用MySQL進行資料讀寫,但由於時代演進,資料庫的負擔隨著量大而導致效能瓶頸,且硬碟IO讀寫操作相較記憶體是非常慢的,後來演進成將一部分常被讀取的數據放入記憶體中,降低資料庫負擔以及增加效能查詢,因此就有了Redis的出現 ![image](https://hackmd.io/_uploads/HysNQ_0tA.png) https://x.com/bytebytego/status/1578259927490969600 ## 數據結構與優點 Redis 支援多種數據結構以及高階結構 - String 字串 - List 列表 - Set 集合 - SortedSet 有序排列 - Hash 哈希 - Stream 流 - Geospatial 地理空間 - HyperLogLog - Bitmap 位圖 - Bitfield 位域 特點 - 性能極高 - 數據結構支援豐富 - 支援各種程式語言 - 支持多種模式(資料持久化、主從設置等高效可靠特性) > 💡Redis鍵與值都是由二進制儲存的,因此不支援中文 ## 操作方式 操作方式可用 - CLI - API - GUI ## Redis環境安裝 個人喜歡用Docker所以只用docker docker compose ``` dockerfile version: "3.3" services: redis: image: redis:7.2.5 container_name: redis restart: always volumes: - redis_volume_data:/data ports: - 6379:6379 redis_insight: image: redislabs/redisinsight:1.14.0 container_name: redis_insight restart: always ports: - 8001:8001 volumes: - redis_insight_volume_data:/db volumes: redis_volume_data: redis_insight_volume_data: ``` 這邊要注意Host 為你的docker compose services name,這邊是redis ![image](https://hackmd.io/_uploads/ry19oI0Y0.png) 成功連上 ![image](https://hackmd.io/_uploads/Hkuqj8CKR.png) ![image](https://hackmd.io/_uploads/ry19oI0Y0.png) # Redis 資料結構與操作 ## String ### SET(新增) 透過SET來建立鍵值 範例 ``` redis Connecting... Pinging Redis server on redis:6379... Connected. Ready to execute commands. >> set Jed Good "OK" >> get Jed "Good" ``` 另外前面所提Redis是由二進制儲存,不支援儲存中文的,不過可以透過 --raw 以原始格式(raw format)顯示 Redis 命令的輸出 ``` redis-cli --raw ``` ``` redis >> SET jed 是個大帥哥 "OK" >> get jed "\xe6\x98\xaf\xe5\x80\x8b\xe5\xa4\xa7\xe5\xb8\xa5\xe5\x93\xa5" ``` 這邊需要注意Redis默認為String,透過以下範例可以驗證取得的型態是String 範例 ``` redis >> set year 2024 "OK" >> get year "2024" ``` ### DEL(刪除) 如果要刪除可以使用DEL ``` redis >> del jed (integer) 0 >> get jed (nil) ``` ### EXISTS(是否存在) 也能透過 EXISTS來查詢鍵值是否存在,1代表存在,0代表不存在 ``` redis >> exists jed (integer) 0 >> exists year (integer) 1 ``` ### KEYS(查詢鍵) 查詢所有存在的鍵 ``` redis >> KEYS * 1) "year" 2) "Jed" ``` 還能透過*表示like來查鍵 ``` redis >> KEYS ye* 1) "year" ``` ### FLUSHALL(刪除所有鍵) 如果要刪除全部鍵可以使用FLUSHALL,注意謹慎使用此語法,並免快取雪崩 ``` redis >> FLUSHALL "OK" >> KEYS * (empty list or set) ``` ### TTL(設置過期時間) TTL全名 Time To Live,可設置過期時間,在後端開發上會依據需求而定過期時間,-1表示為時間永久 ``` redis >> TTL jed (integer) -1 ``` #### EXPIRE、SETEX、SETNX(設置過期時間) 也可以透過EXPIRE設置時間 ``` redis >> EXPIRE jed 180 (integer) 1 >> TTL jed (integer) 175 ``` 也能透過SETEX設置鍵值並給予過期時間 ``` redis >> SETEX jed 180 666 "OK" >> get jed "666" >> ttl jed (integer) 170 ``` 如果要針對不存在的鍵在更新可以使用SETNX,如果存在鍵則不做任何動作 jed-girl-firendQQ為不存在的鍵,可看到建立成功傳回1, ttl也設置為-1, ``` redis >> SETNX jed-girl-firendQQ -1 (integer) 1 >> get jed-girl-firendQQ "-1" ``` jed為存在的鍵,可以看到結果為0也沒更新為777 ``` redis >> SETNX jed 777 (integer) 0 >> set jed >> get jed "666" ``` ## List 列表, ### LPUSH、RPUSH(插入列表) 從左至右 新增element 至list ``` redis >> LPUSH queue a (integer) 1 ``` 也能一次push多個element ``` redis >> LPUSH queue p p l e (integer) 5 >> LRANGE queue 0 -1 1) "e" 2) "l" 3) "p" 4) "p" 5) "a" ``` 可以發現順序是相反的從左至右,所以一開始輸入的a為index 4 同理由右至左就使用RPUSH ``` redis >> RPUSH newqueue a (integer) 1 >> RPUSH newqueue p p l e (integer) 7 >> LRANGE newqueue 0 -1 1) "a" 2) "p" 3) "p" 4) "l" 5) "e" ``` ### LRANGE(讀取列表範圍) 讀取list 從index 0 到 -1(最後) ``` redis >> LRANGE queue 0 -1 1) "a" ``` ### LPOP、RPOP(刪除列表元素) 刪除list中的element,LPOP由左,R由右,來回顧目前list的狀態 > 💡注意 POP刪除多個element 只支援redis 6.2以上 | 0 | 1 | 2 | 3 | 4 | | -------- | -------- | -------- | -------- | -------- | | a | p | p | l | e | 接著使用RPOP 刪除右邊兩個element ``` redis >> RPUSH queue a p p l e (integer) 5 >> LRANGE queue 0 -1 1) "a" 2) "p" 3) "p" 4) "l" 5) "e" >> RPOP queue 2 1) "e" 2) "l" >> LPOP queue 2 ``` 刪除list左邊前兩個element,所以是e l | 0 | 1 | 2 | -------- | -------- | -------- | | a | p | p | 然後再使用LPOP一樣刪除兩個element ``` 1) "a" 2) "p" >> LRANGE queue 0 -1 1) "p" ``` 刪除list左邊前兩個element,所以是a p,最終只會留下 p | 0 | | -------- | | p | ### LTRIM(保留指定範圍刪除全部元素) 保留指定範圍的element,並刪除以外所有element 我們新增一個 a p p l e與 b a r 的 list,接著把b a r刪除 ``` redis >> RPUSH newqueue a p p l e b a r (integer) 8 >> LRANGE newqueue 0 -1 1) "a" 2) "p" 3) "p" 4) "l" 5) "e" 6) "b" 7) "a" 8) "r" >> LTRIM newqueue 0 4 "OK" >> LRANGE newqueue 0 -1 1) "a" 2) "p" 3) "p" 4) "l" 5) "e" ``` ### LLEN(查看列表長度) 由上面例子可透過LLEN來查看目前list中的長度 ``` redis >> LLEN queue (integer) 1 ``` ## SET 集合,特性為無序的且不能有重複的element ### SADD(集合新增元素) 建立SET與新增element,當有重複element會return 0代表操作失敗 ``` redis >> SADD box apple (integer) 1 >> SMEMBERS box 1) "apple" >> SADD box apple (integer) 0 ``` ### SMEMBERS(列出所有集合元素) 列出所有SET element ``` redis >> SMEMBERS box 1) "apple" ``` ### SISMEMBER(是否存在在集合) 查詢element是否在SET裡面 ``` redis >> SISMEMBER box apple (integer) 1 >> SISMEMBER box bar (integer) 0 ``` ### SREM(刪除集和元素) 刪除SET中element ``` redis >> SREM box apple (integer) 1 >> SMEMBERS box (empty list or set) ``` ### SINTER、SUNION、SDIFF(交集、聯集、差集) 交集、聯集、差集,透過以下兩個SET操作 set1 | 0 | 1 | 2 | |----|----|----| | a | b | c | set2 | 0 | 1 | 2 | |----|----|----| | b | c | d | 建立SET ``` resis >> SADD set1 a b c (integer) 3 >> SADD set2 b c d ``` SINTER 集合 ``` (integer) 3 >> SINTER set1 set2 1) "b" 2) "c" ``` SUNION 聯集 ``` >> SUNION set1 set2 1) "a" 2) "b" 3) "c" 4) "d" ``` SDIFF 差集 ``` redis >> SDIFF set1 set2 1) "a" ``` ## SortedSets 有序SET一樣不可重複,且每個element都會對應一個double score,score是可以重複的,當分數重複時會取字符字串順序 ### 字符串排序規則 當多個字符串具有相同的分數時,可以按詞典順序(字典順序 lexicographical order))對這些字符串進行排序。詞典順序類似於字典中的單詞排序規則,即先比較字符串的第一個字符,如果相同則比較第二個字符,以此類推。 假設我們有以下字符串及其分數: - `"apple"`: 5 分 - `"banana"`: 3 分 - `"cherry"`: 5 分 - `"date"`: 3 分 - `"elderberry"`: 5 分 1. **按分數排序**: 先按分數從低到高進行排序: - `"banana"`: 3 分 - `"date"`: 3 分 - `"apple"`: 5 分 - `"cherry"`: 5 分 - `"elderberry"`: 5 分 > 💡SortedSets排序是由小到大 2. **按詞典順序排序**: 在分數相同的情況下,按詞典順序進行排序: - `"apple"`: 5 分 - `"cherry"`: 5 分 - `"elderberry"`: 5 分 ### ZADD 常見情境為排行榜,舉成績單排行榜例子,先新增所有學生成績 ``` redis >> ZADD transcript 100 Jed 95 Jack 99 Maggie 100 Jason 94 Tom (integer) 5 ``` ### ZRANGE 查看element ``` redis >> ZRANGE transcript 0 -1 1) "Tom" 2) "Jack" 3) "Maggie" 4) "Jason" 5) "Jed" ``` 如果要看分數可以加上 WITHSCORES ``` redis >> ZRANGE transcript 0 -1 WITHSCORES 1) "Tom" 2) "94" 3) "Jack" 4) "95" 5) "Maggie" 6) "99" 7) "Jason" 8) "100" 9) "Jed" 10) "100" ``` ### ZSCORE 查看element 分數 ``` redis >> ZSCORE transcript Jed "100" ``` ### ZRANK、ZREVRANK 查看element 排名,分別由小到大與由大到小 ``` redis >> ZRANK transcript Jed (integer) 4 >> ZREVRANK transcript Jed (integer) 0 ``` ## Hash 鍵與值的集合 ### HSET 新增鍵與值 name 與 age ``` redis >> HSET person name Jed (integer) 1 >> HSET person age 26 ``` ### HGET 獲取鍵的值 ``` redis (integer) 1 >> HGET person name "Jed" >> HGET person age "26" ``` ### HGETALL 獲取所有Hash key value ``` redis >> HGETALL person 1) "name" 2) "Jed" 3) "age" 4) "26" ``` ### HDEL 刪除key value ``` redis >> HDEL person age (integer) 1 >> HGETALL person 1) "name" 2) "Jed" ``` ### HEXISTS 查看key是否存在 ``` redis >> HEXISTS person age (integer) 0 ``` ### HKEYS 列出所有key ``` redis >> HKEYS person 1) "name" ``` ### HLEN 查看Hash長度 ``` redis >> HLEN person (integer) 1 ``` # 訂閱發布模式 有使用MQTT的人一定很熟悉 Subscribe與Publish,其實不難理解 以youtube為例,你訂閱了Youtube官方頻道就相對於Subscrib,當官方頻道上傳新影片時你會收到消息,官方上傳並發布訊息給你就相對於Publish ### SUBSCRIBE、PUBLISH ``` redis SUBSCRIBE mychannel ``` ``` redis publish mychannel 海嘯警報 ``` ![image](https://hackmd.io/_uploads/B1_cgUz90.png)