WhyRedis4.0?

仲肥發表於2018-06-08

前言

Redis作為時下最火爆的NoSQL資料庫以效能強悍、資料結構豐富著稱,同時其成長的腳步也從未停止,自誕生伊始已經歷多次蛻變不斷推出新功能。

社群最新GA版本Redis 4.0推出已近一年,阿里雲資料庫Redis 4.0版也上線近半年,之前關於Redis 4.0的系列文章從原始碼實現來分析這些新功能,本文旨在從使用者角度出發,讓Redis的使用者能夠快速瞭解並使用Redis 4.0帶來的福利。

Lazyfree

大key刪除的問題想必很多使用者都遇到過,Redis除string外還支援list、set、hash和sorted set等複雜資料結構,這些資料結構豐富了Redis的用法,但是如果使用不當造成單key體積過大的話就會引起一些問題。

舉個簡單的例子,假如某社交網站有一個大V,有上百萬的粉絲,我們可以用set集合型別的資料結構來儲存他的粉絲ID,儲存粉絲集合的key叫做funs好了,我們來看下粉絲數:

127.0.0.1:6379> SCARD funs
(integer) 6320505

的確是大V,有600多萬的粉絲,但是很不幸的有一天這個大V登出了,這時就要刪除他的資訊,我們用DEL命令來刪除這個key:

127.0.0.1:6379> DEL funs
(integer) 1
(3.11s)
127.0.0.1:6379> slowlog get
1) 1) (integer) 4
   2) (integer) 1528169923
   3) (integer) 3104812
   4) 1) "DEL"
      2) "funs"
   5) "127.0.0.1:48398"
   6) ""
  • 小插曲:Redis 4.0擴充套件了slowlog的返回結果,展示了產生慢日誌的客戶端IP:PORT以便追本溯源。

可以看到刪除這個動作居然耗時3秒多,也就意味著這3秒內Redis無法執行其他命令,這對於線上業務來講是有傷害的,那麼如何避免刪除大key時的阻塞問題呢?Redis 4.0推出了Lazyfree這一功能,使用UNLINK命令來刪除大key,主執行緒只負責把key從資料庫中”摘除”,真正的釋放動作放在了BIO後臺執行緒去做,我們來看下效果:

127.0.0.1:6379> UNLINK funs
(integer) 1
(3.11s)
127.0.0.1:6379> slowlog get
(empty list or set)

可以看到UNLINK執行很快沒有產生slowlog。

Lazyfree一共有3個命令:

  1. UNLINK:非同步刪除key
  2. FLUSHDB ASYNC:非同步清空當前DB
  3. FLUSHALL ASYNC:非同步清空所有DB

以及4個配置項:

  1. lazyfree-lazy-expire:非同步刪除過期key
  2. lazyfree-lazy-eviction:非同步淘汰key
  3. lazyfree-lazy-server-del:隱式刪除時採取非同步刪除,比如rename a b,若b存在則需刪除b
  4. slave-lazy-flush:全量同步時,slave非同步清空所有DB

對於原始碼實現有興趣的讀者可以閱讀《Redis 4.0之Lazyfree》

Lua指令碼支援隨機操作

Redis內嵌了Lua環境來支援使用者擴充套件功能,但是出於資料一致性考慮,要求指令碼必須是純函式的形式,也就是說對於一段Lua指令碼給定相同的引數,重複執行其結果都是相同的。

為什麼要有這個限制呢?原因是Redis不僅僅是單機版的記憶體資料庫,它還支援主從複製和持久化,執行過的Lua指令碼會複製給slave以及持久化到磁碟,如果重複執行得到結果不同,那麼就會出現記憶體、磁碟、slave之間的資料不一致,在failover或者重啟之後造成資料錯亂影響業務。

還是以具體例子來看,假設有這麼一段Lua指令碼,目的很簡單就是想記錄下當前時間:

local now = redis.call(`time`)[1]

redis.call(`set`,`now`,now)

return redis.call(`get`,`now`)

這裡使用了Redis的TIME命令來獲取時間戳,然後儲存到名為now的key中,但是其執行時會報錯:

$redis-cli --eval escript
(error) ERR Error running script (call to f_cfba5ec6a699dad183456f19d1099d8dabfdb80c): 
@user_script:3: @user_script: 3: Write commands not allowed after non deterministic commands. 
Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.

錯誤提示也很明顯,如果執行過非確定性命令(也就是TIME,因為時間是隨機的),Redis就不允許執行寫命令,以此來保證資料一致性。那如何才能實現隨機寫入呢?剛才的錯誤提示也給出了答案,使用redis.replicate_commands(),在執行redis.replicate_commands()之後,Redis就不再是把整個Lua指令碼同步給slave和持久化,而是把指令碼中呼叫Redis的寫命令直接去做複製,那麼slave和持久化也可以得到確定的結果。

指令碼修改如下:

redis.replicate_commands()

local now = redis.call(`time`)[1]

redis.call(`set`,`now`,now)

return redis.call(`get`,`now`)

再執行就可以實現隨機寫入了:

$redis-cli --eval escript
"1528191578"

$redis-cli --eval escript
"1528191804"

基於LFU的熱點key發現機制

LFU是Redis 4.0新增的一類記憶體逐出策略,提供了更精確的記憶體淘汰演算法,其本質是記錄了一段時間內key的訪問頻率,同時也帶來了額外的福利就是熱點key的發現。

LFU簡單來講就是用0-255來表示key的訪問頻率,值越大說明訪問頻率越高,並且這裡對頻率的計數採用的是基於對數的概率增長,LFU為255可以代表100W次的訪問,關於LFU的實現有興趣的讀者可以參考《Redis 4.0之基於LFU的熱點key發現機制》

使用OBJECT FREQ命令即可獲取指定key的訪問頻率,不過需要首先把記憶體逐出策略設定為allkeys-lfu或者volatile-lfu:

127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
127.0.0.1:6379> object freq counter:000000006889
(error) ERR An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.

127.0.0.1:6379> config set maxmemory-policy allkeys-lfu
OK
127.0.0.1:6379> object freq counter:000000006889
(integer) 3

使用scan命令遍歷所有key,再通過OBJECT FREQ獲取訪問頻率並排序,即可得到熱點key。為了方便使用者使用,Redis自帶的客戶端redis-cli也提供了熱點key發現功能,執行redis-cli時加上–hotkeys選項即可,示例如下:

$./redis-cli --hotkeys

# Scanning the entire keyspace to find hot keys as well as
# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).

[00.00%] Hot key `counter:000000000002` found so far with counter 87
[00.00%] Hot key `key:000000000001` found so far with counter 254
[00.00%] Hot key `mylist` found so far with counter 107
[00.00%] Hot key `key:000000000000` found so far with counter 254
[45.45%] Hot key `counter:000000000001` found so far with counter 87
[45.45%] Hot key `key:000000000002` found so far with counter 254
[45.45%] Hot key `myset` found so far with counter 64
[45.45%] Hot key `counter:000000000000` found so far with counter 93

-------- summary -------

Sampled 22 keys in the keyspace!
hot key found with counter: 254    keyname: key:000000000001
hot key found with counter: 254    keyname: key:000000000000
hot key found with counter: 254    keyname: key:000000000002
hot key found with counter: 107    keyname: mylist
hot key found with counter: 93    keyname: counter:000000000000
hot key found with counter: 87    keyname: counter:000000000002
hot key found with counter: 87    keyname: counter:000000000001
hot key found with counter: 64    keyname: myset

MEMORY記憶體分析命令

分析記憶體可以優化Redis的使用方式,全新的MEMORY命令可以幫助使用者來實現這一操作。

MEMORY命令一共有5個子命令,可以通過MEMORY HELP來檢視:

127.0.0.1:6379> memory help
1) "MEMORY DOCTOR                        - Outputs memory problems report"
2) "MEMORY USAGE <key> [SAMPLES <count>] - Estimate memory usage of key"
3) "MEMORY STATS                         - Show memory usage details"
4) "MEMORY PURGE                         - Ask the allocator to release memory"
5) "MEMORY MALLOC-STATS                  - Show allocator internal stats"

關於各個子命令的詳細使用方式可以參考《Redis 4.0之MEMORY命令詳解》

開始體驗Redis 4.0

作者簡介

趙釗,花名仲肥,阿里雲技術專家,專注於阿里雲資料庫Redis版的開發工作,活躍於Redis開源社群,致力讓開發者使用最好的雲資料庫服務。

歡迎加入:阿里雲-技術專家-KVstore