数据结构(字典,跳跃表)、使用场景(计数器、缓存、查找表、消息队列、会话缓存、分布式锁)、Redis 与 Memcached、 键的过期时间、数据淘汰策略、持久化(RDB、AOF)
1. 數(shù)據(jù)結(jié)構(gòu)
1.1 字典
dictht 是一個(gè)散列表結(jié)構(gòu),使用拉鏈法保存哈希沖突的 dictEntry
/* This is our hash table structure. Every dictionary has two of this as we * implement incremental rehashing, for the old to the new table.*/typedef struct dictht {dictEntry **table;unsigned long size;unsigned long sizemask;unsigned long used; } dictht;typedef struct dictEntry {void *key;union {void *val;uint64_t u64;int64_t s64;double d;} v;struct dictEntry *next; } dictEntry;Redis 的字典 dict 中包含兩個(gè)哈希表 dictht,這是為了方便進(jìn)行 rehash 操作。
- 在擴(kuò)容時(shí),將其中一個(gè) dictht 上的鍵值對(duì) rehash 到另一個(gè) dictht 上面
- 完成之后釋放空間并交換兩個(gè) dictht 的角色。
rehash 操作不是一次性完成,而是采用漸進(jìn)方式,這是為了避免一次性執(zhí)行過多的rehash 操作給服務(wù)器帶來過大的負(fù)擔(dān)。
漸進(jìn)式 rehash 通過記錄 dict 的 rehashidx 完成,它從 0 開始,然后每執(zhí)行一次rehash 都會(huì)遞增。
- 例如在一次 rehash 中,要把 dict[0] rehash 到 dict[1],
- 這一次會(huì)把 dict[0] 上 table[rehashidx] 的鍵值對(duì) rehash 到 dict[1] 上,
- dict[0] 的table[rehashidx] 指向 null,并令 rehashidx++。
在 rehash 期間,每次對(duì)字典執(zhí)行添加、刪除、查找或者更新操作時(shí),都會(huì)執(zhí)行一次漸進(jìn)式 rehash。
采用漸進(jìn)式 rehash 會(huì)導(dǎo)致字典中的數(shù)據(jù)分散在兩個(gè) dictht 上,因此對(duì)字典的操作也需要到對(duì)應(yīng)的 dictht 去執(zhí)行。
/* Performs N steps of incremental rehashing. Returns 1 if thereare still * keys to move from the old to the new hash table, otherwise 0is returned. * *Note that a rehashing step consists in moving a bucket (thatmay have more * than one key as we use chaining) from the old to the new hashtable, however * since part of the hash table may be composed of empty spaces,it is not * guaranteed that this function will rehash even a single bucket, since it * will visit at max N*10 empty buckets in total, otherwise theamount of * work it does would be unbound and the function may block fora long time. */int dictRehash(dict *d, int n) {int empty_visits = n * 10; /* Max number of empty buckets tovisit. */if (!dictIsRehashing(d)) return 0;while (n-- && d->ht[0].used != 0) {dictEntry *de, *nextde;/* Note that rehashidx can't overflow as we are sure the re are more* elements because ht[0].used != 0 */assert(d->ht[0].size > (unsigned long) d->rehashidx);while (d->ht[0].table[d->rehashidx] == NULL) {d->rehashidx++;if (--empty_visits == 0) return 1;} de = d->ht[0].table[d->rehashidx];/* Move all the keys in this bucket from the old to the new hash HT */while (de) {uint64_t h;nextde = de->next;/* Get the index in the new hash table */h = dictHashKey(d, de->key) & d->ht[1].sizemask;de->next = d->ht[1].table[h];d->ht[1].table[h] = de;d->ht[0].used--;d->ht[1].used++;de = nextde;} d->ht[0].table[d->rehashidx] = NULL;d->rehashidx++;} /* Check if we already rehashed the whole table... */if (d->ht[0].used == 0) {zfree(d->ht[0].table);d->ht[0] = d->ht[1];_dictReset(&d->ht[1]);d->rehashidx = -1;return 0;} /* More to rehash... */return 1;}1.2?跳躍表
是有序集合的底層實(shí)現(xiàn)之一。
跳躍表是基于多指針有序鏈表實(shí)現(xiàn)的,可以看成多個(gè)有序鏈表。
在查找時(shí),從上層指針開始查找,找到對(duì)應(yīng)的區(qū)間之后再到下一層去查找。下圖演示了查找 22 的過程。
與紅黑樹等平衡樹相比,跳躍表具有以下優(yōu)點(diǎn):
- 插入速度非常快速,因?yàn)椴恍枰M(jìn)行旋轉(zhuǎn)等操作來維護(hù)平衡性;
- 更容易實(shí)現(xiàn);
- 支持無鎖操作。
2. 使用場(chǎng)景
2.1 計(jì)數(shù)器
可以對(duì) String 進(jìn)行自增自減運(yùn)算,從而實(shí)現(xiàn)計(jì)數(shù)器功能。
Redis 這種內(nèi)存型數(shù)據(jù)庫的讀寫性能非常高,很適合存儲(chǔ)頻繁讀寫的計(jì)數(shù)量。
2.2 緩存
將熱點(diǎn)數(shù)據(jù)放到內(nèi)存中,設(shè)置內(nèi)存的最大使用量以及淘汰策略來保證緩存的命中率。
2.3 查找表
例如 DNS 記錄就很適合使用 Redis 進(jìn)行存儲(chǔ)。
查找表和緩存類似,也是利用了 Redis 快速的查找特性。
但是查找表的內(nèi)容不能失效,而緩存的內(nèi)容可以失效,因?yàn)榫彺娌蛔鳛榭煽康臄?shù)據(jù)來源。
2.4 消息隊(duì)列
List 是一個(gè)雙向鏈表,可以通過 lpop 和 lpush 寫入和讀取消息。
不過最好使用 Kafka、RabbitMQ 等消息中間件。
2.5 會(huì)話緩存
在分布式場(chǎng)景下具有多個(gè)應(yīng)用服務(wù)器,可以使用 Redis 來統(tǒng)一存儲(chǔ)這些應(yīng)用服務(wù)器的會(huì)話信息。
當(dāng)應(yīng)用服務(wù)器不再存儲(chǔ)用戶的會(huì)話信息,也就不再具有狀態(tài),一個(gè)用戶可以請(qǐng)求任意一個(gè)應(yīng)用服務(wù)器。
2.7 分布式鎖實(shí)現(xiàn)
在分布式場(chǎng)景下,無法使用單機(jī)環(huán)境下的鎖來對(duì)多個(gè)節(jié)點(diǎn)上的進(jìn)程進(jìn)行同步。
可以使用 Reids 自帶的 SETNX 命令實(shí)現(xiàn)分布式鎖,除此之外,還可以使用官方提供的 RedLock 分布式鎖實(shí)現(xiàn)。
2.8 其它
Set 可以實(shí)現(xiàn)交集、并集等操作,從而實(shí)現(xiàn)共同好友等功能。
ZSet 可以實(shí)現(xiàn)有序性操作,從而實(shí)現(xiàn)排行榜等功能。
3. Redis 與 Memcached
兩者都是非關(guān)系型內(nèi)存鍵值數(shù)據(jù)庫,主要有以下不同:
| 支持五種不同的數(shù)據(jù)類型,可以更靈活地解決問題。 | 僅支持字符串類型 |
| 兩種持久化策略:RDB 快照和 AOF 日志 | 不支持 |
| Redis Cluster 實(shí)現(xiàn)了分布式的支持 | 不支持(只能通過在客戶端使用一致性哈希來實(shí)現(xiàn)分布式存儲(chǔ),這種方式在存儲(chǔ)和查詢時(shí)都需要先在客戶端計(jì)算一次數(shù)據(jù)所在的節(jié)點(diǎn)) |
| 并不是所有數(shù)據(jù)都一直存儲(chǔ)在內(nèi)存中,可以將一些很久沒用的value 交換到磁盤 | 數(shù)據(jù)會(huì)一直在內(nèi)存中; 將內(nèi)存分割成特定長(zhǎng)度的塊來存儲(chǔ)數(shù)據(jù),以完全解決內(nèi)存碎片的問題,但是這種方式會(huì)使得內(nèi)存的利用率不高(例如塊的大小為 128 bytes,只存儲(chǔ) 100 bytes 的數(shù)據(jù),那么剩下的 28 bytes 就浪費(fèi)掉了) |
4.?鍵的過期時(shí)間
Redis 可以為每個(gè)鍵設(shè)置過期時(shí)間,當(dāng)鍵過期時(shí),會(huì)自動(dòng)刪除該鍵。
對(duì)于散列表這種容器,只能為整個(gè)鍵設(shè)置過期時(shí)間(整個(gè)散列表) ,而不能為鍵里面的單個(gè)元素設(shè)置過期時(shí)間。
5.?數(shù)據(jù)淘汰策略
可以設(shè)置內(nèi)存最大使用量,當(dāng)內(nèi)存使用量超出時(shí),會(huì)施行數(shù)據(jù)淘汰策略。
Reids 具體有 6 種淘汰策略:
| 從已設(shè)置過期時(shí)間的數(shù)據(jù)集中挑選最近最少使用的數(shù)據(jù)淘汰 |
| 從已設(shè)置過期時(shí)間的數(shù)據(jù)集中挑選將要過期的數(shù)據(jù)淘汰 |
| 從已設(shè)置過期時(shí)間的數(shù)據(jù)集中任意選擇數(shù)據(jù)淘汰 |
| 從所有數(shù)據(jù)集中挑選最近最少使用的數(shù)據(jù)淘汰 |
| 從所有數(shù)據(jù)集中任意選擇數(shù)據(jù)進(jìn)行淘汰 |
| 禁止驅(qū)逐數(shù)據(jù) |
作為內(nèi)存數(shù)據(jù)庫,出于對(duì)性能和內(nèi)存消耗的考慮,Redis 的淘汰算法實(shí)際實(shí)現(xiàn)上并非針對(duì)所有 key,而是抽樣一小部分并且從中選出被淘汰的 key。
使用 Redis 緩存數(shù)據(jù)時(shí),為了提高緩存命中率,需要保證緩存數(shù)據(jù)都是熱點(diǎn)數(shù)據(jù)。
可以將內(nèi)存最大使用量設(shè)置為熱點(diǎn)數(shù)據(jù)占用的內(nèi)存量,然后啟用 allkeys-lru 淘汰策略,將最近最少使用的數(shù)據(jù)淘汰。
Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通過統(tǒng)計(jì)訪問頻率,將訪問頻率最少的鍵值對(duì)淘汰。
6. 持久化
Redis 是內(nèi)存型數(shù)據(jù)庫,為了保證數(shù)據(jù)在斷電后不會(huì)丟失,需要將內(nèi)存中的數(shù)據(jù)持久化到硬盤上。
6.1 RDB 持久化
將某個(gè)時(shí)間點(diǎn)的所有數(shù)據(jù)都存放到硬盤上。
可以將快照復(fù)制到其它服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本。
如果系統(tǒng)發(fā)生故障,將會(huì)丟失最后一次創(chuàng)建快照之后的數(shù)據(jù)。
如果數(shù)據(jù)量很大,保存快照的時(shí)間會(huì)很長(zhǎng)。
6.2 AOF 持久化
將寫命令添加到 AOF 文件(Append Only File) 的末尾。
使用 AOF 持久化需要設(shè)置同步選項(xiàng),從而確保寫命令什么時(shí)候會(huì)同步到磁盤文件上。
這是因?yàn)閷?duì)文件進(jìn)行寫入并不會(huì)馬上將內(nèi)容同步到磁盤上,而是先存儲(chǔ)到緩沖區(qū),然后由操作系統(tǒng)決定什么時(shí)候同步到磁盤。有以下同步選項(xiàng):
| 每個(gè)寫命令都同步 |
| 每秒同步一次 |
| 讓操作系統(tǒng)來決定何時(shí)同步 |
?
- always 選項(xiàng)會(huì)嚴(yán)重減低服務(wù)器的性能;
- everysec 選項(xiàng)比較合適,可以保證系統(tǒng)崩潰時(shí)只會(huì)丟失一秒左右的數(shù)據(jù),并且
- Redis 每秒執(zhí)行一次同步對(duì)服務(wù)器性能幾乎沒有任何影響;
- no 選項(xiàng)并不能給服務(wù)器性能帶來多大的提升,而且也會(huì)增加系統(tǒng)崩潰時(shí)數(shù)據(jù)丟失的數(shù)量。
隨著服務(wù)器寫請(qǐng)求的增多,AOF 文件會(huì)越來越大。
Redis 提供了一種將 AOF 重寫的特性,能夠去除 AOF 文件中的冗余寫命令。
總結(jié)
以上是生活随笔為你收集整理的数据结构(字典,跳跃表)、使用场景(计数器、缓存、查找表、消息队列、会话缓存、分布式锁)、Redis 与 Memcached、 键的过期时间、数据淘汰策略、持久化(RDB、AOF)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis概述、数据类型
- 下一篇: linux cmake编译源码,linu