Redis4.0新特性(三)-PSYNC2

摘要:1 什么是Redis部分重新同步-psync redis部分重新同步:是指redis因某種原因引起復(fù)制中斷后满着,從庫重新同步時,只同步主實(shí)例的差異數(shù)據(jù)(寫入指令)贯莺,不進(jìn)行bgsave復(fù)制整個RDB文件风喇。

1 什么是Redis部分重新同步-psync

redis部分重新同步:是指redis因某種原因引起復(fù)制中斷后,從庫重新同步時乖篷,只同步主實(shí)例的差異數(shù)據(jù)(寫入指令)响驴,不進(jìn)行bgsave復(fù)制整個RDB文件。

本文的名詞規(guī)約:

部分重新同步:后文簡稱

psync

全量重新同步:后文簡稱fullsync

redis2.8第一版部分重新同步:后文簡稱psync1

redis4.0第二版本部分重新同步:后文簡稱psync2

在說明psync2功能前撕蔼,先簡單闡述redis2.8版本發(fā)布的psync1

Redis2.8 psync1解決什么問題

在psync1功能出現(xiàn)前豁鲤,redis復(fù)制秒級中斷秽誊,就會觸發(fā)從實(shí)例進(jìn)行fullsync。

每一次的fullsync琳骡,集群的性能和資源使用都可能帶來抖動锅论;如果redis所處的網(wǎng)絡(luò)環(huán)境不穩(wěn)定,那么fullsync的出步頻率可能較高楣号。為解決此問題最易,redis2.8引入psync1, 有效地解決這種復(fù)制閃斷,帶來的影響炫狱。redis的fullsync對業(yè)務(wù)而言藻懒,算是比較“重”的影響;對性能和可用性都有一定危險视译。

這里列舉幾個fullsync常見的影響:

master需運(yùn)行bgsave,出現(xiàn)fork()嬉荆,可能造成master達(dá)到毫秒或秒級的卡頓(latest_fork_usec狀態(tài)監(jiān)控);

redis進(jìn)程fork導(dǎo)致Copy-On-Write內(nèi)存使用消耗(后文簡稱COW)酷含,最大能導(dǎo)致master進(jìn)程內(nèi)存使用量的消耗鄙早。(eg 日志中輸出 RDB: 5213 MB of memory used by copy-on-write)

redis slave load RDB過程,會導(dǎo)致復(fù)制線程的client output buffer增長很大椅亚;增大Master進(jìn)程內(nèi)存消耗限番;

redis保存RDB(不考慮disless replication),導(dǎo)致服務(wù)器磁盤IO和CPU(壓縮)資源消耗

發(fā)送數(shù)GB的RDB文件,會導(dǎo)致服務(wù)器網(wǎng)絡(luò)出口爆增,如果千兆網(wǎng)卡服務(wù)器,期間會影響業(yè)務(wù)正常請求響應(yīng)時間(以及其他連鎖影響)

psync1的基本實(shí)現(xiàn)

因?yàn)閜sync2是在psync1基礎(chǔ)上的增強(qiáng)實(shí)現(xiàn)呀舔,介紹psync2之前弥虐,簡單分析psync1的實(shí)現(xiàn)。

redis2.8為支持psync1媚赖,引入了replication backlog buffer(后文稱:復(fù)制積壓緩沖區(qū))躯舔;復(fù)制積壓緩沖區(qū)是redis維護(hù)的固定長度緩沖隊(duì)列(由參數(shù)repl-backlog-size設(shè)置,默認(rèn)1MB)省古,master的寫入命令在同步給slaves的同時,會在緩沖區(qū)中寫入一份(master只有1個積壓緩沖區(qū)丧失,所有slaves共享)豺妓。

當(dāng)redis復(fù)制中斷后,slave會嘗試采用psync, 上報原master runid + 當(dāng)前已同步master的offset(復(fù)制偏移量布讹,類似mysql的binlog file和position)琳拭;

如果runid與master的一致,且復(fù)制偏移量在master的復(fù)制積壓緩沖區(qū)中還有(即offset >= min(backlog值)描验,master就認(rèn)為部分重同步成功白嘁,不再進(jìn)行全量同步。

部分重同步成功膘流,master的日志顯示如下:

30422:M 04 Aug 14:33:48.505 * Slave xxxxx:10005 asks for synchronization

30422:M 04 Aug 14:33:48.506 * Partial resynchronization request from xxx:10005 accepted. Sending 0 bytes of backlog starting from offset 6448313.

redis2.8的部分同步機(jī)制絮缅,有效解決了網(wǎng)絡(luò)環(huán)境不穩(wěn)定鲁沥、redis執(zhí)行高時間復(fù)雜度的命令引起的復(fù)制中斷,從而導(dǎo)致全量同步耕魄。但在應(yīng)對slave重啟和Master故障切換的場景時画恰,psync1還是需進(jìn)行全量同步。

psync1的不足

從上文可知吸奴,psync1需2個條件同時滿足允扇,才能成功psync:master runid不變 和復(fù)制偏移量在master復(fù)制積緩沖區(qū)中。

那么在redis slave重啟,因master runid和復(fù)制偏移量都會丟失则奥,需進(jìn)行全量重同步考润;redis master發(fā)生故障切換,因master runid發(fā)生了變化读处;故障切換后糊治,新的slave需進(jìn)行全量重同步。而slave維護(hù)性重啟档泽、master故障切換都是redis運(yùn)維常見場景俊戳,為redis的psync1是不能解決這兩類場景的成功部分重同步問題。

因此redis4.0的加強(qiáng)版部分重同步功能-psync2馆匿,主要解決這兩類場景的部分重新同步抑胎。

2 psync2的實(shí)現(xiàn)簡述

在redis cluster的實(shí)際生產(chǎn)運(yùn)營中,實(shí)例的維護(hù)性重啟渐北、主實(shí)例的故障切換(如cluster failover)操作都是比較常見的(如實(shí)例升級阿逃、rename command和釋放實(shí)例內(nèi)存碎片等)。而在redis4.0版本前赃蛛,這類維護(hù)性的處理恃锉,redis都會發(fā)生全量重新同步,導(dǎo)到性能敏感的服務(wù)有少量受損呕臂。

如前文所述破托,psync2主要讓redis在從實(shí)例重啟和主實(shí)例故障切換場景下,也能使用部分重新同步歧蒋。本節(jié)主要簡述psync2在這兩種場景的邏輯實(shí)現(xiàn)土砂。

名詞解釋:

master_replid: 復(fù)制ID1(后文簡稱:replid1),一個長度為41個字節(jié)(40個隨機(jī)串+’\0’)的字符串谜洽。redis實(shí)例都有萝映,和runid沒有直接關(guān)聯(lián),但和runid生成規(guī)則相同阐虚,都是由getRandomHexChars函數(shù)生成序臂。當(dāng)實(shí)例變?yōu)閺膶?shí)例后,自己的replid1會被主實(shí)例的replid1覆蓋实束。

master_replid2:復(fù)制ID2(后文簡稱:replid2),默認(rèn)初始化為全0奥秆,用于存儲上次主實(shí)例的replid1

實(shí)例的replid信息逊彭,可通過info replication進(jìn)行查看; 示例如下:

127.0.0.1:6385> info replication

# Replication

role:slave

master_host:xxxx // IP模糊處理

master_port:6382

master_link_status:up

slave_repl_offset:119750master_replid:fe093add4ab71544ce6508d2e0bf1dd0b7d1c5b2 //這里是主實(shí)例的replid1相同

master_replid2:0000000000000000000000000000000000000000 //未發(fā)生切換吭练,即主實(shí)例未發(fā)生過變化诫龙,所以是初始值全"0"master_repl_offset:119750

second_repl_offset:-1

3 Redis從實(shí)例重啟的部分重新同步

在之前的版本,redis重啟后鲫咽,復(fù)制信息是完全丟失;所以從實(shí)例重啟后签赃,只能進(jìn)行全量重新同步。

redis4.0為實(shí)現(xiàn)重啟后分尸,仍可進(jìn)行部分重新同步锦聊,主要做以下3點(diǎn):

redis關(guān)閉時,把復(fù)制信息作為輔助字段(AUX Fields)存儲在RDB文件中箩绍;以實(shí)現(xiàn)同步信息持久化孔庭;

redis啟動加載RDB文件時,會把復(fù)制信息賦給相關(guān)字段材蛛;

redis重新同步時圆到,會上報repl-id和repl-offset同步信息,如果和主實(shí)例匹配卑吭,且offset還在主實(shí)例的復(fù)制積壓緩沖區(qū)內(nèi)芽淡,則只進(jìn)行部分重新同步。

接下來豆赏,我們詳細(xì)分析每步的簡單實(shí)現(xiàn)

redis關(guān)閉時挣菲,持久化復(fù)制信息到RDB

redis在關(guān)閉時,通過shutdown save,都會調(diào)用rdbSaveInfoAuxFields函數(shù)掷邦,

把當(dāng)前實(shí)例的repl-id和repl-offset保存到RDB文件中白胀。

說明:當(dāng)前的RDB存儲的數(shù)據(jù)內(nèi)容和復(fù)制信息是一致性的。熟悉MySQL的同學(xué)抚岗,可以認(rèn)為MySQL中全量備份數(shù)和binlog信息是一致的或杠。

rdbSaveInfoAuxFields函數(shù)實(shí)現(xiàn)在rdb.c源文件中,省略后代碼如下:

/* Save a few default AUX fields with information about the RDB generated. */

int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {

/* Add a few fields about the state when the RDB was created. */

if (rdbSaveAuxFieldStrStr(rdb,"redis-ver",REDIS_VERSION) == -1) return -1;

//把實(shí)例的repl-id和repl-offset作為輔助字段宣蔚,存儲在RDB中

if (rdbSaveAuxFieldStrStr(rdb,"repl-id",server.replid) == -1) return -1;

if (rdbSaveAuxFieldStrInt(rdb,"repl-offset",server.master_repl_offset) == -1) return -1;

return 1;

}

生成的RDB文件廷痘,可以通過redis自帶的redis-check-rdb工具查看輔助字段信息。

其中repl兩字段信息和info中的相同件已;

$shell> /src/redis-check-rdb dump.rdb

[offset 0] Checking RDB file dump.rdb

[offset 26] AUX FIELD redis-ver = '4.0.1'[offset 133] AUX FIELD repl-id = '44873f839ae3a57572920cdaf70399672b842691'

[offset 148] AUX FIELD repl-offset = '0'[offset 167] \o/ RDB looks OK! \o/

[info] 1 keys read

[info] 0 expires

[info] 0 already expired

redis啟動讀取RDB中復(fù)制信息

redis實(shí)例啟動讀取RDB文件,通過rdb.c文件中rdbLoadRio()函數(shù)實(shí)現(xiàn)元暴。

redis加載RDB文件篷扩,會專門處理文件中輔助字段(AUX fields)信息,把其中repl_id和repl_offset加載到實(shí)例中茉盏,分別賦給master_replid和master_repl_offset兩個變量值鉴未。

以下代碼枢冤,是從RDB文件中讀取兩個輔助字段值。

int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {

----------省略-----------

else if (!strcasecmp(auxkey->ptr,"repl-id")) {//讀取的aux字段是repl-id

if (rsi && sdslen(auxval->ptr) == CONFIG_RUN_ID_SIZE) {

memcpy(rsi->repl_id,auxval->ptr,CONFIG_RUN_ID_SIZE+1);

rsi->repl_id_is_set = 1;

}

} else if (!strcasecmp(auxkey->ptr,"repl-offset")) {

if (rsi) rsi->repl_offset = strtoll(auxval->ptr,NULL,10);

} else {

/* We ignore fields we don't understand, as by AUX field

* contract. */

serverLog(LL_DEBUG,"Unrecognized RDB AUX field: '%s'",

(char*)auxkey->ptr);

}

}

redis從實(shí)例嘗試部分重新同步

redis實(shí)例重啟后铜秆,從RDB文件中加載(注:此處不討論AOF和RDB加載優(yōu)先權(quán))master_replid和master_repl_offset淹真;相當(dāng)于實(shí)例的server.cached_master。當(dāng)我們把它作為某個實(shí)例的從庫時(包含如被動的cluster slave或主動執(zhí)行slaveof指令)连茧,實(shí)例向主實(shí)例上報master_replid和master_repl_offset+1核蘸;從實(shí)例同時滿足以下兩條件,就可以部分重新同步:

1.從實(shí)例上報master_replid串啸驯,與主實(shí)例的master_replid1或replid2有一個相等

2. 從實(shí)例上報的master_repl_offset+1字節(jié)客扎,還存在于主實(shí)例的復(fù)制積壓緩沖區(qū)中

從實(shí)例嘗試部分重新同步函數(shù)slaveTryPartialResynchronization(replication.c文件中);

主實(shí)例判斷能否進(jìn)行部分重新同步函數(shù)masterTryPartialResynchronization(replication.c文件中)罚斗。

redis重啟時徙鱼,臨時調(diào)整主實(shí)例的復(fù)制積壓緩沖區(qū)大小

redis的復(fù)制積壓緩沖區(qū)是通過參數(shù)repl-backlog-size設(shè)置,默認(rèn)1MB针姿;為確保從實(shí)例重啟后袱吆,還能部分重新同步,需設(shè)置合理的repl-backlog-size值距淫。

1 計(jì)算合理的repl-backlog-size值大小

通過主庫每秒增量的master復(fù)制偏移量master_repl_offset(info replication指令獲取)大小绞绒,

如每秒offset增加是5MB,那么主實(shí)例復(fù)制積壓緩沖區(qū)要保留最近60秒寫入內(nèi)容,backlog_size設(shè)置就得大于300MB(60*5)溉愁。而從實(shí)例重啟加載RDB文件是較耗時的過程处铛,如重啟某個重實(shí)例需120秒(RDB大小和CPU配置相關(guān)),那么主實(shí)例backlog_size就得設(shè)置至少600MB.

計(jì)算公式:backlog_size = 重啟從實(shí)例時長 * 主實(shí)例offset每秒寫入量

2 重啟從實(shí)例前拐揭,調(diào)整主實(shí)例的動態(tài)調(diào)整repl-backlog-size的值撤蟆。

因?yàn)橥ㄟ^config set動態(tài)調(diào)整redis的repl-backlog-size時,redis會釋放當(dāng)前的積壓緩沖區(qū)堂污,重新分配一個指定大小的緩沖區(qū)家肯。 所以我們必須在從實(shí)例重啟前,調(diào)整主實(shí)例的repl-backlog-size盟猖。

調(diào)整backlog_size處理函數(shù)resizeReplicationBacklog讨衣,代碼邏輯如下:

void resizeReplicationBacklog(long long newsize) {

if (newsize < CONFIG_REPL_BACKLOG_MIN_SIZE) //如果設(shè)置新值小于16KB,則修改為16KB

newsize = CONFIG_REPL_BACKLOG_MIN_SIZE;

if (server.repl_backlog_size == newsize) return; //如果新值與原值相同,則不作任何處理式镐,直接返回反镇。

server.repl_backlog_size = newsize; //修改backlog參數(shù)大小

if (server.repl_backlog != NULL) { //當(dāng)backlog內(nèi)容非空時,釋放當(dāng)前backlog娘汞,并按新值分配一個新的backlog

/* What we actually do is to flush the old buffer and realloc a new

* empty one. It will refill with new data incrementally.

* The reason is that copying a few gigabytes adds latency and even

* worse often we need to alloc additional space before freeing the

* old buffer. */

zfree(server.repl_backlog);

server.repl_backlog = zmalloc(server.repl_backlog_size);

server.repl_backlog_histlen = 0; //修改backlog內(nèi)容長度和首字節(jié)offset都為0

server.repl_backlog_idx = 0;

/* Next byte we have is... the next since the buffer is empty. */

server.repl_backlog_off = server.master_repl_offset+1;

}

}

3 psync2實(shí)現(xiàn)Redis Cluster Failover部分全新同步

為解決主實(shí)例故障切換后歹茶,重新同步新主實(shí)例數(shù)據(jù)時使用psync,而分fullsync;

1 redis4.0使用兩組replid惊豺、offset替換原來的master runid和offset.

2 redis slave默認(rèn)開啟復(fù)制積壓緩沖區(qū)功能燎孟;以便slave故障切換變化master后,其他落后從可以從緩沖區(qū)中獲取寫入指令尸昧。

第一組:master_replid和master_repl_offset

如果redis是主實(shí)例揩页,則表示為自己的replid和復(fù)制偏移量; 如果redis是從實(shí)例烹俗,則表示為自己主實(shí)例的replid1和同步主實(shí)例的復(fù)制偏移量爆侣。

第二組:master_replid2和second_repl_offset

無論主從,都表示自己上次主實(shí)例repid1和復(fù)制偏移量衷蜓;用于兄弟實(shí)例或級聯(lián)復(fù)制累提,主庫故障切換psync.

初始化時, 前者是40個字符長度為0,后者是-1磁浇; 只有當(dāng)主實(shí)例發(fā)生故障切換時斋陪,redis把自己replid1和master_repl_offset+1分別賦值給master_replid2和second_repl_offset。

這個交換邏輯實(shí)現(xiàn)在函數(shù)shiftReplicationId中置吓。

void shiftReplicationId(void) {

memcpy(server.replid2,server.replid,sizeof(server.replid)); //replid賦值給replid2

/* We set the second replid offset to the master offset + 1, since

* the slave will ask for the first byte it has not yet received, so

* we need to add one to the offset: for example if, as a slave, we are

* sure we have the same history as the master for 50 bytes, after we

* are turned into a master, we can accept a PSYNC request with offset

* 51, since the slave asking has the same history up to the 50th

* byte, and is asking for the new bytes starting at offset 51. */

server.second_replid_offset = server.master_repl_offset+1;

changeReplicationId();

serverLog(LL_WARNING,"Setting secondary replication ID to %s, valid up to offset: %lld. New replication ID is %s", server.replid2, server.second_replid_offset, server.replid);

}

這樣發(fā)生主庫故障切換无虚,以下三種常見結(jié)構(gòu),都能進(jìn)行psync:

一主一從發(fā)生切換衍锚,A->B 切換變成 B->A ;

一主多從發(fā)生切換友题,兄弟節(jié)點(diǎn)變成父子節(jié)點(diǎn)時;

級別復(fù)制發(fā)生切換戴质, A->B->C 切換變成 B->C->A

主實(shí)例判斷能否進(jìn)行psync的邏輯函數(shù)在masterTryPartialResynchronization()

int masterTryPartialResynchronization(client *c) {

//如果slave提供的master_replid與master的replid不同度宦,且與master的replid2不同,或同步速度快于master告匠; 就必須進(jìn)行fullsync.

if (strcasecmp(master_replid, server.replid) &&

(strcasecmp(master_replid, server.replid2) ||

psync_offset > server.second_replid_offset))

{

/* Run id "?" is used by slaves that want to force a full resync. */

if (master_replid[0] != '?') {

if (strcasecmp(master_replid, server.replid) &&

strcasecmp(master_replid, server.replid2))

{

serverLog(LL_NOTICE,"Partial resynchronization not accepted: "

"Replication ID mismatch (Slave asked for '%s', my "

"replication IDs are '%s' and '%s')",

master_replid, server.replid, server.replid2);

} else {

serverLog(LL_NOTICE,"Partial resynchronization not accepted: "

"Requested offset for second ID was %lld, but I can reply "

"up to %lld", psync_offset, server.second_replid_offset);

}

} else {

serverLog(LL_NOTICE,"Full resync requested by slave %s",

replicationGetSlaveName(c));

}

goto need_full_resync;

}

/* We still have the data our slave is asking for? */

if (!server.repl_backlog ||

psync_offset < server.repl_backlog_off ||

psync_offset > (server.repl_backlog_off + server.repl_backlog_histlen))

{

serverLog(LL_NOTICE,

"Unable to partial resync with slave %s for lack of backlog (Slave request was: %lld).", replicationGetSlaveName(c), psync_offset);

if (psync_offset > server.master_repl_offset) {

serverLog(LL_WARNING,

"Warning: slave %s tried to PSYNC with an offset that is greater than the master replication offset.", replicationGetSlaveName(c));

}

goto need_full_resync;

}

原文發(fā)布時間為:2017-11-13

本文作者:RogerZhuo

本文來自云棲社區(qū)合作伙伴“老葉茶館”戈抄,了解相關(guān)信息可以關(guān)注“老葉茶館”微信公眾號

原文鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市后专,隨后出現(xiàn)的幾起案子划鸽,更是在濱河造成了極大的恐慌,老刑警劉巖戚哎,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裸诽,死亡現(xiàn)場離奇詭異,居然都是意外死亡型凳,警方通過查閱死者的電腦和手機(jī)丈冬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甘畅,“玉大人殷蛇,你說我怎么就攤上這事实夹。” “怎么了粒梦?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長荸实。 經(jīng)常有香客問我匀们,道長,這世上最難降的妖魔是什么准给? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任泄朴,我火速辦了婚禮,結(jié)果婚禮上露氮,老公的妹妹穿的比我還像新娘祖灰。我一直安慰自己,他們只是感情好畔规,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布局扶。 她就那樣靜靜地躺著,像睡著了一般叁扫。 火紅的嫁衣襯著肌膚如雪三妈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天莫绣,我揣著相機(jī)與錄音畴蒲,去河邊找鬼。 笑死对室,一個胖子當(dāng)著我的面吹牛模燥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掩宜,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蔫骂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锭亏?” 一聲冷哼從身側(cè)響起纠吴,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎慧瘤,沒想到半個月后戴已,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡锅减,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年糖儡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怔匣。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡握联,死狀恐怖桦沉,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情金闽,我是刑警寧澤纯露,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站代芜,受9級特大地震影響埠褪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜挤庇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一钞速、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫡秕,春花似錦渴语、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至潮改,卻和暖如春狭郑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背汇在。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工翰萨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糕殉。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓亩鬼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阿蝶。 傳聞我的和親對象是個殘疾皇子雳锋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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