深入理解Redis


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'))    
View Code

 

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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM