■河南 劉景云
某單位的網站后臺采用的是Redis數據庫,為了便于管理Web應用的緩存信息,使用Python開發了對應的模塊,來提供的不同的HTTP接口,可以用來執行數據查新、數據插入、數據測試等功能。
Redis是常用的鍵值存儲系統,提供數據的高速處理功能。但在實際過程中,在查詢和插入數據時,接口的響應時間卻比較緩慢,這給網站的管理和維護帶來了很大的問題。
對于上述故障,首先考慮的是否在系統配置上存儲問題,例如CPU資源占用率過高,內存消耗嚴重等,有可能造成Redis反應遲緩的問題。在后臺服務器上執行“top”命令,在返回信息中的發現只有CPU 1的IOWait值較高,達到了89%,各進程的CPU占用率都不高,Python和Redis-Server進程的CPU使用率也不超過10%,存在很多的空閑內存,從“load average”欄數據來看那么,系統的負荷并不高。
那么是不是因為系統的I/O性能存在瓶頸呢?為了便于發現問題,在前臺執行連續查詢操作,不斷的從特定的接口讀取Redis緩存信息,之后在后臺服務薇上執行“iostat-d-x 1”命令,對iostat的輸出信息進行觀察,發現磁盤每秒寫入數據大約為7.9MB,“%util”的值為0,說明雖然存在一些I/O操作,但是沒有比較嚴重的I/O瓶頸問題。根據以上分析,雖然發現CPU,內存和I/O都不存在明顯的問題,
但是對于上述案例來說,在從接口中讀取Redis數據時,涉及到的是只是數據的讀取操作,并沒有指定數據的頻繁寫入操作,根據上述iostat命令檢測信息,卻發現存在明顯的寫操作。
執行“pidstat-d 1”命令,顯示所有進程I/O使用情況,發現PID為8701的進程在頻繁的寫入數據,與其對應的是“redis-server”進程,說明Redis服務確實在不停的向磁盤寫入數據。
為了進一步進行觀察,執行“strace-f-T-tt p 8701”命令,檢測該進程的詳細的I/O操作情況,其中的“-f”參數表示跟蹤子進程和子線程信息,“-T”參數表示顯示系統調用的時長,“tt”表示顯示跟蹤時間。
根據顯示的系統調用信息,發現頻繁出現諸如“epoll_pawit”“read”“write”“fdatasync”等系統調用行為,對于磁盤些操作來說,無疑是由于“write”“fdatasync”行為造成的。
執行“lsof-p 8701”命令,顯示該進程調用的操作對象信息。可以看到,其操作的對象包括Pipe管道、Eventpoll文件描述符、名為“/data/appendonly”的文件(編號為7),以及“Protocol:TCP”端口(編號為8)。
在這些對象中,只有涉及到到普通文件的操作,才會觸發磁盤些操作,即上述“write”和“fdatasync”調用行為針對的就是名為/data/appendonly”的文件。這其實和Redis的數據持久化有著緊密的聯系,因為它和Redis持久化中配置中的“appendonly”及“appendfync”項目有關。
在客戶端上執行“rediscli config get 'append*'”命令,在返回信息中顯示“appendfync”參數的值為“always”,“appendonly”參數的值為“yes”。Redis的持久化指的是將數據存儲到斷電后不會丟失的設備中。
Redis支持RDB和AOF兩種持久化方式。前者是基于內存快照的持久化方式,后者可以將所有的操作命令記錄下來形成日志文件,這樣即使Redis出現異常情況,也可以利用日志進行數據的快速恢復。
在Redis配置文件中,將“appendonly”參數值設置為“yes”,表示啟用AOF功能。將“appendfsync”參數值設置為“always”,表示將每一個命令都立即同步到aof日志。如果設置為“everysec”,表示每秒鐘調用一次fsync操作,這樣即使出現問題,也只是丟失1秒鐘內的數據。如果設置為“no”,表示交由操作系統處理。
快照方式會按照指定的周期,生成數據的快照,并最終保存到磁盤文件中,為了避免阻塞主進程,Redis會利用Fork函數通過系統調用來創建一個子進程,用來負責保存快照,該方式具有速度快性能好的特點。但其缺點是,在處理較大的數據量時,該子進程會占用較大的內存,保存數據比較耗時。如果在指定的時間間隔內發生故障的話,就會丟失一定的數據。
AOF方式采用的是在文件尾部追加數據,因此Redis寫入的的數據持久化顯得更加安全。利用“appendfsync”參數參數,可以設置fsync策略,保證寫入的數據都保存在磁盤中。根據上面檢測的信息,說明“appendfsync”參數設置為“always”方式,說明在每次寫入數據時,都會觸發一次fsnc操作,所以造成了較大的磁盤I/O壓力。
為了驗證這一想法,可以借助于Strace這款工具加以檢測,Strace是一個集診斷、調試、統計與一體的工具,可以使用其對系統調用和信號傳遞進行跟蹤分析。
例如,執行“strace-f-p 8701-T-tt-e fdatasync”命令,對fsync調用情況進行跟蹤,根據返回信息,可以發現每隔幾毫秒就會出現一個fdatasync調用信息,每次調用本身也會消耗一定的時間。根據上面的分析,可以看出之所以Redis出現反應遲緩的問題,和其配置方式存在很大的關系。
之所以在讀取數據時,依然會出現寫磁盤的操作,可以執行“strace-f-p 8701-T-tt”命令,會發現存在TCP Socket相關的數據讀寫操作,其中包含了“SADD”之類的指令,SADD指令的作用是將一個或多個成員元素加入到集合中。
執行“lsof-i”命令,查看所有進程的網絡訪問信息,在其中找到“redisserver”進程,對應的“FD”列中顯示使用了“8u”,在“NAME”列中顯示自身監聽的端口(如“TCP 6379”),以及與其連接的端口(例如“TCP 59166”),而Python進程正好連接在后面的端口上。
也就說是,雖然通過對應接口執行了讀操作,但是也涉及到了寫操作,Redis會將相關的數據保存到“appendonly.aof”的持久化文件中。
經過上面一番分析,就已經找到了引發上述故障的原因,這其實是由于兩方面的問題造成的,首先在Redis的配置信息中,“appendsync”參數設置為了“always”,造成Redis的所有寫操作都會頻繁的觸發fdatasync調用,造成時間延遲的現象,將其設置為“everysec”表示每秒同步一次,就可以滿足滿足實際的需求。
在客戶端執行“rediscli config set appendsync everysec”命令,即可更改該參數。其次在開發的Python查詢接口模塊中,對緩存的使用存在不完善的地方。例如,將Redis作為臨時空間,來保存相關的數據,從而造成在查詢時會調用Redis的SADD命令的情況。為此可以對程序進行優化,將這些查詢數據保存到內存中即可。
完成了以上處理后,在客戶端利用HTTP接口執行查詢和寫入等操作時,Redis的反應速度大大提高,遲緩的故障徹底消失。