你知道 Redis 使用的什么協(xié)議嗎?
有個小伙伴面試回來說面試官問了他一些 Redis 問題,但是他好像沒有回答上來。
我說,你 Redis 不是用的很溜嗎,什么問題難住你了。
他說,事情是這樣的,剛開始,問了一些基礎(chǔ)的問題,比如 Redis 的幾種基本數(shù)據(jù)類型和使用場景,以及主從復(fù)制和集群的一些問題,這些都還好。
然后問 Redis 的兩種持久化方式,他說與 RDB 和 AOF 兩種方式,RDB 數(shù)據(jù)文件小,恢復(fù)速度快,但是對性能有影響,而且不適合實(shí)時存儲。而 AOF 是現(xiàn)在最常用的持久化方式,它的一大優(yōu)點(diǎn)就是實(shí)時性,并且對 Redis 半身性能影響最小。
那面試又問了,你知道 AOF 持久化之后的文件是什么格式嗎?
答:好像就是文本文件吧?
好,文本文件,那你知道它有什么規(guī)則嗎?或者說,它和 Redis 的協(xié)議有什么關(guān)系嗎?
答:啊,這個,恩,不太清楚呢。
現(xiàn)在就來看一下 AOF 和 RESP 協(xié)議的關(guān)系
從兩種持久化方式說起。
RESP 協(xié)議是什么
動手實(shí)現(xiàn)一個簡單的協(xié)議解析命令行工具

先從持久化說起,雖然一提到 Redis,首先想到的就是緩存,但是 Redis 不僅僅是緩存這么簡單,它的定位是內(nèi)存型數(shù)據(jù)庫,可以存儲多種類型的數(shù)據(jù)結(jié)構(gòu),還可以當(dāng)做簡單消息隊列使用。既然是數(shù)據(jù)庫,持久化功能是必不可少的。
?
Redis 的兩種持久化方式
Redis 提供了兩種持久化方式,一種是 RDB 方式,另外一種是 AOF 方式,AOF 是目前比較流行的持久化方案。
RDB 方式
RDB持久化是通過快照的方式,在指定的時間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤。它以一種緊湊壓縮的二進(jìn)制文件的形式出現(xiàn)。可以將快照復(fù)制到其他服務(wù)器以創(chuàng)建相同數(shù)據(jù)的服務(wù)器副本,或者在重啟服務(wù)器后恢復(fù)數(shù)據(jù)。RDB是Redis默認(rèn)的持久化方式,也是早期版本的必須方案。
RDB 由下面幾個參數(shù)控制。
#?設(shè)置?dump?的文件名
dbfilename?dump.rdb
#?持久化文件的存儲目錄
dir?./
#?900秒內(nèi),如果至少有1個key發(fā)生變化,就會自動觸發(fā)bgsave命令創(chuàng)建快照
save?900?1
#?300秒內(nèi),如果至少有10個key發(fā)生變化,就會自動觸發(fā)bgsave命令創(chuàng)建快照
save?300?10
#?60秒內(nèi),如果至少有10000個key發(fā)生變化,就會自動觸發(fā)bgsave命令創(chuàng)建快照
save?60?10000
持久化流程
上面說到了配置文件中的幾個觸發(fā)持久化的機(jī)制,比如 900 秒、300秒、60秒,當(dāng)然也可以手動執(zhí)行命令 save或bgsave進(jìn)行觸發(fā)。bgsave是非阻塞版本,通過 fork 出子進(jìn)程的方式來進(jìn)行快照生成,而 save會阻塞主進(jìn)程,不建議使用。
1、首先 bgsave命令觸發(fā);
2、父進(jìn)程 fork 出一個子進(jìn)程,這一步是比較重量級的操作,也是 RDB 方式性能不及 AOF 的一個重要原因;
3、父進(jìn)程 fork 出子進(jìn)程后就可以正常的相應(yīng)客戶端發(fā)來的其他命令了;
4、子進(jìn)程開始進(jìn)行持久化工作,對現(xiàn)有數(shù)據(jù)進(jìn)行完整的快照存儲;
5、子進(jìn)程完成操作后,通知父進(jìn)程;

RDB的優(yōu)點(diǎn):
RDB是一個緊湊壓縮的二進(jìn)制文件,代表Redis在某個時間點(diǎn)上的數(shù)據(jù) 快照。非常適用于備份,全量復(fù)制等場景。比如每6小時執(zhí)行bgsave備份, 并把RDB文件拷貝到遠(yuǎn)程機(jī)器或者文件系統(tǒng)中(如hdfs),用于災(zāi)難恢復(fù)。 Redis加載RDB恢復(fù)數(shù)據(jù)遠(yuǎn)遠(yuǎn)快于AOF的方式。
RDB的缺點(diǎn):
RDB方式數(shù)據(jù)沒辦法做到實(shí)時持久化/秒級持久化。因為bgsave每次運(yùn) 行都要執(zhí)行fork操作創(chuàng)建子進(jìn)程,屬于重量級操作,頻繁執(zhí)行成本過高。 RDB文件使用特定二進(jìn)制格式保存,Redis版本演進(jìn)過程中有多個格式 的RDB版本,存在老版本Redis服務(wù)無法兼容新版RDB格式的問題。
AOF 方式
#?appendonly參數(shù)開啟AOF持久化
appendonly?yes
#?AOF持久化的文件名,默認(rèn)是appendonly.aof
appendfilename?"appendonly.aof"
#?AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數(shù)設(shè)置的
dir?./
#?同步策略
#?appendfsync?always
appendfsync?everysec
#?appendfsync?no
#?aof重寫期間是否同步
no-appendfsync-on-rewrite?no
#?重寫觸發(fā)配置
auto-aof-rewrite-percentage?100
auto-aof-rewrite-min-size?64mb
#?加載aof出錯如何處理
aof-load-truncated?yes
#?文件重寫策略
aof-rewrite-incremental-fsync?yes

AOF 文件里存的是什么

?
?
RESP 協(xié)議
實(shí)現(xiàn)簡單; 快速解析; 可閱讀;
redis-cli或者一些客戶端工具連接 Redis 服務(wù)端。./redis-cli

用 telnet 試試
redis-cli工具,還可以用 telnet 查看,用 telnet 就可以看出 RESP 返回的原始數(shù)據(jù)格式了。telnet?127.0.0.1?6379

get str:hello這條,返回的結(jié)果除了 world值本身,上面還多了一行 $5,是不是有點(diǎn)迷糊了。協(xié)議規(guī)則
請求命令
*<參數(shù)數(shù)量>?CR?LF
$<參數(shù)?1?的字節(jié)數(shù)量>?CR?LF
<參數(shù)?1?的數(shù)據(jù)>?CR?LF
...
$<參數(shù)?N?的字節(jié)數(shù)量>?CR?LF
<參數(shù)?N?的數(shù)據(jù)>?CR?LF
\r\n作為分隔符,會表明此條命令的具體參數(shù)個數(shù),在命令上看來,空格分隔的都表示一個參數(shù),例如 set str:hello world 這條命令就是3個參數(shù),會表明每個參數(shù)的字符數(shù)和具體內(nèi)容。*3\r\n$3\r\nset\r\n$9str:hello\r\n$5world\r\n

服務(wù)端回復(fù)
"+"ping命令的回復(fù),+PONG\r\n"-"auth,auth 命令后面需要有一個密碼參數(shù)的,如果不輸入就會返回錯誤回復(fù)類型。-ERR wrong number of arguments for 'auth' command\r\n":"INCR、DECR 自增自減命令,返回的結(jié)果是這樣的 :2\r\n"$"$5\r\nworld\r\n,$后面的數(shù)字 5 表示返回的結(jié)果有 5 個字符,后面是返回結(jié)果的實(shí)際內(nèi)容。"*"LRANGE key start stop或者 hgetall等返回多條結(jié)果的命令,比如 lrange命令返回的結(jié)果:*2\r\n$6\r\nnews-2\r\n$6\r\nnews-1\r\n
?
實(shí)現(xiàn)一個簡單的 Redis 交互工具


public?Socket?createSocket()?throws?IOException?{
??Socket?socket?=?null;
??try?{
????socket?=?new?Socket();
????socket.setReuseAddress(true);
????socket.setKeepAlive(true);
????socket.setTcpNoDelay(true);
????socket.setSoLinger(true,?0);
????socket.connect(new?InetSocketAddress(host,?port),?DEFAULT_TIMEOUT);
????socket.setSoTimeout(DEFAULT_TIMEOUT);
????outputStream?=?socket.getOutputStream();
????inputStream?=?socket.getInputStream();
????return?socket;
??}?catch?(Exception?ex)?{
????if?(socket?!=?null)?{
??????socket.close();
????}
????throw?ex;
??}
}

有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
