Redis協議
Redis 的通信協議是基於文本的,且以行為划分,每行以 \r\n 結束。每一行都有一個消息頭,消息頭共分為5種分別如下:
+表示一個正確的狀態信息,具體信息是當前行 + 后面的字符;
- 表示一個錯誤信息,具體信息是當前行-后面的字符;
* 表示消息體總共有多少行,不包括當前行,* 后面是具體的行數;
$ 表示下一行數據長度,不包括換行符長度 \r\n, $ 后面則是對應的長度的數據;
: 表示返回一個數值,:后面是相應的數字節符;

from socket import * client = socket(AF_INET, SOCK_STREAM) client.connect(('localhost', 6379)) def set(key,value): value = str(value) sendData = '*3\r\n$3\r\nSET\r\n${0}\r\n{1}\r\n${2}\r\n{3}\r\n'.format(len(key),key,len(value),value) client.send(sendData.encode('utf-8')) print(client.recv(1024).decode('utf-8')) def get(key): sendData = '*2\r\n$3\r\nGET\r\n${0}\r\n{1}\r\n'.format(len(key),key) client.send(sendData.encode('utf-8')) print(client.recv(1024).decode('utf-8'))
Redis持久化
什么是持久化?為什么要持久化?
redis之所以高效是因為數據存儲在內存中,內存中數據會斷電丟失,所以需要持久化,持久化是將內存中的數據寫到磁盤中
redis持久化的方式:
- RDB 持久化可以在指定的時間間隔內生成數據集的時間點快照
- AOF 持久化記錄服務器執行的所有寫操作命令,並在服務器啟動時,通過重新執行這些命令來還原數據集
RDB(redis database)
redis將數據庫快照保存在名為dump.rdb的二進制文件中,可以對redis進行設置,讓他在“N秒內數據集至少有M個改動”時保存數據集。
例:60s內至少有1000個鍵被改動時,自動保存一次數據集
save 60 1000
例:60s內至少有1000個鍵被改動,或300s內至少有10個鍵被改動,或600s內至少有1個鍵被改動(如果離上次保存數據集的時間越來越長,觸發條件就越來越容易,這很符合實際需求)
save 900 1 save 300 10 save 60 10000
RDB優點:
rdb文件緊湊,體積小,無IO阻塞(fork子進程來持久化數據),恢復速度比AOF快
RDP缺點:
實時性比較差(全量備份,備份頻率比較低,數據丟失較多),當數據比較多時fork比較耗時
AOF(append-only file)
快照功能實時性不好,如果故障停機,服務器會丟失最近寫入,且未保存到快照中的數據。
重1.1版本開始,redis增加了一種完全實時性的持久化方式:AOF持久化
通過配置文件打開AOF功能:
appendonly yes
AOF配置選項
- 每次有新命令追加到AOF文件就執行一次
fsync
:非常慢,也非常安全 - 每秒
fsync
一次:足夠快(和使用RDB持久化差不多),並且故障只丟失1s中的數據 - 從不
fsync
:將數據交給操作系統來處理:更快,也更不安全
AOF優點:
實時性比RDB好(增量備份,備份頻率比較高,數據丟失較少)
AOF缺點:
AOF文件大,恢復速度慢,對性能影響大
Redis事務
redis事務有兩個重要保證:
- 事務是一個單獨的隔離操作:事務中所有命令都會序列化、按順序執行。事務在執行過程中,不會被其他客戶端發過來的命令所打斷。
- 事務是一個原子操作:事務中的命令要么全部被執行,要么全部都不執行。
以上是來自於http://redisdoc.com/topic/transaction.html的解釋,我之所以畫杠是因為和下面的說話沖突
redis事務不支持回滾:
- Redis 命令只會因為錯誤的語法而失敗(並且這些問題不能在入隊時發現),或是命令用在了錯誤類型的鍵上面:這也就是說,從實用性的角度來說,失敗的命令是由編程錯誤造成的,而這些錯誤應該在開發的過程中被發現,而不應該出現在生產環境中。
- 因為不需要對回滾進行支持,所以 Redis 的內部可以保持簡單且快速。
redis事務的用法
一個事務
> MULTI OK > INCR foo QUEUED > INCR bar QUEUED > EXEC 1) (integer) 1 2) (integer) 1
事務取消
redis> SET foo 1 OK redis> MULTI OK redis> INCR foo QUEUED redis> DISCARD OK redis> GET foo "1"
check-and-set(樂觀鎖)
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC
競爭問題
當多個client對一個資源同時寫的時候會發生競爭
例:
count被cli1,cli2共同寫入,先初始化count的值
127.0.0.1:6379> set count 0 OK
cli1.py和cli2.py都是如下程序,兩個程序同時執行,若沒有競爭發生,count的值應該是2000
import redis import time rc = redis.StrictRedis() for i in range(1000): count = int(rc.get("count")) count += 1 rc.set('count', count) time.sleep(0.01)
cli1.py和cli2.py執行結束后,查看count的值
127.0.0.1:6379> get count "1285"
對於計數問題,解決競爭方式很簡單,將程序改成這樣就行了
import redis import time rc = redis.StrictRedis() for i in range(1000): rc.incr('count') time.sleep(0.01)
除了計數,還有很多競爭資源並發寫的例子。為了程序簡單,下面就用計數這個例子來討論怎么用redis事務來解決
樂觀鎖解決競爭問題
check-and-set.py
import redis import time rc = redis.StrictRedis() def add(): with rc.pipeline() as pipe: try: pipe.watch('count') count = int(pipe.get('count')) + 1 pipe.multi() pipe.set('count', count) pipe.execute() except redis.WatchError: add() finally: pipe.reset() for i in range(1000): add() time.sleep(0.01)
先將count清0,將這個程序起兩個終端同時執行,在查看count值
127.0.0.1:6379> get count "2000"
Redis命令
Redis性能測試
redis 性能測試的基本命令如下:
redis-benchmark [option] [option value]
序號 | 選項 | 描述 | 默認值 |
---|---|---|---|
1 | -h | 指定服務器主機名 | 127.0.0.1 |
2 | -p | 指定服務器端口 | 6379 |
3 | -s | 指定服務器 socket | |
4 | -c | 指定並發連接數 | 50 |
5 | -n | 指定請求數 | 10000 |
6 | -d | 以字節的形式指定 SET/GET 值的數據大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用隨機 key, SADD 使用隨機值 | |
9 | -P | 通過管道傳輸 <numreq> 請求 |
1 |
10 | -q | 強制退出 redis。僅顯示 query/sec 值 | |
11 | --csv | 以 CSV 格式輸出 | |
12 | -l | 生成循環,永久執行測試 | |
13 | -t | 僅運行以逗號分隔的測試命令列表。 | |
14 | -I | Idle 模式。僅打開 N 個 idle 連接並等待。 |
keep-alive對性能的影響
1w個SET操作的請求,保持TCP連接情況下用了0.11s
redis-benchmark -n 10000 -t set -k 1 ====== SET ====== 10000 requests completed in 0.11 seconds 50 parallel clients 3 bytes payload keep alive: 1 99.99% <= 1 milliseconds 100.00% <= 1 milliseconds 87719.30 requests per second
1w個SET操作的請求,不保持TCP連接情況下用了1.7s,因為TCP通過三次握手來建立連接,頻繁的建立連接比較耗時
redis-benchmark -n 10000 -t set -k 0 ====== SET ====== 10000 requests completed in 1.70 seconds 50 parallel clients 3 bytes payload keep alive: 0 5.15% <= 1 milliseconds ... 5878.90 requests per second
並發連接數對性能影響
5個client並發20W SET請求用了2.27s
redis-benchmark -n 200000 -t set -c 5 ====== SET ====== 200000 requests completed in 2.27 seconds 5 parallel clients 3 bytes payload keep alive: 1 99.99% <= 1 milliseconds ... 88300.22 requests per second
50個client並發20W SET請求2.28s,
redis-benchmark -n 200000 -t set -c 50 ====== SET ====== 200000 requests completed in 2.28 seconds 50 parallel clients 3 bytes payload keep alive: 1 99.71% <= 1 milliseconds ... 87565.68 requests per second
500個client並發20W SET請求2.62, 多client並發請求對性能影響不大
redis-benchmark -n 200000 -t set -c 500 ====== SET ====== 200000 requests completed in 2.62 seconds 500 parallel clients 3 bytes payload keep alive: 1 0.00% <= 3 milliseconds ... 76248.57 requests per second