神奇的Redis延遲


最近在做某業務Redis的縮容工作,涉及到數據遷移,而Redis的數據遷移看起來蠻簡單的,一對一的數據遷移只需要在slave行配置masterauth 和slaveof 兩個參數即可,當然遷移過程中涉及到其它特殊情況需要特殊處理外。

以上幾個步驟都做好后, 就等着實例的切換了,不過在實例切換前我們還要檢查同步情況、數據一致性等。在檢查實例同步情況的時候發現了奇怪的現象:在遷移的540個實例中有個別實例(20個) lag 比較高,而且還有增加的趨勢,奇怪的是offset值一直是0。

slave0:ip=xxxx,port=xxxx,state=online,offset=0,lag=38392

經過觀察一段時間后,發現這個現象並沒有消失,而且 lag 也是隨着時間的增加在增加,offset值始終保持0。難道這個數據slave一直沒有應用? 
cli 上 slave 后發現slave的 dbsize 和master 的dbsize 基本一致;monitor slave 也發現有命令正常同步;master set 一個key,在slave 上也能正確的讀取到這個key;在看看slave 上的日志:

[13739] 10 Aug 15:49:46.017 * Connecting to MASTER xxxx.xxxx.xxx:xxx
[13739] 10 Aug 15:49:46.017 * MASTER <-> SLAVE sync started
[13739] 10 Aug 15:49:46.018 * Non blocking connect for SYNC fired the event.
[13739] 10 Aug 15:49:46.032 * Master replied to PING, replication can continue...
[13739] 10 Aug 15:49:46.092 * Partial resynchronization not possible (no cached master)
[13739] 10 Aug 15:49:46.120 # Unexpected reply to PSYNC from master: -LOADING Redis is loading the dataset in memory
[13739] 10 Aug 15:49:46.120 * Retrying with SYNC...
[13739] 10 Aug 15:49:46.641 * MASTER <-> SLAVE sync: receiving 26845995 bytes from master
[13739] 10 Aug 15:49:47.276 * MASTER <-> SLAVE sync: Flushing old data
[13739] 10 Aug 15:49:47.276 * MASTER <-> SLAVE sync: Loading DB in memory
[13739] 10 Aug 15:49:47.620 * MASTER <-> SLAVE sync: Finished with success
[13739] 10 Aug 15:49:47.621 * Background append only file rewriting started by pid 22605
[22605] 10 Aug 15:49:48.724 * SYNC append only file rewrite performed
[22605] 10 Aug 15:49:48.725 * AOF rewrite: 0 MB of memory used by copy-on-write
[13739] 10 Aug 15:49:48.822 * Background AOF rewrite terminated with success
[13739] 10 Aug 15:49:48.822 * Parent diff successfully flushed to the rewritten AOF (1148 bytes)
[13739] 10 Aug 15:49:48.822 * Background AOF rewrite finished successfully

看起來似乎好像也正常 :(

那么問題來了,既然我們的數據能正常同步,為什么master 上看到的信息顯示slave一直在延遲呢?難道? 
打開代碼找到這個lag 、offset的計算方式:

    if (slave->replstate == SLAVE_STATE_ONLINE)
lag = time(NULL) - slave->repl_ack_time;

info = sdscatprintf(info,
"slave%d:ip=%s,port=%d,state=%s,"
"offset=%lld,lag=%ld\r\n",
slaveid,slaveip,slave->slave_listening_port,state,
slave->repl_ack_off, lag);
long long repl_ack_off; /* Replication ack offset, if this is a slave. */
long long repl_ack_time;/* Replication ack time, if this is a slave. */

可以發現這個lag 是通過master 的當前時間減去slave 通過ACK上報上來的時間得到的,到這里可以懷疑是 slave 一直沒有 ACK ?

這里通過到lag,offset值正常的master 節點執行 monitor 命令可以發現,這些實例的slave 確實是有發送 ACK 命令回來的 ,而這種看上去似乎異常的節點確實是沒有ACK返回 。

到這里似乎找到了問題的關鍵了,那么為什么這種看似異常的slave 不發送ACK 給master 呢?這個還是得一層一層的扒開slave運行的面紗。

再次查看slave 運行日志,並且和其它實例對比,貌似可以發現一些不太相同的地方:

通過上圖可以看到,能正常發送ACK給master實例的日志多了幾行日志,而這些日志或許就是發送與不發送ACK的關鍵所在。

通過查看源碼得到如下信息: 
replicationCron 函數每秒執行一次,如果當前實例有配置masterhost,那么會去檢查同步的狀態 server.repl_state , 如果這個狀態 為: REPL_STATE_CONNECT /* Must connect to master */,那么會嘗試和Master 建立連接connectWithMaster(),如果連接建立正常,那么和master 進行數據同步 syncWithMaster(),並更新初次發送PING 包給Master,等待Master 會用PONG,當收到Master的回應,並且進行AUTH等操作后,slave會嘗試進行部分同步,當部分同步不成功時會進行全同步slaveTryPartialResynchronization(),而這個函數里邊發送的同步指令就是 PSYNC 指令,當master 回應的不是 +FULLRESYNC 或者 +CONTINUE時,那么統一的認為Master 不認識PSYNC 指令,而這時,為了兼容老版本的同步方式,這里會用SYNC指令重新發給Master。

psync_result = slaveTryPartialResynchronization(fd);
if (psync_result == PSYNC_CONTINUE) {
redisLog(REDIS_NOTICE, "MASTER <-> SLAVE sync: Master accepted a Partial Resynchronization.");
return;
}
/* Fall back to SYNC if needed. Otherwise psync_result == PSYNC_FULLRESYNC
* and the server.repl_master_runid and repl_master_initial_offset are
* already populated. */
if (psync_result == PSYNC_NOT_SUPPORTED) {
redisLog(REDIS_NOTICE,"Retrying with SYNC...");
}

執行玩指令后,如果成功了就會繼續進行后面的步驟,全同步接收RDB,FLUSHDB,LOADRDB等步驟。

到這里就可以解釋兩個實例輸出的日志不太一致的地方,可以認為一個實例使用了PSYNC的同步方式,另個一個實例使用了SYNC 的方式。使用了SYNC同步方式的實例 server.repl_master_initial_offset = -1 , 而 使用了PSYNC 同步方式的實例 server.repl_master_initial_offset = 1 。(關於這個變量可以通過 gdb 工具進行驗證,gdb有風險且用且珍惜~)

而現在回到發送ACK函數的地方:

    /* Send ACK to master from time to time.
* Note that we do not send periodic acks to masters that don't
* support PSYNC and replication offsets. */
if (server.masterhost && server.master &&
!(server.master->flags & CLIENT_PRE_PSYNC))
replicationSendAck();
#define CLIENT_PRE_PSYNC (1<<16) /* Instance don't understand PSYNC. */

也就是說使用SYNC 同步方式的實例 (server.master->flags & CLIENT_PRE_PSYNC) 這個條件是不滿足的,所以這個函數不會被執行。

為什么slave 不發送ACK給 master 的根本原因找到了,是因為 slave使用的同步方式為SYNC 方式。 
那么使用PSYNC 和 SYNC 時,master 會做不同的處理么?對同步數據有什么影響?show me the code :

    {"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0},
{"psync",syncCommand,3,"ars",0,NULL,0,0,0,0,0},

if (!strcasecmp(c->argv[0]->ptr,"psync")) {
if (masterTryPartialResynchronization(c) == C_OK) {
server.stat_sync_partial_ok++;
return; /* No full resync needed, return. */
} else {
char *master_replid = c->argv[1]->ptr;
if (master_replid[0] != '?') server.stat_sync_partial_err++;
}
} else {
/* If a slave uses SYNC, we are dealing with an old implementation
* of the replication protocol (like redis-cli --slave). Flag the client
* so that we don't expect to receive REPLCONF ACK feedbacks. */ ----這里也表明,使用了SYNC 方式的,master 也不期望slave 發送 ACK 回來
c->flags |= CLIENT_PRE_PSYNC;
}

可以看到,master對PSYNC和SYNC 兩種同步方式的入口一致,不同的就是PSYNC 可以進行部分同步,而SYNC只能進行完全同步。

小結: 
redis slave 同步數據的方式有兩種,PSYNC 和 SYNC ,當slave 使用PSYNC 同步數據失敗時,會嘗試使用SYNC方式同步,而使用SYNC方式同步數據時,並不會給Master 發送ACK 數據,導致master 上看到slave 的lag 信息不准確。

lag 這個值不一定能用來確定一個slave 是否有延遲,延遲多長時間。我們可以根據 master_last_io_seconds 來判斷這個slave是否有延遲;或者我們需要通過外圍的監控來發現。


免責聲明!

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



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