深入Redis集群

前言

在前面的文章中再愈,已經(jīng)介紹了Redis的幾種高可用技術(shù):持久化榜苫、主從復(fù)制和哨兵,但這些方案仍有不足翎冲,其中最主要的問題是存儲能力受單機限制垂睬,以及無法實現(xiàn)寫操作的負(fù)載均衡。

Redis集群解決了上述問題,實現(xiàn)了較為完善的高可用方案驹饺。本文將詳細介紹集群钳枕,主要內(nèi)容包括:集群的作用;集群的搭建方法及設(shè)計方案赏壹;集群的基本原理鱼炒;客戶端訪問集群的方法;以及其他實踐中需要的集群知識(集群擴容蝌借、故障轉(zhuǎn)移昔瞧、參數(shù)優(yōu)化等)

一菩佑、集群的作用

集群自晰,即Redis Cluster,是Redis 3.0開始引入的分布式存儲方案稍坯。

集群由多個節(jié)點(Node)組成酬荞,Redis的數(shù)據(jù)分布在這些節(jié)點中。集群中的節(jié)點分為主節(jié)點和從節(jié)點:只有主節(jié)點負(fù)責(zé)讀寫請求和集群信息的維護瞧哟;從節(jié)點只進行主節(jié)點數(shù)據(jù)和狀態(tài)信息的復(fù)制混巧。

集群的作用,可以歸納為兩點:

1绢涡、數(shù)據(jù)分區(qū):數(shù)據(jù)分區(qū)(或稱數(shù)據(jù)分片)是集群最核心的功能牲剃。

集群將數(shù)據(jù)分散到多個節(jié)點,一方面突破了Redis單機內(nèi)存大小的限制雄可,存儲容量大大增加;另一方面每個主節(jié)點都可以對外提供讀服務(wù)和寫服務(wù)缠犀,大大提高了集群的響應(yīng)能力数苫。

Redis單機內(nèi)存大小受限問題,在介紹持久化和主從復(fù)制時都有提及辨液;例如虐急,如果單機內(nèi)存太大,bgsave和bgrewriteaof的fork操作可能導(dǎo)致主進程阻塞滔迈,主從環(huán)境下主機切換時可能導(dǎo)致從節(jié)點長時間無法提供服務(wù)止吁,全量復(fù)制階段主節(jié)點的復(fù)制緩沖區(qū)可能溢出……。

2燎悍、高可用:集群支持主從復(fù)制和主節(jié)點的自動故障轉(zhuǎn)移(與哨兵類似)敬惦;當(dāng)任一節(jié)點發(fā)生故障時,集群仍然可以對外提供服務(wù)谈山。

本文內(nèi)容基于Redis 3.0.6俄删。

二夏漱、集群的搭建

這一部分我們將搭建一個簡單的集群:共6個節(jié)點,3主3從阵幸。方便起見:所有節(jié)點在同一臺服務(wù)器上癞埠,以端口號進行區(qū)分;配置從簡斜脂。3個主節(jié)點端口號:7000/7001/7002抓艳,對應(yīng)的從節(jié)點端口號:8000/8001/8002。

集群的搭建有兩種方式:(1)手動執(zhí)行Redis命令帚戳,一步步完成搭建壶硅;(2)使用Ruby腳本搭建。二者搭建的原理是一樣的销斟,只是Ruby腳本將Redis命令進行了打包封裝庐椒;在實際應(yīng)用中推薦使用腳本方式,簡單快捷不容易出錯蚂踊。下面分別介紹這兩種方式约谈。

1. 執(zhí)行Redis命令搭建集群

集群的搭建可以分為四步:(1)啟動節(jié)點:將節(jié)點以集群模式啟動,此時節(jié)點是獨立的犁钟,并沒有建立聯(lián)系棱诱;(2)節(jié)點握手:讓獨立的節(jié)點連成一個網(wǎng)絡(luò);(3)分配槽:將16384個槽分配給主節(jié)點涝动;(4)指定主從關(guān)系:為從節(jié)點指定主節(jié)點迈勋。

實際上,前三步完成后集群便可以對外提供服務(wù)醋粟;但指定從節(jié)點后靡菇,集群才能夠提供真正高可用的服務(wù)。

(1)啟動節(jié)點

集群節(jié)點的啟動仍然是使用redis-server命令米愿,但需要使用集群模式啟動厦凤。下面是7000節(jié)點的配置文件(只列出了節(jié)點正常工作關(guān)鍵配置,其他配置(如開啟AOF)可以參照單機節(jié)點進行):

#redis-7000.conf

port 7000

cluster-enabled yes

cluster-config-file?"node-7000.conf"

logfile?"log-7000.log"

dbfilename?"dump-7000.rdb"

daemonize yes

其中的cluster-enabled和cluster-config-file是與集群相關(guān)的配置育苟。

cluster-enabled yesRedis實例可以分為單機模式(standalone)和集群模式(cluster)较鼓;cluster-enabled yes可以啟動集群模式。在單機模式下啟動的Redis實例违柏,如果執(zhí)行info server命令博烂,可以發(fā)現(xiàn)redis_mode一項為standalone,如下圖所示:

集群模式下的節(jié)點漱竖,其redis_mode為cluster禽篱,如下圖所示:

cluster-config-file該參數(shù)指定了集群配置文件的位置。每個節(jié)點在運行過程中闲孤,會維護一份集群配置文件谆级;每當(dāng)集群信息發(fā)生變化時(如增減節(jié)點)烤礁,集群內(nèi)所有節(jié)點會將最新信息更新到該配置文件;當(dāng)節(jié)點重啟后肥照,會重新讀取該配置文件脚仔,獲取集群信息,可以方便的重新加入到集群中舆绎。也就是說鲤脏,當(dāng)Redis節(jié)點以集群模式啟動時,會首先尋找是否有集群配置文件吕朵,如果有則使用文件中的配置啟動猎醇,如果沒有,則初始化配置并將配置保存到文件中努溃。集群配置文件由Redis節(jié)點維護硫嘶,不需要人工修改。

編輯好配置文件后梧税,使用redis-server命令啟動該節(jié)點:

1redis-server redis-7000.conf

節(jié)點啟動以后沦疾,通過cluster nodes命令可以查看節(jié)點的情況,如下圖所示第队。

其中返回值第一項表示節(jié)點id哮塞,由40個16進制字符串組成,節(jié)點id與主從復(fù)制一文中提到的runId不同:Redis每次啟動runId都會重新創(chuàng)建凳谦,但是節(jié)點id只在集群初始化時創(chuàng)建一次忆畅,然后保存到集群配置文件中,以后節(jié)點重新啟動時會直接在集群配置文件中讀取尸执。

其他節(jié)點使用相同辦法啟動家凯,不再贅述。需要特別注意剔交,在啟動節(jié)點階段肆饶,節(jié)點是沒有主從關(guān)系的,因此從節(jié)點不需要加slaveof配置岖常。

(2)節(jié)點握手

節(jié)點啟動以后是相互獨立的,并不知道其他節(jié)點存在葫督;需要進行節(jié)點握手竭鞍,將獨立的節(jié)點組成一個網(wǎng)絡(luò)。

節(jié)點握手使用cluster meet {ip} {port}命令實現(xiàn)橄镜,例如在7000節(jié)點中執(zhí)行cluster meet 192.168.72.128 7001偎快,可以完成7000節(jié)點和7001節(jié)點的握手;注意ip使用的是局域網(wǎng)ip而不是localhost或127.0.0.1洽胶,是為了其他機器上的節(jié)點或客戶端也可以訪問晒夹。此時再使用cluster nodes查看:

在7001節(jié)點下也可以類似查看:

同理裆馒,在7000節(jié)點中使用cluster meet命令,可以將所有節(jié)點加入到集群丐怯,完成節(jié)點握手:

cluster meet 192.168.72.128 7002

cluster meet 192.168.72.128 8000

cluster meet 192.168.72.128 8001

cluster meet 192.168.72.128 8002

執(zhí)行完上述命令后喷好,可以看到7000節(jié)點已經(jīng)感知到了所有其他節(jié)點:

通過節(jié)點之間的通信,每個節(jié)點都可以感知到所有其他節(jié)點读跷,以8000節(jié)點為例:

(3)分配槽

在Redis集群中梗搅,借助槽實現(xiàn)數(shù)據(jù)分區(qū),具體原理后文會介紹效览。集群有16384個槽无切,槽是數(shù)據(jù)管理和遷移的基本單位。當(dāng)數(shù)據(jù)庫中的16384個槽都分配了節(jié)點時丐枉,集群處于上線狀態(tài)(ok)哆键;如果有任意一個槽沒有分配節(jié)點,則集群處于下線狀態(tài)(fail)瘦锹。

cluster info命令可以查看集群狀態(tài)籍嘹,分配槽之前狀態(tài)為fail:

分配槽使用cluster addslots命令,執(zhí)行下面的命令將槽(編號0-16383)全部分配完畢:

redis-cli -p 7000 cluster addslots {0..5461}

redis-cli -p 7001 cluster addslots {5462..10922}

redis-cli -p 7002 cluster addslots {10923..16383}

此時查看集群狀態(tài)沼本,顯示所有槽分配完畢噩峦,集群進入上線狀態(tài):

(4)指定主從關(guān)系

集群中指定主從關(guān)系不再使用slaveof命令,而是使用cluster replicate命令抽兆;參數(shù)使用節(jié)點id识补。

通過cluster nodes獲得幾個主節(jié)點的節(jié)點id后,執(zhí)行下面的命令為每個從節(jié)點指定主節(jié)點:

redis-cli -p 8000 cluster replicate be816eba968bc16c884b963d768c945e86ac51ae

redis-cli -p 8001 cluster replicate 788b361563acb175ce8232569347812a12f1fdb4

redis-cli -p 8002 cluster replicate a26f1624a3da3e5197dde267de683d61bb2dcbf1

此時執(zhí)行cluster nodes查看各個節(jié)點的狀態(tài)辫红,可以看到主從關(guān)系已經(jīng)建立凭涂。

至此,集群搭建完畢贴妻。

2. 使用Ruby腳本搭建集群

在{REDIS_HOME}/src目錄下可以看到redis-trib.rb文件切油,這是一個Ruby腳本,可以實現(xiàn)自動化的集群搭建名惩。

(1)安裝Ruby環(huán)境

以Ubuntu為例澎胡,如下操作即可安裝Ruby環(huán)境:

apt-get install ruby #安裝ruby環(huán)境

gem install redis #gem是ruby的包管理工具,該命令可以安裝ruby-redis依賴

(2)啟動節(jié)點

與第一種方法中的“啟動節(jié)點”完全相同娩鹉。

(3)搭建集群

redis-trib.rb腳本提供了眾多命令攻谁,其中create用于搭建集群,使用方法如下:

1./redis-trib.rb?create--replicas 1 192.168.72.128:7000 192.168.72.128:7001 192.168.72.128:7002 192.168.72.128:8000 192.168.72.128:8001 192.168.72.128:8002

其中:--replicas=1表示每個主節(jié)點有1個從節(jié)點弯予;后面的多個{ip:port}表示節(jié)點地址戚宦,前面的做主節(jié)點,后面的做從節(jié)點锈嫩。使用redis-trib.rb搭建集群時受楼,要求節(jié)點不能包含任何槽和數(shù)據(jù)垦搬。在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流圈:609164807 ?幫助突破瓶頸 提升思維能力

執(zhí)行創(chuàng)建命令后,腳本會給出創(chuàng)建集群的計劃艳汽,如下圖所示猴贰;計劃包括哪些是主節(jié)點,哪些是從節(jié)點骚灸,以及如何分配槽糟趾。

輸入yes確認(rèn)執(zhí)行計劃,腳本便開始按照計劃執(zhí)行甚牲,如下圖所示义郑。

至此,集群搭建完畢丈钙。

3. 集群方案設(shè)計

設(shè)計集群方案時非驮,至少要考慮以下因素:

(1)高可用要求:根據(jù)故障轉(zhuǎn)移的原理,至少需要3個主節(jié)點才能完成故障轉(zhuǎn)移雏赦,且3個主節(jié)點不應(yīng)在同一臺物理機上劫笙;每個主節(jié)點至少需要1個從節(jié)點,且主從節(jié)點不應(yīng)在一臺物理機上星岗;因此高可用集群至少包含6個節(jié)點填大。

(2)數(shù)據(jù)量和訪問量:估算應(yīng)用需要的數(shù)據(jù)量和總訪問量(考慮業(yè)務(wù)發(fā)展,留有冗余)俏橘,結(jié)合每個主節(jié)點的容量和能承受的訪問量(可以通過benchmark得到較準(zhǔn)確估計)允华,計算需要的主節(jié)點數(shù)量。

(3)節(jié)點數(shù)量限制:Redis官方給出的節(jié)點數(shù)量限制為1000寥掐,主要是考慮節(jié)點間通信帶來的消耗靴寂。在實際應(yīng)用中應(yīng)盡量避免大集群;如果節(jié)點數(shù)量不足以滿足應(yīng)用對Redis數(shù)據(jù)量和訪問量的要求召耘,可以考慮:(1)業(yè)務(wù)分割百炬,大集群分為多個小集群;(2)減少不必要的數(shù)據(jù)污它;(3)調(diào)整數(shù)據(jù)過期策略等剖踊。

(4)適度冗余:Redis可以在不影響集群服務(wù)的情況下增加節(jié)點,因此節(jié)點數(shù)量適當(dāng)冗余即可衫贬,不用太大蜜宪。

三、集群的基本原理

上一章介紹了集群的搭建方法和設(shè)計方案祥山,下面將進一步深入,介紹集群的原理掉伏。集群最核心的功能是數(shù)據(jù)分區(qū)缝呕,因此首先介紹數(shù)據(jù)的分區(qū)規(guī)則澳窑;然后介紹集群實現(xiàn)的細節(jié):通信機制和數(shù)據(jù)結(jié)構(gòu);最后以cluster meet(節(jié)點握手)供常、cluster addslots(槽分配)為例摊聋,說明節(jié)點是如何利用上述數(shù)據(jù)結(jié)構(gòu)和通信機制實現(xiàn)集群命令的。

1. 數(shù)據(jù)分區(qū)方案

數(shù)據(jù)分區(qū)有順序分區(qū)栈暇、哈希分區(qū)等麻裁,其中哈希分區(qū)由于其天然的隨機性,使用廣泛源祈;集群的分區(qū)方案便是哈希分區(qū)的一種煎源。

哈希分區(qū)的基本思路是:對數(shù)據(jù)的特征值(如key)進行哈希,然后根據(jù)哈希值決定數(shù)據(jù)落在哪個節(jié)點香缺。常見的哈希分區(qū)包括:哈希取余分區(qū)手销、一致性哈希分區(qū)、帶虛擬節(jié)點的一致性哈希分區(qū)等图张。

衡量數(shù)據(jù)分區(qū)方法好壞的標(biāo)準(zhǔn)有很多锋拖,其中比較重要的兩個因素是(1)數(shù)據(jù)分布是否均勻(2)增加或刪減節(jié)點對數(shù)據(jù)分布的影響。由于哈希的隨機性祸轮,哈希分區(qū)基本可以保證數(shù)據(jù)分布均勻兽埃;因此在比較哈希分區(qū)方案時,重點要看增減節(jié)點對數(shù)據(jù)分布的影響适袜。

(1)哈希取余分區(qū)

哈希取余分區(qū)思路非常簡單:計算key的hash值柄错,然后對節(jié)點數(shù)量進行取余,從而決定數(shù)據(jù)映射到哪個節(jié)點上痪蝇。該方案最大的問題是鄙陡,當(dāng)新增或刪減節(jié)點時,節(jié)點數(shù)量發(fā)生變化躏啰,系統(tǒng)中所有的數(shù)據(jù)都需要重新計算映射關(guān)系趁矾,引發(fā)大規(guī)模數(shù)據(jù)遷移。

(2)一致性哈希分區(qū)

一致性哈希算法將整個哈希值空間組織成一個虛擬的圓環(huán)给僵,如下圖所示毫捣,范圍為0-2^32-1;對于每個數(shù)據(jù)帝际,根據(jù)key計算hash值蔓同,確定數(shù)據(jù)在環(huán)上的位置,然后從此位置沿環(huán)順時針行走蹲诀,找到的第一臺服務(wù)器就是其應(yīng)該映射到的服務(wù)器斑粱。

與哈希取余分區(qū)相比,一致性哈希分區(qū)將增減節(jié)點的影響限制在相鄰節(jié)點脯爪。以上圖為例则北,如果在node1和node2之間增加node5矿微,則只有node2中的一部分?jǐn)?shù)據(jù)會遷移到node5;如果去掉node2尚揣,則原node2中的數(shù)據(jù)只會遷移到node4中涌矢,只有node4會受影響。

一致性哈希分區(qū)的主要問題在于快骗,當(dāng)節(jié)點數(shù)量較少時娜庇,增加或刪減節(jié)點,對單個節(jié)點的影響可能很大方篮,造成數(shù)據(jù)的嚴(yán)重不平衡名秀。還是以上圖為例,如果去掉node2恭取,node4中的數(shù)據(jù)由總數(shù)據(jù)的1/4左右變?yōu)?/2左右泰偿,與其他節(jié)點相比負(fù)載過高。

(3)帶虛擬節(jié)點的一致性哈希分區(qū)

該方案在一致性哈希分區(qū)的基礎(chǔ)上蜈垮,引入了虛擬節(jié)點的概念耗跛。Redis集群使用的便是該方案,其中的虛擬節(jié)點稱為槽(slot)攒发。槽是介于數(shù)據(jù)和實際節(jié)點之間的虛擬概念调塌;每個實際節(jié)點包含一定數(shù)量的槽,每個槽包含哈希值在一定范圍內(nèi)的數(shù)據(jù)惠猿。引入槽以后羔砾,數(shù)據(jù)的映射關(guān)系由數(shù)據(jù)hash->實際節(jié)點,變成了數(shù)據(jù)hash->槽->實際節(jié)點偶妖。

在使用了槽的一致性哈希分區(qū)中姜凄,槽是數(shù)據(jù)管理和遷移的基本單位。槽解耦了數(shù)據(jù)和實際節(jié)點之間的關(guān)系趾访,增加或刪除節(jié)點對系統(tǒng)的影響很小态秧。仍以上圖為例,系統(tǒng)中有4個實際節(jié)點扼鞋,假設(shè)為其分配16個槽(0-15)申鱼; 槽0-3位于node1,4-7位于node2云头,以此類推捐友。如果此時刪除node2,只需要將槽4-7重新分配即可溃槐,例如槽4-5分配給node1匣砖,槽6分配給node3,槽7分配給node4;可以看出刪除node2后脆粥,數(shù)據(jù)在其他節(jié)點的分布仍然較為均衡砌溺。

槽的數(shù)量一般遠小于2^32,遠大于實際節(jié)點的數(shù)量变隔;在Redis集群中,槽的數(shù)量為16384蟹倾。

下面這張圖很好的總結(jié)了Redis集群將數(shù)據(jù)映射到實際節(jié)點的過程:

(1)Redis對數(shù)據(jù)的特征值(一般是key)計算哈希值匣缘,使用的算法是CRC16。

(2)根據(jù)哈希值鲜棠,計算數(shù)據(jù)屬于哪個槽肌厨。

(3)根據(jù)槽與節(jié)點的映射關(guān)系,計算數(shù)據(jù)屬于哪個節(jié)點豁陆。

2. 節(jié)點通信機制

集群要作為一個整體工作柑爸,離不開節(jié)點之間的通信。

兩個端口

在哨兵系統(tǒng)中盒音,節(jié)點分為數(shù)據(jù)節(jié)點和哨兵節(jié)點:前者存儲數(shù)據(jù)表鳍,后者實現(xiàn)額外的控制功能。在集群中祥诽,沒有數(shù)據(jù)節(jié)點與非數(shù)據(jù)節(jié)點之分:所有的節(jié)點都存儲數(shù)據(jù)譬圣,也都參與集群狀態(tài)的維護。為此雄坪,集群中的每個節(jié)點厘熟,都提供了兩個TCP端口:

普通端口:即我們在前面指定的端口(7000等)。普通端口主要用于為客戶端提供服務(wù)(與單機節(jié)點類似)维哈;但在節(jié)點間數(shù)據(jù)遷移時也會使用绳姨。

集群端口:端口號是普通端口+10000(10000是固定值,無法改變)阔挠,如7000節(jié)點的集群端口為17000飘庄。集群端口只用于節(jié)點之間的通信,如搭建集群谒亦、增減節(jié)點竭宰、故障轉(zhuǎn)移等操作時節(jié)點間的通信;不要使用客戶端連接集群接口份招。為了保證集群可以正常工作切揭,在配置防火墻時,要同時開啟普通端口和集群端口锁摔。

Gossip協(xié)議

節(jié)點間通信廓旬,按照通信協(xié)議可以分為幾種類型:單對單、廣播、Gossip協(xié)議等孕豹。重點是廣播和Gossip的對比涩盾。

廣播是指向集群內(nèi)所有節(jié)點發(fā)送消息;優(yōu)點是集群的收斂速度快(集群收斂是指集群內(nèi)所有節(jié)點獲得的集群信息是一致的)励背,缺點是每條消息都要發(fā)送給所有節(jié)點春霍,CPU、帶寬等消耗較大叶眉。

Gossip協(xié)議的特點是:在節(jié)點數(shù)量有限的網(wǎng)絡(luò)中址儒,每個節(jié)點都“隨機”的與部分節(jié)點通信(并不是真正的隨機,而是根據(jù)特定的規(guī)則選擇通信的節(jié)點)衅疙,經(jīng)過一番雜亂無章的通信莲趣,每個節(jié)點的狀態(tài)很快會達到一致。Gossip協(xié)議的優(yōu)點有負(fù)載(比廣播)低饱溢、去中心化喧伞、容錯性高(因為通信有冗余)等;缺點主要是集群的收斂速度慢绩郎。

消息類型

集群中的節(jié)點采用固定頻率(每秒10次)的定時任務(wù)進行通信相關(guān)的工作:判斷是否需要發(fā)送消息及消息類型潘鲫、確定接收節(jié)點、發(fā)送消息等嗽上。如果集群狀態(tài)發(fā)生了變化次舌,如增減節(jié)點、槽狀態(tài)變更兽愤,通過節(jié)點間的通信彼念,所有節(jié)點會很快得知整個集群的狀態(tài),使集群收斂浅萧。

節(jié)點間發(fā)送的消息主要分為5種:meet消息逐沙、ping消息、pong消息洼畅、fail消息吩案、publish消息。不同的消息類型帝簇,通信協(xié)議徘郭、發(fā)送的頻率和時機、接收節(jié)點的選擇等是不同的丧肴。

MEET消息:在節(jié)點握手階段残揉,當(dāng)節(jié)點收到客戶端的CLUSTER MEET命令時,會向新加入的節(jié)點發(fā)送MEET消息芋浮,請求新節(jié)點加入到當(dāng)前集群;新節(jié)點收到MEET消息后會回復(fù)一個PONG消息。

PING消息:集群里每個節(jié)點每秒鐘會選擇部分節(jié)點發(fā)送PING消息镇草,接收者收到消息后會回復(fù)一個PONG消息。PING消息的內(nèi)容是自身節(jié)點和部分其他節(jié)點的狀態(tài)信息梯啤;作用是彼此交換信息,以及檢測節(jié)點是否在線条辟。PING消息使用Gossip協(xié)議發(fā)送,接收節(jié)點的選擇兼顧了收斂速度和帶寬成本羽嫡,具體規(guī)則如下:(1)隨機找5個節(jié)點杭棵,在其中選擇最久沒有通信的1個節(jié)點(2)掃描節(jié)點列表,選擇最近一次收到PONG消息時間大于cluster_node_timeout/2的所有節(jié)點魂爪,防止這些節(jié)點長時間未更新艰管。

PONG消息:PONG消息封裝了自身狀態(tài)數(shù)據(jù)×冒剩可以分為兩種:第一種是在接到MEET/PING消息后回復(fù)的PONG消息;第二種是指節(jié)點向集群廣播PONG消息缸浦,這樣其他節(jié)點可以獲知該節(jié)點的最新信息夕冲,例如故障恢復(fù)后新的主節(jié)點會廣播PONG消息。

在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流圈:609164807 ?幫助突破瓶頸 提升思維能力

FAIL消息:當(dāng)一個主節(jié)點判斷另一個主節(jié)點進入FAIL狀態(tài)時裂逐,會向集群廣播這一FAIL消息歹鱼;接收節(jié)點會將這一FAIL消息保存起來,便于后續(xù)的判斷卜高。

PUBLISH消息:節(jié)點收到PUBLISH命令后弥姻,會先執(zhí)行該命令,然后向集群廣播這一消息掺涛,接收節(jié)點也會執(zhí)行該PUBLISH命令庭敦。

3. 數(shù)據(jù)結(jié)構(gòu)

節(jié)點需要專門的數(shù)據(jù)結(jié)構(gòu)來存儲集群的狀態(tài)。所謂集群的狀態(tài)鸽照,是一個比較大的概念螺捐,包括:集群是否處于上線狀態(tài)、集群中有哪些節(jié)點、節(jié)點是否可達定血、節(jié)點的主從狀態(tài)赔癌、槽的分布……

節(jié)點為了存儲集群狀態(tài)而提供的數(shù)據(jù)結(jié)構(gòu)中,最關(guān)鍵的是clusterNode和clusterState結(jié)構(gòu):前者記錄了一個節(jié)點的狀態(tài)澜沟,后者記錄了集群作為一個整體的狀態(tài)灾票。

clusterNode

clusterNode結(jié)構(gòu)保存了一個節(jié)點的當(dāng)前狀態(tài),包括創(chuàng)建時間茫虽、節(jié)點id刊苍、ip和端口號等濒析。每個節(jié)點都會用一個clusterNode結(jié)構(gòu)記錄自己的狀態(tài),并為集群內(nèi)所有其他節(jié)點都創(chuàng)建一個clusterNode結(jié)構(gòu)來記錄節(jié)點狀態(tài)婴氮。

下面列舉了clusterNode的部分字段主经,并說明了字段的含義和作用:

typedefstructclusterNode {

????//節(jié)點創(chuàng)建時間

????mstime_t?ctime;


????//節(jié)點id

????charname[REDIS_CLUSTER_NAMELEN];


????//節(jié)點的ip和端口號

????charip[REDIS_IP_STR_LEN];

????intport;


????//節(jié)點標(biāo)識:整型,每個bit都代表了不同狀態(tài)护赊,如節(jié)點的主從狀態(tài)爽哎、是否在線呀枢、是否在握手等

????intflags;


????//配置紀(jì)元:故障轉(zhuǎn)移時起作用祈秕,類似于哨兵的配置紀(jì)元

????uint64_t configEpoch;


????//槽在該節(jié)點中的分布:占用16384/8個字節(jié)请毛,16384個比特方仿;每個比特對應(yīng)一個槽:比特值為1,則該比特對應(yīng)的槽在節(jié)點中厂汗;比特值為0娶桦,則該比特對應(yīng)的槽不在節(jié)點中

????unsigned?charslots[16384/8];


????//節(jié)點中槽的數(shù)量

????intnumslots;


????…………


} clusterNode;

除了上述字段,clusterNode還包含節(jié)點連接知牌、主從復(fù)制铛嘱、故障發(fā)現(xiàn)和轉(zhuǎn)移需要的信息等。

clusterState

clusterState結(jié)構(gòu)保存了在當(dāng)前節(jié)點視角下纹磺,集群所處的狀態(tài)橄杨。主要字段包括:

typedefstructclusterState {


????//自身節(jié)點

????clusterNode *myself;


????//配置紀(jì)元

????uint64_t currentEpoch;


????//集群狀態(tài):在線還是下線

????intstate;


????//集群中至少包含一個槽的節(jié)點數(shù)量

????intsize;


????//哈希表式矫,節(jié)點名稱->clusterNode節(jié)點指針

????dict *nodes;


????//槽分布信息:數(shù)組的每個元素都是一個指向clusterNode結(jié)構(gòu)的指針采转;如果槽還沒有分配給任何節(jié)點故慈,則為NULL

????clusterNode *slots[16384];


????…………


} clusterState;

除此之外,clusterState還包括故障轉(zhuǎn)移津辩、槽遷移等需要的信息。

4. 集群命令的實現(xiàn)

這一部分將以cluster meet(節(jié)點握手)竭贩、cluster addslots(槽分配)為例娶视,說明節(jié)點是如何利用上述數(shù)據(jù)結(jié)構(gòu)和通信機制實現(xiàn)集群命令的肪获。

cluster meet

假設(shè)要向A節(jié)點發(fā)送cluster meet命令孝赫,將B節(jié)點加入到A所在的集群青柄,則A節(jié)點收到命令后致开,執(zhí)行的操作如下:

1) ?A為B創(chuàng)建一個clusterNode結(jié)構(gòu),并將其添加到clusterState的nodes字典中

2) ?A向B發(fā)送MEET消息

3) ?B收到MEET消息后飒货,會為A創(chuàng)建一個clusterNode結(jié)構(gòu)塘辅,并將其添加到clusterState的nodes字典中

4) ?B回復(fù)A一個PONG消息

5) ?A收到B的PONG消息后扣墩,便知道B已經(jīng)成功接收自己的MEET消息

6) ?然后沮榜,A向B返回一個PING消息

7) ?B收到A的PING消息后蟆融,便知道A已經(jīng)成功接收自己的PONG消息型酥,握手完成

8) ?之后弥喉,A通過Gossip協(xié)議將B的信息廣播給集群內(nèi)其他節(jié)點由境,其他節(jié)點也會與B握手虏杰;一段時間后纺阔,集群收斂质况,B成為集群內(nèi)的一個普通節(jié)點

通過上述過程可以發(fā)現(xiàn)结榄,集群中兩個節(jié)點的握手過程與TCP類似囤捻,都是三次握手:A向B發(fā)送MEET最蕾;B向A發(fā)送PONG;A向B發(fā)送PING枝秤。

cluster addslots

集群中槽的分配信息丹壕,存儲在clusterNode的slots數(shù)組和clusterState的slots數(shù)組中菌赖,兩個數(shù)組的結(jié)構(gòu)前面已做介紹琉用;二者的區(qū)別在于:前者存儲的是該節(jié)點中分配了哪些槽邑时,后者存儲的是集群中所有槽分別分布在哪個節(jié)點黍氮。

cluster addslots命令接收一個槽或多個槽作為參數(shù)沫浆,例如在A節(jié)點上執(zhí)行cluster addslots {0..10}命令件缸,是將編號為0-10的槽分配給A節(jié)點他炊,具體執(zhí)行過程如下:

1) ?遍歷輸入槽,檢查它們是否都沒有分配凿叠,如果有一個槽已分配盒件,命令執(zhí)行失敵吹蟆翔始;方法是檢查輸入槽在clusterState.slots[]中對應(yīng)的值是否為NULL城瞎。

2) ?遍歷輸入槽脖镀,將其分配給節(jié)點A认然;方法是修改clusterNode.slots[]中對應(yīng)的比特為1卷员,以及clusterState.slots[]中對應(yīng)的指針指向A節(jié)點

3) ?A節(jié)點執(zhí)行完成后削饵,通過節(jié)點通信機制通知其他節(jié)點窿撬,所有節(jié)點都會知道0-10的槽分配給了A節(jié)點

四劈伴、客戶端訪問集群

在集群中跛璧,數(shù)據(jù)分布在不同的節(jié)點中追城,客戶端通過某節(jié)點訪問數(shù)據(jù)時,數(shù)據(jù)可能不在該節(jié)點中物舒;下面介紹集群是如何處理這個問題的锋玲。

1. redis-cli

當(dāng)節(jié)點收到redis-cli發(fā)來的命令(如set/get)時,過程如下:

(1)計算key屬于哪個槽:CRC16(key) & 16383

集群提供的cluster keyslot命令也是使用上述公式實現(xiàn)割粮,如:

(2)判斷key所在的槽是否在當(dāng)前節(jié)點:假設(shè)key位于第i個槽,clusterState.slots[i]則指向了槽所在的節(jié)點京髓,如果clusterState.slots[i]==clusterState.myself堰怨,說明槽在當(dāng)前節(jié)點,可以直接在當(dāng)前節(jié)點執(zhí)行命令灿巧;否則抠藕,說明槽不在當(dāng)前節(jié)點盾似,則查詢槽所在節(jié)點的地址(clusterState.slots[i].ip/port)零院,并將其包裝到MOVED錯誤中返回給redis-cli门粪。

(3)redis-cli收到MOVED錯誤后玄妈,根據(jù)返回的ip和port重新發(fā)送請求。

下面的例子展示了redis-cli和集群的互動過程:在7000節(jié)點中操作key1酝锅,但key1所在的槽9189在節(jié)點7001中搔扁,因此節(jié)點返回MOVED錯誤(包含7001節(jié)點的ip和port)給redis-cli,redis-cli重新向7001發(fā)起請求苛聘。

上例中唱捣,redis-cli通過-c指定了集群模式震缭,如果沒有指定蛀序,redis-cli無法處理MOVED錯誤:

2. Smart客戶端

redis-cli這一類客戶端稱為Dummy客戶端,因為它們在執(zhí)行命令前不知道數(shù)據(jù)在哪個節(jié)點,需要借助MOVED錯誤重新定向回懦。與Dummy客戶端相對應(yīng)的是Smart客戶端潜圃。

Smart客戶端(以Java的JedisCluster為例)的基本原理:

(1)JedisCluster初始化時谭期,在內(nèi)部維護slot->node的緩存隧出,方法是連接任一節(jié)點胀瞪,執(zhí)行cluster slots命令凄诞,該命令返回如下所示:

(2)此外,JedisCluster為每個節(jié)點創(chuàng)建連接池(即JedisPool)。

(3)當(dāng)執(zhí)行命令時嗦玖,JedisCluster根據(jù)key->slot->node選擇需要連接的節(jié)點苛吱,發(fā)送命令。如果成功援所,則命令執(zhí)行完畢。如果執(zhí)行失敗滔岳,則會隨機選擇其他節(jié)點進行重試,并在出現(xiàn)MOVED錯誤時挽牢,使用cluster slots重新同步slot->node的映射關(guān)系谱煤。

下面代碼演示了如何使用JedisCluster訪問集群(未考慮資源釋放、異常處理等):

publicstaticvoidtest() {

???Set<HostAndPort> nodes =?newHashSet<>();

???nodes.add(newHostAndPort("192.168.72.128",?7000));

???nodes.add(newHostAndPort("192.168.72.128",?7001));

???nodes.add(newHostAndPort("192.168.72.128",?7002));

???nodes.add(newHostAndPort("192.168.72.128",?8000));

???nodes.add(newHostAndPort("192.168.72.128",?8001));

???nodes.add(newHostAndPort("192.168.72.128",?8002));

???JedisCluster cluster =?newJedisCluster(nodes);

???System.out.println(cluster.get("key1"));

???cluster.close();

}

注意事項如下:

(1)JedisCluster中已經(jīng)包含所有節(jié)點的連接池禽拔,因此JedisCluster要使用單例刘离。

(2)客戶端維護了slot->node映射關(guān)系以及為每個節(jié)點創(chuàng)建了連接池,當(dāng)節(jié)點數(shù)量較多時奏赘,應(yīng)注意客戶端內(nèi)存資源和連接資源的消耗寥闪。

(3)Jedis較新版本針對JedisCluster做了一些性能方面的優(yōu)化缚柳,如cluster slots緩存更新和鎖阻塞等方面的優(yōu)化构舟,應(yīng)盡量使用2.8.2及以上版本的Jedis苦蒿。

五、實踐須知

前面介紹了集群正常運行和訪問的方法和原理,下面是一些重要的補充內(nèi)容。

1. 集群伸縮

實踐中常常需要對集群進行伸縮苍蔬,如訪問量增大時的擴容操作。Redis集群可以在不影響對外服務(wù)的情況下實現(xiàn)伸縮;伸縮的核心是槽遷移:修改槽與節(jié)點的對應(yīng)關(guān)系侮东,實現(xiàn)槽(即數(shù)據(jù))在節(jié)點之間的移動宽闲。在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流圈:609164807 ?幫助突破瓶頸 提升思維能力例如,如果槽均勻分布在集群的3個節(jié)點中吱殉,此時增加一個節(jié)點,則需要從3個節(jié)點中分別拿出一部分槽給新節(jié)點流礁,從而實現(xiàn)槽在4個節(jié)點中的均勻分布元镀。

增加節(jié)點

假設(shè)要增加7003和8003節(jié)點遇革,其中8003是7003的從節(jié)點;步驟如下:

(1)啟動節(jié)點:方法參見集群搭建

(2)節(jié)點握手:可以使用cluster meet命令蚌铜,但在生產(chǎn)環(huán)境中建議使用redis-trib.rb的add-node工具审葬,其原理也是cluster meet官册,但它會先檢查新節(jié)點是否已加入其它集群或者存在數(shù)據(jù),避免加入到集群后帶來混亂愚争。

redis-trib.rb?add-node 192.168.72.128:7003 192.168.72.128 7000

redis-trib.rb?add-node 192.168.72.128:8003 192.168.72.128 7000

(3)遷移槽:推薦使用redis-trib.rb的reshard工具實現(xiàn)步淹。reshard自動化程度很高,只需要輸入redis-trib.rb reshard ip:port (ip和port可以是集群中的任一節(jié)點),然后按照提示輸入以下信息嘴高,槽遷移會自動完成:

待遷移的槽數(shù)量:16384個槽均分給4個節(jié)點颠印,每個節(jié)點4096個槽袄琳,因此待遷移槽數(shù)量為4096

目標(biāo)節(jié)點id:7003節(jié)點的id

源節(jié)點的id:7000/7001/7002節(jié)點的id

(4)指定主從關(guān)系:方法參見集群搭建

減少節(jié)點

假設(shè)要下線7000/8000節(jié)點,可以分為兩步:

(1)遷移槽:使用reshard將7000節(jié)點中的槽均勻遷移到7001/7002/7003節(jié)點

(2)下線節(jié)點:使用redis-trib.rb del-node工具昙读;應(yīng)先下線從節(jié)點再下線主節(jié)點,因為若主節(jié)點先下線囚似,從節(jié)點會被指向其他主節(jié)點,造成不必要的全量復(fù)制需曾。

redis-trib.rb del-node 192.168.72.128:7001 {節(jié)點8000的id}

redis-trib.rb del-node 192.168.72.128:7001 {節(jié)點7000的id}

ASK錯誤

集群伸縮的核心是槽遷移桑嘶。在槽遷移過程中,如果客戶端向源節(jié)點發(fā)送命令,源節(jié)點執(zhí)行流程如下:


客戶端收到ASK錯誤后技矮,從中讀取目標(biāo)節(jié)點的地址信息,并向目標(biāo)節(jié)點重新發(fā)送請求,就像收到MOVED錯誤時一樣芋哭。但是二者有很大區(qū)別:ASK錯誤說明數(shù)據(jù)正在遷移减牺,不知道何時遷移完成固以,因此重定向是臨時的,SMART客戶端不會刷新slots緩存骤坐;MOVED錯誤重定向則是(相對)永久的,SMART客戶端會刷新slots緩存。

2. 故障轉(zhuǎn)移

在 上?一文中余掖,介紹了哨兵實現(xiàn)故障發(fā)現(xiàn)和故障轉(zhuǎn)移的原理析二。雖然細節(jié)上有很大不同,但集群的實現(xiàn)與哨兵思路類似:通過定時任務(wù)發(fā)送PING消息檢測其他節(jié)點狀態(tài)哲戚;節(jié)點下線分為主觀下線和客觀下線;客觀下線后選取從節(jié)點進行故障轉(zhuǎn)移忱屑。

與哨兵一樣,集群只實現(xiàn)了主節(jié)點的故障轉(zhuǎn)移暇昂;從節(jié)點故障時只會被下線莺戒,不會進行故障轉(zhuǎn)移。因此急波,使用集群時从铲,應(yīng)謹(jǐn)慎使用讀寫分離技術(shù),因為從節(jié)點故障會導(dǎo)致讀服務(wù)不可用澄暮,可用性變差名段。

這里不再詳細介紹故障轉(zhuǎn)移的細節(jié),只對重要事項進行說明:

節(jié)點數(shù)量:在故障轉(zhuǎn)移階段赏寇,需要由主節(jié)點投票選出哪個從節(jié)點成為新的主節(jié)點吉嫩;從節(jié)點選舉勝出需要的票數(shù)為N/2+1价认;其中N為主節(jié)點數(shù)量(包括故障主節(jié)點)嗅定,但故障主節(jié)點實際上不能投票。因此為了能夠在故障發(fā)生時順利選出從節(jié)點用踩,集群中至少需要3個主節(jié)點(且部署在不同的物理機上)渠退。

故障轉(zhuǎn)移時間:從主節(jié)點故障發(fā)生到完成轉(zhuǎn)移,所需要的時間主要消耗在主觀下線識別脐彩、主觀下線傳播碎乃、選舉延遲等幾個環(huán)節(jié);具體時間與參數(shù)cluster-node-timeout有關(guān)惠奸,一般來說:

故障轉(zhuǎn)移時間(毫秒) ≤ 1.5 * cluster-node-timeout + 1000

cluster-node-timeout的默認(rèn)值為15000ms(15s)梅誓,因此故障轉(zhuǎn)移時間會在20s量級。

3. 集群的限制及應(yīng)對方法

由于集群中的數(shù)據(jù)分布在不同節(jié)點中,導(dǎo)致一些功能受限梗掰,包括:

(1)key批量操作受限:例如mget嵌言、mset操作,只有當(dāng)操作的key都位于一個槽時及穗,才能進行摧茴。針對該問題,一種思路是在客戶端記錄槽與key的信息埂陆,每次針對特定槽執(zhí)行mget/mset苛白;另外一種思路是使用Hash Tag,將在下一小節(jié)介紹焚虱。

(2)keys/flushall等操作:keys/flushall等操作可以在任一節(jié)點執(zhí)行购裙,但是結(jié)果只針對當(dāng)前節(jié)點,例如keys操作只返回當(dāng)前節(jié)點的所有鍵著摔。針對該問題缓窜,可以在客戶端使用cluster nodes獲取所有節(jié)點信息,并對其中的所有主節(jié)點執(zhí)行keys/flushall等操作谍咆。

(3)事務(wù)/Lua腳本:集群支持事務(wù)及Lua腳本禾锤,但前提條件是所涉及的key必須在同一個節(jié)點。Hash Tag可以解決該問題摹察。

(4)數(shù)據(jù)庫:單機Redis節(jié)點可以支持16個數(shù)據(jù)庫恩掷,集群模式下只支持一個,即db0供嚎。

(5)復(fù)制結(jié)構(gòu):只支持一層復(fù)制結(jié)構(gòu)黄娘,不支持嵌套。

4. Hash Tag

Hash Tag原理是:當(dāng)一個key包含 {}?的時候克滴,不對整個key做hash逼争,而僅對 {}?包括的字符串做hash

Hash Tag可以讓不同的key擁有相同的hash值劝赔,從而分配在同一個槽里誓焦;這樣針對不同key的批量操作(mget/mset等),以及事務(wù)着帽、Lua腳本等都可以支持杂伟。不過Hash Tag可能會帶來數(shù)據(jù)分配不均的問題,這時需要:(1)調(diào)整不同節(jié)點中槽的數(shù)量仍翰,使數(shù)據(jù)分布盡量均勻赫粥;(2)避免對熱點數(shù)據(jù)使用Hash Tag,導(dǎo)致請求分布不均予借。

下面是使用Hash Tag的一個例子越平;通過對product加Hash Tag频蛔,可以將所有產(chǎn)品信息放到同一個槽中,便于操作秦叛。

5. 參數(shù)優(yōu)化

cluster_node_timeout

cluster_node_timeout參數(shù)在前面已經(jīng)初步介紹帽驯;它的默認(rèn)值是15s,影響包括:

(1)影響PING消息接收節(jié)點的選擇:值越大對延遲容忍度越高书闸,選擇的接收節(jié)點越少尼变,可以降低帶寬,但會降低收斂速度浆劲;應(yīng)根據(jù)帶寬情況和應(yīng)用要求進行調(diào)整嫌术。

(2)影響故障轉(zhuǎn)移的判定和時間:值越大,越不容易誤判牌借,但完成轉(zhuǎn)移消耗時間越長度气;應(yīng)根據(jù)網(wǎng)絡(luò)狀況和應(yīng)用要求進行調(diào)整。

cluster-require-full-coverage

前面提到膨报,只有當(dāng)16384個槽全部分配完畢時磷籍,集群才能上線。這樣做是為了保證集群的完整性现柠,但同時也帶來了新的問題:當(dāng)主節(jié)點發(fā)生故障而故障轉(zhuǎn)移尚未完成院领,原主節(jié)點中的槽不在任何節(jié)點中,此時會集群處于下線狀態(tài)够吩,無法響應(yīng)客戶端的請求比然。

cluster-require-full-coverage參數(shù)可以改變這一設(shè)定:如果設(shè)置為no,則當(dāng)槽沒有完全分配時周循,集群仍可以上線强法。參數(shù)默認(rèn)值為yes,如果應(yīng)用對可用性要求較高湾笛,可以修改為no饮怯,但需要自己保證槽全部分配。

6. redis-trib.rb

redis-trib.rb提供了眾多實用工具:創(chuàng)建集群嚎研、增減節(jié)點蓖墅、槽遷移、檢查完整性嘉赎、數(shù)據(jù)重新平衡等置媳;通過help命令可以查看詳細信息于樟。在實踐中如果能使用redis-trib.rb工具則盡量使用公条,不但方便快捷,還可以大大降低出錯概率迂曲。

在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流圈:609164807 幫助突破瓶頸 提升思維能力


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末靶橱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌关霸,老刑警劉巖传黄,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異队寇,居然都是意外死亡膘掰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門佳遣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來识埋,“玉大人,你說我怎么就攤上這事零渐≈现郏” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵诵盼,是天一觀的道長惠豺。 經(jīng)常有香客問我风宁,道長,這世上最難降的妖魔是什么戒财? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮固翰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘骂际。我一直安慰自己,他們只是感情好歉铝,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布盈简。 她就那樣靜靜地躺著太示,像睡著了一般。 火紅的嫁衣襯著肌膚如雪臼勉。 梳的紋絲不亂的頭發(fā)上餐弱,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音畸写,去河邊找鬼氓扛。 笑死,一個胖子當(dāng)著我的面吹牛破停,可吹牛的內(nèi)容都是我干的真慢。 我是一名探鬼主播理茎,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼皂林,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烛占?” 一聲冷哼從身側(cè)響起沟启,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤德迹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后卸例,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肌毅,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡悬而,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年摊滔,在試婚紗的時候發(fā)現(xiàn)自己被綠了艰躺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡左电,死狀恐怖篓足,靈堂內(nèi)的尸體忽然破棺而出闰蚕,到底是詐尸還是另有隱情没陡,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布贴彼,位于F島的核電站器仗,受9級特大地震影響童番,放射性物質(zhì)發(fā)生泄漏剃斧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一弹沽、第九天 我趴在偏房一處隱蔽的房頂上張望策橘。 院中可真熱鬧娜亿,春花似錦买决、人聲如沸吼畏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽羹奉。三九已至,卻和暖如春迁筛,著一層夾襖步出監(jiān)牢的瞬間耕挨,已是汗流浹背俗孝。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工赋铝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人农尖。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓盛卡,卻偏偏與公主長得像筑凫,于是被迫代替她去往敵國和親巍实。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

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