系列
redis數(shù)據(jù)淘汰原理
redis過期數(shù)據(jù)刪除策略
redis server事件模型
redis cluster mget 引發(fā)的討論
redis 3.x windows 集群搭建
redis 命令執(zhí)行過程
redis string底層數(shù)據(jù)結(jié)構(gòu)
redis list底層數(shù)據(jù)結(jié)構(gòu)
redis hash底層數(shù)據(jù)結(jié)構(gòu)
redis set底層數(shù)據(jù)結(jié)構(gòu)
redis zset底層數(shù)據(jù)結(jié)構(gòu)
redis 客戶端管理
redis 主從同步-slave端
redis 主從同步-master端
redis 主從超時檢測
redis aof持久化
redis rdb持久化
redis 數(shù)據(jù)恢復(fù)過程
redis TTL實現(xiàn)原理
redis cluster集群建立
redis cluster集群選主
TTL存儲的數(shù)據(jù)結(jié)構(gòu)
?redis針對TTL時間有專門的dict進行存儲,就是redisDb當中的dict *expires字段照棋,dict顧名思義就是一個hashtable饱溢,key為對應(yīng)的rediskey祈餐,value為對應(yīng)的TTL時間摄乒。
?dict的數(shù)據(jù)結(jié)構(gòu)中含有2個dictht對象蛹磺,主要是為了解決hash沖突過程中重新hash數(shù)據(jù)使用卸亮。
?dictEntry當中的dictEntry就是hashtable當中的hash桶叫榕,作用應(yīng)該不言自明了吧。
typedef struct redisDb {
// 數(shù)據(jù)庫鍵空間详炬,保存著數(shù)據(jù)庫中的所有鍵值對
dict *dict; /* The keyspace for this DB */
// 鍵的過期時間盐类,字典的鍵為鍵,字典的值為過期事件 UNIX 時間戳
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 */
struct evictionPoolEntry *eviction_pool; /* Eviction pool of keys */
// 數(shù)據(jù)庫號碼
int id; /* Database ID */
// 數(shù)據(jù)庫的鍵的平均 TTL ,統(tǒng)計信息
long long avg_ttl; /* Average TTL, just for stats */
} redisDb;
/*
* 字典
*/
typedef struct dict {
// 類型特定函數(shù)
dictType *type;
// 私有數(shù)據(jù)
void *privdata;
// 哈希表
dictht ht[2];
// rehash 索引
// 當 rehash 不在進行時在跳,值為 -1
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
// 目前正在運行的安全迭代器的數(shù)量
int iterators; /* number of iterators currently running */
} dict;
/*
* 哈希表
*
* 每個字典都使用兩個哈希表枪萄,從而實現(xiàn)漸進式 rehash 。
*/
typedef struct dictht {
// 哈希表數(shù)組
dictEntry **table;
// 哈希表大小
unsigned long size;
// 哈希表大小掩碼猫妙,用于計算索引值
// 總是等于 size - 1
unsigned long sizemask;
// 該哈希表已有節(jié)點的數(shù)量
unsigned long used;
} dictht;
TTL 設(shè)置過期時間
?TTL設(shè)置key過期時間的方法主要是下面4個:
- expire 按照相對時間且以秒為單位的過期策略
- expireat 按照絕對時間且以秒為單位的過期策略
- pexpire 按照相對時間且以毫秒為單位的過期策略
- pexpireat 按照絕對時間且以毫秒為單位的過期策略
{"expire",expireCommand,3,"w",0,NULL,1,1,1,0,0},
{"expireat",expireatCommand,3,"w",0,NULL,1,1,1,0,0},
{"pexpire",pexpireCommand,3,"w",0,NULL,1,1,1,0,0},
{"pexpireat",pexpireatCommand,3,"w",0,NULL,1,1,1,0,0},
expire expireat pexpire pexpireat
?從實際設(shè)置過期時間的實現(xiàn)函數(shù)來看瓷翻,相對時間的策略會有一個當前時間作為基準時間,絕對時間的策略會以0作為一個基準時間割坠。
void expireCommand(redisClient *c) {
expireGenericCommand(c,mstime(),UNIT_SECONDS);
}
void expireatCommand(redisClient *c) {
expireGenericCommand(c,0,UNIT_SECONDS);
}
void pexpireCommand(redisClient *c) {
expireGenericCommand(c,mstime(),UNIT_MILLISECONDS);
}
void pexpireatCommand(redisClient *c) {
expireGenericCommand(c,0,UNIT_MILLISECONDS);
}
?整個過期時間最后都會換算到絕對時間進行存儲逻悠,通過公式基準時間+過期時間來進行計算。
?對于相對時間而言基準時間就是當前時間韭脊,對于絕對時間而言相對時間就是0。
?中途考慮設(shè)置的過期時間是否已經(jīng)過期单旁,如果已經(jīng)過期那么在master就會刪除該數(shù)據(jù)并同步刪除動作到slave沪羔。
?正常的設(shè)置過期時間是通過setExpire方法保存到 dict *expires對象當中。
/*
*
* 這個函數(shù)是 EXPIRE 象浑、 PEXPIRE 蔫饰、 EXPIREAT 和 PEXPIREAT 命令的底層實現(xiàn)函數(shù)。
*
* 命令的第二個參數(shù)可能是絕對值愉豺,也可能是相對值篓吁。
* 當執(zhí)行 *AT 命令時, basetime 為 0 蚪拦,在其他情況下杖剪,它保存的就是當前的絕對時間。
*
* unit 用于指定 argv[2] (傳入過期時間)的格式驰贷,
* 它可以是 UNIT_SECONDS 或 UNIT_MILLISECONDS 盛嘿,
* basetime 參數(shù)則總是毫秒格式的。
*/
void expireGenericCommand(redisClient *c, long long basetime, int unit) {
robj *key = c->argv[1], *param = c->argv[2];
long long when; /* unix time in milliseconds when the key will expire. */
// 取出 when 參數(shù)
if (getLongLongFromObjectOrReply(c, param, &when, NULL) != REDIS_OK)
return;
// 如果傳入的過期時間是以秒為單位的括袒,那么將它轉(zhuǎn)換為毫秒
if (unit == UNIT_SECONDS) when *= 1000;
when += basetime;
/* No key, return zero. */
// 取出鍵
if (lookupKeyRead(c->db,key) == NULL) {
addReply(c,shared.czero);
return;
}
/*
* 在載入數(shù)據(jù)時次兆,或者服務(wù)器為附屬節(jié)點時,
* 即使 EXPIRE 的 TTL 為負數(shù)锹锰,或者 EXPIREAT 提供的時間戳已經(jīng)過期芥炭,
* 服務(wù)器也不會主動刪除這個鍵,而是等待主節(jié)點發(fā)來顯式的 DEL 命令恃慧。
*
* 程序會繼續(xù)將(一個可能已經(jīng)過期的 TTL)設(shè)置為鍵的過期時間园蝠,
* 并且等待主節(jié)點發(fā)來 DEL 命令。
*/
if (when <= mstime() && !server.loading && !server.masterhost) {
// when 提供的時間已經(jīng)過期糕伐,服務(wù)器為主節(jié)點砰琢,并且沒在載入數(shù)據(jù)
robj *aux;
redisAssertWithInfo(c,key,dbDelete(c->db,key));
server.dirty++;
/* Replicate/AOF this as an explicit DEL. */
// 傳播 DEL 命令
aux = createStringObject("DEL",3);
rewriteClientCommandVector(c,2,aux,key);
decrRefCount(aux);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",key,c->db->id);
addReply(c, shared.cone);
return;
} else {
// 設(shè)置鍵的過期時間
// 如果服務(wù)器為附屬節(jié)點,或者服務(wù)器正在載入,
// 那么這個 when 有可能已經(jīng)過期的
setExpire(c->db,key,when);
addReply(c,shared.cone);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"expire",key,c->db->id);
server.dirty++;
return;
}
}
?setExpire函數(shù)主要是對db->expires中的key對應(yīng)的dictEntry設(shè)置過期時間陪汽。
/*
* 將鍵 key 的過期時間設(shè)為 when
*/
void setExpire(redisDb *db, robj *key, long long when) {
dictEntry *kde, *de;
/* Reuse the sds from the main dict in the expire dict */
// 取出鍵
kde = dictFind(db->dict,key->ptr);
redisAssertWithInfo(NULL,key,kde != NULL);
// 根據(jù)鍵取出鍵的過期時間
de = dictReplaceRaw(db->expires,dictGetKey(kde));
// 設(shè)置鍵的過期時間
// 這里是直接使用整數(shù)值來保存過期時間训唱,不是用 INT 編碼的 String 對象
dictSetSignedIntegerVal(de,when);
}
TTL 獲取過期時間
?通過ttl或者pttl返回剩余過期時間的邏輯其實非常簡單,就是通過key去db->expires找到過期時間對象挚冤,然后與當前系統(tǒng)時間相比計算差值况增。
{"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0},
{"pttl",pttlCommand,2,"r",0,NULL,1,1,1,0,0},
void ttlCommand(redisClient *c) {
ttlGenericCommand(c, 0);
}
void pttlCommand(redisClient *c) {
ttlGenericCommand(c, 1);
}
/*
* 返回鍵的剩余生存時間。
*
* output_ms 指定返回值的格式:
*
* - 為 1 時训挡,返回毫秒
*
* - 為 0 時澳骤,返回秒
*/
void ttlGenericCommand(redisClient *c, int output_ms) {
long long expire, ttl = -1;
/* If the key does not exist at all, return -2 */
// 取出鍵
if (lookupKeyRead(c->db,c->argv[1]) == NULL) {
addReplyLongLong(c,-2);
return;
}
/* The key exists. Return -1 if it has no expire, or the actual
* TTL value otherwise. */
// 取出過期時間
expire = getExpire(c->db,c->argv[1]);
if (expire != -1) {
// 計算剩余生存時間
ttl = expire-mstime();
if (ttl < 0) ttl = 0;
}
if (ttl == -1) {
// 鍵是持久的
addReplyLongLong(c,-1);
} else {
// 返回 TTL
// (ttl+500)/1000 計算的是漸近秒數(shù)
addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000));
}
}
/*
* 返回給定 key 的過期時間。
*
* 如果鍵沒有設(shè)置過期時間澜薄,那么返回 -1 为肮。
*/
long long getExpire(redisDb *db, robj *key) {
dictEntry *de;
/* No expire? return ASAP */
// 獲取鍵的過期時間
// 如果過期時間不存在,那么直接返回
if (dictSize(db->expires) == 0 ||
(de = dictFind(db->expires,key->ptr)) == NULL) return -1;
/* The entry was found in the expire dict, this means it should also
* be present in the main dict (safety check). */
redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
// 返回過期時間
return dictGetSignedIntegerVal(de);
}