Redis數(shù)據(jù)庫(kù)的鍵管理示例詳解
一、Redis 數(shù)據(jù)庫(kù)管理
Redis 是一個(gè)鍵值對(duì)(key-value pair)的數(shù)據(jù)庫(kù)服務(wù)器,其數(shù)據(jù)保存在 src/server.h/redisDb 中(網(wǎng)上很多帖子說(shuō)在 redis.h 文件中,但是 redis 6.x版本目錄中都沒(méi)有這個(gè)文件。redisDb 結(jié)構(gòu)應(yīng)該在 server.h文件中)
typedef redisServer {
....
// Redis數(shù)據(jù)庫(kù)
redisDb *db;
....
}Redis 默認(rèn)會(huì)創(chuàng)建 16 個(gè)數(shù)據(jù)庫(kù),每個(gè)數(shù)據(jù)庫(kù)是獨(dú)立互不影響。其默認(rèn)的目標(biāo)數(shù)據(jù)庫(kù)是 0 號(hào)數(shù)據(jù)庫(kù),可以通過(guò) select 命令來(lái)切換目標(biāo)數(shù)據(jù)庫(kù)。在 redisClient 結(jié)構(gòu)中記錄客戶端當(dāng)前的目標(biāo)數(shù)據(jù)庫(kù):
typedef struct redisClient {
// 套接字描述符
int fd;
// 當(dāng)前正在使用的數(shù)據(jù)庫(kù)
redisDb *db;
// 當(dāng)前正在使用的數(shù)據(jù)庫(kù)的 id (號(hào)碼)
int dictid;
// 客戶端的名字
robj *name; /* As set by CLIENT SETNAME */
} redisClient;下面是客戶端和服務(wù)器狀態(tài)之間的關(guān)系實(shí)例,客戶端的目標(biāo)數(shù)據(jù)庫(kù)目前為 1 號(hào)數(shù)據(jù)庫(kù):

通過(guò)修改 redisClient.db 的指針來(lái)指向不同數(shù)據(jù)庫(kù),這也就是 select 命令的實(shí)現(xiàn)原理。但是,到目前為止,Redis 仍然沒(méi)有可以返回客戶端目標(biāo)數(shù)據(jù)庫(kù)的命令。雖然在 redis-cli 客戶端中輸入時(shí)會(huì)顯示:
redis> SELECT 1 Ok redis[1]>
但是在其他語(yǔ)言客戶端沒(méi)有顯示目標(biāo)數(shù)據(jù)庫(kù)的號(hào)端,所以在頻繁切換數(shù)據(jù)庫(kù)后,會(huì)導(dǎo)致忘記目前使用的是哪一個(gè)數(shù)據(jù)庫(kù),也容易產(chǎn)生誤操作。因此要謹(jǐn)慎處理多數(shù)據(jù)庫(kù)程序,必須要執(zhí)行時(shí),可以先顯示切換指定數(shù)據(jù)庫(kù),然后再執(zhí)行別的命令。
二、Redis 數(shù)據(jù)庫(kù)鍵
2.1 數(shù)據(jù)庫(kù)鍵空間
Redis 服務(wù)器中的每一個(gè)數(shù)據(jù)庫(kù)是由一個(gè) server.h/redisDb 結(jié)構(gòu)來(lái)表示的,其具體結(jié)構(gòu)如下:
typedef struct redisDb {
//數(shù)據(jù)庫(kù)鍵空間
dict *dict; /* The keyspace for this DB */
//鍵的過(guò)期時(shí)間,字典的值為過(guò)期事件 UNIX 時(shí)間戳
dict *expires; /* Timeout of keys with a timeout set */
//正處于阻塞狀態(tài)的鍵
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
//可以解除阻塞的鍵
dict *ready_keys; /* Blocked keys that received a PUSH */
//正在被 WATCH 命令監(jiān)視的鍵
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
//數(shù)據(jù)庫(kù)號(hào)端
int id; /* Database ID */
//數(shù)據(jù)庫(kù)鍵的平均 TTL,統(tǒng)計(jì)信息
long long avg_ttl; /* Average TTL, just for stats */
//
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;鍵空間和用戶所見(jiàn)的數(shù)據(jù)庫(kù)是直接對(duì)應(yīng):
- 鍵空間的 key 就是數(shù)據(jù)庫(kù)的 key, 每個(gè) key 都是一個(gè)字符串對(duì)象
- 鍵空間的 value 是數(shù)據(jù)庫(kù)的 value, 每個(gè) value 可以是字符串對(duì)象、列表對(duì)象和集合對(duì)象等等任意一種 Redis 對(duì)象
舉個(gè)實(shí)例,若在空白數(shù)據(jù)庫(kù)中執(zhí)行一下命令:插入字符串對(duì)象、列表對(duì)象和哈希對(duì)象
# 插入一個(gè)字符串對(duì)象 redis> SET message "hello world" OK # 插入包含三個(gè)元素的列表對(duì)象 redis> RPUSH alphabet "a" "b" "c" (integer)3 # 插入包含三個(gè)元素的哈希表對(duì)象 redis> HSET book name "Redis in Action" (integer) 1 redis> HSET book author "Josiah L. Carlson" (integer) 1 redis> HSET book publisher "Manning" (integer) 1

所以說(shuō) redis 對(duì)數(shù)據(jù)的增刪改查是通過(guò)操作 dict 來(lái)操作 Redis 中的數(shù)據(jù)
2.2 數(shù)據(jù)庫(kù)鍵的過(guò)期
我們可以通過(guò)兩種方式設(shè)置鍵的生命周期:
通過(guò) EXPIRE 或者 PEXPIRE 命令來(lái)為數(shù)據(jù)庫(kù)中的某個(gè)鍵設(shè)置生存時(shí)間(TTL,Time To Live)。在經(jīng)過(guò) TTL 個(gè)生存時(shí)間后,服務(wù)器會(huì)自動(dòng)刪除生存時(shí)間為0 的鍵。比如:
redis> set key value OK # 設(shè)置鍵的 TTL 為 5 redis> EXPIRE key 5 (integer)1
此外,客戶端也可以通過(guò) EXPIREAT 或者PEXPIREAT 命令,為數(shù)據(jù)庫(kù)中的某個(gè)鍵設(shè)置過(guò)期時(shí)間(expire time)。過(guò)期時(shí)間是一個(gè) UNIX 時(shí)間戳,當(dāng)過(guò)期時(shí)間來(lái)臨時(shí),服務(wù)器就會(huì)自動(dòng)從數(shù)據(jù)庫(kù)中刪除這個(gè)鍵。比如
redis> SET key value OK redis> EXPIREAT key 1377257300 (integer) 1 # 當(dāng)前系統(tǒng)時(shí)間 redis> TIME 1)"1377257296" # 過(guò)一段時(shí)間后,再查詢key redis> GET key // 1377257300 (nil)
2.2.1 過(guò)期時(shí)間
redisDb 中的dict *dict 和 dict *expires 字典 分別保存了數(shù)據(jù)庫(kù)中的鍵和鍵的過(guò)期時(shí)間,分別叫做鍵空間和過(guò)期字典。
- 過(guò)期字典的鍵是一個(gè)指向鍵空間中的某個(gè)鍵對(duì)象
- 過(guò)期字典的值是一個(gè) long long 類型的整數(shù),這個(gè)整數(shù)保存了鍵所指向的數(shù)據(jù)庫(kù)鍵的過(guò)期時(shí)間
2.3 過(guò)期鍵的刪除策略
對(duì)于已經(jīng)過(guò)期的數(shù)據(jù)是如何刪除這些過(guò)期鍵的呢?主要有兩種方式:惰性刪除和定期刪除:
1.惰性刪除
是指 Redis 服務(wù)器不主動(dòng)刪除過(guò)期的鍵值,而是通過(guò)訪問(wèn)鍵值時(shí),檢查當(dāng)前的鍵值是否過(guò)期
- 如果過(guò)期則執(zhí)行刪除并返回 null
- 沒(méi)有過(guò)期則正常訪問(wèn)值信息給客戶端
惰性刪除的源碼在 src/db.c/expireIfNeeded 方法中
int expireIfNeeded(redisDb *db, robj *key) {
// 判斷鍵是否過(guò)期
if (!keyIsExpired(db,key)) return 0;
if (server.masterhost != NULL) return 1;
/* 刪除過(guò)期鍵 */
// 增加過(guò)期鍵個(gè)數(shù)
server.stat_expiredkeys++;
// 傳播鍵過(guò)期的消息
propagateExpire(db,key,server.lazyfree_lazy_expire);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",key,db->id);
// server.lazyfree_lazy_expire 為 1 表示異步刪除,否則則為同步刪除
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
}
// 判斷鍵是否過(guò)期
int keyIsExpired(redisDb *db, robj *key) {
mstime_t when = getExpire(db,key);
if (when < 0) return 0;
if (server.loading) return 0;
mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
return now > when;
}
// 獲取鍵的過(guò)期時(shí)間
long long getExpire(redisDb *db, robj *key) {
dictEntry *de;
if (dictSize(db->expires) == 0 ||
(de = dictFind(db->expires,key->ptr)) == NULL) return -1;
serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
return dictGetSignedIntegerVal(de);
}2.定期刪除
與惰性刪除不同,定期刪除是指 Redis 服務(wù)器會(huì)每隔一段時(shí)間就會(huì)檢查一下數(shù)據(jù)庫(kù),看看是否有過(guò)期鍵可以清除,默認(rèn)情況下,Redis 定期檢查的頻率是每秒掃描 10 次,這個(gè)值在 redis.conf 中的 "hz" , 默認(rèn)是 10 ,可以進(jìn)行修改。
定期刪除的掃描并不是遍歷所有的鍵值對(duì),這樣的話比較費(fèi)時(shí)且太消耗系統(tǒng)資源。Redis 服務(wù)器采用的是隨機(jī)抽取形式,每次從過(guò)期字典中,取出 20 個(gè)鍵進(jìn)行過(guò)期檢測(cè),過(guò)期字典中存儲(chǔ)的是所有設(shè)置了過(guò)期時(shí)間的鍵值對(duì)。如果這批隨機(jī)檢查的數(shù)據(jù)中有 25% 的比例過(guò)期,那么會(huì)再抽取 20 個(gè)隨機(jī)鍵值進(jìn)行檢測(cè)和刪除,并且會(huì)循環(huán)執(zhí)行這個(gè)流程,直到抽取的這批數(shù)據(jù)中過(guò)期鍵值小于 25%,此次檢測(cè)才算完成。
定期刪除的源碼在 expire.c/activeExpireCycle 方法中:
void activeExpireCycle(int type) {
static unsigned int current_db = 0; /* 上次定期刪除遍歷到的數(shù)據(jù)庫(kù)ID */
static int timelimit_exit = 0;
static long long last_fast_cycle = 0; /* 上次執(zhí)行定期刪除的時(shí)間點(diǎn) */
int j, iteration = 0;
int dbs_per_call = CRON_DBS_PER_CALL; // 需要遍歷數(shù)據(jù)庫(kù)的數(shù)量
long long start = ustime(), timelimit, elapsed;
if (clientsArePaused()) return;
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
if (!timelimit_exit) return;
// ACTIVE_EXPIRE_CYCLE_FAST_DURATION 快速定期刪除的執(zhí)行時(shí)長(zhǎng)
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
last_fast_cycle = start;
}
if (dbs_per_call > server.dbnum || timelimit_exit)
dbs_per_call = server.dbnum;
// 慢速定期刪除的執(zhí)行時(shí)長(zhǎng)
timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
timelimit_exit = 0;
if (timelimit <= 0) timelimit = 1;
if (type == ACTIVE_EXPIRE_CYCLE_FAST)
timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* 刪除操作花費(fèi)的時(shí)間 */
long total_sampled = 0;
long total_expired = 0;
for (j = 0; j < dbs_per_call && timelimit_exit == 0; j++) {
int expired;
redisDb *db = server.db+(current_db % server.dbnum);
current_db++;
do {
// .......
expired = 0;
ttl_sum = 0;
ttl_samples = 0;
// 每個(gè)數(shù)據(jù)庫(kù)中檢查的鍵的數(shù)量
if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
// 從數(shù)據(jù)庫(kù)中隨機(jī)選取 num 個(gè)鍵進(jìn)行檢查
while (num--) {
dictEntry *de;
long long ttl;
if ((de = dictGetRandomKey(db->expires)) == NULL) break;
ttl = dictGetSignedInteger
// 過(guò)期檢查,并對(duì)過(guò)期鍵進(jìn)行刪除
if (activeExpireCycleTryExpire(db,de,now)) expired++;
if (ttl > 0) {
ttl_sum += ttl;
ttl_samples++;
}
total_sampled++;
}
total_expired += expired;
if (ttl_samples) {
long long avg_ttl = ttl_sum/ttl_samples;
if (db->avg_ttl == 0) db->avg_ttl = avg_ttl;
db->avg_ttl = (db->avg_ttl/50)*49 + (avg_ttl/50);
}
if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
elapsed = ustime()-start;
if (elapsed > timelimit) {
timelimit_exit = 1;
server.stat_expired_time_cap_reached_count++;
break;
}
}
/* 判斷過(guò)期鍵刪除數(shù)量是否超過(guò) 25% */
} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
}
// .......
}以上就是Redis 的刪除策略。下面來(lái)看一個(gè)面試題:
面試題:你知道 Redis 內(nèi)存淘汰策略和鍵的刪除策略的區(qū)別嗎?
Redis 內(nèi)存淘汰策略
我們可以通過(guò) config get maxmemory-policy 命令來(lái)查看當(dāng)前 Redis 的內(nèi)存淘汰策略:
127.0.0.1:6379> config get maxmemory-policy 1) "maxmemory-policy" 2) "noeviction"
當(dāng)前服務(wù)器設(shè)置的是 noeviction 類型的,對(duì)于 redis 6.x版本,主要有以下幾種內(nèi)存淘汰策略
- noeviction:不淘汰任何數(shù)據(jù),當(dāng)內(nèi)存不足時(shí),執(zhí)行緩存新增操作會(huì)報(bào)錯(cuò),它是 Redis 默認(rèn)內(nèi)存淘汰策略。
- allkeys-lru:淘汰整個(gè)鍵值中最久未使用的鍵值。
- allkeys-random:隨機(jī)淘汰任意鍵值。
- volatile-lru:淘汰所有設(shè)置了過(guò)期時(shí)間的鍵值中最久未使用的鍵值。
- volatile-random:隨機(jī)淘汰設(shè)置了過(guò)期時(shí)間的任意鍵值。
- volatile-ttl:優(yōu)先淘汰更早過(guò)期的鍵值。
- volatile-lfu: 淘汰所有設(shè)置了過(guò)期時(shí)間的鍵值中最少使用的鍵值。
- alkeys-lfu: 淘汰整個(gè)鍵值中最少使用的鍵值
也就是 alkeys 開(kāi)頭的表示從所有鍵值中淘汰相關(guān)數(shù)據(jù),而 volatile 表示從設(shè)置了過(guò)期鍵的鍵值中淘汰數(shù)據(jù)。
Redis 內(nèi)存淘汰算法
內(nèi)存淘汰算法主要分為 LRU 和 LFU 淘汰算法
LRU(Least Recently Used) 淘汰算法
是一種常用的頁(yè)面置換算法,LRU 是基于鏈表結(jié)構(gòu)實(shí)現(xiàn),鏈表中的元素按照操作順序從前往后排列。最新操作的鍵會(huì)被移動(dòng)到表頭,當(dāng)需要進(jìn)行內(nèi)存淘汰時(shí),只需要?jiǎng)h除鏈表尾部的元素。
Redis 使用的是一種近似 LRU 算法,目的是為了更好的節(jié)約內(nèi)存,給現(xiàn)有的數(shù)據(jù)結(jié)構(gòu)添加一個(gè)額外的字段,用于記錄此鍵值的最后一次訪問(wèn)時(shí)間。Redis 內(nèi)存淘汰時(shí),會(huì)使用隨機(jī)采樣的方式來(lái)淘汰數(shù)據(jù),隨機(jī)取5個(gè)值,然后淘汰最久沒(méi)有使用的數(shù)據(jù)。
LFU(Least Frequently Used)淘汰算法
根據(jù)總訪問(wèn)次數(shù)來(lái)淘汰數(shù)據(jù),核心思想是如果數(shù)據(jù)過(guò)去被訪問(wèn)多次,那么將來(lái)被訪問(wèn)的頻率也更高
以上就是Redis數(shù)據(jù)庫(kù)的鍵管理示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Redis數(shù)據(jù)庫(kù)鍵管理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redis消息隊(duì)列的三種實(shí)現(xiàn)方式
本文主要介紹了Redis消息隊(duì)列的三種實(shí)現(xiàn)方式,主要包括List實(shí)現(xiàn)消息隊(duì)列,PubSub消息隊(duì)列,Stream消息隊(duì)列,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12
使用Redis實(shí)現(xiàn)用戶積分排行榜的教程
這篇文章主要介紹了使用Redis實(shí)現(xiàn)用戶積分排行榜的教程,包括一個(gè)用PHP腳本進(jìn)行操作的例子,需要的朋友可以參考下2015-04-04
Windows環(huán)境下安裝Redis并設(shè)置Redis開(kāi)機(jī)自啟實(shí)踐
文章介紹了如何在Windows系統(tǒng)上安裝和配置Redis,包括下載Windows版本的Redis、設(shè)置連接密碼、啟動(dòng)Redis、設(shè)置開(kāi)機(jī)自啟等步驟2025-12-12
如何高效地向Redis插入大量的數(shù)據(jù)(推薦)
本篇文章主要介紹了如何高效地向Redis插入大量的數(shù)據(jù),現(xiàn)在分享給大家,感興趣的小伙伴們可以參考一下。2016-11-11
Redis報(bào)錯(cuò)NOAUTH?Authentication?required簡(jiǎn)單解決辦法
這篇文章主要給大家介紹了關(guān)于Redis報(bào)錯(cuò)NOAUTH?Authentication?required的簡(jiǎn)單解決辦法,Redis無(wú)密碼報(bào)錯(cuò)NOAUTH Authentication required的原因是客戶端訪問(wèn)Redis時(shí)需要提供密碼,但是沒(méi)有提供或提供的密碼不正確,需要的朋友可以參考下2024-05-05

