Redis常用數(shù)據(jù)結(jié)構(gòu)及應(yīng)用場景

1. 概述

Redis 一個開源的基于鍵值對(Key-Value)NoSQL 數(shù)據(jù)庫社搅。使用 ANSIC 語言編寫、支持網(wǎng)絡(luò)乳规、基于內(nèi)存但支持持久化形葬。性能優(yōu)秀,并提供多種語言的 API驯妄。

我們要首先理解一點(diǎn)荷并,我們把 Redis 稱為 KV 數(shù)據(jù)庫,鍵值對數(shù)據(jù)庫青扔,那就可以把 Redis 內(nèi)部的存儲視為存在著一個巨大的 Map源织,對 Map 的操作無非就是get 和 put,然后通過 key 操作這個 key 所對應(yīng)的 value微猖,而這個 value 的類型可以多種多樣谈息,也就是 Redis 為我們提供的那些數(shù)據(jù)結(jié)構(gòu),比如字符串(String)凛剥、哈希(Hash)等等侠仇。

Redis 會將所有數(shù)據(jù)都存放在內(nèi)存中,所以它的讀寫性能非常驚人犁珠。不僅如此逻炊,Redis 還可以將內(nèi)存的數(shù)據(jù)利用快照和日志的形式保存到硬盤上,這樣在發(fā)生類似斷電或者機(jī)器故障的時候犁享,內(nèi)存中的數(shù)據(jù)不會丟失余素。

除了上述功能以外,Redis 還提供了鍵過期炊昆、發(fā)布訂閱桨吊、事務(wù)威根、流水線、Lua 腳本等附加功能视乐。

1.1 應(yīng)用場景

緩存

緩存機(jī)制幾乎在所有的大型網(wǎng)站都有使用洛搀,合理地使用緩存不僅可以加快數(shù)據(jù)的訪問速度,而且能夠有效地降低后端數(shù)據(jù)源的壓力佑淀。Redis 提供了鍵值過期時間設(shè)置留美,并且也提供了靈活控制最大內(nèi)存和內(nèi)存溢出后的淘汰策略≡郏可以這么說独榴,一個合理的緩存設(shè)計(jì)能夠?yàn)橐粋€網(wǎng)站的穩(wěn)定保駕護(hù)航。

排行榜系統(tǒng)

排行榜系統(tǒng)幾乎存在于所有的網(wǎng)站奕枝,例如按照熱度排名的排行榜,按照發(fā)布時間的排行榜瓶堕,按照各種復(fù)雜維度計(jì)算出的排行榜隘道,Redis 提供了列表和有序集合數(shù)據(jù)結(jié)構(gòu),合理地使用這些數(shù)據(jù)結(jié)構(gòu)可以很方便地構(gòu)建各種排行榜系統(tǒng)郎笆。

計(jì)數(shù)器應(yīng)用

計(jì)數(shù)器在網(wǎng)站中的作用至關(guān)重要谭梗,例如視頻網(wǎng)站有播放數(shù)、電商網(wǎng)站有瀏覽數(shù)宛蚓,為了保證數(shù)據(jù)的實(shí)時性激捏,每一次播放和瀏覽都要做+1 的操作,如果并發(fā)量很大對于傳統(tǒng)關(guān)系型數(shù)據(jù)的性能是一種挑戰(zhàn)凄吏。Redis 天然支持計(jì)數(shù)功能而且計(jì)數(shù)的性能也非常好远舅,可以說是計(jì)數(shù)器系統(tǒng)的重要選擇。

社交網(wǎng)絡(luò)

贊/踩痕钢、粉絲图柏、共同好友/喜好、推送任连、下拉刷新等是社交網(wǎng)站的必備功能蚤吹,由于社交網(wǎng)站訪問量通常比較大,而且傳統(tǒng)的關(guān)系型數(shù)據(jù)不太適合保存這種類型的數(shù)據(jù)随抠,Redis 提供的數(shù)據(jù)結(jié)構(gòu)可以相對比較容易地實(shí)現(xiàn)這些功能裁着。

消息隊(duì)列系統(tǒng)

消息隊(duì)列系統(tǒng)可以說是一個大型網(wǎng)站的必備基礎(chǔ)組件,因?yàn)槠渚哂袠I(yè)務(wù)解耦拱她、 非實(shí)時業(yè)務(wù)削峰等特性二驰。Redis 提供了發(fā)布訂閱功能和阻塞隊(duì)列的功能,雖然和專業(yè)的消息隊(duì)列比還不夠足夠強(qiáng)大椭懊,但是對于一般的消息隊(duì)列功能基本可以滿足诸蚕。(我上家公司就用過 Redis 做消息隊(duì)列步势,雖然后面換了其他 MQ)。

1.2 特性

速度快

正常情況下背犯,Redis 執(zhí)行命令的速度非郴荡瘢快,官方給出的數(shù)字是讀寫性能可以達(dá)到 10 萬/秒漠魏。

基于鍵值對的數(shù)據(jù)結(jié)構(gòu)服務(wù)器

幾乎所有的編程語言都提供了類似字典的功能倔矾,例如 Java 里的 map,類似于這種組織數(shù)據(jù)的方式叫作基于鍵值的方式柱锹,與很多鍵值對數(shù)據(jù)庫不同的是哪自,Redis 中的值不僅可以是字符串,而且還可以是具體的數(shù)據(jù)結(jié)構(gòu)禁熏,這樣不僅能便于在許多應(yīng)用場景的開發(fā)壤巷,同時也能夠提高開發(fā)效率。

Redis 的全稱是 Remote Dictionary Server瞧毙,它主要提供了 5 種數(shù)據(jù)結(jié)構(gòu):字符串胧华、哈希、列表宙彪、集合矩动、有序集合,同時在字符串的基礎(chǔ)之上演變出了位圖(Bitmaps)和 HyperLogLog 兩種數(shù)據(jù)結(jié)構(gòu)释漆,并且隨著 LBS (Location BasedService悲没,基于位置服務(wù))的不斷發(fā)展,Redis 中加入有關(guān) GEO(地理信息定位)的功能男图。

豐富的功能

除了 5 種數(shù)據(jù)結(jié)構(gòu)示姿,Redis 還提供了許多額外的功能:提供了鍵過期功能,可以用來實(shí)現(xiàn)緩存享言。

提供了發(fā)布訂閱功能峻凫,可以用來實(shí)現(xiàn)消息系統(tǒng)。支持 Lua 腳本功能览露,可以利用 Lua 創(chuàng)造出新的 Redis 命令荧琼。提供了簡單的事務(wù)功能,能在一定程度上保證事務(wù)特性差牛。提供了流水線(Pipeline)功能命锄,這樣客戶端能將一批命令一次性傳到 Redis,減少了網(wǎng)絡(luò)的開銷偏化。

簡單穩(wěn)定

Redis 的簡單主要表現(xiàn)在三個方面脐恩。

首先,Redis 的源碼很少侦讨,早期版本的代碼只有 2 萬行左右驶冒,3.0 版本以后由于添加了集群特性苟翻,代碼增至 5 萬行左右。

其次骗污,Redis 使用單線程模型崇猫,這樣不僅使得 Redis 服務(wù)端處理模型變得簡單,而且也使得客戶端開發(fā)變得簡單需忿。

最后诅炉,Redis 不需要依賴于操作系統(tǒng)中的類庫。

Redis 雖然很簡單屋厘,但是不代表它不穩(wěn)定涕烧。實(shí)際的運(yùn)行中很少出現(xiàn)因?yàn)?Redis 自身 bug 而宕掉的情況。

客戶端語言多

Redis 提供了簡單的 TCP 通信協(xié)議汗洒,很多編程語言可以很方便地接人到 Redis议纯。

持久化

通常看仲翎,將數(shù)據(jù)放在內(nèi)存中是不安全的痹扇,一旦發(fā)生斷電或者機(jī)器故障,重要的數(shù)據(jù)可能就會丟失溯香,因此Redis提供了兩種持久化方式:RDB 和 AOF,即可以用兩種策略將內(nèi)存的數(shù)據(jù)保存到硬盤中浓恶,這樣就保證了數(shù)據(jù)的可持久性玫坛。

主從復(fù)制

Redis 提供了復(fù)制功能,實(shí)現(xiàn)了多個相同數(shù)據(jù)的 Redis 副本,復(fù)制功能是分布式Redis 的基礎(chǔ)包晰。

高可用和分布式

Redis Sentinel湿镀,它能夠保證 Redis 節(jié)點(diǎn)的故障發(fā)現(xiàn)和故障自動轉(zhuǎn)移。Redis 從 3.0 版本正式提供了分布式實(shí)現(xiàn) Redis Cluster伐憾,它是 Redis 真正的分布式實(shí)現(xiàn)勉痴,提供了高可用、讀寫和容量的擴(kuò)展性树肃。

2. 下載安裝

目前演示的安裝操作是基于 Centos7 下講解蒸矛。

官方下載地址:https://redis.io/download

# 先新建一個目錄
mkdir /usr/local/redis
cd /usr/local/redis
# 下載
wget https://download.redis.io/releases/redis-6.2.6.tar.gz
# 解壓
tar -xzvf redis-6.2.6.tar.gz
# 編譯
cd redis-6.2.6
make

2.1 啟動

Redis 有三種方法啟動 Redis:默認(rèn)配置、帶參數(shù)啟動胸嘴、配置文件啟動雏掠。

1?? 默認(rèn)配置

進(jìn)入安裝好的 Redis 的 src目錄下執(zhí)行以下命令:

./redis-server 
image-20211215203151805

可以看到直接使用 redis-server 啟動 Redis 后,會打印出一些日志劣像,通過日志 可以看到一些信息:

當(dāng)前的 Redis 版本的是 64 位的 6.2.6乡话,默認(rèn)端口是 6379。Redis 建議使用配置文件來啟動耳奕,所以這種方式是不會在生產(chǎn)環(huán)境中使用的绑青。

2?? 參數(shù)啟動

redis-server 加上要修改配置名和值(可以是多對)诬像,沒有設(shè)置的配置將使用默認(rèn)配置,例如:如果要用 6380 作為端口啟動 Redis闸婴,那么可以執(zhí)行:

./redis-server --port 6380 
image-20211215203128782

不過這種方式一般也用得比較少坏挠。

3?? 配置文件啟動

將配置寫到指定文件里,并啟動掠拳,主要修改的是安裝目錄下的 redis.conf文件癞揉。

./redis-server ../redis.conf
image-20211215203701640

2.2 操作

Redis 服務(wù)啟動完成后,就可以使用 redis-cli 連接和操作 Redis 服務(wù)溺欧。

image-20211215203942143

2.3 停止

Redis 提供了 shutdown 命令來停止 Redis 服務(wù)喊熟,例如我們目前已經(jīng)啟動的 Redis 服務(wù),可以執(zhí)行:

./redis-cli -p 6379 shutdown 

Redis 服務(wù)端將會顯示:

2853:M 15 Dec 2021 20:41:26.593 # User requested shutdown...
2853:M 15 Dec 2021 20:41:26.593 * Saving the final RDB snapshot before exiting.
2853:M 15 Dec 2021 20:41:26.594 * DB saved on disk
2853:M 15 Dec 2021 20:41:26.594 * Removing the pid file.
2853:M 15 Dec 2021 20:41:26.594 # Redis is now ready to exit, bye bye...

除了可以通過 shutdown 命令關(guān)閉 Redis 服務(wù)以外姐刁,還可以通過 kill 進(jìn)程號的方式關(guān)閉掉 Redis芥牌,但是強(qiáng)烈不建議使用 kill -9 強(qiáng)制殺死 Redis 服務(wù),不但不會做持久化操作聂使,還會造成緩沖區(qū)等資源不能被優(yōu)雅關(guān)閉壁拉,極端情況會造成 AOF 和復(fù)制丟失數(shù)據(jù)的情況。

shutdown 還有一個參數(shù)柏靶,代表是否在關(guān)閉 Redis 前弃理,生成持久化文件:

./redis-cli -p 6379 shutdown nosave/save

默認(rèn)是 save,生成持久化文件屎蜓,如果是 nosave 則不生成持久化文件痘昌。

3. 全局命令

在了解 Redis 的數(shù)據(jù)結(jié)構(gòu)之前,先了解 Redis 的一些全局命令炬转。

命令 說明
keys * 查看所有鍵辆苔,同時也支持通配符,如 keys n*
dbsize 返回當(dāng)前數(shù)據(jù)庫中鍵的總數(shù)
exists 檢查鍵是否存在扼劈,存在返回 1驻啤,不存在返回 0,如 exists name
del 刪除鍵荐吵,無論值是什么數(shù)據(jù)結(jié)構(gòu)類型骑冗,del 命令都可以將其刪除。返回刪除鍵個數(shù)捍靠,刪除不存在鍵返回 0沐旨。同時 del 命令可以支持刪除多個鍵,如 del name age
expire Redis 支持對鍵添加過期時間榨婆,當(dāng)超過過期時間后磁携,會自動刪除鍵,時間單位秒良风,如 expire name 10
ttl ttl 命令會返回鍵的剩余過期時間谊迄,若返回 -1 則表示鍵沒設(shè)置過期時間闷供,-2 鍵不存在
type 返回鍵的數(shù)據(jù)結(jié)構(gòu)類型
randomkey 隨機(jī)返回一個鍵
rename 鍵重命名,為了防止被強(qiáng)行 rename统诺,Redis 提供了 renamenx 命令歪脏,確保只有 newKey 不存在時候才被覆蓋。由于重命名鍵期間會執(zhí)行 del 命令刪除舊的鍵粮呢,如果鍵對應(yīng)的值比較大婿失,會存在阻塞 Redis 的可能性

注:

  1. dbsize 命令在計(jì)算鍵總數(shù)時不會遍歷所有鍵,而是直接獲取 Redis 內(nèi)置的鍵總數(shù)變量,所以 dbsize 命令的時間復(fù)雜度是 O(1)。而 keys 命令會遍歷所有鍵啄寡,所以它的時間復(fù)雜度是 o(n)豪硅,當(dāng) Redis 保存了大量鍵時線上環(huán)境禁止使用 keys 命令;
  2. 除了 expire挺物、ttl 命令以外懒浮,Redis 還提供了 expireat、pexpire,pexpireat识藤、pttl砚著、persist 等一系列命令,可自行查驗(yàn)痴昧。

4. 基本數(shù)據(jù)結(jié)構(gòu)

Redis 提供了一些數(shù)據(jù)結(jié)構(gòu)供我們往 Redis 中存取數(shù)據(jù)稽穆,最常用的的有 5 種,字符串(String)赶撰、哈希(Hash)秧骑、列表(list)、集合(set)扣囊、有序集合(ZSET)。

4.1 String

字符串類型是 Redis 最基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)绒疗。首先鍵都是字符串類型侵歇,而且其他幾種數(shù)據(jù)結(jié)構(gòu)都是在字符串類型基礎(chǔ)上構(gòu)建的,所以字符串類型能為其他四種數(shù)據(jù)結(jié)構(gòu)的學(xué)習(xí)奠定基礎(chǔ)吓蘑。字符串類型的值實(shí)際可以是字符串(簡單的字符串惕虑、復(fù) 雜的字符串(例如 JSON、XML))磨镶、數(shù)字(整數(shù)溃蔫、浮點(diǎn)數(shù)),甚至是二進(jìn)制(圖片琳猫、音頻伟叛、視頻),但是值最大不能超過 512 MB脐嫂。

1?? 常用命令

設(shè)置值 set

set key value [ex seconds] [px milliseconds] [nxlxx]
  • ex seconds:為鍵設(shè)置秒級過期時間统刮。
  • px milliseconds:為鍵設(shè)置毫秒級過期時間紊遵。
  • nx:鍵必須不存在,才可以設(shè)置成功侥蒙,用于添加暗膜。
  • xx:與 nx 相反,鍵必須存在鞭衩,才可以設(shè)置成功学搜,用于更新。

其中论衍,ex 參數(shù)和 expire 命令基本一樣瑞佩。還有一個需要特別注意的地方是如果一個字符串已經(jīng)設(shè)置了過期時間,然后你調(diào)用了 set 方法修改了它饲齐,它的過期時間會消失钉凌。

除了 set 選項(xiàng),Redis 還提供了 setex 和 setnx 兩個命令:

  • setex key seconds value
  • setnx key value

setex 和 setnx 的作用和 ex 和 nx 選項(xiàng)是一樣的捂人。也就是御雕,setex 為鍵設(shè)置秒級過期時間,setnx 設(shè)置時鍵必須不存在滥搭,才可以設(shè)置成功酸纲。

有什么應(yīng)用場景嗎?

以 setnx 命令為例子,由于 Redis 的單線程命令處理機(jī)制瑟匆,如果有多個客戶端同時執(zhí)行 setnx key value闽坡,根據(jù) setnx 的特性只有一個客戶端能設(shè)置成功,setnx 可以作為分布式鎖的一種實(shí)現(xiàn)方案愁溜。

獲取值 get

get key

如果要獲取的鍵不存在疾嗅,則返回 nil

另外冕象,除了單個設(shè)置和獲取鍵值代承,Redis 還支持批量操作。

批量設(shè)置值 mset

mset name ayue age 20 sex 男

批量獲取值 mget

mget name age sex
image-20211216163431238

如果有些鍵不存在渐扮,那么它的值為 nil论悴,結(jié)果是按照傳入鍵的順序返回。

批量操作命令可以有效提高效率墓律,假如沒有 mget 這樣的命令膀估,要執(zhí)行 n 次 get 命令具體耗時如下:

n 次 get 時間 = n 次網(wǎng)絡(luò)時間 + n 次命令時間

使用 mget 命令后,要執(zhí)行 n 次 get 命令操作具體耗時如下:

n 次 get 時間 = 1 次網(wǎng)絡(luò)時間 + n 次命令時間

Redis 可以支撐每秒數(shù)萬的讀寫操作耻讽,但是這指的是 Redis 服務(wù)端的處理能力察纯,對于客戶端來說,一次命令除了命令時間還是有網(wǎng)絡(luò)時間,假設(shè)網(wǎng)絡(luò)時間為 1 毫秒捐寥,命令時間為 0.1 毫秒(按照每秒處理 1 萬條命令算)笤昨,那么執(zhí)行 1000 次 get 命令需要 1.1 秒(1000*1+1000*0.1=1100ms),1 次 mget 命令的需要 0.101 秒 (1*1+1000*0.1=101ms)握恳。

數(shù)字運(yùn)算 incr

incr 命令用于對值做自增操作瞒窒,返回結(jié)果分為三種情況:

  1. 值不是整數(shù),返回錯誤乡洼;
  2. 值是整數(shù)崇裁,返回自增后的結(jié)果;
  3. 鍵不存在束昵,按照值為 0 自增,返回結(jié)果為 1拔稳。
incr key
image-20211216170800153

除了 incr 命令,Redis 提供了 decr(自減)锹雏、 incrby(自增指定數(shù)字)巴比、decrby(自減指定數(shù)字)、incrbyfloat(自增浮點(diǎn)數(shù))礁遵。

追加指令 append

append 可以向字符串尾部追加值轻绞。

append key value
image-20211216171635960

strlen

返回字符串長度。

strlen key

截取字符串 getrange

getrange 截取字符串中的一部分佣耐,形成一個子串政勃,需要指明開始和結(jié)束的偏移量,截取的范圍是個閉區(qū)間兼砖。

image-20211216171944312
命令 說明 時間復(fù)雜度
get key 獲取值 O(1)
del key [key ...] 刪除key O(N)(N是鍵的個數(shù))
mset key [key value ...] 批量設(shè)置值 O(N)(N是鍵的個數(shù))
mget key [key ...] 批量獲取值 O(N)(N是鍵的個數(shù))
incr key 將 key 中儲存的數(shù)字值增一 O(1)
decr key 將 key 中儲存的數(shù)字值減一 O(1)
incrby key increment 將 key 所儲存的值加上給定的增量值(increment) O(1)
decrby key increment key 所儲存的值減去給定的減量值(decrement) O(1)
incrbyfloat key increment 將 key 所儲存的值加上給定的浮點(diǎn)增量值(increment) O(1)
append key value 如果 key 已經(jīng)存在并且是一個字符串奸远, APPEND 命令將指定的 value 追加到該 key 原來值(value)的末尾 O(1)
strlen key 返回 key 所儲存的字符串值的長度。 O(1)
setrange key offset value 用 value 參數(shù)覆寫給定 key 所儲存的字符串值讽挟,從偏移量 offset 開始 O(1)
getrange key start end 返回 key 中字符串值的子字符 O(N)(N是字符串的長度)

2?? 命令的時間復(fù)雜度

字符串這些命令中懒叛,除了 del 、mset耽梅、 mget 支持多個鍵的批量操作芍瑞,時間復(fù)雜度和鍵的個數(shù)相關(guān),為 O(n)褐墅,getrange 和字符串長度相關(guān),也是 O(n)洪己,其余的命令基本上都是 O(1)的時間復(fù)雜度妥凳,在速度上是非常快的答捕。

3?? 使用場景

字符串類型的使用場景很廣泛逝钥,如下:

1、緩存功能

Redis 作為緩存層,MySQL 作為存儲層艘款,絕大部分請求的數(shù)據(jù)都是從 Redis 中獲取持际。由于 Redis 具有支撐高并發(fā)的特性,所以緩存通常能起到加速讀寫和降低 后端壓力的作用哗咆。

2蜘欲、計(jì)數(shù)

使用 Redis 作為計(jì)數(shù)的基礎(chǔ)工具,它可以實(shí)現(xiàn)快速計(jì)數(shù)晌柬、查詢緩存的功能姥份,同時數(shù)據(jù)可以異步落地到其他數(shù)據(jù)源。

3年碘、共享 Session

一個分布式 Web 服務(wù)將用戶的 Session 信息(例如用戶登錄信息)保存在各 自服務(wù)器中澈歉,這樣會造成一個問題,出于負(fù)載均衡的考慮屿衅,分布式服務(wù)會將用戶的訪問均衡到不同服務(wù)器上埃难,用戶刷新一次訪問可能會發(fā)現(xiàn)需要重新登錄,這個問題是用戶無法容忍的涤久。

為了解決這個問題涡尘, 可以使用 Redis 將用戶的 Session 進(jìn)行集中管理,在這種模式下只要保證 Redis 是高可用和擴(kuò)展性的拴竹,每次用戶更新或者查詢登錄信息都直接從 Redis 中集中獲取悟衩。

4、限時

很多應(yīng)用出于安全的考慮栓拜,會在每次進(jìn)行登錄時座泳,讓用戶輸入手機(jī)驗(yàn)證碼,從而確定是否是用戶本人幕与。但是為了短信接口不被頻繁訪問挑势,會限制用戶每分鐘獲取驗(yàn)證碼的頻率,例如一分鐘不能超過 5 次啦鸣。一些網(wǎng)站限制一個 IP 地址不能在一秒鐘之內(nèi)訪問超過 n 次潮饱。或者同一 IP 在短時間內(nèi)多次瀏覽謀篇文章瀏覽次數(shù)不會一直增加诫给。點(diǎn)贊次數(shù)在短時間內(nèi)不能重復(fù)點(diǎn)贊香拉。

4.2 Hash

Redis hash 是一個 string 類型的 field(字段) 和 value(值) 的映射表,hash 特別適合用于存儲對象中狂。

Redis 中每個 hash 可以存儲 232 - 1 鍵值對(40多億)凫碌。

1?? 常用命令

基本上,哈希的操作命令和字符串的操作命令很類似胃榕,很多命令在字符串類型的命令前面加上了 h 字母盛险,代表是操作哈希類型,同時還要指明要操作的 field 的值籽懦。

hset

hset key field value

如果設(shè)置成功會返回 1纬傲,反之會返回 0。此外 Redis 提供了 hsetnx 命令涯塔,它們的關(guān)系就像 set 和 setnx 命令一樣鹤啡,只不過作用域由鍵變?yōu)?field惯驼。

127.0.0.1:6379> hset hash:test name ayue
(integer) 1
127.0.0.1:6379> 

hget

hget key field

獲取值

127.0.0.1:6379> hget hash:test name
"ayue"
127.0.0.1:6379> 

其他命令:

命令 說明 時間復(fù)雜度
HDEL key field [field] 刪除一個或多個Hash的field O(N) N是被刪除的字段數(shù)量
HEXISTS key field 判斷field是否存在于Hash中 O(1)
HGET key field 獲取Hash中field的值 O(1)
HGETALL key 從Hash中讀取全部的域和值 O(N) N是Hash的長度
HINCRBY key field increment 將Hash中指定域的值增加給定的數(shù)字 O(1)
HINCRBYFLOAT key field increment 將Hash中指定域的值增加給定的浮點(diǎn)數(shù) O(1)
HKEYS key 獲取Hash的所有字段 O(N) N是Hash的長度
HLEN key 獲取Hash里所有字段的數(shù)量 O(1)
HMGET key field field 獲取Hash里面指定字段的值 O(N) N是請求的字段數(shù)
HMSET key field value [field value ...] 批量設(shè)置Hash字段值 O(N) N是設(shè)置的字段數(shù)
HSET key field value 設(shè)置Hash里面一個字段的值 O(1)
HSETNX key field value 設(shè)置Hash的一個字段,只有當(dāng)這個字段不存在時有效 O(1)
HSTRLEN key field 獲取Hash里面指定field的長度 O(1)
HVALS key 獲得 Hash 的所有值 O(N) N是Hash的長度
HSCAN key cursor [MATCH pattern] [COUNT count] 迭代 Hash 里面的元素

2?? 命令的時間復(fù)雜度

哈希類型的操作命令中揉忘,hdel跳座,hmget,hmset 的時間復(fù)雜度和命令所帶的 field 的個數(shù)相關(guān) O(k)泣矛,hkeys疲眷,hgetall,hvals 和存儲的 field 的總數(shù)相關(guān)您朽,O(N)狂丝。其余的命令時間復(fù)雜度都是 O(1)。

3?? 使用場景

1哗总、存儲對象

Redis哈希對象常常用來緩存一些對象信息几颜,如用戶信息、商品信息讯屈、配置信息等蛋哭。

我們以用戶信息為例,它在關(guān)系型數(shù)據(jù)庫中的結(jié)構(gòu)是這樣的:

id name age
1 Tom 15
2 Jerry 13

而使用Redis Hash存儲其結(jié)構(gòu)如下圖:

image-20211220192747612
hmset user:1 name Tom age 15
hmset user:2 name Jerry age 13

相比較于使用Redis字符串存儲涮母,其有以下幾個優(yōu)缺點(diǎn):

  1. 原生字符串每個屬性一個鍵谆趾。

    set user:1:name Tom
    set user:1:age 15
    

    優(yōu)點(diǎn):簡單直觀,每個屬性都支持更新操作叛本。
    缺點(diǎn):占用過多的鍵沪蓬,內(nèi)存占用量較大,同時用戶信息內(nèi)聚性比較差来候,所以此種方案一般不會在生產(chǎn)環(huán)境使用跷叉。

  2. 序列化字符串后,將用戶信息序列化后用一個鍵保存

    set user:1 serialize(userInfo)
    

    優(yōu)點(diǎn):簡化編程营搅,如果合理的使用序列化可以提高內(nèi)存的使用效率云挟。
    缺點(diǎn):序列化和反序列化有一定的開銷,同時每次更新屬性都需要把全部數(shù)據(jù)取出進(jìn)行反序列化转质,更新后再序列化到Redis中植锉。

  3. 序列化字符串后,將用戶信息序列化后用一個鍵保存

    hmset user:1 name Tom age 15 
    

    優(yōu)點(diǎn):簡單直觀峭拘,如果使用合理可以減少內(nèi)存空間的使用。
    缺點(diǎn):要控制哈希在ziplist和hashtable兩種內(nèi)部編碼的轉(zhuǎn)換,hashtable會消耗更多內(nèi)存鸡挠。

2辉饱、購物車

購物車主要功能是臨時存放欲購買的商品,然后在結(jié)算或下訂單時拣展,把購物里面的數(shù)據(jù)全部移除彭沼。其數(shù)據(jù)結(jié)構(gòu)主要包含的字段有:用戶ID、商品ID备埃、商品數(shù)量等等姓惑。通常我們需要實(shí)現(xiàn)以下幾個功能:

  1. 全選功能,獲取所有該用戶的所有購物車商品按脚;
  2. 商品數(shù)量于毙,購物車圖標(biāo)上要顯示購物車?yán)锷唐返目倲?shù);
  3. 刪除辅搬,要能移除購物車?yán)锬硞€商品唯沮;
  4. 增加或減少某個商品的數(shù)量。
image-20211221154409694

在之前很多電商網(wǎng)站通過 cookie 實(shí)現(xiàn)購物車功能堪遂,也就是將整個購物車都存儲到 cookie里面介蛉。

  • 優(yōu)點(diǎn):無須對數(shù)據(jù)庫進(jìn)行寫入就可以實(shí)現(xiàn)購物車功能,這種方式大大提高了購物車的性能。
  • 缺點(diǎn):程序需要重新解析和驗(yàn)證( validate) cookie溶褪,確保 cookie 的格式正確币旧,并且包含的商品都是真正可購買的商品。另外猿妈,因?yàn)闉g覽器每次發(fā)送請求都會連 cookie 一起發(fā)送吹菱,所以如果購物車 cookie 的體積比較大,那么請求發(fā)送和處理的速度可能會有所降低于游。

而通過 Redis 定義購物車非常簡單:當(dāng)前登錄用戶 ID 號做為key毁葱,商品 ID 號為 field,加入購物車數(shù)量為 value贰剥,如下:

hmset cart:001 prod:01  1 prod:02  1
          |        |    |
          |        |    |
          |        |    |
         key    field value

而對于上述功能倾剿,可以通過 Hash 的相關(guān)命令來操作。

4.3 List

列表( list)類型是用來存儲多個有序的字符串蚌成,a前痘、b、c担忧、d芹缔、e 五個元素從左到右組成了一個有序的列表,列表中的每個字符串稱為元素(element)瓶盛,一個列表最多可以存儲 2-1 個元素最欠。

在 Redis 中示罗,可以對列表兩端插入( push)和彈出(pop),還可以獲取指定范圍的元素列表芝硬、獲取指定索引下標(biāo)的元素等蚜点。

列表是一種比較靈活的數(shù)據(jù)結(jié)構(gòu),它可以充當(dāng)棧和隊(duì)列的角色拌阴,在實(shí)際開發(fā)上有很多應(yīng)用場景绍绘。

image-20211221105439714

列表類型有兩個特點(diǎn):

  1. 列表中的元素是有序的,這就意味著可以通過索引下標(biāo)獲取某個元素或者某個范圍內(nèi)的元素列表迟赃。
  2. 列表中的元素可以是重復(fù)的陪拘。

1?? 常用命令

Redis列表對象常用命令如下表(點(diǎn)擊命令可查看命令詳細(xì)說明):

命令 說明 時間復(fù)雜度
BLPOP key [key ...] timeout 刪除,并獲得該列表中的第一元素纤壁,或阻塞左刽,直到有一個可用 O(1)
BRPOP key [key ...] timeout 刪除,并獲得該列表中的最后一個元素摄乒,或阻塞悠反,直到有一個可用 O(1)
BRPOPLPUSH source destination timeout 彈出一個列表的值,將它推到另一個列表馍佑,并返回它;或阻塞斋否,直到有一個可用 O(1)
LINDEX key index 獲取一個元素,通過其索引列表 O(N)
LINSERT key BEFORE AFTER pivot value在列表中的另一個元素之前或之后插入一個元素 O(N)
LLEN key 獲得隊(duì)列(List)的長度 O(1)
LPOP key 從隊(duì)列的左邊出隊(duì)一個元素 O(1)
LPUSH key value [value ...] 從隊(duì)列的左邊入隊(duì)一個或多個元素 O(1)
LPUSHX key value 當(dāng)隊(duì)列存在時拭荤,從隊(duì)到左邊入隊(duì)一個元素 O(1)
LRANGE key start stop 從列表中獲取指定返回的元素 O(S+N)
LREM key count value 從列表中刪除元素 O(N)
LSET key index value 設(shè)置隊(duì)列里面一個元素的值 O(N)
LTRIM key start stop 修剪到指定范圍內(nèi)的清單 O(N)
RPOP key 從隊(duì)列的右邊出隊(duì)一個元 O(1)
RPOPLPUSH source destination 刪除列表中的最后一個元素茵臭,將其追加到另一個列表 O(1)
RPUSH key value [value ...] 從隊(duì)列的右邊入隊(duì)一個元素 O(1)
RPUSHX key value 從隊(duì)列的右邊入隊(duì)一個元素,僅隊(duì)列存在時有效 O(1)

2?? 命令的時間復(fù)雜度

列表類型的操作命令中舅世,llen旦委,lpop,rpop雏亚,blpop 和 brpop 命令時間復(fù)雜度都是 O(1)缨硝,其余的命令的時間復(fù)雜度都是 O(n),只不過 n 的值根據(jù)命令不同而不同罢低,比如 lset查辩,lindex 時間復(fù)雜度和命令后的索引值大小相關(guān),rpush 和 lpush 和插入元素的個數(shù)相關(guān)等等网持。

3?? 使用場景

1宜岛、消息隊(duì)列

但使用 Redis 做消息隊(duì)列存在很多問題,如消息確認(rèn) ACK功舀,消息丟失等萍倡,所以一般來說還是用比較專業(yè)的 MQ 中間件。

2辟汰、文章列表

如下面這樣的文章列表列敲,當(dāng)用戶和文章都越來越多時阱佛,為了加快程序的響應(yīng)速度,我們可以把用戶自己的文章存入到 List 中戴而,因?yàn)?List 是有序的結(jié)構(gòu)瘫絮,所以這樣又可以完美的實(shí)現(xiàn)分頁功能,從而加速了程序的響應(yīng)速度填硕。

image-20211221154339179

上圖可表示為:

# 深圳衛(wèi)健委發(fā)布一條消息,消息ID為 99
lpush mes:001 99
# 武漢本地寶發(fā)布一條消息鹿鳖,消息ID為 100
lpush mes:001 100
# 獲取消息列表‘
lrange mes:001 0 5

4.4 Set

集合( set)類型也是用來保存多個的字符串元素,但和列表類型不一樣的是扁眯,集合中不允許有重復(fù)元素,并且集合中的元素是無序的翅帜,不能通過索引下標(biāo)獲取元素姻檀。

一個集合最多可以存儲 232 - 1 個元素。Redis 除了支持集合內(nèi)的增刪改查涝滴,同時還支持多個集合取交集绣版、并集、差集歼疮,合理地使用好集合類型杂抽,能在實(shí)際開發(fā)中解決很多實(shí)際問題。

1?? 常用命令

Redis Set 對象常用命令如下表(點(diǎn)擊命令可查看命令詳細(xì)說明):

命令 說明 時間復(fù)雜度
SADD key member [member ...] 添加一個或者多個元素到集合(set)里 O(N)
SCARD key 獲取集合里面的元素數(shù)量 O(1)
SDIFF key [key ...] 獲得隊(duì)列不存在的元素 O(N)
SDIFFSTORE destination key [key ...] 獲得隊(duì)列不存在的元素韩脏,并存儲在一個關(guān)鍵的結(jié)果集 O(N)
SINTER key [key ...] 獲得兩個集合的交集 O(N*M)
SINTERSTORE destination key [key ...] 獲得兩個集合的交集缩麸,并存儲在一個關(guān)鍵的結(jié)果集 O(N*M)
SISMEMBER key member 確定一個給定的值是一個集合的成員 O(1)
SMEMBERS key 獲取集合里面的所有元素 O(N)
SMOVE source destination member 移動集合里面的一個元素到另一個集合 O(1)
SPOP key [count] 刪除并獲取一個集合里面的元素 O(1)
SRANDMEMBER key [count] 從集合里面隨機(jī)獲取一個元素
SREM key member [member ...] 從集合里刪除一個或多個元素 O(N)
SUNION key [key ...] 添加多個set元素 O(N)
SUNIONSTORE destination key [key ...] 合并set元素,并將結(jié)果存入新的set里面 O(N)
[SSCAN key cursor MATCH pattern] [COUNT count] 迭代set里面的元素 O(1)

2?? 命令的時間復(fù)雜度

scard赡矢,sismember 時間復(fù)雜度為 O(1)杭朱,其余的命令時間復(fù)雜度為 O(n),其中 sadd吹散,srem 和命令后所帶的元素個數(shù)相關(guān)弧械,spop,srandmember 和命令后所帶 count 值相關(guān)空民,交集運(yùn)算 O(m*k)刃唐,k 是多個集合中元素最少的個數(shù),m 是鍵個數(shù)袭景,并集唁桩、差集和所有集合的元素個數(shù)和相關(guān)。

3?? 使用場景

1耸棒、抽獎活動

常見的抽獎活動荒澡,比如基于 Redis 實(shí)現(xiàn)抽獎功能。

SPOP(隨機(jī)移除并返回集合中一個或多個元素)SRANDMEMBER(隨機(jī)返回集合中一個或多個元素) 命令可以幫助我們實(shí)現(xiàn)一個抽獎系統(tǒng)与殃,如果允許重復(fù)中獎单山,可以使用SRANDMEMBER 命令碍现。

活動 ID 為 001,則

# Tom userID:01 參加活動
sadd action:001 01
# Jerry userID:02 參加活動
sadd action:001 02
# 開始抽獎1名中獎?wù)?srandmember action:001 1 或 spop action:001 1
# 查看有多少用戶參加了本次抽獎
smembers action:001

2米奸、點(diǎn)贊功能

比如設(shè)計(jì)一個微信點(diǎn)贊功能昼接。

# 張三用戶ID 為userId:01
# 張三對消息 ID008點(diǎn)贊啦
sadd zan:008 userId:01

# 張三取消了對消息008的點(diǎn)贊
srem zan:008 userId:01

# 檢查用戶是否點(diǎn)過贊
sismember zan:008 userId:01

# 獲取消息ID008所有的點(diǎn)贊用戶列表
smembers zan:008

# 消息ID008的點(diǎn)贊數(shù)計(jì)算
scard zan:008

3、關(guān)系設(shè)計(jì)

如我們要設(shè)計(jì)一個微博的共同關(guān)注悴晰,或者可能認(rèn)識的人慢睡。設(shè)計(jì)如下:

① A 關(guān)注的人

sadd A:cares B C D E

② B 關(guān)注的人

sadd B:cares A C D F

③ C 關(guān)注的人

sadd C:cares A F

按照以上條件:

④ A 和 B 共同關(guān)注的人

# D,C
sinter A:cares B:cares

⑤ 我關(guān)注的人也關(guān)注他

# A 關(guān)注的 B 也關(guān)注了 F,返回 1 否則返回 0
sismember B:cares F

⑥ 可能認(rèn)識的人

# C 可能認(rèn)識的人 C,D
sdiff B:cares C:cares

4铡溪、集合操作

setA={A,B,C}     setB={B, C}

① 集合與集合之間的交集

sinter setA setB-->得到集合{B,C}

② 集合與集合之間的并集

sunion setA setB -->得到集合{A,B,C}

③ 集合與集合之間的差集

sdiff  setA setB-->得到集合{A}
127.0.0.1:6379> SADD setA A B C
(integer) 3
127.0.0.1:6379> SADD setB B C
(integer) 2
127.0.0.1:6379> SINTER setA setB
1) "C"
2) "B"
127.0.0.1:6379> SUNION setA setB
1) "A"
2) "B"
3) "C"
127.0.0.1:6379> SDIFF setA setB
1) "A"
127.0.0.1:6379> 

4.5 ZSet

ZSet漂辐,有序集合,相對于哈希棕硫、列表髓涯、集合來說會有一點(diǎn)點(diǎn)陌生,但既然叫有序集合哈扮,那么它和集合必然有著聯(lián)系纬纪,它保留了集合不能有重復(fù)成員的特性,但不同的是滑肉,有序集合中的元素可以排序包各。但是它和列表使用索引下標(biāo)作為排序依據(jù)不同的是,它給每個元素設(shè)置一個分?jǐn)?shù)( score)作為排序的依據(jù)赦邻。

有序集合中的元素不能重復(fù)髓棋,但是 score 可以重復(fù),就和一個班里的同學(xué)學(xué)號不能重復(fù)惶洲,但是考試成績可以相同按声。

有序集合提供了獲取指定分?jǐn)?shù)和元素范圍查詢、計(jì)算成員排名等功能恬吕,合理的利用有序集合签则,能幫助我們在實(shí)際開發(fā)中解決很多問題。

1?? 常用命令

zadd

向有序集合 top:20211221 添加話題和點(diǎn)擊量铐料。

zadd hot:20211220 10 薇婭逃稅

zadd 命令還有四個選項(xiàng) nx渐裂、xx、ch钠惩、incr 四個選項(xiàng):

  • nx柒凉,member 必須不存在,才可以設(shè)置成功篓跛,用于添加膝捞;
  • xx,member 必須存在愧沟,才可以設(shè)置成功蔬咬,用于更新鲤遥;
  • ch,返回此次操作后林艘,有序集合元素和分?jǐn)?shù)發(fā)生變化的個數(shù)盖奈;
  • incr,對 score 做增加狐援,相當(dāng)于 zincrby 钢坦。

Redis列表對象常用命令如下表:

命令 說明 時間復(fù)雜度
BZPOPMAX key [key ...] timeout 從一個或多個排序集中刪除并返回得分最高的成員,或阻塞啥酱,直到其中一個可用為止 O(log(N))
BZPOPMIN key [key ...] timeout 從一個或多個排序集中刪除并返回得分最低的成員场钉,或阻塞,直到其中一個可用為止 O(log(N))
[ZADD key NXXX] [CH] [INCR] score member [score member ...] 添加到有序set的一個或多個成員懈涛,或更新的分?jǐn)?shù),如果它已經(jīng)存在 O(log(N))
ZCARD key 獲取一個排序的集合中的成員數(shù)量 O(1)
ZCOUNT key min max 返回分?jǐn)?shù)范圍內(nèi)的成員數(shù)量 O(log(N))
ZINCRBY key increment member 增量的一名成員在排序設(shè)置的評分 O(log(N))
ZINTERSTORE 相交多個排序集泳猬,導(dǎo)致排序的設(shè)置存儲在一個新的關(guān)鍵 O(NK)+O(Mlog(M))
ZLEXCOUNT key min max 返回成員之間的成員數(shù)量 O(log(N))
ZPOPMAX key [count] 刪除并返回排序集中得分最高的成員 O(log(N)*M)
ZPOPMIN key [count] 刪除并返回排序集中得分最低的成員 O(log(N)*M)
ZRANGE key start stop [WITHSCORES] 根據(jù)指定的index返回批钠,返回sorted set的成員列表 O(log(N)+M)
ZRANGEBYLEX key min max [LIMIT offset count] 返回指定成員區(qū)間內(nèi)的成員,按字典正序排列, 分?jǐn)?shù)必須相同得封。 O(log(N)+M)
ZREVRANGEBYLEX key max min [LIMIT offset count] 返回指定成員區(qū)間內(nèi)的成員埋心,按字典倒序排列,分?jǐn)?shù)必須相同 O(log(N)+M)
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 返回有序集合中指定分?jǐn)?shù)區(qū)間內(nèi)的成員忙上,分?jǐn)?shù)由低到高排序拷呆。 O(log(N)+M)
ZRANK key member 確定在排序集合成員的索引 O(log(N))
ZREM key member [member ...] 從排序的集合中刪除一個或多個成員 O(M*log(N))
ZREMRANGEBYLEX key min max 刪除名稱按字典由低到高排序成員之間所有成員。 O(log(N)+M)
ZREMRANGEBYRANK key start stop 在排序設(shè)置的所有成員在給定的索引中刪除 O(log(N)+M)
ZREMRANGEBYSCORE key min max 刪除一個排序的設(shè)置在給定的分?jǐn)?shù)所有成員 O(log(N)+M)
ZREVRANGE key start stop [WITHSCORES] 在排序的設(shè)置返回的成員范圍疫粥,通過索引茬斧,下令從分?jǐn)?shù)高到低 O(log(N)+M)
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] 返回有序集合中指定分?jǐn)?shù)區(qū)間內(nèi)的成員,分?jǐn)?shù)由高到低排序梗逮。 O(log(N)+M)
ZREVRANK key member 確定指數(shù)在排序集的成員项秉,下令從分?jǐn)?shù)高到低 O(log(N))
ZSCORE key member 獲取成員在排序設(shè)置相關(guān)的比分 O(1)
ZUNIONSTORE 添加多個排序集和導(dǎo)致排序的設(shè)置存儲在一個新的鍵 O(N)+O(M log(M))
ZSCAN key cursor [MATCH pattern] [COUNT count] 迭代sorted sets里面的元素 O(1)

2?? 命令的時間復(fù)雜度

參考上表。

3?? 使用場景

有序集合比較典型的使用場景就是排行榜系統(tǒng)慷彤。例如視頻網(wǎng)站需要對用戶上傳的視頻做排行榜娄蔼,榜單的維度可能是多個方面的:按照時間、按照播放數(shù)量底哗、按照獲得的贊數(shù)岁诉。

image-20211221212056683

如上熱搜榜,以日期為 key :

① 點(diǎn)擊熱搜跋选,每次加 1

zincrby hot:20211220 1 薇婭逃稅

② 右側(cè)排行實(shí)現(xiàn)涕癣,展示今日前 50 排名

# zrange 是從低到高返回,zrevrange 反之
zrevrange  hot:20211221 0 49 withscores

本文由博客群發(fā)一文多發(fā)等運(yùn)營工具平臺 OpenWrite 發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市野建,隨后出現(xiàn)的幾起案子属划,更是在濱河造成了極大的恐慌恬叹,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件同眯,死亡現(xiàn)場離奇詭異绽昼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)须蜗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進(jìn)店門硅确,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人明肮,你說我怎么就攤上這事菱农。” “怎么了柿估?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵循未,是天一觀的道長。 經(jīng)常有香客問我秫舌,道長的妖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任足陨,我火速辦了婚禮嫂粟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘墨缘。我一直安慰自己星虹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布镊讼。 她就那樣靜靜地躺著宽涌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蝶棋。 梳的紋絲不亂的頭發(fā)上护糖,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機(jī)與錄音嚼松,去河邊找鬼嫡良。 笑死,一個胖子當(dāng)著我的面吹牛献酗,可吹牛的內(nèi)容都是我干的寝受。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼罕偎,長吁一口氣:“原來是場噩夢啊……” “哼很澄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤甩苛,失蹤者是張志新(化名)和其女友劉穎蹂楣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讯蒲,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痊土,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了墨林。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赁酝。...
    茶點(diǎn)故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖旭等,靈堂內(nèi)的尸體忽然破棺而出酌呆,到底是詐尸還是另有隱情,我是刑警寧澤搔耕,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布隙袁,位于F島的核電站,受9級特大地震影響弃榨,放射性物質(zhì)發(fā)生泄漏藤乙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一惭墓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧而姐,春花似錦腊凶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至政鼠,卻和暖如春风瘦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背公般。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工万搔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人官帘。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓瞬雹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刽虹。 傳聞我的和親對象是個殘疾皇子酗捌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評論 2 348

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