shell腳本操作redis
⑴ 怎麼向redis導入大量數據
具體實現步驟如下:1.新建一個文本文件,包含redis命令如果有了原始數據,其實構造這個文件並不難,譬如shell,python都可以2.將這些命令轉化成RedisProtocol。因為Redis管道功能支持的是RedisProtocol,而不是直接的Redis命令。如何轉化,可參考後面的腳本。3.利用管道插入catdata.txt|redis-cli--pipeShellVSRedispipe下面通過測試來具體看看Shell批量導入和Redispipe之間的效率。測試思路:分別通過shell腳本和Redispipe向資料庫中插入10萬相同數據,查看各自所花費的時間。Shell腳本如下:#!/bin/bashfor((i=0;i>redis.logdone每次插入的值都是helloworld,但鍵不同,name0,name1name99999。RedispipeRedispipe會稍微麻煩一點1>首先構造redis命令的文本文件在這里,我選用了python#!/usr/bin/pythonforiinrange(100000):print'setname'+str(i),'helloworld'#python1.py>redis_commands.txt#head-2redis_commands.>將這些命令轉化成RedisProtocol在這里,我利用了github上一個shell腳本,#!/bin/bashwhilereadCMD;do#eachcommandbeginswith*{numberargumentsincommand}\r\nXS=($CMD);printf"*${#XS[@]}\r\n"#foreachargument,weappend${length}\r\n{argument}\r\nforXin$CMD;doprintf"\$${#X}\r\n$X\r\n";donedoneredis_data.txt#head-7redis_data.txt*3$3set$5name0$10helloworld至此,數據構造完畢。測試結果
⑵ redis shell 導入到指定的資料庫
1、導出redis
[plain]view plain
#!/bin/bash
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_DB=1
KEYNAME=redis:hash:*
KEYFILE=key.txt
echo"KEYS$KEYNAME"|redis-cli-h$REDIS_HOST-p$REDIS_PORT-n$REDIS_DB>$KEYFILE
OUTFILE=valuelist.txt
TEMPFILE=$OUTFILE.tmp
echo>$TEMPFILE
forkeyin`cat$KEYFILE`
do
echo$key
#echo"HGETALL$key"|redis-cli-h$REDIS_HOST-p$REDIS_PORT-n$REDIS_DB>>$TEMPFILE
echo"GET$key"|redis-cli-h$REDIS_HOST-p$REDIS_PORT-n$REDIS_DB>>$TEMPFILE
done
- 2、整理導出的結果
cat$TEMPFILE|xargs-n2|awk-F""-vKEYNAME=$KEYNAME'{print"HSET"KEYNAME""$1,"""$2"""}'>$OUTFILE
- 其中的xargs -n 2是把2行合成一行,換行符用空格代替
cat$OUTFILE|redis-cli-hlocalhost-p6379
redis-cli--rawmptest|head-c-1|redis-cli-xrestoretest10
[plain]view plain
然後用awk按空格分開,$1是keynam $2是key的值
再次轉換成HSET的格式,其中用到KEYNAME變數,這個要用-v預先定義。
最後轉化為 HSET KEYNAME KEY "VALUE"格式。
3、導入轉換的結果
[plain]view plain
二、直接用Redis的Dump和Restore導出和導入
[plain]view plain
IT路邊社
前言
與其它的編碼規范一樣,這里所討論的不僅僅是編碼格式美不美觀的問題, 同時也討論一些約定及編碼標准。這份文檔主要側重於我們所普遍遵循的規則,對於那些不是明確強制要求的,我們盡量避免提供意見。
編碼規范對於程序員而言尤為重要,有以下幾個原因:
本文檔中的准則致力於最大限度達到以下原則:
盡管本文檔涵蓋了許多基礎知識,但應注意的是,沒有編碼規范可以為我們回答所有問題,開發人員始終需要再編寫完代碼後,對上述原則做出正確的判斷。
注 :未明確指明的則默認為必須(Mandatory)
主要參考如下文檔:
僅建議Shell用作相對簡單的實用工具或者包裝腳本。因此單個shell腳本內容不宜太過復雜。
在選擇何時使用shell腳本時時應遵循以下原則:
可執行文件不建議有擴展名,庫文件必須使用 .sh 作為擴展名,且應是不可執行的。
執行一個程序時,無需知道其編寫語言,且shell腳本並不要求具有擴展名,所以更傾向可執行文件沒有擴展名。
而庫文件知道其編寫語言十分重要,使用 .sh 作為特定語言後綴的擴展名,可以和其他語言編寫的庫文件加以區分。
文件名要求全部小寫, 可以包含下劃線 _ 或連字元 - , 建議可執行文件使用連字元,庫文件使用下劃線。
正例:
反例:
源文件編碼格式為UTF-8。避免不同操作系統對文件換行處理的方式不同,一律使用 LF 。
每行最多不超過120個字元。每行代碼最大長度限制的根本原因是過長的行會導致閱讀障礙,使得縮進失效。
除了以下兩種情況例外:
如出現長度必須超過120個字元的字元串,應盡量使用here document或者嵌入的換行符等合適的方法使其變短。
示例:
除了在行結束使用換行符,空格是源文件中唯一允許出現的空白字元。
對從來沒有用到的或者被注釋的方法、變數等要堅決從代碼中清理出去,避免過多垃圾造成干擾。
Bash 是唯一被允許使用的可執行腳本shell。
可執行文件必須以 #!/bin/bash 開始。請使用 set 來設置shell的選項,使得用 bash echo "Process $: Done making $$$."
# 示例7:命令參數及路徑不需要引號 grep -li Hugo /dev/ "$1"
# 示例8:常規變數用雙引號,ccs可能為空的特殊情況可不用引號 git send-email --to "${reviewers}" ${ccs:+"--cc" "${ccs}"}
# 示例9:正則用單引號,$1可能為空的特殊情況可不用引號 grep -cP '([Ss]pecial||?characters*) ${1:+"$1"}
# 示例10:位置參數傳遞推薦帶引號的"$@",所有參數作為單字元串傳遞用帶引號的"$*" # content of t.sh func_t { echo num: $# echo args: 1:$1 2:$2 3:$3 }
func_t "$@" func_t "$*" # 當執行 ./t.sh a b c 時輸出如下: num: 3 args: 1:a 2:b 3:c num: 1 args: 1:a b c 2: 3:
使用 $(command) 而不是反引號。
因反引號如果要嵌套則要求用反斜杠轉義內部的反引號。而 $(command) 形式的嵌套無需轉義,且可讀性更高。
正例:
反例:
條件測試
使用 [[ ... ]] ,而不是 [ , test , 和 /usr/bin/[ 。
因為在 [[ 和 ]] 之間不會出現路徑擴展或單詞切分,所以使用 [[ ... ]] 能夠減少犯錯。且 [[ ... ]] 支持正則表達式匹配,而 [ ... ] 不支持。參考以下示例:
盡可能使用變數引用,而非字元串過濾。
Bash可以很好的處理空字元串測試,請使用空/非空字元串測試方法,而不是過濾字元,讓代碼具有更高的可讀性。正例:
反例:
正例:
反例:
正例:
反例:
文件名擴展
當進行文件名的通配符擴展時,請指定明確的路徑。
當目錄中有特殊文件名如以 - 開頭的文件時,使用帶路徑的擴展通配符 ./* 比不帶路徑的 * 要安全很多。
應該避免使用eval。
Eval在用於分配變數時會修改輸入內容,但設置變數的同時並不能檢查這些變數是什麼。反例:
請使用進程替換或者for循環,而不是通過管道連接while循環。
這是因為在管道之後的while循環中,命令是在一個子shell中運行的,因此對變數的修改是不能傳遞給父shell的。
這種管道連接while循環中的隱式子shell使得bug定位非常困難。反例:
如果你確定輸入中不包含空格或者其他特殊符號(通常不是來自用戶輸入),則可以用for循環代替。例如:
使用進程替換可實現重定向輸出,但是請將命令放入顯式子 shell,而非 while 循環創建的隱式子 shell。例如:
總是檢查返回值,且提供有用的返回值。
對於非管道命令,使用 $? 或直接通過 if 語句來檢查以保持其簡潔。
例如:
當內建命令可以完成相同的任務時,在shell內建命令和調用外部命令之間,應盡量選擇內建命令。
因內建命令相比外部命令而言會產生更少的依賴,且多數情況調用內建命令比調用外部命令可以獲得更好的性能(通常外部命令會產生額外的進程開銷)。
正例:
反例:
載入外部庫文件不建議用使用.,建議使用source,已提升可閱讀性。正例:
反例:
除非必要情況,盡量使用單個命令及其參數組合來完成一項任務,而非多個命令加上管道的不必要組合。常見的不建議的用法例如:cat和grep連用過濾字元串; cat和wc連用統計行數; grep和wc連用統計行數等。
正例:
除特殊情況外,幾乎所有函數都不應該使用exit直接退出腳本,而應該使用return進行返回,以便後續邏輯中可以對錯誤進行處理。正例:
反例:
推薦以下工具幫助我們進行代碼的規范:
原文鏈接:http://itxx00.github.io/blog/2020/01/03/shell-standards/
獲取更多的面試題、腳本等運維資料點擊: 運維知識社區 獲取
腳本之---簡訊轟炸機
腳本之---QQ微信轟炸機
ansible---一鍵搭建redis5.0.5集群
elk7.9真集群docker部署文檔
全球最全loki部署及配置文檔
最強安全加固腳本2.0
一鍵設置iptbales腳本
⑷ 如何使用SHELL編寫redis的啟動腳本
#!/bin/bash
#
# Init file for redis
# From: JC
# Email:[email protected]
# chkconfig: - 80 12
# description: redis daemon
#
# processname: redis
# config: /opt/redis-2.6.4/redis.conf
# pidfile: /var/run/redis.pid
source /etc/init.d/functions
BIN="/opt/redis/bin"
CONFIG="/opt/redis-2.6.4/redis.conf"
PIDFILE="/var/run/redis.pid"
### Read configuration
[ -r "$SYSCONFIG" ] && source "$SYSCONFIG"
RETVAL=0
prog="redis-server"
desc="Redis Server"
start() {
if [ -e $PIDFILE ];then
echo "$desc already running...."
exit 1
fi
echo -n $"Starting $desc: "
daemon $BIN/$prog $CONFIG &
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog
return $RETVAL
}
stop() {
echo -n $"Stop $desc: "
killproc $prog
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog $PIDFILE
return $RETVAL
}
restart() {
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
condrestart)
[ -e /var/lock/subsys/$prog ] && restart
RETVAL=$?
;;
status)
status $prog
RETVAL=$?
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
RETVAL=1
esac
exit $RETVAL
[root@dg-web opt]# /etc/init.d/redis status
redis-server (pid 26147) is running...
[root@dg-web opt]# netstat -lntp|grep redis-server
URL:http://www.bianceng.cn/database/Mysql/201410/46081.htm
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 26147/redis-server
[root@dg-web opt]# /etc/init.d/redis stop
Stop Redis Server: [ OK ]
[root@dg-web opt]# /etc/init.d/redis status
redis-server is stopped
[root@dg-web opt]# netstat -lntp|grep redis-server
⑸ 使用python同步mysql到redis由於數據較多,一條一條讀出來寫到redis太慢,有沒有可以批量操作的。
MYSQL快速同步數據到Redis
舉例場景:存儲游戲玩家的任務數據,游戲伺服器啟動時將mysql中玩家的數據同步到redis中。
從MySQL中將數據導入到Redis的Hash結構中。當然,最直接的做法就是遍歷MySQL數據,一條一條寫入到Redis中。這樣沒什麼錯,但是速度會非常慢。如果能夠想法使得MySQL的查詢輸出數據直接能夠與Redis命令行的輸入數據協議相吻合,可以節省很多消耗和縮短時間。
Mysql資料庫名稱為:GAME_DB, 表結構舉例:
CREATE TABLE TABLE_MISSION (
playerId int(11) unsigned NOT NULL,
missionList varchar(255) NOT NULL,
PRIMARY KEY (playerId)
);
Redis中的數據結構使用哈希表:
鍵KEY為mission, 哈希域為mysql中對應的playerId, 哈希值為mysql中對應的missionList。 數據如下:
[root@iZ23zcsdouzZ ~]# redis-cli
127.0.0.1:6379> hget missions 36598
"{\"10001\":{\"status\":1,\"progress\":0},\"10002\":{\"status\":1,\"progress\":0},\"10003\":{\"status\":1,\"progress\":0},\"10004\":{\"status\":1,\"progress\":0}}"
快速同步方法:
新建一個後綴.sql文件:mysql2redis_mission.sql
內容如下:
SELECT CONCAT(
"*4\r\n",
'$', LENGTH(redis_cmd), '\r\n',
redis_cmd, '\r\n',
'$', LENGTH(redis_key), '\r\n',
redis_key, '\r\n',
'$', LENGTH(hkey), '\r\n',
hkey, '\r\n',
'$', LENGTH(hval), '\r\n',
hval, '\r'
)
FROM (
SELECT
'HSET' as redis_cmd,
'missions' AS redis_key,
playerId AS hkey,
missionList AS hval
FROM TABLE_MISSION
) AS t
創建shell腳本mysql2redis_mission.sh
內容:
mysql GAME_DB --skip-column-names --raw < mission.sql | redis-cli --pipe
Linux系統終端執行該shell腳本或者直接運行該系統命令,即可將mysql資料庫GAME_DB的表TABLE_MISSION數據同步到redis中鍵missions中去。mysql2redis_mission.sql文件就是將mysql數據的輸出數據格式和redis的輸入數據格式協議相匹配,從而大大縮短了同步時間。
經過測試,同樣一份數據通過單條取出修改數據格式同步寫入到redis消耗的時間為5min, 使用上面的sql文件和shell命令,同步完數據僅耗時3s左右。
⑹ 關於Redis批量寫入的介紹
最近測試redis性能,需要批量插入1千萬條以上的數據,
就簡單進行了研究,大概有以下幾種方法:
一:java程序調用,簡單的for循環,通過Jedis的方法,直接插入,
至於速度,不用看,不用試,根本不行,就不給實現了。
二:通過shell腳本,也比較簡單,但也是因為要通過port埠的形式,
連接到redis,速度也是慢的不行,放棄。
三:通過redis提供管道,感覺這種方法是最靠譜的,一下是實現:
步驟1:首先簡單寫一個shell腳本:
目的是生成一個用批量插入的腳本文件,
步驟2:這里linux或者windows上生成的文件不能直接當做redis語句來跑,
簡單來說,就是因為linux,windows和dos的換行並不一樣,
所以需要簡單轉換一下:
轉換的方法有好多種:
1:一般Linux發行版中都帶有這個小工具,只能把DOS轉換為UNIX文件,命令如下:
上面的命令會去掉行尾的^M符號。(如果機器上沒有安裝nuix2dos,直接用方法2即可)
2:用vim,vim是vi的改進版本,使用方式:
一行命令即可,保存退出,
步驟3:就是跑腳本了,通過redis提供的管道--pipe形式,
三步即可,完成redis的批量插入,速度,在2000萬條的情況下,生成腳本的速度稍慢,插入的速度還在幾分鍾,
速度可以說是非常快了~
本文來自php中文網的 redis教程 欄目: https://www.php.cn/redis/
⑺ Redis持久化
Redis支持RDB和AOF兩種持久化機制,持久化功能有效地避免因進程退出造成的數據丟失問題,當下次重啟時利用之前持久化的文件即可實現數據恢復。理解掌握持久化機制對於Redis運維非常重要。本章內容如下:
·首先介紹RDB、AOF的配置和運行流程,以及控制持久化的相關命令,如bgsave和bgrewriteaof。
·其次對常見持久化問題進行分析定位和優化。
·最後結合Redis常見 的單機多實例部署場景進行優化。
5.1RDB
RDB持久化是把當前進程數據生成快照保存到硬碟的過程,觸發RDB持久化過程分為手動觸發和自動觸發。
5.1.1觸發機制
手動觸發分別對應save和bgsave命令:
·save命令:阻塞當前Redis伺服器,直到RDB過程完成為止,對於內存比較大的實例會造成長時間阻塞,線上環境不建議使用。運行save命令對應
的Redis日誌如下:
* DB saved on disk
·bgsave命令:Redis進程執行fork操作創建子進程,RDB持久化過程由子進程負責,完成後自動結束。阻塞只發生在fork階段,一般時間很短。運行bgsave命令對應的Redis日誌如下:
* Background saving started by pid 3151
* DB saved on disk
* RDB: 0 MB of memory used by -on-write
* Background saving terminated with success
顯然bgsave命令是針對save阻塞問題做的優化。因此Redis內部所有的涉及RDB的操作都採用bgsave的方式,而save命令已經廢棄。
除了執行命令手動觸發之外,Redis內部還存在自動觸發RDB的持久化機制,例如以下場景:
1)使用save相關配置,如「save m n」。表示m秒內數據集存在n次修改時,自動觸發bgsave。
2)如果從節點執行全量復制操作,主節點自動執行bgsave生成RDB文件並發送給從節點,更多細節見6.3節介紹的復制原理。
3)執行debug reload命令重新載入Redis時,也會自動觸發save操作。
4)默認情況下執行shutdown命令時,如果沒有開啟AOF持久化功能則自動執行bgsave。
5.1.2流程說明
bgsave是主流的觸發RDB持久化方式,下面根據圖5-1了解它的運作流程。
1)執行bgsave命令,Redis父進程判斷當前是否存在正在執行的子進程,如RDB/AOF子進程,如果存在bgsave命令直接返回。
2)父進程執行fork操作創建子進程,fork操作過程中父進程會阻塞,通過info stats命令查看latest_fork_usec選項,可以獲取最近一個fork操作的耗時,單位為微秒。
3)父進程fork完成後,bgsave命令返回「Background saving started」信息並不再阻塞父進程,可以繼續響應其他命令。
4)子進程創建RDB文件,根據父進程內存生成臨時快照文件,完成後對原有文件進行原子替換。執行lastsave命令可以獲取最後一次生成RDB的時間,對應info統計的rdb_last_save_time選項。
5)進程發送信號給父進程表示完成,父進程更新統計信息,具體見info Persistence下的rdb_*相關選項。
5.1.3RDB文件的處理
保存:RDB文件保存在dir配置指定的目錄下,文件名通過dbfilename配置指定。可以通過執行config set dir{newDir}和config setdbfilename{newFileName}運行期動態執行,當下次運行時RDB文件會保存到新目錄。
運維提示
當遇到壞盤或磁碟寫滿等情況時,可以通過config set dir{newDir}在線修改文件路徑到可用的磁碟路徑,之後執行bgsave進行磁碟切換,同樣適用於AOF持久化文件。
壓縮:Redis默認採用LZF演算法對生成的RDB文件做壓縮處理,壓縮後的文件遠遠小於內存大小,默認開啟,可以通過參數config set rdbcompression{yes|no}動態修改。
運維提示
雖然壓縮RDB會消耗CPU,但可大幅降低文件的體積,方便保存到硬碟或通過網路發送給從節點,因此線上建議開啟。
校驗:如果Redis載入損壞的RDB文件時拒絕啟動,並列印如下日誌:
# Short read or OOM loading DB. Unrecoverable error, aborting now.
這時可以使用Redis提供的redis-check-mp工具檢測RDB文件並獲取對應的錯誤報告。
5.1.4RDB的優缺點
RDB的優點:
·RDB是一個緊湊壓縮的二進制文件,代表Redis在某個時間點上的數據快照。非常適用於備份,全量復制等場景。比如每6小時執行bgsave備份,並把RDB文件拷貝到遠程機器或者文件系統中(如hdfs),用於災難恢復。
·Redis載入RDB恢復數據遠遠快於AOF的方式。
RDB的缺點:
·RDB方式數據沒辦法做到實時持久化/秒級持久化。因為bgsave每次運行都要執行fork操作創建子進程,屬於重量級操作,頻繁執行成本過高。
·RDB文件使用特定二進制格式保存,Redis版本演進過程中有多個格式的RDB版本,存在老版本Redis服務無法兼容新版RDB格式的問題。針對RDB不適合實時持久化的問題,Redis提供了AOF持久化方式來解決。
5.2AOF
AOF(append only file)持久化:以獨立日誌的方式記錄每次寫命令,重啟時再重新執行AOF文件中的命令達到恢復數據的目的。AOF的主要作用是解決了數據持久化的實時性,目前已經是Redis持久化的主流方式。理解掌握好AOF持久化機制對我們兼顧數據安全性和性能非常有幫助。
5.2.1使用AOF
開啟AOF功能需要設置配置:appendonly yes,默認不開啟。AOF文件名通過appendfilename配置設置,默認文件名是appendonly.aof。保存路徑同RDB持久化方式一致,通過dir配置指定。AOF的工作流程操作:命令寫入(append)、文件同步(sync)、文件重寫(rewrite)、重啟載入(load),如圖5-2所示。
1)所有的寫入命令會追加到aof_buf(緩沖區)中。
2)AOF緩沖區根據對應的策略向硬碟做同步操作。
3)隨著AOF文件越來越大,需要定期對AOF文件進行重寫,達到壓縮的目的。
4)當Redis伺服器重啟時,可以載入AOF文件進行數據恢復。了解AOF工作流程之後,下面針對每個步驟做詳細介紹。
5.2.2命令寫入
AOF命令寫入的內容直接是文本協議格式。例如set hello world這條命令,在AOF緩沖區會追加如下文本:*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n
Redis協議格式具體說明見4.1客戶端協議小節,這里不再贅述,下面介
紹關於AOF的兩個疑惑:
1)AOF為什麼直接採用文本協議格式?可能的理由如下:
·文本協議具有很好的兼容性。
·開啟AOF後,所有寫入命令都包含追加操作,直接採用協議格式,避免了二次處理開銷。
·文本協議具有可讀性,方便直接修改和處理。
2)AOF為什麼把命令追加到aof_buf中?Redis使用單線程響應命令,如果每次寫AOF文件命令都直接追加到硬碟,那麼性能完全取決於當前硬碟負載。先寫入緩沖區aof_buf中,還有另一個好處Redis可以提供多種緩沖區同步硬碟的策略,在性能和安全性方面做出平衡。
5.2.3文件同步
Redis提供了多種AOF緩沖區同步文件策略,由參數appendfsync控制,不同值的含義如表5-1所示。
表5-1AOF緩沖區同步文件策略
系統調用write和fsync說明:
·write操作會觸發延遲寫(delayed write)機制。Linux在內核提供頁緩沖區用來提高硬碟IO性能。write操作在寫入系統緩沖區後直接返回。同步硬碟操作依賴於系統調度機制,例如:緩沖區頁空間寫滿或達到特定時間周期。同步文件之前,如果此時系統故障宕機,緩沖區內數據將丟失。
·fsync針對單個文件操作(比如AOF文件),做強制硬碟同步,fsync將阻塞直到寫入硬碟完成後返回,保證了數據持久化。除了write、fsync,Linux還提供了sync、fdatasync操作,具體API說明參
見:http://linux.die.net/man/2/write,http://linux.die.net/man/2/fsync,http://linux.die.net/man/2/sync
·配置為always時,每次寫入都要同步AOF文件,在一般的SATA硬碟上,Redis只能支持大約幾百TPS寫入,顯然跟Redis高性能特性背道而馳,不建議配置。
·配置為no,由於操作系統每次同步AOF文件的周期不可控,而且會加大每次同步硬碟的數據量,雖然提升了性能,但數據安全性無法保證。
·配置為everysec,是建議的同步策略,也是默認配置,做到兼顧性能和數據安全性。理論上只有在系統突然宕機的情況下丟失1秒的數據。(嚴格來說最多丟失1秒數據是不準確的,5.3節會做具體介紹到。)
5.2.4重寫機制
隨著命令不斷寫入AOF,文件會越來越大,為了解決這個問題,Redis引入AOF重寫機制壓縮文件體積。AOF文件重寫是把Redis進程內的數據轉化為寫命令同步到新AOF文件的過程。
重寫後的AOF文件為什麼可以變小?有如下原因:
1)進程內已經超時的數據不再寫入文件。
2)舊的AOF文件含有無效命令,如del key1、hdel key2、srem keys、set
a111、set a222等。重寫使用進程內數據直接生成,這樣新的AOF文件只保留最終數據的寫入命令。
3)多條寫命令可以合並為一個,如:lpush list a、lpush list b、lpush list c可以轉化為:lpush list a b c。為了防止單條命令過大造成客戶端緩沖區溢出,對於list、set、hash、zset等類型操作,以64個元素為界拆分為多條。
AOF重寫降低了文件佔用空間,除此之外,另一個目的是:更小的AOF文件可以更快地被Redis載入。AOF重寫過程可以手動觸發和自動觸發:
·手動觸發:直接調用bgrewriteaof命令。
·自動觸發:根據auto-aof-rewrite-min-size和auto-aof-rewrite-percentage參數確定自動觸發時機。
·auto-aof-rewrite-min-size:表示運行AOF重寫時文件最小體積,默認為64MB。
·auto-aof-rewrite-percentage:代表當前AOF文件空間(aof_current_size)和上一次重寫後AOF文件空間(aof_base_size)的比值。自動觸發時機=aof_current_size>auto-aof-rewrite-min-size&&(aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewrite-percentage其中aof_current_size和aof_base_size可以在info Persistence統計信息中查看。當觸發AOF重寫時,內部做了哪些事呢?下面結合圖5-3介紹它的運行流程。
圖5-3AOF重寫運作流程
流程說明:
1)執行AOF重寫請求。
如果當前進程正在執行AOF重寫,請求不執行並返回如下響應:
ERR Background append only file rewriting already in progress
如果當前進程正在執行bgsave操作,重寫命令延遲到bgsave完成之後再執行,返回如下響應:
Background append only file rewriting scheled
2)父進程執行fork創建子進程,開銷等同於bgsave過程。
3.1)主進程fork操作完成後,繼續響應其他命令。所有修改命令依然寫入AOF緩沖區並根據appendfsync策略同步到硬碟,保證原有AOF機制正確性。
3.2)由於fork操作運用寫時復制技術,子進程只能共享fork操作時的內存數據。由於父進程依然響應命令,Redis使用「AOF重寫緩沖區」保存這部分新數據,防止新AOF文件生成期間丟失這部分數據。
4)子進程根據內存快照,按照命令合並規則寫入到新的AOF文件。每次批量寫入硬碟數據量由配置aof-rewrite-incremental-fsync控制,默認為32MB,防止單次刷盤數據過多造成硬碟阻塞。
5.1)新AOF文件寫入完成後,子進程發送信號給父進程,父進程更新統計信息,具體見info persistence下的aof_*相關統計。
5.2)父進程把AOF重寫緩沖區的數據寫入到新的AOF文件。
5.3)使用新AOF文件替換老文件,完成AOF重寫。
5.2.5重啟載入
AOF和RDB文件都可以用於伺服器重啟時的數據恢復。如圖5-4所示,表示Redis持久化文件載入流程。
流程說明:
1)AOF持久化開啟且存在AOF文件時,優先載入AOF文件,列印如下日誌:
* DB loaded from append only file: 5.841 seconds
2)AOF關閉或者AOF文件不存在時,載入RDB文件,列印如下日誌:
* DB loaded from disk: 5.586 seconds
3)載入AOF/RDB文件成功後,Redis啟動成功。
4)AOF/RDB文件存在錯誤時,Redis啟動失敗並列印錯誤信息。
5.2.6文件校驗
載入損壞的AOF文件時會拒絕啟動,並列印如下日誌:
# Bad file format reading the append only file: make a backup of your AOF file,
then use ./redis-check-aof --fix <filename>
運維提示
對於錯誤格式的AOF文件,先進行備份,然後採用redis-check-aof--fix命令進行修復,修復後使用diff-u對比數據的差異,找出丟失的數據,有些可以人工修改補全。
AOF文件可能存在結尾不完整的情況,比如機器突然掉電導致AOF尾部文件命令寫入不全。Redis為我們提供了aof-load-truncated配置來兼容這種情況,默認開啟。載入AOF時,當遇到此問題時會忽略並繼續啟動,同時列印
如下警告日誌:
# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 397856725 !!!
# AOF loaded anyway because aof-load-truncated is enabled
5.3問題定位與優化
Redis持久化功能一直是影響Redis性能的高發地,本節我們結合常見的持久化問題進行分析定位和優化。
5.3.1fork操作
當Redis做RDB或AOF重寫時,一個必不可少的操作就是執行fork操作創建子進程,對於大多數操作系統來說fork是個重量級錯誤。雖然fork創建的子進程不需要拷貝父進程的物理內存空間,但是會復制父進程的空間內存頁表。例如對於10GB的Redis進程,需要復制大約20MB的內存頁表,因此fork操作耗時跟進程總內存量息息相關,如果使用虛擬化技術,特別是Xen虛擬機,fork操作會更耗時。
fork耗時問題定位:對於高流量的Redis實例OPS可達5萬以上,如果fork操作耗時在秒級別將拖Redis幾萬條命令執行,對線上應用延遲影響非常明顯。正常情況下fork耗時應該是每GB消耗20毫秒左右。可以在info stats統計中查latest_fork_usec指標獲取最近一次fork操作耗時,單位微秒。
如何改善fork操作的耗時:
1)優先使用物理機或者高效支持fork操作的虛擬化技術,避免使用Xen。
2)控制Redis實例最大可用內存,fork耗時跟內存量成正比,線上建議每個Redis實例內存控制在10GB以內。
3)合理配置Linux內存分配策略,避免物理內存不足導致fork失敗,具體細節見12.1節「Linux配置優化」。
4)降低fork操作的頻率,如適度放寬AOF自動觸發時機,避免不必要的全量復制等。
5.3.2子進程開銷監控和優化
子進程負責AOF或者RDB文件的重寫,它的運行過程主要涉及CPU、內存、硬碟三部分的消耗。
1.CPU
·CPU開銷分析。子進程負責把進程內的數據分批寫入文件,這個過程屬於CPU密集操作,通常子進程對單核CPU利用率接近90%.
·CPU消耗優化。Redis是CPU密集型服務,不要做綁定單核CPU操作。由於子進程非常消耗CPU,會和父進程產生單核資源競爭。不要和其他CPU密集型服務部署在一起,造成CPU過度競爭。如果部署多個Redis實例,盡量保證同一時刻只有一個子進程執行重寫工作,具體細節見5.4節多實例部署」。
2.內存
·內存消耗分析。子進程通過fork操作產生,佔用內存大小等同於父進程,理論上需要兩倍的內存來完成持久化操作,但Linux有寫時復制機制(-on-write)。父子進程會共享相同的物理內存頁,當父進程處理寫請求時會把要修改的頁創建副本,而子進程在fork操作過程中共享整個父進程內存快照。
·內存消耗監控。RDB重寫時,Redis日誌輸出容如下:
* Background saving started by pid 7692
* DB saved on disk
* RDB: 5 MB of memory used by -on-write
* Background saving terminated with success
如果重寫過程中存在內存修改操作,父進程負責創建所修改內存頁的副本,從日誌中可以看出這部分內存消耗了5MB,可以等價認為RDB重寫消耗了5MB的內存。
AOF重寫時,Redis日誌輸出容如下:
* Background append only file rewriting started by pid 8937
* AOF rewrite child asks to stop sending diffs.
* Parent agreed to stop sending diffs. Finalizing AOF...
* Concatenating 0.00 MB of AOF diff received from parent.
* SYNC append only file rewrite performed
* AOF rewrite: 53 MB of memory used by -on-write
* Background AOF rewrite terminated with success
* Resial parent diff successfully flushed to the rewritten AOF (1.49 MB)
* Background AOF rewrite finished successfully
父進程維護頁副本消耗同RDB重寫過程類似,不同之處在於AOF重寫需要AOF重寫緩沖區,因此根據以上日誌可以預估內存消耗為:53MB+1.49MB,也就是AOF重寫時子進程消耗的內存量。
運維提示
編寫shell腳本根據Redis日誌可快速定位子進程重寫期間內存過度消耗情況。
內存消耗優化:
1)同CPU優化一樣,如果部署多個Redis實例,盡量保證同一時刻只有一個子進程在工作。
2)避免在大量寫入時做子進程重寫操作,這樣將導致父進程維護大量頁副本,造成內存消耗。Linux kernel在2.6.38內核增加了Transparent Huge Pages(THP),支持huge page(2MB)的頁分配,默認開啟。當開啟時可以降低fork創建子進程的速度,但執行fork之後,如果開啟THP,復制頁單位從原來4KB變為2MB,會大幅增加重寫期間父進程內存消耗。建議設置「sudo echo never>/sys/kernel/mm/transparent_hugepage/enabled」關閉THP。更多THP細節和配置見12.1Linux配置優化」。
3.硬碟
·硬碟開銷分析。子進程主要職責是把AOF或者RDB文件寫入硬碟持久化。勢必造成硬碟寫入壓力。根據Redis重寫AOF/RDB的數據量,結合系統工具如sar、iostat、iotop等,可分析出重寫期間硬碟負載情況。·硬碟開銷優化。優化方法如下:
a)不要和其他高硬碟負載的服務部署在一起。如:存儲服務、消息隊列服務等。
b)AOF重寫時會消耗大量硬碟IO,可以開啟配置no-appendfsync-on-rewrite,默認關閉。表示在AOF重寫期間不做fsync操作。
c)當開啟AOF功能的Redis用於高流量寫入場景時,如果使用普通機械磁碟,寫入吞吐一般在100MB/s左右,這時Redis實例的瓶頸主要在AOF同步硬碟上。
d)對於單機配置多個Redis實例的情況,可以配置不同實例分盤存儲AOF文件,分攤硬碟寫入壓力。運維提示
配置no-appendfsync-on-rewrite=yes時,在極端情況下可能丟失整個AOF重寫期間的數據,需要根據數據安全性決定是否配置。
5.3.3AOF追加阻塞
當開啟AOF持久化時,常用的同步硬碟的策略是everysec,用於平衡性能和數據安全性。對於這種方式,Redis使用另一條線程每秒執行fsync同步硬碟。當系統硬碟資源繁忙時,會造成Redis主線程阻塞,如圖5-5所示。
阻塞流程分析:
1)主線程負責寫入AOF緩沖區。
2)AOF線程負責每秒執行一次同步磁碟操作,並記錄最近一次同步時間。
3)主線程負責對比上次AOF同步時間:
·如果距上次同步成功時間在2秒內,主線程直接返回。
·如果距上次同步成功時間超過2秒,主線程將會阻塞,直到同步操作完成。
通過對AOF阻塞流程可以發現兩個問題:
1)everysec配置最多可能丟失2秒數據,不是1秒。
2)如果系統fsync緩慢,將會導致Redis主線程阻塞影響效率。
AOF阻塞問題定位:
1)發生AOF阻塞時,Redis輸出如下日誌,用於記錄AOF fsync阻塞導致拖慢Redis服務的行為:
Asynchronous AOF fsync is taking too long (disk is busy). Writing the AOF buffer
without waiting for fsync to complete, this may slow down Redis
2)每當發生AOF追加阻塞事件發生時,在info Persistence統計中,aof_delayed_fsync指標會累加,查看這個指標方便定位AOF阻塞問題。
3)AOF同步最多允許2秒的延遲,當延遲發生時說明硬碟存在高負載問題,可以通過監控工具如iotop,定位消耗硬碟IO資源的進程。優化AOF追加阻塞問題主要是優化系統硬碟負載,優化方式見上一節。
5.4多實例部署
Redis單線程架構導致無法充分利用CPU多核特性,通常的做法是在一台機器上部署多個Redis實例。當多個實例開啟AOF重寫後,彼此之間會產生對CPU和IO的競爭。本節主要介紹針對這種場景的分析和優化。上一節介紹了持久化相關的子進程開銷。對於單機多Redis部署,如果同一時刻運行多個子進程,對當前系統影響將非常明顯,因此需要採用一種措施,把子進程工作進行隔離。Redis在info Persistence中為我們提供了監控子進程運行狀況的度量指標,如表5-2所示。
我們基於以上指標,可以通過外部程序輪詢控制AOF重寫操作的執行,整個過程如圖5-6所示。
流程說明:
1)外部程序定時輪詢監控機器(machine)上所有Redis實例。
2)對於開啟AOF的實例,查看(aof_current_size-aof_base_size)/aof_base_size確認增長率。
3)當增長率超過特定閾值(如100%),執行bgrewriteaof命令手動觸發當前實例的AOF重寫。
4)運行期間循環檢查aof_rewrite_in_progress和aof_current_rewrite_time_sec指標,直到AOF重寫結束。
5)確認實例AOF重寫完成後,再檢查其他實例並重復2)~4)步操作。從而保證機器內每個Redis實例AOF重寫串列化執行。
5.5本章重點回顧
1)Redis提供了兩種持久化方式:RDB和AOF。
2)RDB使用一次性生成內存快照的方式,產生的文件緊湊壓縮比更高,因此讀取RDB恢復速度更快。由於每次生成RDB開銷較大,無法做到實時持久化,一般用於數據冷備和復制傳輸。
3)save命令會阻塞主線程不建議使用,bgsave命令通過fork操作創建子進程生成RDB避免阻塞。
4)AOF通過追加寫命令到文件實現持久化,通過appendfsync參數可以控制實時/秒級持久化。因為需要不斷追加寫命令,所以AOF文件體積逐漸變大,需要定期執行重寫操作來降低文件體積。
5)AOF重寫可以通過auto-aof-rewrite-min-size和auto-aof-rewrite-percentage參數控制自動觸發,也可以使用bgrewriteaof命令手動觸發。
6)子進程執行期間使用-on-write機制與父進程共享內存,避免內存消耗翻倍。AOF重寫期間還需要維護重寫緩沖區,保存新的寫入命令避免數據丟失。
7)持久化阻塞主線程場景有:fork阻塞和AOF追加阻塞。fork阻塞時間跟內存量和系統有關,AOF追加阻塞說明硬碟資源緊張。
8)單機下部署多個實例時,為了防止出現多個子進程執行重寫操作,建議做隔離控制,避免CPU和IO資源競爭。