Redis雜談
Redis是近年來(lái)發(fā)展迅速的內(nèi)存數(shù)據(jù)庫(kù)桂塞,網(wǎng)上也已經(jīng)有多Redis的文章。但不管是英文還是中文眉菱,多數(shù)文章的各個(gè)知識(shí)點(diǎn)都比較分散磅轻,本系列是關(guān)于Redis主題的綜合性討論,也算是對(duì)我使用Redis的一個(gè)總結(jié)吵取,主要面向已經(jīng)使用Redis禽额,但對(duì)于整體還不甚了解的Java程序員,當(dāng)然也可以作為入門參考皮官,對(duì)于重要的內(nèi)容脯倒,本文力求把基本思想講到,限于篇幅不能深入的內(nèi)容捺氢,會(huì)給出相關(guān)細(xì)節(jié)的參考來(lái)源藻丢,本文寫作時(shí),Redis穩(wěn)定版為 3.2.8
摄乒。本系列由三部分構(gòu)成:
- (一)Redis雜談悠反,主要包括Redis本身的性質(zhì),包括于Redis的介紹缺狠,基本操作问慎,數(shù)據(jù)持久化,Reids集群相關(guān)內(nèi)容挤茄。
- (二)Spring下使用Redis如叼,主要包括Spring Data Redis
1.8.3
提供的對(duì)于Redis的各種操作和說(shuō)明。 - (三)實(shí)現(xiàn)SimHash網(wǎng)頁(yè)去重穷劈,主要通個(gè)一個(gè)具體的例子笼恰,結(jié)合前述內(nèi)容展示Redis的使用踊沸。
好了,Let's go!
Redis介紹
Redis是什么
先看看維基百科是怎么說(shuō)的:
Redis是一個(gè)使用ANSI C編寫的開源社证、支持網(wǎng)絡(luò)逼龟、基于內(nèi)存、可選持久性的鍵值對(duì)存儲(chǔ)數(shù)據(jù)庫(kù)追葡。從2015年6月開始腺律,Redis的開發(fā)由Redis Labs贊助,而2013年5月至2015年6月期間宜肉,其開發(fā)由Pivotal贊助匀钧。在2013年5月之前,其開發(fā)由VMware贊助谬返。根據(jù)月度排行網(wǎng)站DB-Engines.com的數(shù)據(jù)顯示之斯,Redis是最流行的鍵值對(duì)存儲(chǔ)數(shù)據(jù)庫(kù) 。
這個(gè)描述中遣铝,有幾個(gè)關(guān)鍵詞:開源
支持網(wǎng)絡(luò)
基于內(nèi)存
可選持久性
鍵值對(duì)數(shù)據(jù)庫(kù)
佑刷,基本上概括了Redis的核心特征。Redis通過(guò)TCP套接字和一個(gè)簡(jiǎn)單的協(xié)議構(gòu)建了一個(gè)服務(wù)器-客戶端模式酿炸,因此瘫絮,不同的進(jìn)程能夠以一種共享的方式查詢和修改數(shù)據(jù)。
具體來(lái)說(shuō):
- 基于內(nèi)存:是說(shuō)梁沧,我運(yùn)行的數(shù)據(jù)都加載到內(nèi)存里面檀何,潛臺(tái)詞就是,我很快哦廷支,很快很快哦频鉴!
- NoSQL:對(duì)不起棒坏,我沒(méi)有SQL解析引擎验游,我沒(méi)法這樣玩:select name from employee where age < 60 陆馁。
- 可選持久性:斷電了或者機(jī)器崩潰了徘公,我可以恢復(fù)吧黄,而且持久化策略可以配置驹溃。
- 鍵值對(duì):所有數(shù)據(jù)都通過(guò)Key來(lái)存取舱殿,復(fù)雜度O(1)饲宛。
- 集群:可以組團(tuán)群毆僵娃,而且群毆方案豐富多樣概作。
Redis的特點(diǎn)
理解Redis的特點(diǎn),最好的入口默怨,就是理解Redis常被形容為數(shù)據(jù)結(jié)構(gòu)服務(wù)器讯榕,這到底是個(gè)啥?這的確是一個(gè)不常見(jiàn)的術(shù)語(yǔ),所以Redis在首頁(yè)就掛了這么一段話來(lái)解釋自己對(duì)自己的定位:
Redis支持諸如strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs 和 geospatial indexes with radius等形式的數(shù)據(jù)結(jié)構(gòu)愚屁,它內(nèi)建了復(fù)制济竹,Lua腳本,LRU緩存機(jī)制霎槐,事務(wù)送浊,不同級(jí)別的數(shù)據(jù)持久化,并通過(guò)Redis Sentinel提供高可用性丘跌,和通過(guò)Redis Cluster提供自動(dòng)分區(qū)袭景。
其他類似產(chǎn)品
下圖是DB-Engines.com根據(jù)一些特征,對(duì)使用Key-Value存儲(chǔ)模式的數(shù)據(jù)庫(kù)引擎進(jìn)行的排名碍岔。常常拿來(lái)和Redis比較的浴讯,可能就是排在第二位的Memcached了。
雖然Memcached和Redis有眾多不同蔼啦,比如線程模式、存儲(chǔ)模式等仰猖,但如果一言蔽之捏肢,誠(chéng)如這篇StackOverflow中提到的:
Memcached 是一個(gè)非持久化的內(nèi)存key/value數(shù)據(jù)庫(kù),Redis也能做得這一點(diǎn), 但Redis還是一個(gè)可持久化的數(shù)據(jù)結(jié)構(gòu)服務(wù)器饥侵。
Redis的各種資料獲取
Redis的資料非常豐富鸵赫,建議優(yōu)先閱讀和查詢官方的文檔:
- Redis的數(shù)據(jù)類型介紹: http://redis.io/topics/data-types-intro
- 所有Redis命令列表: http://redis.io/commands
- 更多的Redis官方文檔: http://redis.io/documentation
- Spring Data Redis官方文檔:http://docs.spring.io/spring-data/redis/docs/current/reference/html
- Redis的漢化鏡像站: http://www.redis.cn/
- Salvatore(Redis發(fā)起者)的博客:http://oldblog.antirez.com/
Redis基本命令
Redis使用的是server-client模式,使用Redis存取數(shù)據(jù)躏升,就是通過(guò)Redis客戶端向服務(wù)器端發(fā)送各種操作命令存取數(shù)據(jù)辩棒,大致的過(guò)程就是這樣:
redis> ping
PONG
redis> info
# Server
redis_version:3.2.8
....
這里有一份不錯(cuò)的命令手冊(cè)中文翻譯。
需要強(qiáng)調(diào)和不容易理解的一點(diǎn)是: Redis的整體結(jié)構(gòu)是Key-Value的膨疏,但是和其他一些Key-Value產(chǎn)品不一樣的是一睁,這個(gè)Value本身可以是有數(shù)據(jù)結(jié)構(gòu)的,比如Value本身可以是這么多類型:
- Strings
- Hashes
- Lists
- Sets
- Sorted sets with range queries
- Hyperloglogs
- Geospatial indexes with radius queries.
怎么理解這一點(diǎn)呢佃却,我們通過(guò)一個(gè)例子來(lái)說(shuō)明:
redis> HSET myhash field1 "foo"
(integer) 1
redis> HGET myhash field1
"foo"
redis> HGET myhash field2
(nil)
redis> HSET myhash field2 "bar"
(integer) 1
redis> HMGET myhash field1 field2
1) "foo"
2) "bar"
這個(gè)例子存儲(chǔ)的類型是Hash者吁,也就是說(shuō)Value本身也是一個(gè)Key-Value結(jié)構(gòu)的數(shù)據(jù)。整體上就是Key-(Key-Value)饲帅,我們可以理解為myhash是一個(gè)Hash表的名字复凳,filed1,filed2是myhash這張Hash表中的鍵值灶泵,而myhash同時(shí)也是Redis這張大Hash表中的一個(gè)Key育八。
同理,不管Value是什么類型赦邻,它都有一個(gè)Key髓棋,這個(gè)Key就是Redis本身Key-Vaule的Key。
好的深纲,現(xiàn)在就可以去對(duì)照命令手冊(cè)使用Redis仲锄,什么劲妙,你還沒(méi)有Redis的環(huán)境!沒(méi)關(guān)系儒喊,Redis官方提供了一個(gè) ==網(wǎng)頁(yè)版Redis體驗(yàn)== 的供大家練手:
Redis的安裝與配置
安裝
單機(jī)版的Redis的安裝非常簡(jiǎn)單镣奋,Redis兼容的操作系統(tǒng)為:Linux, OSX, OpenBSD, NetBSD, FreeBSD。支持Big endian和Little endian 處理器架構(gòu), 支持64位和32位系統(tǒng)怀愧。
在Linux下的安裝過(guò)程侨颈,只需要make命令就可:
% make
如果是32位系統(tǒng):
% make 32bit
編譯后可以通過(guò)make test測(cè)試:
% make test
如有問(wèn)題,可參考官方編譯說(shuō)明芯义。
Redis官方?jīng)]有Windows版本哈垢,但是微軟實(shí)現(xiàn)了一個(gè)Windows版的Redis Server 。
配置
Redis的配置文件是自注釋的扛拨,寫的密密麻麻耘分,含義非常詳細(xì),清楚绑警。運(yùn)行時(shí)求泰,把配置路徑作為參數(shù)啟動(dòng),使配置生效:
redis>redis-server /opt/redis/redis.conf
總共的配置項(xiàng)目超過(guò)50項(xiàng)计盒,不過(guò)渴频,在非集群模式下,通常關(guān)注的配置項(xiàng)目只有這些:
- maxmemory [3000m] 最大使用內(nèi)存
- daemonize [yes|no] 是否后臺(tái)啟動(dòng)
- dir [path] 持久化數(shù)據(jù)存放目錄
- requirepass [password] 登錄密碼
- save [seconds] [changes] 在多少秒內(nèi)有多少次寫操作北启,就刷入一次數(shù)據(jù)到磁盤
- appendonly [yes|no] 是否開啟APPEND ONLY模式卜朗,這也是一種持久化策略,下一節(jié)會(huì)介紹
這幾個(gè)常用配置都非常好理解咕村,但是第一個(gè)maxmemory要需要注意场钉,這個(gè)配置涉及到配置Eviction policies,更多內(nèi)容可以參考 LRU算法進(jìn)行緩存回收培廓。
也可以參考這一份不錯(cuò)的中文配置說(shuō)明惹悄,但是由于Redis發(fā)展非常迅速,所以生產(chǎn)環(huán)境中使用的配置項(xiàng)一定要對(duì)照官方說(shuō)明肩钠。
比如Redis配置中的VM配置(虛擬內(nèi)存機(jī)制)泣港,很多文章還在提,但是這個(gè)配置其實(shí)已經(jīng)在不斷的發(fā)展中被廢棄了,這個(gè)配置的用意是VM機(jī)制將數(shù)據(jù)分頁(yè)存放价匠,由Redis將訪問(wèn)量較少的頁(yè)即冷數(shù)據(jù)swap到磁盤上当纱,訪問(wèn)多的頁(yè)面由磁盤自動(dòng)換出到內(nèi)存中,棒棒噠踩窖,對(duì)吧坡氯?但是對(duì)于Redis這么一個(gè)小軟件,希望把存儲(chǔ)做成如同Oracle一樣的方式,具備自動(dòng)淘汰冷熱數(shù)據(jù)功能箫柳,并且比Linux操作系統(tǒng)本身更加優(yōu)秀手形,太難了。
對(duì)于Linux系統(tǒng)悯恍,在配置文件之外库糠,還有一些配置需要考慮:
修改內(nèi)存分配策略,使系統(tǒng)請(qǐng)求分配內(nèi)存時(shí)涮毫,永遠(yuǎn)假裝還有足夠的內(nèi)存
echo 'vm.overcommit_memory = 1' >>/etc/sysctl.conf
然后執(zhí)行: sysctl vm.overcommit_memory=1
定義了系統(tǒng)中每一個(gè)端口最大的監(jiān)聽隊(duì)列的長(zhǎng)度瞬欧,這是個(gè)全局的參數(shù),默認(rèn)128罢防。
echo 1024 > /proc/sys/net/core/somaxconn
禁用透明緩存頁(yè)
echo never > /sys/kernel/mm/transparent_hugepage/enabled
關(guān)于這部分的內(nèi)容艘虎,可以參考 Redis Administration 和 Redis latency problems troubleshooting。
Redis 的數(shù)據(jù)持久化
理解Redis的數(shù)據(jù)持久化對(duì)于使用Redis特別重要咒吐,因?yàn)椋?當(dāng)然是因?yàn)椴荒茈S便把數(shù)據(jù)搞丟野建,還有什么比這更重要么!況且可配置持久化恬叹,也是很多用戶選擇Redis的重要原因贬墩。
Redis官方有一篇專門闡述其持久化的文檔,以及Redis開發(fā)者Salvatore針對(duì)這個(gè)問(wèn)題撰寫的一篇長(zhǎng)文《Redis 持久化解密》妄呕。
不管他們?cè)趺凑f(shuō),其實(shí)歸納起來(lái)我們就想知道3個(gè)問(wèn)題:
- Redis的持久化是如何工作的嗽测?
- 這樣工作對(duì)性能的影響有多大绪励?
- 我應(yīng)該如何選擇?
一般來(lái)說(shuō)Redis的所有工作數(shù)據(jù)都在內(nèi)存中唠粥,這也是內(nèi)存數(shù)據(jù)庫(kù)的特點(diǎn)疏魏,持久化數(shù)據(jù)只是啟動(dòng)時(shí)加載,或者作為災(zāi)備手段晤愧。前面已經(jīng)提到了Redis的持久化有兩種方式:RDB和AOF大莫。
RDB持久化方式能夠在指定的時(shí)間間隔能對(duì)你的數(shù)據(jù)進(jìn)行快照存儲(chǔ),它是一個(gè)非常緊湊的文件,它保存了某個(gè)時(shí)間點(diǎn)得數(shù)據(jù)集,非常適用于數(shù)據(jù)集的備份官份,比如你可以在每個(gè)小時(shí)報(bào)保存一下過(guò)去24小時(shí)內(nèi)的數(shù)據(jù),同時(shí)每天保存過(guò)去30天的數(shù)據(jù)只厘,這樣即使出了問(wèn)題你也可以根據(jù)需求恢復(fù)到不同版本的數(shù)據(jù)集。
RDB的持久化方式被稱為快照舅巷,在默認(rèn)情況下羔味,Redis將數(shù)據(jù)庫(kù)快照保存在名字為dump.rdb的二進(jìn)制文件中。你可以對(duì) Redis 進(jìn)行設(shè)置钠右, 讓它在“ N 秒內(nèi)數(shù)據(jù)集至少有 M 個(gè)改動(dòng)”這一條件被滿足時(shí)赋元, 自動(dòng)保存一次數(shù)據(jù)集。你也可以通過(guò)調(diào)用 SAVE或者 BGSAVE , 手動(dòng)讓 Redis 進(jìn)行數(shù)據(jù)集保存操作搁凸。
比如說(shuō)媚值, 以下設(shè)置會(huì)讓 Redis 在滿足“ 60 秒內(nèi)有至少有 1000 個(gè)鍵被改動(dòng)”這一條件時(shí), 自動(dòng)保存一次數(shù)據(jù)集:
save 60 1000
AOF持久化方式記錄每次對(duì)服務(wù)器寫的操作护糖,當(dāng)服務(wù)器重啟的時(shí)候會(huì)重新執(zhí)行這些命令來(lái)恢復(fù)原始的數(shù)據(jù)褥芒,AOF命令以redis協(xié)議追加保存每次寫的操作到文件末尾。Redis還能對(duì)AOF文件進(jìn)行后臺(tái)重寫,使得AOF文件的體積不至于過(guò)大椅文。一個(gè)AOF文件就像這個(gè)樣子:
$ cat appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$4
key1
$5
Hello
*3
$6
append
$4
key1
$7
World!
*2
$3
del
$4
key1
你可以配置 Redis 多久才將數(shù)據(jù) fsync 到磁盤一次喂很。有三種方式:
- 每次有新命令追加到 AOF 文件時(shí)就執(zhí)行一次 fsync :非常慢,也非常安全
- 每秒 fsync 一次:足夠快(和使用 RDB 持久化差不多)皆刺,并且在故障時(shí)只會(huì)丟失 1 秒鐘的數(shù)據(jù)少辣。
- 從不 fsync :將數(shù)據(jù)交給操作系統(tǒng)來(lái)處理。更快羡蛾,也更不安全的選擇漓帅。
官方推薦(并且也是默認(rèn))的措施為每秒 fsync 一次, 這種 fsync 策略可以兼顧速度和安全性痴怨。
有兩點(diǎn)需要注意:
如果同時(shí)開啟兩種持久化方式忙干,在這種情況下, 當(dāng)redis重啟的時(shí)候會(huì)優(yōu)先載入AOF文件來(lái)恢復(fù)原始的數(shù)據(jù)浪藻,因?yàn)樵谕ǔG闆r下AOF文件保存的數(shù)據(jù)集要比RDB文件保存的數(shù)據(jù)集要完整捐迫。
RDB快照會(huì)被用于master -> slave同步。
優(yōu)缺點(diǎn)
RDB的優(yōu)點(diǎn)
RDB是一個(gè)非常緊湊的文件爱葵,它保存了某個(gè)時(shí)間點(diǎn)得數(shù)據(jù)集施戴,非常適用于數(shù)據(jù)集的備份,比如你可以在每個(gè)小時(shí)報(bào)保存一下過(guò)去24小時(shí)內(nèi)的數(shù)據(jù)萌丈,同時(shí)每天保存過(guò)去30天的數(shù)據(jù)赞哗,這樣即使出了問(wèn)題你也可以根據(jù)需求恢復(fù)到不同版本的數(shù)據(jù)集。
RDB是一個(gè)緊湊的單一文件辆雾,很方便傳送到另一個(gè)遠(yuǎn)端數(shù)據(jù)中心或者亞馬遜的S3(可能加密)肪笋,非常適用于災(zāi)難恢復(fù)。
RDB在保存RDB文件時(shí)父進(jìn)程唯一需要做的就是fork出一個(gè)子進(jìn)程度迂,接下來(lái)的工作全部由子進(jìn)程來(lái)做藤乙,父進(jìn)程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能英岭。
與AOF相比湾盒,在恢復(fù)大的數(shù)據(jù)集的時(shí)候,RDB方式會(huì)更快一些诅妹。
RDB的缺點(diǎn)
如果你希望在redis意外停止工作(例如電源中斷)的情況下丟失的數(shù)據(jù)最少的話罚勾,那么RDB不適合你毅人。雖然你可以配置不同的save時(shí)間點(diǎn)(例如每隔5分鐘并且對(duì)數(shù)據(jù)集有100個(gè)寫的操作,是Redis要完整的保存整個(gè)數(shù)據(jù)集是一個(gè)比較繁重的工作尖殃,你通常會(huì)每隔5分鐘或者更久做一次完整的保存丈莺,萬(wàn)一在Redis意外宕機(jī),你可能會(huì)丟失幾分鐘的數(shù)據(jù)送丰。
RDB 需要經(jīng)常fork子進(jìn)程來(lái)保存數(shù)據(jù)集到硬盤上缔俄,當(dāng)數(shù)據(jù)集比較大的時(shí)候,fork的過(guò)程是非常耗時(shí)的器躏,可能會(huì)導(dǎo)致Redis在一些毫秒級(jí)內(nèi)不能響應(yīng)客戶端的請(qǐng)求俐载。如果數(shù)據(jù)集巨大并且CPU性能不是很好的情況下,這種情況會(huì)持續(xù)1秒登失,AOF也需要fork遏佣,但是你可以調(diào)節(jié)重寫日志文件的頻率來(lái)提高數(shù)據(jù)集的耐久度。
AOF 優(yōu)點(diǎn)
使用AOF 會(huì)讓你的Redis更加耐久: 你可以使用不同的fsync策略:無(wú)fsync揽浙,每秒fsync状婶,每次寫的時(shí)候fsync。使用默認(rèn)的每秒fsync策略馅巷,Redis的性能依然很好(fsync是由后臺(tái)線程進(jìn)行處理的膛虫,主線程會(huì)盡力處理客戶端請(qǐng)求),一旦出現(xiàn)故障钓猬,你最多丟失1秒的數(shù)據(jù)稍刀。
AOF文件是一個(gè)只進(jìn)行追加的日志文件,所以不需要寫入seek敞曹,即使由于某些原因(磁盤空間已滿掉丽,寫的過(guò)程中宕機(jī)等等)未執(zhí)行完整的寫入命令,你也也可使用redis-check-aof工具修復(fù)這些問(wèn)題异雁。
Redis 可以在 AOF 文件體積變得過(guò)大時(shí),自動(dòng)地在后臺(tái)對(duì) AOF 進(jìn)行重寫: 重寫后的新 AOF 文件包含了恢復(fù)當(dāng)前數(shù)據(jù)集所需的最小命令集合僧须。 整個(gè)重寫操作是絕對(duì)安全的纲刀,因?yàn)?Redis 在創(chuàng)建新 AOF 文件的過(guò)程中,會(huì)繼續(xù)將命令追加到現(xiàn)有的 AOF 文件里面担平,即使重寫過(guò)程中發(fā)生停機(jī)示绊,現(xiàn)有的 AOF 文件也不會(huì)丟失。 而一旦新 AOF 文件創(chuàng)建完畢暂论,Redis 就會(huì)從舊 AOF 文件切換到新 AOF 文件面褐,并開始對(duì)新 AOF 文件進(jìn)行追加操作。
AOF 文件有序地保存了對(duì)數(shù)據(jù)庫(kù)執(zhí)行的所有寫入操作取胎, 這些寫入操作以 Redis 協(xié)議的格式保存展哭, 因此 AOF 文件的內(nèi)容非常容易被人讀懂湃窍, 對(duì)文件進(jìn)行分析(parse)也很輕松。 導(dǎo)出(export) AOF 文件也非常簡(jiǎn)單: 舉個(gè)例子匪傍, 如果你不小心執(zhí)行了 FLUSHALL 命令您市, 但只要 AOF 文件未被重寫, 那么只要停止服務(wù)器役衡, 移除 AOF 文件末尾的 FLUSHALL 命令茵休, 并重啟 Redis , 就可以將數(shù)據(jù)集恢復(fù)到 FLUSHALL 執(zhí)行之前的狀態(tài)手蝎。
AOF 缺點(diǎn)
對(duì)于相同的數(shù)據(jù)集來(lái)說(shuō)榕莺,AOF 文件的體積通常要大于 RDB 文件的體積。
根據(jù)所使用的 fsync 策略棵介,AOF 的速度可能會(huì)慢于 RDB 钉鸯。 在一般情況下, 每秒 fsync 的性能依然非常高鞍时, 而關(guān)閉 fsync 可以讓 AOF 的速度和 RDB 一樣快亏拉, 即使在高負(fù)荷之下也是如此。 不過(guò)在處理巨大的寫入載入時(shí)逆巍,RDB 可以提供更有保證的最大延遲時(shí)間(latency)及塘。
如何選擇
關(guān)于如何選擇,官方這么說(shuō):
一般來(lái)說(shuō)锐极, 如果想達(dá)到足以媲美 PostgreSQL 的數(shù)據(jù)安全性笙僚, 你應(yīng)該同時(shí)使用兩種持久化功能。
如果你非常關(guān)心你的數(shù)據(jù)灵再, 但仍然可以承受數(shù)分鐘以內(nèi)的數(shù)據(jù)丟失肋层, 那么你可以只使用 RDB 持久化。
有很多用戶都只使用 AOF 持久化翎迁, 但我們并不推薦這種方式: 因?yàn)槎〞r(shí)生成 RDB 快照(snapshot)非常便于進(jìn)行數(shù)據(jù)庫(kù)備份栋猖, 并且 RDB 恢復(fù)數(shù)據(jù)集的速度也要比 AOF 恢復(fù)的速度要快, 除此之外汪榔, 使用 RDB 還可以避免之前提到的 AOF 程序的 bug 蒲拉。
注意: 因?yàn)橐陨咸岬降姆N種原因, 未來(lái)我們可能會(huì)將 AOF 和 RDB 整合成單個(gè)持久化模型痴腌。
Two More Things ^_^
首先雌团,官方所謂“未來(lái)我們可能會(huì)將 AOF 和 RDB 整合成單個(gè)持久化模型”在某種程度上已經(jīng)在4.0版本中實(shí)現(xiàn)了,這個(gè)改進(jìn)可以分為兩個(gè)層次來(lái)看:其一是AOF的實(shí)現(xiàn)機(jī)制士聪,導(dǎo)致AOF文件太大锦援,4.0 可以配置AOF,使其僅僅進(jìn)行增量記錄剥悟;其二是集群下主備必須全量復(fù)制灵寺,這種機(jī)制被更改為稱為PSYNC2.0的帶標(biāo)簽復(fù)制曼库,看到Salvatore給五年前的一個(gè)留言的回復(fù),我想他那刻的內(nèi)心必是喜悅的(被你們TM懟了5年了啦)替久。
其次凉泄,我們回過(guò)頭來(lái),討論一下到底什么叫數(shù)據(jù)持久化蚯根。非常簡(jiǎn)化的來(lái)看后众,數(shù)據(jù)持久化可以分為這么5步:
- 客戶端發(fā)送一個(gè)寫命令到數(shù)據(jù)庫(kù)(數(shù)據(jù)在客戶端的內(nèi)存中)。
- 數(shù)據(jù)庫(kù)接收到這個(gè)寫命令(數(shù)據(jù)在服務(wù)器的內(nèi)存中)颅拦。
- 數(shù)據(jù)庫(kù)調(diào)用系統(tǒng)調(diào)用把寫數(shù)據(jù)存入磁盤(數(shù)據(jù)在內(nèi)核緩沖區(qū)kernel's buffer)
- 操作系統(tǒng)把寫緩沖區(qū)數(shù)據(jù)傳輸?shù)酱疟P控制器(數(shù)據(jù)在磁盤緩存中)
- 磁盤控制器真正把數(shù)據(jù)寫到物理介質(zhì)上蒂誉。
傳統(tǒng)的Unix系統(tǒng)(Linux)實(shí)現(xiàn)在內(nèi)核中沒(méi)有緩沖區(qū)高速緩存或頁(yè)高速緩存,大多數(shù)磁盤I/O都通過(guò)緩沖區(qū)進(jìn)行距帅。當(dāng)我們向文件寫入數(shù)據(jù)時(shí)(比如 POSIX API 的write系統(tǒng)調(diào)用)右锨,內(nèi)核通常先將數(shù)據(jù)復(fù)制到緩沖區(qū)中,然后排入隊(duì)列碌秸,晚些時(shí)候再寫入磁盤绍移。這種方式被稱為延遲寫。通常讥电,當(dāng)內(nèi)核需要重用緩沖區(qū)來(lái)存放其他磁盤數(shù)據(jù)時(shí)蹂窖,他會(huì)把所有延遲寫數(shù)據(jù)寫入磁盤。為保證磁盤上實(shí)際文件與緩沖區(qū)中的內(nèi)容一致恩敌,Unix系統(tǒng)提供了sync,fsync和fdatasync三個(gè)函數(shù)瞬测。
其中,sync與fsync的區(qū)別在于纠炮,sync只是將所有修改過(guò)的塊緩沖區(qū)排入寫隊(duì)列月趟,然后返回,它并不等待實(shí)際寫磁盤操作結(jié)束恢口,一般update系統(tǒng)守護(hù)進(jìn)程會(huì)周期性調(diào)用sync函數(shù)(Linux是30s)孝宗,fsync函數(shù)需要傳入文件描述符,它會(huì)等到磁盤寫操作結(jié)束才返回耕肩√及可以想見(jiàn),雖然每個(gè)操作都調(diào)用fsync是最保險(xiǎn)的做法看疗,但是這種大量隨機(jī)尋址對(duì)于任何運(yùn)行于Rotational disks的應(yīng)用來(lái)說(shuō),都是非常慢的睦授。
上述內(nèi)容旨在讓讀者理解數(shù)據(jù)持久化為什么需要各種策略两芳,以及各種策略的意義,都是點(diǎn)到為止去枷。
Redis集群
這一節(jié)怖辆,我們會(huì)談?wù)揜edis的集群是复,。集群簡(jiǎn)單的理解就是一堆機(jī)器齊心協(xié)力提供某種類型的服務(wù)竖螃。對(duì)于Redis集群淑廊,我們關(guān)心這么幾個(gè)內(nèi)容:數(shù)據(jù)是如何分布的,消息是如何傳遞的特咆,出現(xiàn)異常的如何應(yīng)對(duì)季惩。Redis集群研究和實(shí)踐(基于redis 3.0.5)這篇文章根據(jù)官方的安裝指南,記錄了非常詳細(xì)的安裝和操作步驟腻格,本文就不在贅述了画拾,我們主要重點(diǎn)理解一下前面提到的三個(gè)問(wèn)題。
一致性哈希
之所以要介紹這個(gè)概念菜职,是因?yàn)橐恢滦怨J荳eb Cache類系統(tǒng)青抛,用于數(shù)據(jù)分布最典型的設(shè)計(jì)(Memcached使用一致性哈希),我們將在后面一節(jié)與Redis的哈希槽(hash slot)進(jìn)行一個(gè)比較酬核。
一致性哈希在Wiki上講的非常清楚:
需求
在使用n臺(tái)緩存服務(wù)器時(shí)蜜另,一種常用的負(fù)載均衡方式是,對(duì)資源o的請(qǐng)求使用hash(o) = o mod n來(lái)映射到某一臺(tái)緩存服務(wù)器嫡意。當(dāng)增加或減少一臺(tái)緩存服務(wù)器時(shí)這種方式可能會(huì)改變所有資源對(duì)應(yīng)的hash值举瑰,也就是所有的緩存都失效了,這會(huì)使得緩存服務(wù)器大量集中地向原始內(nèi)容服務(wù)器更新緩存鹅很。因些需要一致哈希算法來(lái)避免這樣的問(wèn)題嘶居。
一致哈希盡可能使同一個(gè)資源映射到同一臺(tái)緩存服務(wù)器。這種方式要求增加一臺(tái)緩存服務(wù)器時(shí)促煮,新的服務(wù)器盡量分擔(dān)存儲(chǔ)其他所有服務(wù)器的緩存資源邮屁。減少一臺(tái)緩存服務(wù)器時(shí),其他所有服務(wù)器也可以盡量分擔(dān)存儲(chǔ)它的緩存資源菠齿。 一致哈希算法的主要思想是將每個(gè)緩存服務(wù)器與一個(gè)或多個(gè)哈希值域區(qū)間關(guān)聯(lián)起來(lái)佑吝,其中區(qū)間邊界通過(guò)計(jì)算緩存服務(wù)器對(duì)應(yīng)的哈希值來(lái)決定。(定義區(qū)間的哈希函數(shù)不一定和計(jì)算緩存服務(wù)器哈希值的函數(shù)相同绳匀,但是兩個(gè)函數(shù)的返回值的范圍需要匹配芋忿。)如果一個(gè)緩存服務(wù)器被移除,則它會(huì)從對(duì)應(yīng)的區(qū)間會(huì)被并入到鄰近的區(qū)間疾棵,其他的緩存服務(wù)器不需要任何改變戈钢。
也許上個(gè)圖,更容易理解:
實(shí)現(xiàn)
一致哈希將每個(gè)對(duì)象映射到圓環(huán)邊上的一個(gè)點(diǎn)是尔,系統(tǒng)再將可用的節(jié)點(diǎn)機(jī)器映射到圓環(huán)的不同位置殉了。查找某個(gè)對(duì)象對(duì)應(yīng)的機(jī)器時(shí),需要用一致哈希算法計(jì)算得到對(duì)象對(duì)應(yīng)圓環(huán)邊上位置拟枚,沿著圓環(huán)邊上查找直到遇到某個(gè)節(jié)點(diǎn)機(jī)器薪铜,這臺(tái)機(jī)器即為對(duì)象應(yīng)該保存的位置众弓。
當(dāng)刪除一臺(tái)節(jié)點(diǎn)機(jī)器時(shí),這臺(tái)機(jī)器上保存的所有對(duì)象都要移動(dòng)到下一臺(tái)機(jī)器隔箍。添加一臺(tái)機(jī)器到圓環(huán)邊上某個(gè)點(diǎn)時(shí)谓娃,這個(gè)點(diǎn)的下一臺(tái)機(jī)器需要將這個(gè)節(jié)點(diǎn)前對(duì)應(yīng)的對(duì)象移動(dòng)到新機(jī)器上。 更改對(duì)象在節(jié)點(diǎn)機(jī)器上的分布可以通過(guò)調(diào)整節(jié)點(diǎn)機(jī)器的位置來(lái)實(shí)現(xiàn)蜒滩。
其實(shí)滨达,也要不了幾行代碼:
import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;
public class ConsistentHash<T> {
private final HashFunction hashFunction;
private final int numberOfReplicas;
private final SortedMap<Integer, T> circle = new TreeMap<Integer, T>();
public ConsistentHash(HashFunction hashFunction, int numberOfReplicas,
Collection<T> nodes) {
this.hashFunction = hashFunction;
this.numberOfReplicas = numberOfReplicas;
for (T node : nodes) {
add(node);
}
}
public void add(T node) {
for (int i = 0; i < numberOfReplicas; i++) {
circle.put(hashFunction.hash(node.toString() + i), node);
}
}
public void remove(T node) {
for (int i = 0; i < numberOfReplicas; i++) {
circle.remove(hashFunction.hash(node.toString() + i));
}
}
public T get(Object key) {
if (circle.isEmpty()) {
return null;
}
int hash = hashFunction.hash(key);
if (!circle.containsKey(hash)) {
SortedMap<Integer, T> tailMap = circle.tailMap(hash);
hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
}
return circle.get(hash);
}
}
Redis哈希槽與數(shù)據(jù)分布
Redis采用的是哈希槽的機(jī)制,它通過(guò)函數(shù)hash(key) = CRC16(key)%16384
將任意一個(gè)key映射到0-16383這個(gè)范圍帮掉,每個(gè)節(jié)點(diǎn)承接16384個(gè)key中的一段弦悉。如果增加或刪除一個(gè)節(jié)點(diǎn),手動(dòng)變更節(jié)點(diǎn)的承接范圍蟆炊,具體的操作可以參考前面提到的《Redis集群研究和實(shí)踐》稽莉。
現(xiàn)在,我們?cè)倩氐缴厦鎸?shí)現(xiàn)的這個(gè)一致性哈希涩搓,假設(shè)傳入了這樣一個(gè)hash函數(shù)污秆,并且手動(dòng)對(duì)node添加后的節(jié)點(diǎn)承載范圍進(jìn)行調(diào)整:
private static final int RING = 16384;
public Integer hash(String key){
return CRC16(key)%RING;
}
public T get(Object key) {
return key-nodesTable.get(hash);
}
public void add(T node) {
//手動(dòng)
//Step 1: ...
//Step 2: ...
}
這幾乎就是Reids的哈希槽方案。這樣的方案昧甘,簡(jiǎn)單良拼、粗暴、直接并且有效充边,不過(guò)就是要麻煩你動(dòng)動(dòng)手去規(guī)劃庸推。我認(rèn)為,一致性哈希的本質(zhì)就是通過(guò)在Hash->Node之間虛擬一個(gè)中間層使之變成Hash->RING Point->Node浇冰,從而避免Node增刪帶來(lái)的全局映射變動(dòng)贬媒。從這個(gè)意義上說(shuō),Redis的哈希槽就是一個(gè)簡(jiǎn)化版的一致性哈希方案肘习。
這里我們不評(píng)價(jià)這種簡(jiǎn)化版一致性哈希方案的優(yōu)劣际乘,但它的確規(guī)避了系統(tǒng)對(duì)于節(jié)點(diǎn)增加或者刪除后,自動(dòng)處理數(shù)據(jù)遷移漂佩,以及節(jié)點(diǎn)規(guī)劃給系統(tǒng)帶來(lái)的復(fù)雜性脖含。
Gossip協(xié)議
Redis節(jié)點(diǎn)間的消息使用Gossip協(xié)議傳播,它常用于P2P的通信協(xié)議投蝉,這個(gè)協(xié)議就是模擬人類中傳播謠言的行為而來(lái)养葵。
協(xié)議的核心內(nèi)容就是節(jié)點(diǎn)通過(guò)將信息隨機(jī)發(fā)送到N個(gè)節(jié)點(diǎn)來(lái)完成本次信息的傳播,其涉及到周期性瘩缆、配對(duì)关拒、交互模式。Gossip的交互模式分為兩種:Anti-entropy和Rumor mongering。
- Anti-entropy:每個(gè)節(jié)點(diǎn)周期性地隨機(jī)選擇其他節(jié)點(diǎn)夏醉,然后通過(guò)相互交換自己的所有數(shù)據(jù)來(lái)消除兩者之間的差異。
- Rumor mongering:當(dāng)一個(gè)節(jié)點(diǎn)有來(lái)新信息后涌韩,該節(jié)點(diǎn)變成活躍狀態(tài)畔柔,并周期性地聯(lián)系其他節(jié)點(diǎn)向其發(fā)送新信息。
每個(gè)節(jié)點(diǎn)維護(hù)一個(gè)自己的信息表<key, (value, version)>
臣樱,即屬性的值以及版本號(hào)靶擦;和一個(gè)記錄其他節(jié)點(diǎn)的信息表<node, <key, (value, version)>>
。每個(gè)節(jié)點(diǎn)和系統(tǒng)中的某個(gè)節(jié)點(diǎn)相互配對(duì)成為peer雇毫。而節(jié)點(diǎn)的信息交換方式主要有3種玄捕。
- Push:擁有狀態(tài)新信息的節(jié)點(diǎn)隨機(jī)選擇聯(lián)系節(jié)點(diǎn)并想起發(fā)送自己得到信息。
- Pull:發(fā)起信息交換的節(jié)點(diǎn)隨機(jī)選擇聯(lián)系節(jié)點(diǎn)并從對(duì)方獲取信息棚放。
- Push-Pull混合模式:發(fā)起信息交換的節(jié)點(diǎn)向選擇的節(jié)點(diǎn)發(fā)送信息枚粘。
可以證明Gossip協(xié)議的傳播次數(shù)是收斂的。
傳播起來(lái)整個(gè)Redis集群內(nèi)部一共有N*(N-1)條傳輸路徑飘蚯,路徑真的實(shí)在太多了馍迄,以至于開發(fā)者畫出來(lái)的圖都少了兩條(紅線補(bǔ)齊),就大概就像這個(gè)樣子:
和Server與Client的不一樣局骤,Redis內(nèi)部節(jié)點(diǎn)間采用的是二進(jìn)制協(xié)議以優(yōu)化帶寬攀圈。Redis節(jié)點(diǎn)間的“謠言”,大概是這個(gè)樣子的:
部分故障
對(duì)于一個(gè)分布式系統(tǒng)峦甩,最大的挑戰(zhàn)就是要是節(jié)點(diǎn)掛了怎么辦赘来,或者更具體的說(shuō)如何知道一個(gè)節(jié)點(diǎn)是不是真的掛了,這也就是所謂的分布式系統(tǒng)的本質(zhì)困難:“partial failure(部分故障)”凯傲。但是不得不說(shuō)犬辰,Redis的實(shí)現(xiàn)弱化了這個(gè)困難,因?yàn)樗鼪](méi)有提供通常意義上說(shuō)的高可用性泣洞。
當(dāng)Redis集群中的一個(gè)主節(jié)點(diǎn)掛了之后忧风,Goosip協(xié)議會(huì)選擇一個(gè)備節(jié)點(diǎn)替換上來(lái),如果沒(méi)有備節(jié)點(diǎn)球凰,整個(gè)集群系統(tǒng)就不可用了狮腿。是的,整體不可用呕诉!
這樣的設(shè)計(jì)避免了數(shù)據(jù)遷移和數(shù)據(jù)分布自動(dòng)平衡缘厢,也避免了部分可用性需要進(jìn)行的一些屏蔽和邏輯阻斷。
具體來(lái)說(shuō)甩挫,Redis的每個(gè)節(jié)點(diǎn)都擁有一個(gè)與其他節(jié)點(diǎn)相關(guān)的狀態(tài)標(biāo)示贴硫。有兩種狀態(tài)是用于失敗(失效)檢測(cè)的:PFAIL
標(biāo)示和FAIL
標(biāo)示。 PFAIL
意味著可能失敗英遭,這一個(gè)還沒(méi)有得到確認(rèn)的失敗類型间护。FAIL
意味著一個(gè)節(jié)點(diǎn)失敗已經(jīng)在一個(gè)固定的時(shí)間范圍內(nèi)被大多數(shù)主節(jié)點(diǎn)確認(rèn)。
PFAIL
被確認(rèn)為FAIL
需要滿足下面這些條件:
- A節(jié)點(diǎn)已經(jīng)將B節(jié)點(diǎn)標(biāo)示為
PFAIL
挖诸。 - 節(jié)點(diǎn)A通過(guò)gossip收集了集群中大多數(shù)主節(jié)點(diǎn)關(guān)于B的狀態(tài)記錄汁尺。
- 這些大多數(shù)的節(jié)點(diǎn)已經(jīng)在
NODE_TIMEOUT * FAIL_REPORT_VALIDITY_MULT
這個(gè)時(shí)間范圍內(nèi)將B標(biāo)記為PFAIL
或者FAIL
。
如果上述條件為真多律,那么節(jié)點(diǎn)A將做如下兩個(gè)動(dòng)作:
- 標(biāo)記B節(jié)點(diǎn)為
FAIL
痴突。 - 把這個(gè)
FAIL
消息發(fā)送給其它所有可達(dá)的節(jié)點(diǎn)。
當(dāng)然關(guān)于Redis的失敗檢測(cè)狼荞,還有更細(xì)節(jié)的內(nèi)容和更復(fù)雜的情況辽装,上面沒(méi)有提到,感興趣的讀者可以閱讀Redis集群規(guī)范相味。需要注意的是拾积,FAIL
標(biāo)識(shí)只是備節(jié)點(diǎn)提升為主節(jié)點(diǎn)的一個(gè)啟動(dòng)條件。
節(jié)點(diǎn)選舉
備節(jié)點(diǎn)選舉和提升是備節(jié)點(diǎn)來(lái)處理的攻走,并且需要主節(jié)點(diǎn)進(jìn)行選舉殷勘。一個(gè)備節(jié)點(diǎn)選舉發(fā)生在一個(gè)主節(jié)點(diǎn)被它的至少一個(gè)備節(jié)點(diǎn)標(biāo)記為FAIL
狀態(tài),并且這些備節(jié)點(diǎn)具備成為主節(jié)點(diǎn)的先決條件下昔搂。
一個(gè)備節(jié)點(diǎn)為了把自己提升為主節(jié)點(diǎn)玲销,它需要發(fā)起一輪選舉并且獲勝。一個(gè)主節(jié)點(diǎn)的所有備節(jié)點(diǎn)都可以在這個(gè)主節(jié)點(diǎn)處于FAIL
狀態(tài)下發(fā)起選舉摘符,然而最后只有一個(gè)備節(jié)點(diǎn)能夠贏得選舉并提升自己成為主節(jié)點(diǎn)贤斜。
一個(gè)備節(jié)點(diǎn)發(fā)起一輪選舉必須滿足下面這些條件:
- 它的主節(jié)點(diǎn)處于
FAIL
狀態(tài)。 - 這個(gè)主節(jié)點(diǎn)承載了非零數(shù)量的哈希槽逛裤。
- 備節(jié)點(diǎn)與主節(jié)點(diǎn)的失聯(lián)時(shí)間在一個(gè)范圍內(nèi)瘩绒,這是為了確保備節(jié)點(diǎn)的數(shù)據(jù)足夠近,這個(gè)時(shí)間用戶可配置带族。
為了被選中锁荔,對(duì)于一個(gè)備節(jié)點(diǎn)來(lái)說(shuō)蝙砌,第一步就是增加自己的 currentEpoch
計(jì)數(shù)阳堕,并且從主節(jié)點(diǎn)實(shí)例請(qǐng)求選票。
備節(jié)點(diǎn)通過(guò)廣播一個(gè)FAILOVER_AUTH_REQUEST
包給每個(gè)主節(jié)點(diǎn)來(lái)請(qǐng)求選票择克。然后恬总,它等待一個(gè)最大 NODE_TIMEOUT*2
(至少2秒)的時(shí)間接受回復(fù)。
一旦一個(gè)主節(jié)點(diǎn)投票給一個(gè)備節(jié)點(diǎn)肚邢,它主動(dòng)回復(fù)一個(gè)FAILOVER_AUTH_ACK
壹堰,它不能NODE_TIMEOUT * 2
時(shí)間范圍內(nèi)再給這個(gè)備節(jié)點(diǎn)的競(jìng)爭(zhēng)對(duì)手投票拭卿。這不是必須的安全性保障,但是對(duì)于阻止多個(gè)備節(jié)點(diǎn)同時(shí)選上非常有用贱纠。
一個(gè)備節(jié)點(diǎn)會(huì)丟棄發(fā)送選舉請(qǐng)求后峻厚,小于當(dāng)前 currentEpoch
周期的所有AUTH_ACK
回復(fù)。這確保了避免它錯(cuò)誤地把上一輪選舉記票記到當(dāng)前周期谆焊。
一旦一個(gè)備節(jié)點(diǎn)得到大多數(shù)主節(jié)點(diǎn)的ACKs目木,它就贏得了選舉。另外懊渡,如果這個(gè)大多數(shù)主節(jié)點(diǎn)在NODE_TIMEOUT*2
(至少2秒)時(shí)間內(nèi)沒(méi)有達(dá)到,當(dāng)前選舉會(huì)被廢棄军拟,并且在NODE_TIMEOUT * 4
(至少4秒)時(shí)間后剃执,嘗試開始一輪新的選舉机隙。
Redis集群方案對(duì)比
關(guān)于不同的集群方案對(duì)比肉盹,阿里云有一篇軟文做了一些介紹,我認(rèn)為:隨著Redis3.2.8的發(fā)布沪么,Redis的集群已經(jīng)基本可以應(yīng)用于生產(chǎn)環(huán)境了辫继。
關(guān)于不同集群對(duì)于高級(jí)功能的支持怒见,軟文中有一個(gè)列表:
redis 4.0 | 阿里云redis | codis | |
---|---|---|---|
事務(wù) | 支持相同slot | 支持相同的slot | 不支持 |
sub/pub | 支持相同slot | 支持 | 不支持 |
flushall | 支持 | 支持 | 不支持 |
select | 不支持 | 不支持 | 不支持 |
mset/mget | 支持相同slot | 支持 | 支持 |
以及性能對(duì)比:
這篇軟文中說(shuō):
在實(shí)際生產(chǎn)環(huán)境中,使用原生的redis cluster姑宽,客戶端需要實(shí)現(xiàn)cluster protocol遣耍, 解析move, ask等指令并重定向節(jié)點(diǎn)舵变,隨意訪問(wèn)key可能需要兩次訪問(wèn)操作才能完成,性能上并不能完全如單節(jié)點(diǎn)一樣扛或。
實(shí)際對(duì)于java來(lái)說(shuō)熙兔,Jedis是支持redis cluster的黔姜,在后面一個(gè)主題“Spring下使用Redis”秆吵,我們會(huì)發(fā)現(xiàn)除非節(jié)點(diǎn)出現(xiàn)變動(dòng),幾乎所有的客戶端命令都可以一次完成泻拦,所以可以認(rèn)為redis-cluster的性能就是實(shí)際應(yīng)用時(shí)的性能争拐,真是1core頂人家8core凹懿堋绑雄!
結(jié)語(yǔ)
關(guān)于Redis本身的內(nèi)容我們就聊到這里万牺,希望這篇文章能給大家起一個(gè)拋磚引玉的作用脚粟。鑒于作者水平有限核无,如果大家覺(jué)得什么地方不對(duì)厕宗,歡迎提出來(lái)已慢,大家一起學(xué)習(xí)佑惠,一起進(jìn)步膜楷。
最后附上Books在《人月神話》中的一句話赌厅,這句話來(lái)自于書中“貴族專制特愿、民主政治和系統(tǒng)設(shè)計(jì) ( Aristocracy,
Democracy, and System Design)”一節(jié)目养,是Redis作者Salvatore Sanfilippo的Google Group簽名癌蚁,希望對(duì)你從一個(gè)側(cè)面理解Redis設(shè)計(jì)者的設(shè)計(jì)意圖:
If a system is to have conceptual integrity, someone must control the concepts.(如果要得到系統(tǒng)概念上的完整性努释, 那么必須有人控制這些概念)——— 《人月神話》