Redis高級

Redis高級

發(fā)布訂閱

Redis提供了發(fā)布訂閱功能,可以用于消息的傳輸

Redis的發(fā)布訂閱機制包括三個部分,publisher,subscriber和Channel


發(fā)布訂閱.png

發(fā)布者和訂閱者都是Redis客戶端,Channel則為Redis服務器端。 發(fā)布者將消息發(fā)送到某個的頻道,訂閱了這個頻道的訂閱者就能接收到這條消息。

頻道/模式的訂閱與退訂

subscribe:訂閱 subscribe channel1 channel2 ..

Redis客戶端1訂閱頻道1和頻道2

127.0.0.1:6379> subscribe ch1 ch2
Reading messages... (press Ctrl-C to quit) 1) "subscribe"
2) "ch1"
3) (integer) 1
1) "subscribe"
2) "ch2"
3) (integer) 2

publish:發(fā)布消息 publish channel message Redis客戶端2將消息發(fā)布在頻道1和頻道2上

127.0.0.1:6379> publish ch1 hello (integer) 1
127.0.0.1:6379> publish ch2 world (integer) 1

Redis客戶端1接收到頻道1和頻道2的消息

1) "message"
2) "ch1"
3) "hello"
1) "message"
2) "ch2"
3) "world"

unsubscribe:退訂 channel Redis客戶端1退訂頻道1

127.0.0.1:6379> unsubscribe ch1 1) "unsubscribe"
2) "ch1"
3) (integer) 0

psubscribe :模式匹配 psubscribe +模式 Redis客戶端,訂閱所有以ch開頭的頻道

127.0.0.1:6379> psubscribe ch*
Reading messages... (press Ctrl-C to quit) 1) "psubscribe"
2) "ch*"
3) (integer) 1

Redis客戶端2發(fā)布信息在頻道5上

127.0.0.1:6379> publish ch5 helloworld
(integer) 1

Redis客戶端1收到頻道5的信息

1) "pmessage"
2) "ch*"
3) "ch5"
4) "helloworld"

punsubscribe 退訂模式

127.0.0.1:6379> punsubscribe ch* 
1) "punsubscribe"
2) "ch*"
3) (integer) 0
發(fā)布訂閱機制

訂閱某個頻道或模式:
客戶端(client):

  • 屬性為pubsub_channels,該屬性表明了該客戶端訂閱的所有頻道

  • 屬性為pubsub_patterns,該屬性表示該客戶端訂閱的所有模式

服務器端(RedisServer):

  • 屬性為pubsub_channels,該服務器端中的所有頻道以及訂閱了這個頻道的客戶端
  • 屬性為pubsub_patterns,該服務器端中的所有模式和訂閱了這些模式的客戶端
typedef struct redisClient { ...
  dict *pubsub_channels; //該client訂閱的channels,以channel為key用dict的方式組織 
  list *pubsub_patterns; //該client訂閱的pattern,以list的方式組織
  ...
} redisClient;
struct redisServer { ...
  //redis server進程中維護的channel dict,它以channel為key,訂閱channel的client list為value
  dict *pubsub_channels; 
  //redis server進程中維護的pattern list
  list *pubsub_patterns;
  int notify_keyspace_events; 
  ...
};

當客戶端向某個頻道發(fā)送消息時,Redis首先在redisServer中的pubsub_channels中找出鍵為該頻道的結點,遍歷該結點的值,即遍歷訂閱了該頻道的所有客戶端,將消息發(fā)送給這些客戶端。然后,遍歷結構體redisServer中的pubsub_patterns,找出包含該頻道的模式的結點,將消息發(fā)送給訂閱了該模式的客戶端。

使用場景

在Redis哨兵模式中,哨兵通過發(fā)布與訂閱的方式與Redis主服務器和Redis從服務器進行通信。這個我們將在后面的章節(jié)中詳細講解。

Redisson是一個分布式鎖框架,在Redisson分布式鎖釋放的時候,是使用發(fā)布與訂閱的方式通知的, 這個我們將在后面的章節(jié)中詳細講解。

事務

所謂事務(Transaction) ,是指作為單個邏輯工作單元執(zhí)行的一系列操作

ACID回顧
  • Atomicity(原子性):構成事務的的所有操作必須是一個邏輯單元,要么全部執(zhí)行,要么全部不 執(zhí)行。Redis:一個隊列中的命令執(zhí)行或不執(zhí)行
  • Consistency(一致性):數(shù)據(jù)庫在事務執(zhí)行前后狀態(tài)都必須是穩(wěn)定的或者是一致的。 Redis: 集群中不能保證時時的一致性,只能是最終一致性
  • Isolation(隔離性):事務之間不會相互影響。Redis: 命令是順序執(zhí)行的,在一個事務中,有可能被執(zhí)行其他客戶端的命令的
  • Durability(持久性):事務執(zhí)行成功后必須全部寫入磁盤。 Redis有持久化但不保證數(shù)據(jù)的完整性
Redis事務
  • Redis的事務是通過multi、exec、discard和watch這四個命令來完成的。
  • Redis的單個命令都是原子性的,所以這里需要確保事務性的對象是命令集合。
  • Redis將命令集合序列化并確保處于同一事務的命令集合連續(xù)且不被打斷的執(zhí)行
  • Redis不支持回滾操作
事務命令
  • multi:用于標記事務塊的開始,Redis會將后續(xù)的命令逐個放入隊列中,然后使用exec原子化地執(zhí)行這個命令隊列
  • exec:執(zhí)行命令隊列
  • discard:清除命令隊列
  • watch:監(jiān)視key
  • unwatch:清除監(jiān)視key
事務命令.png
127.0.0.1:6379> set name:1 aaa
OK
127.0.0.1:6379> get name:1
"aaa"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name:2 lishi
QUEUED
127.0.0.1:6379> set name:3 zhangsan
QUEUED
127.0.0.1:6379> get name:2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "lishi"
127.0.0.1:6379> get name:3
"zhangsan"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name:4 1111
QUEUED
127.0.0.1:6379> get name:4
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379>
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
127.0.0.1:6379> get name:4
(nil)
127.0.0.1:6379> get name:1
"aaa"
127.0.0.1:6379> watch name:1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name:4 444444
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get name:4
"444444"
127.0.0.1:6379> get name:1
"aaa"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name:4 sfjskljdf
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379> watch name:1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set name:4 sdf
QUEUED
127.0.0.1:6379> get name:4
QUEUED
127.0.0.1:6379> exec
(nil)

事務機制

事務的執(zhí)行
  1. 事務開始 :在RedisClient中,有屬性flags,用來表示是否在事務中 flags=REDIS_MULTI

  2. 命令入隊 :RedisClient將命令存放在事務隊列中 (EXEC,DISCARD,WATCH,MULTI除外)

  3. 事務隊列 : multiCmd *commands 用于存放命令

  4. 執(zhí)行事務 : RedisClient向服務器端發(fā)送exec命令,RedisServer會遍歷事務隊列,執(zhí)行隊列中的命令,最后將執(zhí)行的結果一次性返回給客戶端。

如果某條命令在入隊過程中發(fā)生錯誤,redisClient將flags置為REDIS_DIRTY_EXEC,EXEC命令將會失敗 返回。


事務的執(zhí)行.png
127.0.0.1:6379> set aa 11
QUEUED
127.0.0.1:6379> set bb 22
QUEUED
127.0.0.1:6379> get aa
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "11"
Watch的執(zhí)行
  • 使用WATCH命令監(jiān)視數(shù)據(jù)庫鍵:redisDb有一個watched_keys字典,key是某個被監(jiān)視的數(shù)據(jù)的key,值是一個鏈表.記錄了所有監(jiān)視這個數(shù)據(jù)的客戶端。

  • 監(jiān)視機制的觸發(fā):當修改數(shù)據(jù)后,監(jiān)視這個數(shù)據(jù)的客戶端的flags置為REDIS_DIRTY_CAS

  • 事務執(zhí)行:RedisClient向服務器端發(fā)送exec命令,服務器判斷RedisClient的flags,如果為REDIS_DIRTY_CAS,則清空事務隊列。


    Watch的執(zhí)行.png
typedef struct redisDb{ 
  // .....
  // 正在被WATCH命令監(jiān)視的鍵 
  dict *watched_keys;
  // .....
}redisDb;
Redis的弱事務性
  1. Redis運行錯誤

    在隊列里正確的命令可以執(zhí)行 (弱事務性)

    弱事務性 :

    • 在隊列里正確的命令可以執(zhí)行 (非原子操作)
    • 不支持回滾
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set aa 11
    QUEUED
    127.0.0.1:6379> sets aa 22
    (error) ERR unknown command `sets`, with args beginning with: `aa`, `22`,
    127.0.0.1:6379> exec
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379> get aa
    (nil)
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set aa 11
    QUEUED
    127.0.0.1:6379> lpush aa 1 2 3 4
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
    
  1. Redis不支持事務回滾(為什么呢)

    • 大多數(shù)事務失敗是因為語法錯誤或者類型錯誤,這兩種錯誤,在開發(fā)階段都是可以預見的
    • Redis為了性能方面就忽略了事務回滾。 (回滾記錄歷史版本)

Lua腳本

lua是一種輕量小巧的腳本語言,用標準C語言編寫并以源代碼形式開放, 其設計目的是為了嵌入應用程序中,從而為應用程序提供靈活的擴展和定制功能。 Lua應用場景:游戲開發(fā)、獨立應用腳本、Web應用腳本、擴展和數(shù)據(jù)庫插件。
nginx上使用lua 實現(xiàn)高并發(fā)

OpenRestry:一個可伸縮的基于Nginx的Web平臺,是在nginx之上集成了lua模塊的第三方服務器

OpenRestry是一個通過Lua擴展Nginx實現(xiàn)的可伸縮的Web平臺,內(nèi)部集成了大量精良的Lua庫、第三 方模塊以及大多數(shù)的依賴項。 用于方便地搭建能夠處理超高并發(fā)(日活千萬級別)、擴展性極高的動態(tài) Web應用、Web服務和動態(tài)網(wǎng)關。 功能和nginx類似,就是由于支持lua動態(tài)腳本,所以更加靈活。 OpenRestry通過Lua腳本擴展nginx功能,可提供負載均衡、請求路由、安全認證、服務鑒權、流量控 制與日志監(jiān)控等服務。

類似的還有Kong(Api Gateway)、tengine(阿里)

創(chuàng)建并修改lua環(huán)境

下載地址

可以本地下載上傳到linux,也可以使用curl命令在linux系統(tǒng)中進行在線下載

curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
安裝
yum -y install readline-devel ncurses-devel 
tar -zxvf lua-5.3.5.tar.gz
#在src目錄下
make linux
或make install

如果報錯,說找不到readline/readline.h, 可以通過yum命令安裝

yum -y install readline-devel ncurses-devel

安裝完以后再

make linux
或make install

最后,直接輸入 lua命令即可進入lua的控制臺

Lua環(huán)境協(xié)作組建

從Redis2.6.0版本開始,通過內(nèi)置的lua編譯/解釋器,可以使用EVAL命令對lua腳本進行求值。

腳本的命令是原子的,RedisServer在執(zhí)行腳本命令中,不允許插入新的命令,腳本的命令可以復制,RedisServer在獲得腳本后不執(zhí)行,生成標識返回,Client根據(jù)標識就可以隨時執(zhí) 行

EVAL命令

通過執(zhí)行redis的eval命令,可以運行一段lua腳本。

EVAL script numkeys key [key ...] arg [arg ...]

命令說明:

  • script參數(shù):是一段Lua腳本程序,它會被運行在Redis服務器上下文中,這段腳本不必(也不應該)定義為一個Lua函數(shù)。

  • numkeys參數(shù):用于指定鍵名參數(shù)的個數(shù)。

  • key [key ...]參數(shù): 從EVAL的第三個參數(shù)開始算起,使用了numkeys個鍵(key),表示在腳本中 所用到的那些Redis鍵(key),這些鍵名參數(shù)可以在Lua中通過全局變量KEYS數(shù)組,用1為基址的形 式訪問( KEYS[1] , KEYS[2] ,以此類推)。

  • arg [arg ...]參數(shù):可以在Lua中通過全局變量ARGV數(shù)組訪問,訪問的形式和KEYS變量類似( ARGV[1] 、 ARGV[2] ,諸如此類)。

    eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
    
Lua腳本中調用Redis命令
  • redis.call():
    • 返回值就是redis命令執(zhí)行的返回值
    • 如果出錯,則返回錯誤信息,不繼續(xù)執(zhí)行
  • redis.pcall():
    • 返回值就是redis命令執(zhí)行的返回值
    • 如果出錯,則記錄錯誤信息,繼續(xù)執(zhí)行
  • 注意事項
    • 在腳本中,使用return語句將返回值返回給客戶端,如果沒有return,則返回nil
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 n1 zhaoyun
EVALSHA

EVAL 命令要求你在每次執(zhí)行腳本的時候都發(fā)送一次腳本主體(script body)。Redis 有一個內(nèi)部的緩存機制,因此它不會每次都重新編譯腳本,不過在很多場合,付出無謂的帶寬來傳送腳本主體并不是最佳選擇。為了減少帶寬的消耗, Redis 實現(xiàn)了 EVALSHA 命令,它的作用和 EVAL 一樣,都用于對腳本求值,但它接受的第一個參數(shù)不是腳本,而是腳本的 SHA1 校驗和(sum)

SCRIPT命令
  • SCRIPT FLUSH :清除所有腳本緩存

  • SCRIPT EXISTS :根據(jù)給定的腳本校驗和,檢查指定的腳本是否存在于腳本緩存

  • SCRIPT LOAD :將一個腳本裝入腳本緩存,返回SHA1摘要,但并不立即運行它

    192.168.24.131:6380> script load "return redis.call('set',KEYS[1],ARGV[1])" "c686f316aaf1eb01d5a4de1b0b63cd233010e63d"
    192.168.24.131:6380> evalsha c686f316aaf1eb01d5a4de1b0b63cd233010e63d 1 n2 zhangfei
    OK
    192.168.24.131:6380> get n2
    
  • SCRIPT KILL :殺死當前正在運行的腳本

腳本管理命令實現(xiàn)

使用redis-cli直接執(zhí)行l(wèi)ua腳本。

test.lua:

return redis.call('set',KEYS[1],ARGV[1])
./redis-cli -h 127.0.0.1 -p 6379 --eval test.lua name:6 , caocao 
#key和value中間的逗號兩邊有空格

list.lua

local key=KEYS[1]
local list=redis.call("lrange",key,0,-1); 
return list;
./redis-cli --eval list.lua list

利用Redis整合Lua,主要是為了性能以及事務的原子性。因為redis幫我們提供的事務功能太差。

腳本復制

Redis 傳播 Lua 腳本,在使用主從模式和開啟AOF持久化的前提下: 當執(zhí)行l(wèi)ua腳本時,Redis 服務器有兩種模式:腳本傳播模式和命令傳播模式。

  • 腳本傳播模式

    腳本傳播模式是 Redis 復制腳本時默認使用的模式 Redis會將被執(zhí)行的腳本及其參數(shù)復制到 AOF 文件以及從服務器里面。 執(zhí)行以下命令:

    eval "redis.call('set',KEYS[1],ARGV[1]);redis.call('set',KEYS[2],ARGV[2])" 2 n1 n2 zhaoyun1 zhaoyun2
    

    那么主服務器將向從服務器發(fā)送完全相同的 eval 命令:

    eval "redis.call('set',KEYS[1],ARGV[1]);redis.call('set',KEYS[2],ARGV[2])" 2 n1 n2 zhaoyun1 zhaoyun2
    

    注意:在這一模式下執(zhí)行的腳本不能有時間、內(nèi)部狀態(tài)、隨機函數(shù)(spop)等。執(zhí)行相同的腳本以及參數(shù) 必須產(chǎn)生相同的效果。在Redis5,也是處于同一個事務中。下面是AOF的展示:

    MULTI^M
    *3^M
    $3^M
    set^M
    $2^M
    n1^M
    $8^M
    zhaoyun1^M
    *3^M
    $3^M
    set^M
    $2^M
    n2^M
    $8^M
    zhaoyun2^M
    *1^M
    $4^M
    EXEC^M
    

    腳本執(zhí)行的操作在一個事務里面。

  • 命令傳播模式

    處于命令傳播模式的主服務器會將執(zhí)行腳本產(chǎn)生的所有寫命令用事務包裹起來,然后將事務復制到 AOF 文件以及從服務器里面。因為命令傳播模式復制的是寫命令而不是腳本本身,所以即使腳本本身包含時間、內(nèi)部狀態(tài)、隨機函數(shù) 等,主服務器給所有從服務器復制的寫命令仍然是相同的。為了開啟命令傳播模式,用戶在使用腳本執(zhí)行任何寫操作之前,需要先在腳本里面調用以下函數(shù):

    redis.replicate_commands()
    

    redis.replicate_commands() 只對調用該函數(shù)的腳本有效:在使用命令傳播模式執(zhí)行完當前腳本之后, 服務器將自動切換回默認的腳本傳播模式。如果我們在主服務器執(zhí)行以下命令:

    eval "redis.replicate_commands();redis.call('set',KEYS[1],ARGV[1]);redis.call('set',K EYS[2],ARGV[2])" 2 n1 n2 zhaoyun11 zhaoyun22
    

    那么主服務器將向從服務器復制以下命令:

    MULTI
    *3
    $3
    set
    $2
    n1
    $9
    zhaoyun11
    *3
    $3
    set
    $2
    n2
    $9
    zhaoyun22
    *1
    $4
    EXEC
    
  • 管道(pipeline),事務和腳本(lua)三者的區(qū)別

    三者都可以批量執(zhí)行命令

    • 管道無原子性,命令都是獨立的,屬于無狀態(tài)的操作

    • 事務和腳本是有原子性的,其區(qū)別在于腳本可借助Lua語言可在服務器端存儲的便利性定制和簡化操作

    • 腳本的原子性要強于事務,腳本執(zhí)行期間,另外的客戶端 其它任何腳本或者命令都無法執(zhí)行,腳本的執(zhí)行時間應該盡量短,不能太耗時的腳本

慢查詢?nèi)罩?/h4>

我們都知道MySQL有慢查詢?nèi)罩?Redis也有慢查詢?nèi)罩?,可用于監(jiān)視和優(yōu)化查詢

慢查詢設置

在redis.conf中可以配置和慢查詢?nèi)罩鞠嚓P的選項:

#執(zhí)行時間超過多少微秒的命令請求會被記錄到日志上 0 :全記錄 <0 不記錄 
slowlog-log-slower-than 10000
#slowlog-max-len 存儲慢查詢?nèi)罩緱l數(shù)
slowlog-max-len 128

Redis使用列表存儲慢查詢?nèi)罩荆捎藐犃蟹绞?FIFO)

  • config set的方式可以臨時設置,redis重啟后就無效
  • config set slowlog-log-slower-than 微秒 config set slowlog-max-len 條數(shù)

查看日志:slowlog get [n]

127.0.0.1:6379> config set slowlog-log-slower-than 0
OK
127.0.0.1:6379> config set slowlog-max-len 2
OK
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> slowlog get
1) 1) (integer) 3
   2) (integer) 1596615097
   3) (integer) 8
   4) 1) "set"
      2) "k2"
      3) "v2"
   5) "127.0.0.1:59194"
   6) ""
2) 1) (integer) 2
   2) (integer) 1596615094
   3) (integer) 12
   4) 1) "set"
      2) "k1"
      3) "v1"
   5) "127.0.0.1:59194"
   6) ""
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> slowlog get
1) 1) (integer) 6
   2) (integer) 1596615119
   3) (integer) 5
   4) 1) "get"
      2) "k2"
   5) "127.0.0.1:59194"
   6) ""
2) 1) (integer) 5
   2) (integer) 1596615115
   3) (integer) 4
   4) 1) "get"
      2) "k1"
   5) "127.0.0.1:59194"
   6) ""
慢查詢記錄的保存

在redisServer中保存和慢查詢?nèi)罩鞠嚓P的信息

struct redisServer { // ...
  // 下一條慢查詢?nèi)罩镜?ID
  long long slowlog_entry_id;
  // 保存了所有慢查詢?nèi)罩镜逆湵鞦IFO
  FIFO list *slowlog;
  // 服務器配置 slowlog-log-slower-than 選項的值 
  long long slowlog_log_slower_than;
  // 服務器配置 slowlog-max-len 選項的值 
  unsigned long slowlog_max_len;
  // ...
};

lowlog 鏈表保存了服務器中的所有慢查詢?nèi)罩荆?鏈表中的每個節(jié)點都保存了一個 slowlogEntry 結 構, 每個 slowlogEntry 結構代表一條慢查詢?nèi)罩尽?/p>

typedef struct slowlogEntry { 
  // 唯一標識符
  long long id;
  // 命令執(zhí)行時的時間,格式為 UNIX 時間戳
  time_t time;
  // 執(zhí)行命令消耗的時間,以微秒為單位
  long long duration; 
  // 命令與命令參數(shù)
  robj **argv;
  // 命令與命令參數(shù)的數(shù)量
  int argc;
} slowlogEntry;
慢查詢?nèi)罩镜拈営[&刪除

初始化日志列表

void slowlogInit(void) {
  /* 創(chuàng)建一個list列表 */ 
  server.slowlog = listCreate(); 
  /* 日志ID從0開始 */  
  server.slowlog_entry_id = 0;    
  /* 指定慢查詢?nèi)罩緇ist空間的釋放方法 */ 
  listSetFreeMethod(server.slowlog,slowlogFreeEntry); 
}

獲得慢查詢?nèi)罩居涗?slowlog get [n]

def SLOWLOG_GET(number=None):
  # 用戶沒有給定 number 參數(shù)
  # 那么打印服務器包含的全部慢查詢?nèi)罩?if number is None:
  number = SLOWLOG_LEN() 
  # 遍歷服務器中的慢查詢?nèi)罩?  for log in redisServer.slowlog:
    if number <= 0:
    # 打印的日志數(shù)量已經(jīng)足夠,跳出循環(huán) 
      break
    else:
    # 繼續(xù)打印,將計數(shù)器的值減一 
    number -= 1
    # 打印日志 
    printLog(log)

查看日志數(shù)量的 slowlog len

def SLOWLOG_LEN():
  # slowlog 鏈表的長度就是慢查詢?nèi)罩镜臈l目數(shù)量 
  return len(redisServer.slowlog)

清除日志 slowlog reset

def SLOWLOG_RESET():
  # 遍歷服務器中的所有慢查詢?nèi)罩?  for log in redisServer.slowlog:
  # 刪除日志 
    deleteLog(log)
添加日志的實現(xiàn)

在每次執(zhí)行命令的之前和之后, 程序都會記錄微秒格式的當前 UNIX 時間戳, 這兩個時間戳之間的差就是服務器執(zhí)行命令所耗費的時長, 服務器會將這個時長作為參數(shù)之一傳給slowlogPushEntryIfNeeded 函數(shù), 而 slowlogPushEntryIfNeeded 函數(shù)則負責檢查是否需要為這次執(zhí)行的命令創(chuàng)建慢查詢?nèi)罩?/p>

// 記錄執(zhí)行命令前的時間
before = unixtime_now_in_us()

//執(zhí)行命令
execute_command(argv, argc, client)

//記錄執(zhí)行命令后的時間
after = unixtime_now_in_us()
// 檢查是否需要創(chuàng)建新的慢查詢?nèi)罩?
slowlogPushEntryIfNeeded(argv, argc, before-after)
void slowlogPushEntryIfNeeded(robj **argv, int argc, long long duration) {
  //Slowlog disabled 負數(shù)表示禁用 
  if (server.slowlog_log_slower_than < 0) return; 
  // 如果執(zhí)行時間 > 指定閾值
  if (duration >= server.slowlog_log_slower_than) 
    //創(chuàng)建一個slowlogEntry對象,添加到列表首部
    listAddNodeHead(server.slowlog,slowlogCreateEntry(argv,argc,duration));
  //如果列表長度 > 指定長度 
  while (listLength(server.slowlog) > server.slowlog_max_len) 
  //移除列表尾部元素
  listDelNode(server.slowlog,listLast(server.slowlog)); 
}

slowlogPushEntryIfNeeded 函數(shù)的作用有兩個:

  1. 檢查命令的執(zhí)行時長是否超過 slowlog-log-slower-than 選項所設置的時間, 如果是的話, 就為命令創(chuàng)建一個新的日志, 并將新日志添加到 slowlog 鏈表的表頭。
  2. 檢查慢查詢?nèi)罩镜拈L度是否超過 選項所設置的長度, 如果是的話, 那么將多 出來的日志從 slowlog 鏈表中刪除掉。
慢日志查詢&處理

使用slowlog get 可以獲得執(zhí)行較慢的redis命令,針對該命令可以進行優(yōu)化:

  1. 盡量使用短的key,對于value有些也可精簡,能使用int就int。
  2. 避免使用keys *、hgetall等全量操作。
  3. 減少大key的存取,打散為小key 100K以上
  4. 將rdb改為aof模式,rdb fork 子進程 數(shù)據(jù)量過大主進程阻塞 redis性能大幅下降;關閉持久化 , (適合于數(shù)據(jù)量較小,有固定數(shù)據(jù)源)
  5. 想要一次添加多條數(shù)據(jù)的時候可以使用管道
  6. 盡可能地使用哈希存儲
  7. 盡量限制下redis使用的內(nèi)存大小,這樣可以避免redis使用swap分區(qū)或者出現(xiàn)OOM錯誤 內(nèi)存與硬盤的swap

監(jiān)視器

Redis客戶端通過執(zhí)行MONITOR命令可以將自己變?yōu)橐粋€監(jiān)視器,實時地接受并打印出服務器當前處理 的命令請求的相關信息。此時,當其他客戶端向服務器發(fā)送一條命令請求時,服務器除了會處理這條命令請求之外,還會將這條 命令請求的信息發(fā)送給所有監(jiān)視器。


監(jiān)視器.png

Redis客戶端1

127.0.0.1:6379> monitor
OK
1589706136.030138 [0 127.0.0.1:42907] "COMMAND" 
1589706145.763523 [0 127.0.0.1:42907] "set" "name:10" "zhaoyun" 
1589706163.756312 [0 127.0.0.1:42907] "get" "name:10"

Redis客戶端2

127.0.0.1:6379>
127.0.0.1:6379> set name:10 zhaoyun
OK
127.0.0.1:6379> get name:10 
"zhaoyun"
實現(xiàn)監(jiān)視器

redisServer 維護一個 monitors 的鏈表,記錄自己的監(jiān)視器,每次收到 MONITOR 命令之后,將客戶端追加到鏈表尾。

void monitorCommand(redisClient *c) {
  //ignore MONITOR if already slave or in monitor mode 
  if (c->flags & REDIS_SLAVE) return;
    c->flags |= (REDIS_SLAVE|REDIS_MONITOR); 
    listAddNodeTail(server.monitors,c); 
    addReply(c,shared.ok); //回復OK
}
向監(jiān)視器發(fā)送命令信息
// call() 函數(shù)是執(zhí)行命令的核心函數(shù),這里只看監(jiān)視器部分 
/*src/redis.c/call*/
/* Call() is the core of Redis execution of a command */ 
void call(redisClient *c, int flags) {
  long long dirty, start = ustime(), duration;
  int client_old_flags = c->flags;
/* Sent the command to clients in MONITOR mode, only if the commands are * not generated from reading an AOF. */
cif (listLength(server.monitors) &&!server.loading &&
  !(c->cmd->flags & REDIS_CMD_SKIP_MONITOR))
  { 
    replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc);
  } ......
}

call 主要調用了 replicationFeedMonitors ,這個函數(shù)的作用就是將命令打包為協(xié)議,發(fā)送給監(jiān)視器。

Redis監(jiān)控平臺

grafana、prometheus以及redis_exporter。

  • Grafana 是一個開箱即用的可視化工具,具有功能齊全的度量儀表盤和圖形編輯器,有靈活豐富的圖形化選項,可以混合多種風格,支持多個數(shù)據(jù)源特點。

  • Prometheus是一個開源的服務監(jiān)控系統(tǒng),它通過HTTP協(xié)議從遠程的機器收集數(shù)據(jù)并存儲在本地的時序數(shù)據(jù)庫上。

  • redis_exporter為Prometheus提供了redis指標的導出,配合Prometheus以及grafana進行可視化及監(jiān)控。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Redis 發(fā)布訂閱 Redis 發(fā)布訂閱(pub/sub)是一種消息通信模式:發(fā)送者(pub)發(fā)送消息,訂閱者(...
    枯木風閱讀 194評論 0 0
  • 01.NoSQL入門概述-上 1.互聯(lián)網(wǎng)時代背景下大機遇,為什么用NoSQL 1.單機MySQL的美好年代 在90...
    ytyt1313閱讀 306評論 0 0
  • 除了基本的五種數(shù)據(jù)結構提供的功能以外,redis還提供了其他的功能 慢查詢分析 類似關系性數(shù)據(jù)庫,redis也有機...
    無聊的刀刀閱讀 284評論 0 0
  • 發(fā)布訂閱 特點 可以使用發(fā)布訂閱來實現(xiàn)消息隊列,但因為發(fā)出的消息不會被持久化,所以消費者只能收到從它開始訂閱這個頻...
    劍道_7ffc閱讀 158評論 0 0
  • 1. 發(fā)布訂閱模型 Redis 的 SUBSCRIBE 命令可以讓客戶端訂閱任意數(shù)量的頻道, 每當有新信息發(fā)送到被...
    伊凡的一天閱讀 1,727評論 0 4

友情鏈接更多精彩內(nèi)容