須知:個(gè)人讀書筆記浮庐,很多點(diǎn)沒有具體描述甚负,只是大體羅列下,立個(gè)flag后面完善审残。
文章羅列了基本知識(shí)(類型梭域,小功能),高可靠(持久化搅轿,LRU病涨,復(fù)制,哨兵介时,集群)没宾,實(shí)現(xiàn)原理
基本知識(shí)
redis有5中類型,小功能也很驚喜沸柔。
類型
字符串(string)
> set mykey somevalue
OK
> get mykey
"somevalue"
> set mykey newval nx
(nil)
> set mykey newval xx
OK
> set counter 100
OK
> incr counter
(integer) 101
> incr counter
(integer) 102
> incrby counter 50
(integer) 152
> mset a 10 b 20 c 30
OK
> mget a b c
1) "10"
2) "20"
3) "30"
> set mykey x
> type mykey
string
> exists mykey
(integer) 1
> del mykey
(integer) 1
> type mykey
none
> set key 100 ex 10
OK
> ttl key
(integer) 9
哈希(hash)
由field和關(guān)聯(lián)的value組成的map循衰。field和value都是字符串的。
> hmset user:1000 username antirez birthyear 1977 verified 1
OK
> hget user:1000 username
"antirez"
> hget user:1000 birthyear
"1977"
> hgetall user:1000
1) "username"
2) "antirez"
3) "birthyear"
4) "1977"
5) "verified"
6) "1"
也有一些指令能夠?qū)为?dú)的域執(zhí)行操作褐澎,比如 HINCRBY:
> hincrby user:1000 birthyear 10
(integer) 1987
> hincrby user:1000 birthyear 10
(integer) 1997
列表(list)
按插入順序排序的字符串元素的集合会钝。他們基本上就是鏈表(linked lists)。
> rpush mylist A
(integer) 1
> lpush mylist B
(integer) 2
> rpush mylist C
(integer) 3
> lrange mylist 0 -1
1) "first"
2) "A"
3) "B"
> rpop mylist
"C"
redis> BRPOP list1 list2 0
1) "list1"
2) "c"
集合(set)
不重復(fù)且無序的字符串元素的集合工三。
> sadd myset 1 2 3
(integer) 3
> smembers myset
1. 3
2. 1
3. 2
Redis 有檢測(cè)成員的指令迁酸。一個(gè)特定的元素是否存在?
> sismember myset 3
(integer) 1
> sismember myset 30
(integer) 0
交集
> sinter tag:1:news tag:2:news tag:10:news tag:27:new
通過 SUNIONSTORE 實(shí)現(xiàn)的俭正,它通常用于對(duì)多個(gè)集合取并集
> sunionstore game:1:deck deck
(integer) 52
有序集合 (zset)
類似Sets,但是每個(gè)字符串元素都關(guān)聯(lián)到一個(gè)叫score浮動(dòng)數(shù)值(floating number value)奸鬓。里面的元素總是通過score進(jìn)行著排序,所以不同的是掸读,它是可以檢索的一系列元素串远。
添加 zadd key score member
> zadd user:ranking 251 tom 250 kimi
計(jì)算個(gè)數(shù) zcard key
>zcard user:ranking
計(jì)算排名
>zrank user:ranking kimi
>1
刪除成員 zrem key member
>zrem user:ranking kimi
增加成員分?jǐn)?shù)
>zincrby key increment 【num】 member
查找排名前,withscores會(huì)返回分?jǐn)?shù)
>zrang key start end [withscores]
小功能
bitmaps
通過特殊的命令,你可以將 String 值當(dāng)作一系列 bits 處理:可以設(shè)置和清除單獨(dú)的 bits儿惫,數(shù)出所有設(shè)為 1 的 bits 的數(shù)量澡罚,找到最前的被設(shè)為 1 或 0 的 bit,等等肾请。
常用命令:
setbit key offset value
getbit key offset
(不推薦留搔,較慢)bitcount key [start end]
bitmaps常用來做布隆過濾器。
hyperloglogs
被用于估計(jì)一個(gè) set 中元素?cái)?shù)量的概率性的數(shù)據(jù)結(jié)構(gòu)铛铁。
地理空間(geospatial)
消息發(fā)布/訂閱
d:\Redis>redis-cli.exe
127.0.0.1:6379> publish channel1 "hello world"
(integer) 2
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379> publish channel1 "publish message 'hello,world'"
(integer) 2
127.0.0.1:6379>
d:\Redis>redis-cli
127.0.0.1:6379> SUBSCRIBE channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "hello world"
1) "message"
2) "channel1"
3) "publish message 'hello,world'"
127.0.0.1:6379> SUBSCRIBE channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "hello world"
1) "message"
2) "channel1"
3) "publish message 'hello,world'"
LUA腳本
Lua 數(shù)據(jù)類型和 Redis 數(shù)據(jù)類型之間轉(zhuǎn)換
原子性:Redis 使用單個(gè) Lua 解釋器去運(yùn)行所有腳本隔显,并且却妨, Redis 也保證腳本會(huì)以原子性(atomic)的方式執(zhí)行: 當(dāng)某個(gè)腳本正在運(yùn)行的時(shí)候,不會(huì)有其他腳本或 Redis 命令被執(zhí)行荣月。
事務(wù)
MULTI 管呵、 EXEC 、 DISCARD 和 WATCH 是 Redis 事務(wù)相關(guān)的命令哺窄。
1.MULTI命令用于開啟一個(gè)事務(wù)捐下,它總是返回 OK
。戶端可以繼續(xù)向服務(wù)器發(fā)送任意多條命令萌业, 這些命令不會(huì)立即被執(zhí)行坷襟, 而是被放到一個(gè)隊(duì)列中。
2.當(dāng) EXEC命令被調(diào)用時(shí)生年, 所有隊(duì)列中的命令才會(huì)被執(zhí)行婴程;
當(dāng)DISCARD , 客戶端可以清空事務(wù)隊(duì)列抱婉, 并放棄執(zhí)行事務(wù)
3.WATCH命令可以為 Redis 事務(wù)提供 check-and-set (CAS)行為档叔。
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
使用上面的代碼, 如果在 WATCH執(zhí)行之后蒸绩, EXEC執(zhí)行之前衙四, 有其他客戶端修改了 mykey
的值, 那么當(dāng)前客戶端的事務(wù)就會(huì)失敗患亿。 程序需要做的传蹈, 就是不斷重試這個(gè)操作, 直到?jīng)]有發(fā)生碰撞為止步藕。
高可靠
LRU驅(qū)動(dòng)事件
Redis的maxmemory指令用于將可用內(nèi)存限制成一個(gè)固定大小
- noeviction::當(dāng)內(nèi)存限制達(dá)到并且客戶端嘗試執(zhí)行會(huì)讓更多內(nèi)存被使用的命令(大部分的寫入指令惦界,但DEL和幾個(gè)例外),返回錯(cuò)誤
- allkeys-lru:嘗試回收最少使用的鍵(LRU),使得新添加的數(shù)據(jù)有空間存放咙冗。
- volatile-lru:僅限于在過期集合的鍵,嘗試回收最少使用的鍵(LRU)沾歪,但使得新添加的數(shù)據(jù)有空間存放。
- allkeys-random:回收隨機(jī)的鍵使得新添加的數(shù)據(jù)有空間存放雾消。
- volatile-random:僅限于在過期集合的鍵瞬逊,回收隨機(jī)的鍵使得新添加的數(shù)據(jù)有
- volatile-ttl:回收在過期集合的鍵,并且優(yōu)先回收存活時(shí)間(TTL)較短的鍵,使得新添加的數(shù)據(jù)有空間存放仪或。空間存放士骤。
回收進(jìn)程如何工作
- 一個(gè)客戶端運(yùn)行了新的命令范删,添加了新的數(shù)據(jù)。
- Redi檢查內(nèi)存使用情況拷肌,如果大于maxmemory的限制, 則根據(jù)設(shè)定好的策略進(jìn)行回收到旦。
- 一個(gè)新的命令被執(zhí)行旨巷,等等。
所以我們不斷地穿越內(nèi)存限制的邊界添忘,通過不斷達(dá)到邊界然后不斷地回收回到邊界以下采呐。
持久化
RDB vs AOF
RDB
. RDB持久化方式能夠在指定的時(shí)間間隔能對(duì)你的數(shù)據(jù)進(jìn)行快照存儲(chǔ).
流程
當(dāng) Redis 需要保存 dump.rdb 文件時(shí), 服務(wù)器執(zhí)行以下操作:
- Redis 調(diào)用forks. 同時(shí)擁有父進(jìn)程和子進(jìn)程搁骑。
子進(jìn)程將數(shù)據(jù)集寫入到一個(gè)臨時(shí) RDB 文件中斧吐。 - 當(dāng)子進(jìn)程完成對(duì)新 RDB 文件的寫入時(shí),Redis 用新 RDB 文件替換原來的 RDB 文件仲器,并刪除舊的 RDB 文件煤率。
這種工作方式使得 Redis 可以從寫時(shí)復(fù)制(copy-on-write)機(jī)制中獲益。
“ N 秒內(nèi)數(shù)據(jù)集至少有 M 個(gè)改動(dòng)”這一條件被滿足時(shí)乏冀, 自動(dòng)保存一次數(shù)據(jù)集,調(diào)用 SAVE或者 BGSAVE 蝶糯, 手動(dòng)讓 Redis 進(jìn)行數(shù)據(jù)集保存操作。
save 60 1000
RDB的優(yōu)點(diǎn)
1.非常緊湊的文件,它保存了某個(gè)時(shí)間點(diǎn)得數(shù)據(jù)集,非常適用于數(shù)據(jù)集的備份,比如你可以在每個(gè)小時(shí)報(bào)保存一下過去24小時(shí)內(nèi)的數(shù)據(jù),同時(shí)每天保存過去30天的數(shù)據(jù),這樣即使出了問題你也可以根據(jù)需求恢復(fù)到不同版本的數(shù)據(jù)集.
- RDB是一個(gè)緊湊的單一文件,很方便傳送到另一個(gè)遠(yuǎn)端數(shù)據(jù)中心或者亞馬遜的S3(可能加密)辆沦,非常適用于災(zāi)難恢復(fù).
- RDB在保存RDB文件時(shí)父進(jìn)程唯一需要做的就是fork出一個(gè)子進(jìn)程,接下來的工作全部由子進(jìn)程來做昼捍,父進(jìn)程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
與AOF相比,在恢復(fù)大的數(shù)據(jù)集的時(shí)候肢扯,RDB方式會(huì)更快一些.
RDB的缺點(diǎn)
- fork子進(jìn)程來保存數(shù)據(jù)集到硬盤上,當(dāng)數(shù)據(jù)集比較大的時(shí)候,fork的過程是非常耗時(shí)的,可能會(huì)導(dǎo)致Redis在一些毫秒級(jí)內(nèi)不能響應(yīng)客戶端的請(qǐng)求.如果數(shù)據(jù)集巨大并且CPU性能不是很好的情況下,這種情況會(huì)持續(xù)1秒,AOF也需要fork,但是你可以調(diào)節(jié)重寫日志文件的頻率來提高數(shù)據(jù)集的耐久度.
2.意外停止工作,你可能會(huì)丟失部分?jǐn)?shù)據(jù)
AOF
RDB快照功能并不是非常耐久, 如果 Redis 因?yàn)槟承┰蚨斐晒收贤C(jī)妒茬, 那么服務(wù)器將丟失最近寫入、且仍未保存到快照中的那些數(shù)據(jù)鹃彻。
配置文件中打開AOF方式:
appendonly yes
有三種方式:
- 每次有新命令追加到 AOF 文件時(shí)就執(zhí)行一次 fsync :非常慢郊闯,也非常安全
- 每秒 fsync 一次:足夠快(和使用 RDB 持久化差不多),并且在故障時(shí)只會(huì)丟失 1 秒鐘的數(shù)據(jù)蛛株。
- 從不 fsync :將數(shù)據(jù)交給操作系統(tǒng)來處理团赁。更快,也更不安全的選擇谨履。
. 推薦(并且也是默認(rèn))的措施為每秒 fsync 一次欢摄, 這種 fsync 策略可以兼顧速度和安全性。
流程
寫時(shí)復(fù)制機(jī)制:
- Redis 執(zhí)行 fork() 笋粟,現(xiàn)在同時(shí)擁有父進(jìn)程和子進(jìn)程怀挠。
子進(jìn)程開始將新 AOF 文件的內(nèi)容寫入到臨時(shí)文件。 - 對(duì)于所有新執(zhí)行的寫入命令害捕,父進(jìn)程一邊將它們累積到一個(gè)內(nèi)存緩存中绿淋,一邊將這些改動(dòng)追加到現(xiàn)有 AOF 文件的末尾,這樣樣即使在重寫的中途發(fā)生停機(jī),現(xiàn)有的 AOF 文件也還是安全的尝盼。
- 當(dāng)子進(jìn)程完成重寫工作時(shí)吞滞,它給父進(jìn)程發(fā)送一個(gè)信號(hào),父進(jìn)程在接收到信號(hào)之后,將內(nèi)存緩存中的所有數(shù)據(jù)追加到新 AOF 文件的末尾裁赠。
搞定殿漠!現(xiàn)在 Redis 原子地用新文件替換舊文件,之后所有命令都會(huì)直接追加到新 AOF 文件的末尾佩捞。
AOF優(yōu)點(diǎn)
- 使用AOF 會(huì)讓你的Redis更加耐久: 你可以使用不同的fsync策略:無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,即使由于某些原因(磁盤空間已滿,寫的過程中宕機(jī)等等)未執(zhí)行完整的寫入命令,你也也可使用redis-check-aof工具修復(fù)這些問題.
- Redis 可以在 AOF 文件體積變得過大時(shí)一忱,自動(dòng)地在后臺(tái)對(duì) AOF 進(jìn)行重寫: 重寫后的新 AOF 文件包含了恢復(fù)當(dāng)前數(shù)據(jù)集所需的最小命令集合莲蜘。 整個(gè)重寫操作是絕對(duì)安全的,因?yàn)?Redis 在創(chuàng)建新 AOF 文件的過程中掀潮,會(huì)繼續(xù)將命令追加到現(xiàn)有的 AOF 文件里面菇夸,即使重寫過程中發(fā)生停機(jī),現(xiàn)有的 AOF 文件也不會(huì)丟失仪吧。 而一旦新 AOF 文件創(chuàng)建完畢庄新,Redis 就會(huì)從舊 AOF 文件切換到新 AOF 文件,并開始對(duì)新 AOF 文件進(jìn)行追加操作薯鼠。
4.AOF 文件有序地保存了對(duì)數(shù)據(jù)庫執(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ù)集來說,AOF 文件的體積通常要大于 RDB 文件的體積狞贱。
- 根據(jù)所使用的 fsync 策略刻获,AOF 的速度可能會(huì)慢于 RDB 。 在一般情況下瞎嬉, 每秒 fsync 的性能依然非常高蝎毡, 而關(guān)閉 fsync 可以讓 AOF 的速度和 RDB 一樣快, 即使在高負(fù)荷之下也是如此氧枣。 不過在處理巨大的寫入載入時(shí)沐兵,RDB 可以提供更有保證的最大延遲時(shí)間(latency)。
AOF重寫
寫入命令的不斷增加便监, AOF 文件的體積也會(huì)變得越來越大扎谎,AOF 文件會(huì)進(jìn)行重建(rebuild)。執(zhí)行 BGREWRITEAOF 命令, Redis 將生成一個(gè)新的 AOF 文件簿透, 這個(gè)文件包含重建當(dāng)前數(shù)據(jù)集所需的最少命令。
高可用
復(fù)制
分為全量復(fù)制和增量復(fù)制:
當(dāng)從Redis服務(wù)器啟動(dòng)時(shí)會(huì)向主Redis服務(wù)器發(fā)送SYNC命令解藻,主Redis服務(wù)器接收到SYNC命令后開始進(jìn)行RDB持久化老充,并將這期間接收到的寫入操作命令都緩存起來,等RDB持久化完成后螟左,將快照和緩存起來的命令一并發(fā)送給從Redis服務(wù)器啡浊,從Redis服務(wù)器接收到后開始載入快照和命令,這一過程稱之為復(fù)制初始化胶背。
復(fù)制初始化完成后巷嚣,每當(dāng)主Redis接收到寫入命令后,就會(huì)將命令同步給從Redis服務(wù)器钳吟,保證主從數(shù)據(jù)一致廷粒。
主從斷開重連后會(huì)根據(jù)斷開之前最新的命令偏移量進(jìn)行增量復(fù)制
- 主服務(wù)器在同步命令到從服務(wù)器的時(shí)候,會(huì)先將命令放入一個(gè)緩沖隊(duì)列中并記錄一個(gè)復(fù)制偏移量红且,同時(shí)主從服務(wù)器都會(huì)記錄一個(gè)主服務(wù)器的運(yùn)行ID坝茎。
- 當(dāng)主從斷開重連后,會(huì)判斷主服務(wù)器保存的運(yùn)行ID和從服務(wù)器發(fā)送過來的運(yùn)行ID是否相同暇番,相同則將從復(fù)制偏移量開始往后的所有命令一并發(fā)送給從服務(wù)器嗤放。如果不同,則進(jìn)行一次復(fù)制初始化(將RDB快照和和這期間緩存起來的命令一并發(fā)送給主服務(wù)器)壁酬。
-
緩沖隊(duì)列的大小默認(rèn)是1MB次酌,可以在redis.conf中的配置項(xiàng)repl-backlog-size進(jìn)行設(shè)置,還有一個(gè)配置項(xiàng)repl-backlog-ttl舆乔,表示當(dāng)主從斷開后岳服,緩沖隊(duì)列的緩存時(shí)間。
Redis哨兵(Sentinel)
配置了監(jiān)控主節(jié)點(diǎn)蜕煌,內(nèi)部會(huì)自動(dòng)發(fā)現(xiàn)其他Sentinel節(jié)點(diǎn)和從節(jié)點(diǎn)派阱。
// 當(dāng)前Sentinel節(jié)點(diǎn)監(jiān)控 127.0.0.1:6379 這個(gè)主節(jié)點(diǎn)
// 2代表判斷主節(jié)點(diǎn)失敗至少需要2個(gè)Sentinel節(jié)點(diǎn)節(jié)點(diǎn)同意
// mymaster是主節(jié)點(diǎn)的別名
sentinel monitor mymaster 127.0.0.1 6379 2
參考資料:https://blog.csdn.net/men_wen/article/details/72724406
Cluster
Redis Cluster 是社區(qū)版推出的 Redis 分布式集群解決方案,主要解決 Redis 分布式方面的需求斜纪,比如贫母,當(dāng)遇到單機(jī)內(nèi)存,并發(fā)和流量等瓶頸的時(shí)候盒刚,Redis Cluster 能起到很好的負(fù)載均衡的目的腺劣。
Redis 集群沒有使用一致性hash, 而是引入了 哈希槽的概念., 采用虛擬槽分區(qū)因块,所有的鍵根據(jù)哈希函數(shù)映射到 0~16383 個(gè)整數(shù)槽內(nèi)橘原,每個(gè)節(jié)點(diǎn)負(fù)責(zé)維護(hù)一部分槽以及槽所映射的鍵值數(shù)據(jù)。
每個(gè)key通過CRC16校驗(yàn)后對(duì)16384取模來決定放置哪個(gè)槽.集群的每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分hash槽,舉個(gè)例子,比如當(dāng)前集群有3個(gè)節(jié)點(diǎn):
節(jié)點(diǎn) A 包含 0 到 5500號(hào)哈希槽.
節(jié)點(diǎn) B 包含5501 到 11000 號(hào)哈希槽.
節(jié)點(diǎn) C 包含11001 到 16383號(hào)哈希槽.
優(yōu)點(diǎn):
- 無中心架構(gòu);
- 數(shù)據(jù)按照 slot 存儲(chǔ)分布在多個(gè)節(jié)點(diǎn)趾断,節(jié)點(diǎn)間數(shù)據(jù)共享拒名,可動(dòng)態(tài)調(diào)整數(shù)據(jù)分布;
- 可擴(kuò)展性:可線性擴(kuò)展到 1000 多個(gè)節(jié)點(diǎn)芋酌,節(jié)點(diǎn)可動(dòng)態(tài)添加或刪除增显;
- 高可用性:部分節(jié)點(diǎn)不可用時(shí),集群仍可用脐帝。通過增加 Slave 做 standby 數(shù)據(jù)副本同云,能夠?qū)崿F(xiàn)故障自動(dòng) failover,節(jié)點(diǎn)之間通過 gossip 協(xié)議交換狀態(tài)信息堵腹,用投票機(jī)制完成 Slave 到 Master 的角色提升炸站;
- 降低運(yùn)維成本,提高系統(tǒng)的擴(kuò)展性和可用性疚顷。
缺點(diǎn):
- 為了使在部分節(jié)點(diǎn)失敗或者大部分節(jié)點(diǎn)無法通信的情況下集群仍然可用旱易,所以集群使用了主從復(fù)制模型,每個(gè)節(jié)點(diǎn)都會(huì)有N-1個(gè)復(fù)制,但如果有幾個(gè)節(jié)點(diǎn)主節(jié)點(diǎn)和從節(jié)點(diǎn)掛了荡含,集群不可用
- 不能保證一致性咒唆。在實(shí)際中集群在特定的條件下可能會(huì)丟失寫操作,集群用異步復(fù)制释液,如1. master節(jié)點(diǎn)接受client寫入后發(fā)生故障沒有來得及同步給從節(jié)點(diǎn)全释,2.網(wǎng)絡(luò)分區(qū),client和其中一個(gè)master節(jié)點(diǎn)在一個(gè)分區(qū)误债,寫入了數(shù)據(jù)浸船,在另外分區(qū)中從節(jié)點(diǎn)和其他master節(jié)點(diǎn)將其中一個(gè)從節(jié)點(diǎn)變成master節(jié)點(diǎn),導(dǎo)致數(shù)據(jù)丟失寝蹈。
- 不建議使用 pipeline和multi-keys 操作李命,減少 max redirect 產(chǎn)生的場(chǎng)景。
參考資料:https://www.cnblogs.com/cjsblog/p/9048545.html
http://www.redis.cn/topics/cluster-tutorial.html
https://www.toutiao.com/a6593195936774619656
原理
VM理解箫老,key如何映射成value封字。內(nèi)部主要數(shù)據(jù)結(jié)構(gòu)有哪些。
架構(gòu)
單線程耍鬓,事件驅(qū)動(dòng)阔籽。接收到請(qǐng)求會(huì)進(jìn)入隊(duì)列中,逐個(gè)處理牲蜀。
特點(diǎn):
1.純內(nèi)存訪問笆制,相應(yīng)時(shí)間大約在100納秒
2.非阻塞IO,基于epoll作為IO多路復(fù)用技術(shù)涣达,不在網(wǎng)絡(luò)IO上浪費(fèi)過多時(shí)間在辆;
3.單線程避免了線程切換和競(jìng)態(tài)產(chǎn)生的消耗
但同時(shí)缺點(diǎn)也很明顯:多核CPU利用率低证薇,阻塞造成噩夢(mèng)。
阻塞:
- 單線程匆篓,不合理使用API或數(shù)據(jù)結(jié)構(gòu)浑度,比如獲取大對(duì)象。
- 大對(duì)象的發(fā)現(xiàn)有 redis-cli --bigkeys
- slowlog get {n}獲取最近n條慢查詢
- CPU飽和
- 持久化阻塞:fork阻塞鸦概,AOF刷盤阻塞
- CPU競(jìng)爭(zhēng)俺泣,內(nèi)存交換,網(wǎng)絡(luò)問題:鏈接拒絕完残,鏈接拒絕,鏈接溢出横漏,網(wǎng)絡(luò)延遲
key映射 VS 數(shù)據(jù)結(jié)構(gòu)
參考:https://redis.io/topics/internals-vm
每個(gè)Redis客戶端都有自己的目標(biāo)數(shù)據(jù)庫谨设,每當(dāng)客戶端執(zhí)行數(shù)據(jù)庫的讀寫命令時(shí),目標(biāo)數(shù)據(jù)庫就會(huì)成為這些命令的操作對(duì)象缎浇。在服務(wù)器內(nèi)部記錄客戶端連接的目標(biāo)數(shù)據(jù)庫扎拣,這個(gè)屬性是一個(gè)指向redisDb結(jié)構(gòu)的指針。
typedef struct redisClient {
//..
// 客戶端當(dāng)前正在使用的數(shù)據(jù)庫
redisDb *db;
//..
} redisClient;
redisClient中redisDb的指向redisServer.db數(shù)組中的某個(gè)元素素跺,即是當(dāng)前客戶端的目標(biāo)數(shù)據(jù)庫二蓝。通過修改redisClient指針,讓他指向服務(wù)器中的不同數(shù)據(jù)庫指厌,從而實(shí)現(xiàn)切換數(shù)據(jù)庫的功能刊愚。
int selectDb(redisClient *c, int id) {
// 確保 id 在正確范圍內(nèi)
if (id < 0 || id >= server.dbnum)
return REDIS_ERR;
// 切換數(shù)據(jù)庫(更新指針)
c->db = &server.db[id];
return REDIS_OK;
}
在看redisDb的數(shù)據(jù)結(jié)構(gòu)
typedef struct redisDb {
// 數(shù)據(jù)庫鍵空間,保存著數(shù)據(jù)庫中的所有鍵值對(duì)
dict *dict; /* The keyspace for this DB */
// 鍵的過期時(shí)間踩验,字典的鍵為鍵鸥诽,字典的值為過期事件 UNIX 時(shí)間戳
dict *expires; /* Timeout of keys with a timeout set */
// 數(shù)據(jù)庫號(hào)碼
int id; /* Database ID */
// 數(shù)據(jù)庫的鍵的平均 TTL ,統(tǒng)計(jì)信息
long long avg_ttl; /* Average TTL, just for stats */
//..
} redisDb
dict 展開的數(shù)據(jù)結(jié)構(gòu)如下:
lookupKey函數(shù)箕憾,根據(jù)key找到value,value對(duì)象是redisObject
robj *lookupKey(redisDb *db, robj *key) {
// 查找鍵空間
dictEntry *de = dictFind(db->dict,key->ptr);
// 節(jié)點(diǎn)存在
if (de) {
// 取出該鍵對(duì)應(yīng)的值
robj *val = dictGetVal(de);
// 更新時(shí)間信息
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
val->lru = LRU_CLOCK();
// 返回值
return val;
} else {
// 節(jié)點(diǎn)不存在
return NULL;
}
}
redisObject數(shù)據(jù)結(jié)構(gòu)如下:
在redis-cli命令中牡借,通過如下[debug object key]可以看到其數(shù)據(jù)結(jié)構(gòu)
d:\Redis>redis-cli
127.0.0.1:6379> set foo bar
OK
127.0.0.1:6379> debug object foo
Value at:00007FEC370DA260 refcount:1 encoding:embstr serializedlength:4 lru:8743156 lru_seconds_idle:11
127.0.0.1:6379>
詳細(xì)解讀:
編碼方式
- string
存儲(chǔ):構(gòu)建了一種名為簡(jiǎn)單動(dòng)態(tài)字符串(simple dynamic string SDS)的抽象類型。特點(diǎn)預(yù)分配袭异,降低分配次數(shù)钠龙,惰性空間釋放,對(duì)字符串縮減時(shí)不釋放御铃。
struct sdshdr {
// buf 中已用長(zhǎng)度
int len;
// buf 中可用長(zhǎng)度
int free;
// 數(shù)據(jù)空間
char buf[];
};
編碼 | 描述 |
---|---|
int | 8個(gè)字節(jié)長(zhǎng)整型 |
embstr | 小于44個(gè)字節(jié)的字符串,(redis3.2版本之前是39字節(jié)碴里,之后是44字節(jié)) |
raw | 大于44個(gè)字典的字符串 |
int 編碼是用來保存整數(shù)值,raw編碼是用來保存長(zhǎng)字符串畅买,而embstr是用來保存短字符串并闲。raw和raw可以用下圖來分別:
上圖看出使用均使用redisObject和sds保存數(shù)據(jù),embstr的使用只分配一次內(nèi)存空間(因此redisObject和sds是連續(xù)的)谷羞,而raw需要分配兩次內(nèi)存空間(分別為redisObject和sds分配空間)帝火。embstr的好處在于創(chuàng)建時(shí)少分配一次空間溜徙,刪除時(shí)少釋放一次空間,以及對(duì)象的所有數(shù)據(jù)連在一起犀填,尋找方便蠢壹。而embstr的壞處也很明顯,如果字符串的長(zhǎng)度增加需要重新分配內(nèi)存時(shí)九巡,整個(gè)redisObject和sds都需要重新分配空間图贸,因此redis中的embstr實(shí)現(xiàn)為只讀。
- hash
編碼 | 描述 |
---|---|
ziplist | 壓縮列表冕广,當(dāng)數(shù)據(jù)個(gè)數(shù)小于hash-max-ziplist-enrtries(默認(rèn)512)疏日,單個(gè)值均小于hash-max-ziplist-vaue(64) |
hashtable | 哈希字典表,ziplist不滿足時(shí)使用 |
- ziplist壓縮列表撒汉,實(shí)現(xiàn)為無指針的數(shù)組沟优,內(nèi)部數(shù)據(jù)結(jié)構(gòu):
- zlbytes:用于記錄整個(gè)壓縮列表占用的內(nèi)存字節(jié)數(shù)
- zltail:記錄距離尾節(jié)點(diǎn)的偏移量
- zllen:節(jié)點(diǎn)數(shù)量。
- entry:具體節(jié)點(diǎn)睬辐。entry內(nèi)部結(jié)構(gòu): 1.pre_entry_bytes_length上一個(gè)節(jié)點(diǎn)占用的空間 2.當(dāng)前空間的長(zhǎng)度 3.content
- zlend:用于標(biāo)記壓縮列表的末端
特點(diǎn)是緊湊連續(xù)數(shù)組挠阁,還是雙向列表,新增刪除涉及內(nèi)存分配/釋放有點(diǎn)復(fù)雜溯饵,適合小長(zhǎng)度對(duì)象侵俗。
- hashtable實(shí)現(xiàn)為如下,數(shù)組[桶]+元素是鏈表實(shí)現(xiàn)丰刊。
- list
編碼 | 描述 |
---|---|
ziplist | 壓縮列表隘谣,當(dāng)數(shù)據(jù)個(gè)數(shù)小于list-max-ziplist-enrtries(默認(rèn)512),單個(gè)值均小于list-max-ziplist-vaue(默認(rèn)64) |
linkedlist | ziplist無法滿足時(shí)使用 |
ziplist不在說明啄巧,
linkedlist是雙向鏈表如下洪橘,比較好理解,不說明
- set
編碼 | 描述 |
---|---|
intset | 整數(shù)集合棵帽,當(dāng)元素小于set-max-intset-entries(默認(rèn)512)時(shí)使用 |
hashtable | intset不滿足時(shí)使用 |
intset數(shù)據(jù)結(jié)構(gòu)如下,內(nèi)部實(shí)現(xiàn)是連續(xù)空間的數(shù)組:
typedef struct intset{
uint32_t enconding;
// 元素?cái)?shù)量
uint32_t length;
//元素的數(shù)組
int8_t contents[];
} intset;
- zset
編碼 | 描述 |
---|---|
ziplist | 壓縮列表熄求,元素個(gè)數(shù)小于zset-max-ziplist-entries(默認(rèn)128),單個(gè)值小于zset-max-ziplist-value(默認(rèn)64) |
skiplist | ziplist不滿足時(shí)使用 |
skiplist結(jié)構(gòu)
typedef struct zskiplistNode{
//層
struct zskiplistLevel{
//前進(jìn)指針
struct zskiplistNode *forward;
//跨度
unsigned int span;
} level[];
//后退指針
struct zskiplistNode *backward;
//分值
double score;
//成員對(duì)象
robj *obj;
}
1逗概、層:level 數(shù)組可以包含多個(gè)元素弟晚,每個(gè)元素都包含一個(gè)指向其他節(jié)點(diǎn)的指針。
2逾苫、前進(jìn)指針:用于指向表尾方向的前進(jìn)指針
3卿城、跨度:用于記錄兩個(gè)節(jié)點(diǎn)之間的距離
4、后退指針:用于從表尾向表頭方向訪問節(jié)點(diǎn)
5铅搓、分值和成員:跳躍表中的所有節(jié)點(diǎn)都按分值從小到大排序瑟押。成員對(duì)象指向一個(gè)字符串,這個(gè)字符串對(duì)象保存著一個(gè)SDS值.星掰。
typedef struct zskiplist {
//表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)
structz skiplistNode *header,*tail;
//表中節(jié)點(diǎn)數(shù)量
unsigned long length;
//表中層數(shù)最大的節(jié)點(diǎn)的層數(shù)
int level;
}zskiplist;
從結(jié)構(gòu)圖中我們可以清晰的看到多望,header嫩舟,tail分別指向跳躍表的頭結(jié)點(diǎn)和尾節(jié)點(diǎn)。level 用于記錄最大的層數(shù)怀偷,length 用于記錄我們的節(jié)點(diǎn)數(shù)量家厌。
參考資料:
https://redis.io/topics/internals-vm
https://blog.csdn.net/asd1126163471/article/details/60162221
https://www.cnblogs.com/jaycekon/p/6277653.html