# Redis了解與應用 ###### tags: `RD1` :::spoiler 目錄 [TOC] ::: ## 什麼是 Redis? Redis是完全開源免費的,遵守BSD協議,是一個高性能的key-value數據庫。 Redis與其他key - value緩存產品有以下三個特點: 1. Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啟的時候可以再次加載進行使用。 2. Redis不僅僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。 3. Redis支持數據的備份,即master-slave模式的數據備份。 ### Redis數據類型 1. String(字符串)二進制安全的意思是redis的string可以包含任何數據。比如jpg圖片或者序列化的對象。 string類型是Redis最基本的數據類型,一個鍵最大能存儲512MB。 2. Hash(哈希) Redis hash是一個鍵值對集合。 Redis hash是一個string類型的field和value的映射表,hash特別適合用於存儲對象。每個hash可以存儲2^32 - 1鍵值對(40多億)。 3. List(列表) Redis列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素導列表的頭部(左邊)或者尾部(右邊)。 4. Set(集合) Redis的Set是string類型的無序集合。 集合是通過哈希表實現的,所以添加,刪除,查找的複雜度都是O(1)。 5. zset(sorted set:有序集合) Redis zset和set一樣也是string類型元素的集合,且不允許重複的成員。 不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。zset的成員是唯一的,但分數(score)卻可以重複。 :angry: ## Redis與關聯式資料庫差異 <br> ### SQL (關聯式) 與NoSQL (非關聯式) 資料庫的比較 <br> 幾十年來,用於開發應用程式的主要資料模型,均是諸如 Oracle、DB2、SQL Server、MySQL 和 PostgreSQL 等關聯式資料庫所採用的關聯式資料模型。一直到 2000 年中後期,其他資料模型才開始廣受採納和運用。為了區分和歸類這些新類型的資料庫和資料模組,因此創造了「NoSQL」這個名詞。「NoSQL」這個名詞常常與「非關聯式」互換使用。 雖然 NoSQL 資料類型多樣且功能各異,但您可從以下表格瞭解 SQL 和 NoSQL 資料庫的一些差異性 <br> <br> <br> | 差異 | 關聯式資料庫 | NoSQL 資料庫 | | -------- | -------- | -------- | | 最佳工作負載 | 關聯式資料庫專門用於交易性以及高度一致性的線上交易處理 (OLTP) 應用程式,並且非常適合於線上分析處理 (OLAP) 使用。 | NoSQL 資料庫專門用於包含低延遲應用程式的多樣資料存取模式。NoSQL 搜尋資料庫專門用於進行半結構資料的分析。 | | 資料模型 | 關聯式模型將資料標準化,成為由列和欄組成的表格。結構描述嚴格定義表格、列、欄、索引、表格之間的關係,以及其他資料庫元素。此類資料庫強化資料庫表格間的參考完整性。 | NoSQL 資料庫提供鍵值、文件和圖形等多種資料模型,具有最佳化的效能與規模。 | | ACID 屬性 | 關聯式資料庫則提供單元性、一致性、隔離性和耐用性 (ACID) 的屬性<li>單元性要求交易完整執行或完全不執行。</li><li>一致性要求進行交易時資料就必須符合資料庫結構描述。</li><li>隔離性要求並行的交易必須分開執行。</li><li> 耐用性要求從意外的系統故障還原成上個已知狀態的能力</li>| NoSQL 資料庫通常透過鬆綁部分關聯式資料庫的 ACID 屬性來取捨,以達到能夠橫向擴展的更彈性化資料模型。這使得 NoSQL 資料庫成為橫向擴展超過單執行個體上限的高吞吐量、低延遲使用案例的最佳選擇| | 效能 | 一般而言,效能取決於磁碟子系統。若要達到頂級效能,通常必須針對查詢、索引及表格結構進行優化。 | 效能通常會受到基礎硬體叢集大小、網路延遲,以及呼叫應用程式的影響。 | | 擴展 | 關聯式資料庫通常透過增加硬體運算能力向上擴展,或以新增唯讀工作負載複本的方式向外擴展。 | NoSQL 資料庫通常可分割,因為存取模式可透過使用分散式架構來向外擴展,以近乎無限規模的方式提供一致效能來增加資料吞吐量。 | | API | 存放和擷取資料的請求是透過符合結構式查詢語言 (SQL) 的查詢進行通訊。這些查詢是由關聯式資料庫剖析和執行。 | 以物件為基礎的 API 讓應用程式開發人員可輕鬆存放和擷取資料結構。應用程式可透過分區索引鍵查詢鍵值組、欄集,或包含序列化應用程式物件與屬性的半結構化文件。 | ### SQL 與NoSQL 術語的比較 | SQL | MongoDB | Couchbase | | -------- | -------- | ----| | 表 | 集 | 資料儲存貯體 | | 列 | 列 | 文件 | | 欄 | 欄 | 欄位 | | 主索引鍵| 物件 ID | 文件 ID | | 索引 | 索引 | 索引 | | 檢視 | 檢視 | 檢視 | | 巢狀表格或物件| 內嵌文件|對應 | | 陣列 | 陣列 | 清單 | 由於 NoSQL 的種類很多,而技術的成熟度與使用場景不一,所以目前業界還是以 SQL 資料庫佔大多數。在 [Stackoverflow 2018 年的調查結果](https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-web-frameworks),關聯式資料庫系統還是最多開發者使用的資料庫管理系統,佔前五名中的首四名! ![](https://i.imgur.com/l2BIHey.png) ### Redis 的優勢 > 下方來源 [Redis](https://aws.amazon.com/tw/redis/) #### 記憶體內資料存放區 所有 Redis 資料都放在伺服器的主要記憶體內,與 PostgreSQL、Cassandra、MongoDB 和其他將大多數資料存放在磁碟或 SSD 的資料庫並不相同。使用傳統磁碟的資料庫需要在磁碟來回處理才能執行大多數的操作,而 Redis 這類記憶體內資料存放區則不受此限制。這樣它們便能夠支援更大規模的操作,而且回應時間更快。這項優勢提供超快速的效能,平均讀取和寫入操作時間低於一毫秒,並支援每秒百萬個操作。 #### 彈性的資料結構 簡易的鍵值資料存放區提供的資料結構有限,而 Redis 則提供多樣化的資料結構以滿足應用程式的需要。Redis 資料類型包括: * 字串 – 文字或二進位資料,最大 512 MB * 清單 – 按新增順序排列的字串集合 * 資料集 – 未排序的字串集合,可與其他資料集類型交叉、合併和區分 * 排序資料集 – 依數值排列的資料集 * 雜湊 – 存放欄位和值清單的資料結構 * 點陣圖 – 提供位元層級操作的資料類型 * HyperLogLogs – 用來評估資料集獨特項目的概率資料結構' ## Redis基本操作方法 常用指令 > 下方指令來源 [資料庫的好夥伴:Redis](https://blog.techbridge.cc/2016/06/18/redis-introduction/) ### SET, GET ```redis redis> SET mykey "Hello" redis> GET mykey "Hello" ``` 前面有提到說 Redis 是一個 key-value pair 的資料庫,因此最簡單的 SET 就是設定某個 key 的值是多少,要取出來的話就用 GET 就好。 ### INCR, DECR ```redis redis> SET mykey "10" redis> DECR mykey (integer) 9 redis> INCR mykey (integer) 10 ``` 顧名思義就是針對某個 key 加一或減一的意思,像是程式語言裡面的```mykey++```跟```mykey--```。 還有 ```INCRBY``` 與 ```DECRBY```,可以指定你要加減的數量是多少。 ### HSET, HGET ```redis redis> HSET mydata name "nick" redis> HSET mydata nickname "nicknick" redis> HGET mydata name "nick" ``` H 就是 Hashmap 的意思,所以你可以存取一個 value 底下的 field,讓你可以更多元的使用,例如說你可以定義 key 的規則是:POST + 文章 id,裡面就可以存這篇文章的讚數、回覆數等等,就不用每一次都去 Database 裡面重新抓取。 ### SADD, SCARD ```redis redis> SADD myset "nick" redis> SADD myset "peter" redis> SADD myset "nick" redis> SCARD myset (integer) 2 ``` SADD 的 S 就是 ```Set``` 的意思,這邊的 ```Set``` 指的是資料結構學過的那個 ```Set```,裡面不會有重複的內容,第二次插入的元素會被忽略。 ### ZADD `zadd key score member ` 和 set 一樣是string類型元素的集合,不允許重複的key 每個元素都會關聯一個 double 分數,會通過該分數為該集合排序 ```redis 127.0.0.1:6379> ZADD myset 0 hi (integer) 1 127.0.0.1:6379> ZADD myset 1.3 hi (integer) 0 127.0.0.1:6379> ZADD myset 1.3 hey (integer) 1 127.0.0.1:6379> ZADD myset 22 fine (integer) 1 127.0.0.1:6379> ZRANGEBYSCORE myset 0 20 1) "hey" 2) "hi" ``` ### LPUSH, RPUSH, LSET, LRANGE ```redis redis> LPUSH mylist "a" redis> LPUSH mylist "b" redis> RPUSH mylist "c" redis> LRANGE mylist 0 -1 1) "b" 2) "a" 3) "c" redis> LSET mylist 0 "d" redis> LRANGE mylist 0 -1 1) "d" 2) "a" 3) "c" ``` 這邊的資料結構是 ```List```,你可以選擇從左邊或是右邊 push 值進去,對應到的指令就是 ```LPUSH``` 與 ```RPUSH```,```LSET``` 則是指定某個 index 的 value 是多少。 ```LRANGE```可以印出指定範圍的值,支援```-1```這種形式,表示最後一個值。 ### hash (HMSET,HGET) hash 是 string 類型的 field-value 的映射表 ```redis 127.0.0.1:6379> HMSET myset time 20:00 des "hi" type 1 OK 127.0.0.1:6379> HGETALL myset 1) "time" 2) "20:00" 3) "des" 4) "hi" 5) "type" 6) "1" 127.0.0.1:6379> HGET myset time "20:00" ``` ## Redis Commander 這裡我還沒看 先貼 [How To Configure Redis + Redis Commander + Docker](https://hackernoon.com/how-to-configurate-redis-redis-commander-docker-616136f2) [redis-commander](https://hub.docker.com/r/rediscommander/redis-commander) ## Redis與資料庫資料同步解決方案 ### 資料庫同步到Redis 我們大多傾向於使用這種方式,也就是將資料庫中的變化同步到Redis,這種更加可靠。Redis在這裡只是做緩存。 #### 方案1 做緩存,就要遵循緩存的語義規定: 讀:讀緩存redis,沒有,讀mysql,並將mysql的值寫入到redis。 寫:寫mysql,成功後,更新或者失效掉緩存redis中的值。 ![](https://i.imgur.com/pCaQLtj.png) 對於一致性要求高的,從資料庫中讀,比如金融,交易等資料。其他的從Redis讀。 這種方案的好處是由mysql,常規的關係型資料庫來保證持久化,一致性等,不容易出錯。 #### 方案2 這裡還可以基於binlog使用mysql_udf_redis,將資料庫中的資料同步到Redis。 但是很明顯的,這將整體的複雜性提高了,而且本來我們在系統代碼中能很輕易完成的功能,現在需要依賴第三方工具,而且系統的整個邊界擴大了,變得更加不穩定也不好管理了。 ![](https://i.imgur.com/KThiiQr.png) ### Redis同步到資料庫 也就是說將Redis中的資料變化同步到資料庫,那麼這裡是將Redis做為db,而真的db,資料庫只作為備份。(注意,這裡是一種不同看待事物的方式)。 這樣做的好處是:大大減小了資料庫的壓力,但是用redis做內存資料庫,狀態很不穩定。 雖然redis也有持久化機制,但是redis集群宕機後的重啟,資料加熱都很耗時。 另一方面,隨著大量插入或者更新導致redis持久化操作會嚴重拖累作為內存KV資料庫的優勢。 #### 方案1 將redis變更複制一份,丟到隊列中,給mysql消費。 ![](https://i.imgur.com/yzDKVnB.png) 很明顯這種方案,只能保證最終一致性,而且變更資料複製,隊列維護,這些雜七雜八的東西太複雜,拋棄。 具體做法是:寫redis時,同時將資料寫到redis維護的另外一個隊列中,但這樣又要增加內存消耗了。 其實還有一種方式是使用redis的pipeline通知機制,但是redis是不保證的一定通知到的(得到被通知方的ack)。 #### 方案2 定時刷新redis中的最新資料到mysql。 很明顯的,無論定時任務的間距有多小,都會留下時間縫隙,如果發生宕機,故障等都會造成資料的不一致性。 雖然可以通過:比較redis和資料庫中的資料,同步那些需要同步的變化資料,但是會加大計算量和程序的複雜度。 ## 參考資料 [該用 MySQL 或 MongoDB?選擇資料庫前你該了解的事](https://tw.alphacamp.co/blog/mysql-and-mongodb-comparison) [什麼是 NoSQL?](https://aws.amazon.com/tw/nosql) [Redis](https://aws.amazon.com/tw/redis/) [SQL/NoSQL是什麼?認識資料庫管理系統DBMS](https://tw.alphacamp.co/blog/sql-nosql-database-dbms-introduction) [10 分鐘徹底理解 Redis 的持久化和主從複製](https://iter01.com/437667.html) [Redis 设计与实现-事务](https://redisbook.readthedocs.io/en/latest/feature/transaction.html) [redis語法](https://redis.io/commands)