# 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),關聯式資料庫系統還是最多開發者使用的資料庫管理系統,佔前五名中的首四名!

### 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中的值。

對於一致性要求高的,從資料庫中讀,比如金融,交易等資料。其他的從Redis讀。
這種方案的好處是由mysql,常規的關係型資料庫來保證持久化,一致性等,不容易出錯。
#### 方案2
這裡還可以基於binlog使用mysql_udf_redis,將資料庫中的資料同步到Redis。
但是很明顯的,這將整體的複雜性提高了,而且本來我們在系統代碼中能很輕易完成的功能,現在需要依賴第三方工具,而且系統的整個邊界擴大了,變得更加不穩定也不好管理了。

### Redis同步到資料庫
也就是說將Redis中的資料變化同步到資料庫,那麼這裡是將Redis做為db,而真的db,資料庫只作為備份。(注意,這裡是一種不同看待事物的方式)。
這樣做的好處是:大大減小了資料庫的壓力,但是用redis做內存資料庫,狀態很不穩定。
雖然redis也有持久化機制,但是redis集群宕機後的重啟,資料加熱都很耗時。
另一方面,隨著大量插入或者更新導致redis持久化操作會嚴重拖累作為內存KV資料庫的優勢。
#### 方案1
將redis變更複制一份,丟到隊列中,給mysql消費。

很明顯這種方案,只能保證最終一致性,而且變更資料複製,隊列維護,這些雜七雜八的東西太複雜,拋棄。
具體做法是:寫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)