Redis - List 鏈表

redis使用兩種數(shù)據(jù)結(jié)構(gòu)保存鏈表,分別是ziplist與linkedlist,內(nèi)存占用及常用操作效率各不相同。
本文嘗試簡(jiǎn)要說明他們之間的區(qū)別。

眾所周知,redis中的list有兩種編碼結(jié)構(gòu),ziplist和linkedlist。兩種編碼結(jié)構(gòu)的切換由下面的配置信息決定:

redis 127.0.0.1:6379> config get list*
1) "list-max-ziplist-entries"
2) "512"
3) "list-max-ziplist-value"
4) "64"

以上兩個(gè)配置是默認(rèn)的配置。

針對(duì)以上的配置,當(dāng)列表對(duì)象保存的所有字符串元素的長(zhǎng)度都小于64字節(jié),并且列表對(duì)象保存的元素?cái)?shù)量小于512時(shí),list使用ziplist編碼;不能滿足這兩種情況就是用linkedlist編碼。

ziplist的特點(diǎn)是節(jié)省內(nèi)存,linkedlist是一個(gè)雙向列表,特點(diǎn)就是插入速度快,但是占內(nèi)存。

測(cè)試

正式開始我今天主要想發(fā)表的東西,雖不是什么了不起的東西,但是是我認(rèn)認(rèn)真真測(cè)試出來的結(jié)果,留個(gè)紀(jì)念吧。

測(cè)試方式:
a. 一個(gè)key,分別對(duì)其進(jìn)行rpush、lrange、ltrim三種操作;
b. rpush數(shù)據(jù)為80W個(gè)整型,每插入10W條記錄記錄一次此時(shí)的平均插入速率;
c. 每隔10W條記錄進(jìn)行一次lrange,查看占用時(shí)間;
d. 全部數(shù)據(jù)更新成功后,開始測(cè)試ltrim;
e. 分兩種編碼結(jié)構(gòu)進(jìn)行測(cè)試,作對(duì)比;

以下是測(cè)試結(jié)果:


Paste_Image.png

關(guān)于ziplist和linkedlist的內(nèi)存占用,80W的數(shù)據(jù),ziplist占用內(nèi)存不到5M,而linked占用內(nèi)存為37M+,內(nèi)存占用相差7倍多。但是執(zhí)行速度方面,linkedlist有明顯的優(yōu)勢(shì),在80w級(jí)別的數(shù)據(jù)相差63左右。

通過上面的測(cè)試,我們已經(jīng)知道ziplist在空間利用上有優(yōu)勢(shì),linkedlist在執(zhí)行效率上有優(yōu)勢(shì),具體選擇什么類型,需結(jié)合使用場(chǎng)景而定。

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

鏈表

每個(gè)鏈表節(jié)點(diǎn)使用一個(gè) adlist.h/listNode 結(jié)構(gòu)來表示:

typedef struct listNode {

    // 前置節(jié)點(diǎn)
    struct listNode *prev;

    // 后置節(jié)點(diǎn)
    struct listNode *next;

    // 節(jié)點(diǎn)的值
    void *value;

} listNode;

多個(gè) listNode 可以通過 prev 和 next 指針組成雙端鏈表,如圖 3-1 所示。

Paste_Image.png

雖然僅僅使用多個(gè) listNode 結(jié)構(gòu)就可以組成鏈表, 但使用 adlist.h/list 來持有鏈表的話, 操作起來會(huì)更方便:

typedef struct list {

    // 表頭節(jié)點(diǎn)
    listNode *head;

    // 表尾節(jié)點(diǎn)
    listNode *tail;

    // 鏈表所包含的節(jié)點(diǎn)數(shù)量
    unsigned long len;

    // 節(jié)點(diǎn)值復(fù)制函數(shù)
    void *(*dup)(void *ptr);

    // 節(jié)點(diǎn)值釋放函數(shù)
    void (*free)(void *ptr);

    // 節(jié)點(diǎn)值對(duì)比函數(shù)
    int (*match)(void *ptr, void *key);

} list;

list 結(jié)構(gòu)為鏈表提供了表頭指針 head 、表尾指針 tail , 以及鏈表長(zhǎng)度計(jì)數(shù)器 len , 而 dup 、 free 和 match 成員則是用于實(shí)現(xiàn)多態(tài)鏈表所需的類型特定函數(shù):

  • dup 函數(shù)用于復(fù)制鏈表節(jié)點(diǎn)所保存的值;
  • free 函數(shù)用于釋放鏈表節(jié)點(diǎn)所保存的值;
  • match 函數(shù)則用于對(duì)比鏈表節(jié)點(diǎn)所保存的值和另一個(gè)輸入值是否相等。

圖 3-2 是由一個(gè) list 結(jié)構(gòu)和三個(gè) listNode 結(jié)構(gòu)組成的鏈表:

Paste_Image.png

Redis 的鏈表實(shí)現(xiàn)的特性可以總結(jié)如下:

  • 雙端: 鏈表節(jié)點(diǎn)帶有 prev 和 next 指針, 獲取某個(gè)節(jié)點(diǎn)的前置節(jié)點(diǎn)和后置節(jié)點(diǎn)的復(fù)雜度都是 O(1) 。
  • 無環(huán): 表頭節(jié)點(diǎn)的 prev 指針和表尾節(jié)點(diǎn)的 next 指針都指向 NULL , 對(duì)鏈表的訪問以 NULL 為終點(diǎn)。
  • 帶表頭指針和表尾指針: 通過 list 結(jié)構(gòu)的 head 指針和 tail 指針, 程序獲取鏈表的表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)的復(fù)雜度為 O(1) 。
  • 帶鏈表長(zhǎng)度計(jì)數(shù)器: 程序使用 list 結(jié)構(gòu)的 len 屬性來對(duì) list 持有的鏈表節(jié)點(diǎn)進(jìn)行計(jì)數(shù), 程序獲取鏈表中節(jié)點(diǎn)數(shù)量的復(fù)雜度為 O(1) 。
  • 多態(tài): 鏈表節(jié)點(diǎn)使用 void* 指針來保存節(jié)點(diǎn)值, 并且可以通過 list 結(jié)構(gòu)的 dup 、 free 、 match 三個(gè)屬性為節(jié)點(diǎn)值設(shè)置類型特定函數(shù), 所以鏈表可以用于保存各種不同類型的值。

壓縮列表

壓縮列表(ziplist)是列表鍵和哈希鍵的底層實(shí)現(xiàn)之一。

當(dāng)一個(gè)列表鍵只包含少量列表項(xiàng), 并且每個(gè)列表項(xiàng)要么就是小整數(shù)值, 要么就是長(zhǎng)度比較短的字符串, 那么 Redis 就會(huì)使用壓縮列表來做列表鍵的底層實(shí)現(xiàn)。

比如說, 執(zhí)行以下命令將創(chuàng)建一個(gè)壓縮列表實(shí)現(xiàn)的列表鍵:

redis> RPUSH lst 1 3 5 10086 "hello" "world"
(integer) 6

redis> OBJECT ENCODING lst
"ziplist"

壓縮列表的構(gòu)成

壓縮列表是 Redis 為了節(jié)約內(nèi)存而開發(fā)的, 由一系列特殊編碼的連續(xù)內(nèi)存塊組成的順序型(sequential)數(shù)據(jù)結(jié)構(gòu)。

一個(gè)壓縮列表可以包含任意多個(gè)節(jié)點(diǎn)(entry), 每個(gè)節(jié)點(diǎn)可以保存一個(gè)字節(jié)數(shù)組或者一個(gè)整數(shù)值。

圖 7-1 展示了壓縮列表的各個(gè)組成部分, 表 7-1 則記錄了各個(gè)組成部分的類型、長(zhǎng)度、以及用途。

Paste_Image.png
屬性 類型 長(zhǎng)度 用途
zlbytes uint32_t 4 字節(jié) 記錄整個(gè)壓縮列表占用的內(nèi)存字節(jié)數(shù):在對(duì)壓縮列表進(jìn)行內(nèi)存重分配, 或者計(jì)算 zlend 的位置時(shí)使用。
zltail uint32_t 4 字節(jié) 記錄壓縮列表表尾節(jié)點(diǎn)距離壓縮列表的起始地址有多少字節(jié): 通過這個(gè)偏移量,程序無須遍歷整個(gè)壓縮列表就可以確定表尾節(jié)點(diǎn)的地址。
zllen uint16_t 2 字節(jié) 記錄了壓縮列表包含的節(jié)點(diǎn)數(shù)量: 當(dāng)這個(gè)屬性的值小于 UINT16_MAX (65535)時(shí), 這個(gè)屬性的值就是壓縮列表包含節(jié)點(diǎn)的數(shù)量; 當(dāng)這個(gè)值等于 UINT16_MAX 時(shí), 節(jié)點(diǎn)的真實(shí)數(shù)量需要遍歷整個(gè)壓縮列表才能計(jì)算得出。
entryX 列表節(jié)點(diǎn) 不定 壓縮列表包含的各個(gè)節(jié)點(diǎn),節(jié)點(diǎn)的長(zhǎng)度由節(jié)點(diǎn)保存的內(nèi)容決定。
zlend uint8_t 1字節(jié) 特殊值 0xFF (十進(jìn)制 255 ),用于標(biāo)記壓縮列表的末端。

圖 7-2 展示了一個(gè)壓縮列表示例:

  • 列表 zlbytes 屬性的值為 0x50 (十進(jìn)制 80), 表示壓縮列表的總長(zhǎng)為 80 字節(jié)。
  • 列表 zltail 屬性的值為 0x3c (十進(jìn)制 60), 這表示如果我們有一個(gè)指向壓縮列表起始地址的指針 p , 那么只要用指針 p 加上偏移量 60 , 就可以計(jì)算出表尾節(jié)點(diǎn) entry3 的地址。
  • 列表 zllen 屬性的值為 0x3 (十進(jìn)制 3), 表示壓縮列表包含三個(gè)節(jié)點(diǎn)。
Paste_Image.png

圖 7-3 展示了另一個(gè)壓縮列表示例:

  • 列表 zlbytes 屬性的值為 0xd2 (十進(jìn)制 210), 表示壓縮列表的總長(zhǎng)為 210 字節(jié)。
  • 列表 zltail 屬性的值為 0xb3 (十進(jìn)制 179), 這表示如果我們有一個(gè)指向壓縮列表起始地址的指針 p , 那么只要用指針 p 加上偏移量 179 , 就可以計(jì)算出表尾節(jié)點(diǎn) entry5 的地址。
  • 列表 zllen 屬性的值為 0x5 (十進(jìn)制 5), 表示壓縮列表包含五個(gè)節(jié)點(diǎn)。
Paste_Image.png

壓縮列表節(jié)點(diǎn)的構(gòu)成

每個(gè)壓縮列表節(jié)點(diǎn)可以保存一個(gè)字節(jié)數(shù)組或者一個(gè)整數(shù)值, 其中, 字節(jié)數(shù)組可以是以下三種長(zhǎng)度的其中一種:

  1. 長(zhǎng)度小于等于 63 (2^{6}-1)字節(jié)的字節(jié)數(shù)組;

  2. 長(zhǎng)度小于等于 16383 (2^{14}-1) 字節(jié)的字節(jié)數(shù)組;

  3. 長(zhǎng)度小于等于 4294967295 (2^{32}-1)字節(jié)的字節(jié)數(shù)組;
    而整數(shù)值則可以是以下六種長(zhǎng)度的其中一種:

  4. 4 位長(zhǎng),介于 0 至 12 之間的無符號(hào)整數(shù);

  5. 1 字節(jié)長(zhǎng)的有符號(hào)整數(shù);

  6. 3 字節(jié)長(zhǎng)的有符號(hào)整數(shù);

  7. int16_t 類型整數(shù);

  8. int32_t 類型整數(shù);

  9. int64_t 類型整數(shù)。
    每個(gè)壓縮列表節(jié)點(diǎn)都由 previous_entry_length 、 encoding 、 content 三個(gè)部分組成, 如圖 7-4 所示。

Paste_Image.png

接下來的內(nèi)容將分別介紹這三個(gè)組成部分。

previous_entry_length

節(jié)點(diǎn)的 previous_entry_length 屬性以字節(jié)為單位, 記錄了壓縮列表中前一個(gè)節(jié)點(diǎn)的長(zhǎng)度。

previous_entry_length 屬性的長(zhǎng)度可以是 1 字節(jié)或者 5 字節(jié):

  • 如果前一節(jié)點(diǎn)的長(zhǎng)度小于 254 字節(jié), 那么 previous_entry_length 屬性的長(zhǎng)度為 1 字節(jié): 前一節(jié)點(diǎn)的長(zhǎng)度就保存在這一個(gè)字節(jié)里面。
  • 如果前一節(jié)點(diǎn)的長(zhǎng)度大于等于 254 字節(jié), 那么 previous_entry_length 屬性的長(zhǎng)度為 5 字節(jié): 其中屬性的第一字節(jié)會(huì)被設(shè)置為 0xFE (十進(jìn)制值 254), 而之后的四個(gè)字節(jié)則用于保存前一節(jié)點(diǎn)的長(zhǎng)度。

圖 7-5 展示了一個(gè)包含一字節(jié)長(zhǎng) previous_entry_length 屬性的壓縮列表節(jié)點(diǎn), 屬性的值為 0x05 , 表示前一節(jié)點(diǎn)的長(zhǎng)度為 5 字節(jié)。

Paste_Image.png

圖 7-6 展示了一個(gè)包含五字節(jié)長(zhǎng) previous_entry_length 屬性的壓縮節(jié)點(diǎn), 屬性的值為 0xFE00002766 , 其中值的最高位字節(jié) 0xFE 表示這是一個(gè)五字節(jié)長(zhǎng)的 previous_entry_length 屬性, 而之后的四字節(jié) 0x00002766 (十進(jìn)制值 10086 )才是前一節(jié)點(diǎn)的實(shí)際長(zhǎng)度。

Paste_Image.png

因?yàn)楣?jié)點(diǎn)的 previous_entry_length 屬性記錄了前一個(gè)節(jié)點(diǎn)的長(zhǎng)度, 所以程序可以通過指針運(yùn)算, 根據(jù)當(dāng)前節(jié)點(diǎn)的起始地址來計(jì)算出前一個(gè)節(jié)點(diǎn)的起始地址。

舉個(gè)例子, 如果我們有一個(gè)指向當(dāng)前節(jié)點(diǎn)起始地址的指針 c , 那么我們只要用指針 c 減去當(dāng)前節(jié)點(diǎn) previous_entry_length 屬性的值, 就可以得出一個(gè)指向前一個(gè)節(jié)點(diǎn)起始地址的指針 p , 如圖 7-7 所示。

Paste_Image.png

壓縮列表的從表尾向表頭遍歷操作就是使用這一原理實(shí)現(xiàn)的: 只要我們擁有了一個(gè)指向某個(gè)節(jié)點(diǎn)起始地址的指針, 那么通過這個(gè)指針以及這個(gè)節(jié)點(diǎn)的 previous_entry_length 屬性, 程序就可以一直向前一個(gè)節(jié)點(diǎn)回溯, 最終到達(dá)壓縮列表的表頭節(jié)點(diǎn)。

圖 7-8 展示了一個(gè)從表尾節(jié)點(diǎn)向表頭節(jié)點(diǎn)進(jìn)行遍歷的完整過程:

  • 首先,我們擁有指向壓縮列表表尾節(jié)點(diǎn) entry4 起始地址的指針 p1 (指向表尾節(jié)點(diǎn)的指針可以通過指向壓縮列表起始地址的指針加上 zltail 屬性的值得出);
  • 通過用 p1 減去 entry4 節(jié)點(diǎn) previous_entry_length 屬性的值, 我們得到一個(gè)指向 entry4 前一節(jié)點(diǎn) entry3 起始地址的指針 p2 ;
  • 通過用 p2 減去 entry3 節(jié)點(diǎn) previous_entry_length 屬性的值, 我們得到一個(gè)指向 entry3 前一節(jié)點(diǎn) entry2 起始地址的指針 p3 ;
  • 通過用 p3 減去 entry2 節(jié)點(diǎn) previous_entry_length 屬性的值, 我們得到一個(gè)指向 entry2 前一節(jié)點(diǎn) entry1 起始地址的指針 p4 , entry1 為壓縮列表的表頭節(jié)點(diǎn);
  • 最終, 我們從表尾節(jié)點(diǎn)向表頭節(jié)點(diǎn)遍歷了整個(gè)列表。
Paste_Image.png
Paste_Image.png
Paste_Image.png
Paste_Image.png

encoding

節(jié)點(diǎn)的 encoding 屬性記錄了節(jié)點(diǎn)的 content 屬性所保存數(shù)據(jù)的類型以及長(zhǎng)度:

  • 一字節(jié)、兩字節(jié)或者五字節(jié)長(zhǎng), 值的最高位為 00 、 01 或者 10 的是字節(jié)數(shù)組編碼: 這種編碼表示節(jié)點(diǎn)的 content 屬性保存著字節(jié)數(shù)組, 數(shù)組的長(zhǎng)度由編碼除去最高兩位之后的其他位記錄;
  • 一字節(jié)長(zhǎng), 值的最高位以 11 開頭的是整數(shù)編碼: 這種編碼表示節(jié)點(diǎn)的 content 屬性保存著整數(shù)值, 整數(shù)值的類型和長(zhǎng)度由編碼除去最高兩位之后的其他位記錄;

表 7-2 記錄了所有可用的字節(jié)數(shù)組編碼, 而表 7-3 則記錄了所有可用的整數(shù)編碼。 表格中的下劃線 _ 表示留空, 而 b 、 x 等變量則代表實(shí)際的二進(jìn)制數(shù)據(jù), 為了方便閱讀, 多個(gè)字節(jié)之間用空格隔開。


表 7-2 字節(jié)數(shù)組編碼

編碼 編碼長(zhǎng)度 content 屬性保存的值
00bbbbbb 1 字節(jié) 長(zhǎng)度小于等于 63 字節(jié)的字節(jié)數(shù)組。
01bbbbbb xxxxxxxx 2 字節(jié) 長(zhǎng)度小于等于 16383 字節(jié)的字節(jié)數(shù)組。
10______ aaaaaaaa bbbbbbbb cccccccc dddddddd 5 字節(jié) 長(zhǎng)度小于等于 4294967295 的字節(jié)數(shù)組。

表 7-3 整數(shù)編碼

編碼 編碼長(zhǎng)度 content 屬性保存的值
11000000 1 字節(jié) int16_t 類型的整數(shù)。
11010000 1 字節(jié) int32_t 類型的整數(shù)。
11100000 1 字節(jié) int64_t 類型的整數(shù)。
11110000 1 字節(jié) 24 位有符號(hào)整數(shù)。
11111110 1 字節(jié) 8 位有符號(hào)整數(shù)。
1111xxxx 1 字節(jié) 使用這一編碼的節(jié)點(diǎn)沒有相應(yīng)的 content 屬性, 因?yàn)榫幋a本身的 xxxx 四個(gè)位已經(jīng)保存了一個(gè)介于 0 和 12 之間的值, 所以它無須 content 屬性。

content

節(jié)點(diǎn)的 content 屬性負(fù)責(zé)保存節(jié)點(diǎn)的值, 節(jié)點(diǎn)值可以是一個(gè)字節(jié)數(shù)組或者整數(shù), 值的類型和長(zhǎng)度由節(jié)點(diǎn)的 encoding 屬性決定。

圖 7-9 展示了一個(gè)保存字節(jié)數(shù)組的節(jié)點(diǎn)示例:

  • 編碼的最高兩位 00 表示節(jié)點(diǎn)保存的是一個(gè)字節(jié)數(shù)組;
  • 編碼的后六位 001011 記錄了字節(jié)數(shù)組的長(zhǎng)度 11 ;
  • content 屬性保存著節(jié)點(diǎn)的值 "hello world" 。
Paste_Image.png

圖 7-10 展示了一個(gè)保存整數(shù)值的節(jié)點(diǎn)示例:

  • 編碼 11000000 表示節(jié)點(diǎn)保存的是一個(gè) int16_t 類型的整數(shù)值;
  • content 屬性保存著節(jié)點(diǎn)的值 10086 。
Paste_Image.png

參考

Redis 設(shè)計(jì)與實(shí)現(xiàn)
Redis 命令參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,772評(píng)論 3 422
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,947評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,960評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,350評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,549評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,104評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,914評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,089評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,340評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,834評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,106評(píng)論 2 375

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