Redis雜談

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了。

image

雖然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基本命令

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)== 的供大家練手:

網(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 AdministrationRedis 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年了啦)替久。

Reply4FiveYears

其次凉泄,我們回過(guò)頭來(lái),討論一下到底什么叫數(shù)據(jù)持久化蚯根。非常簡(jiǎn)化的來(lái)看后众,數(shù)據(jù)持久化可以分為這么5步:

  1. 客戶端發(fā)送一個(gè)寫命令到數(shù)據(jù)庫(kù)(數(shù)據(jù)在客戶端的內(nèi)存中)。
  2. 數(shù)據(jù)庫(kù)接收到這個(gè)寫命令(數(shù)據(jù)在服務(wù)器的內(nèi)存中)颅拦。
  3. 數(shù)據(jù)庫(kù)調(diào)用系統(tǒng)調(diào)用把寫數(shù)據(jù)存入磁盤(數(shù)據(jù)在內(nèi)核緩沖區(qū)kernel's buffer)
  4. 操作系統(tǒng)把寫緩沖區(qū)數(shù)據(jù)傳輸?shù)酱疟P控制器(數(shù)據(jù)在磁盤緩存中)
  5. 磁盤控制器真正把數(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è)樣子:

Gossip協(xié)議

和Server與Client的不一樣局骤,Redis內(nèi)部節(jié)點(diǎn)間采用的是二進(jìn)制協(xié)議以優(yōu)化帶寬攀圈。Redis節(jié)點(diǎn)間的“謠言”,大概是這個(gè)樣子的:

Gossip內(nèi)容

部分故障

對(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ì)比:

不同集群性能對(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)概念上的完整性努释, 那么必須有人控制這些概念)——— 《人月神話》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子龄坪,更是在濱河造成了極大的恐慌健田,老刑警劉巖妓局,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異存炮,居然都是意外死亡穆桂,警方通過(guò)查閱死者的電腦和手機(jī)享完,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門彼绷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)苛预,“玉大人热某,你說(shuō)我怎么就攤上這事昔馋∶囟簦” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵倦蚪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我慕购,道長(zhǎng)沪悲,這世上最難降的妖魔是什么可训? 我笑而不...
    開封第一講書人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮谨胞,結(jié)果婚禮上牢裳,老公的妹妹穿的比我還像新娘蒲讯。我一直安慰自己判帮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開白布晌畅。 她就那樣靜靜地躺著,像睡著了一般寡痰。 火紅的嫁衣襯著肌膚如雪抗楔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評(píng)論 1 296
  • 那天拦坠,我揣著相機(jī)與錄音连躏,去河邊找鬼。 笑死贪婉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的卢肃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼示弓,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼囱皿!你這毒婦竟也來(lái)了齿兔?” 一聲冷哼從身側(cè)響起组砚,我...
    開封第一講書人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后罐农,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矛纹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年肄方,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廓啊。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖驯杜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嘁圈,我是刑警寧澤钞澳,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響料按,放射性物質(zhì)發(fā)生泄漏垄潮。R本人自食惡果不足惜逃贝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)号坡。三九已至懊烤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間筋帖,已是汗流浹背奸晴。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工冤馏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留日麸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像代箭,于是被迫代替她去往敵國(guó)和親墩划。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容