深入分析Redis多线程过期机制(redis过期 多线程)
Redis是一种高性能键值存储系统,以其快速、简单的功能而受到开发人员的喜爱。但是在高并发环境下,Redis可能会出现过期键删除不及时的问题。为了解决这个问题,Redis通过多线程机制来实现过期键的删除。在本文中,我们将深入分析Redis的多线程过期机制,探索其中的原理和使用方法。
一、Redis的过期键删除机制
Redis通过使用定期器和惰性删除机制来处理过期键的删除问题。定期器是一个定时器,每隔一段时间就会遍历整个键空间,判断键是否过期,如果键过期,则删除该键。但是,定期器的运行时间比较随机,可能会造成过期键删除不及时的问题。因此,Redis引入了惰性删除机制。当客户端请求一个过期键时,Redis会检查该键是否过期,如果过期,则删除该键。这种方式虽然有效,但是仍然可能造成一定的延迟。因此,Redis引入了多线程过期机制来提高过期键删除的效率。
二、Redis的多线程过期机制
Redis的多线程过期机制是使用多线程来实现过期键的删除。当需要删除一个过期键时,Redis会将该键添加到一个专门的过期键列表中。过期键列表是一个内存缓冲区,Redis会对这个缓冲区进行定期刷盘,把列表中的过期键持久化到磁盘。在实际删除过期键时,Redis会启动多个后台线程来处理过期键列表中的键。当一个线程完成一个键的删除任务时,会重新从过期键列表中获取一个键并开始删除。这种方式可以充分利用CPU资源,提高过期键删除的效率。
三、Redis的过期键删除过程
1. 添加过期键
当一个键过期时,Redis会将该键添加到过期键列表中,以备后续删除。过期键列表是一个FIFO队列。
“`c
//redis/src/db.c
int expireIfNeeded(redisDb *db, robj *key) {
time_t when = getExpire(db,key);
mstime_t now;
long long delta;
/* 剩余时间 */
if (when
now = mstime();
if (now
/* 添加过期键 */
delta = (long long)(when-now);
/* milliseconds级别 */
addReplyProto(c,shared.colon,cshared.colonlen);
addReplyLongLong(c, delta);
addReplyNewline(c);
/* Add the key to the expiring_keys list if needed. */
if (server.active_expire_enabled) {
if (dictAdd(db->expires,key,(void*)REDIS_EXPIRE_DONT_SET) == DICT_OK) {
incrRefCount(key);
listAddNodeHead(server.delKeysSchedule, key);
}
}
return 1;
}
2. 定期刷盘
为了避免系统宕机导致过期键列表丢失,Redis会对过期键列表进行定期刷盘操作。定期刷盘可以通过配置参数来设置,配置文件中的相关配置如下:
```bash################################ SNAPSHOTTING ################################
# save
save 900 1save 300 10
save 60 10000#
# By default Redis will stop accepting writes if RDB snapshots are enabled# (at least one save point) and the latest background save fled.
# This will make the user aware (in a hard way) that data is not persisting# on disk properly, otherwise chances are that no one will notice and some
# disaster will happen.#
# If the background save process will start working agn Redis will# automatically allow writes agn.
## However if you have multiple Redis instances, disable this feature since
# it could trigger a network partition flure. stop-writes-on-bgsave-error yes
# # Compress string objects using LZF when dump .rdb databases?
# For default that's set to 'yes' as it's almost always a win.# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.rdbcompression yes
在定期器中,Redis会对过期键列表进行定时抽取和持久化操作。如下:
“`c
//redis/src/ae.c
void aeTimerProc(struct aeEventLoop *eventLoop, long long id, void *clientData) {
int j;
char buf[64];
if (cluster.enabled) clusterCron();
if (server.lua_timedout) handleLuaTimeout();
if (server.cluster_enabled) {
/* Make sure nodes are pinging or having PONG replies on time. */
clusterCheckPingTimeouts();
/* If a new node was recently added, join it to the cluster. */
clusterJoinCluster();
}
/* 定期器任务 */
serverCron();
/* 更新系统时间 */
server.lruclock = getLRUClock();
/* Increment the fast memory allocator’s internal clock. */
if (server.use_tcmalloc) {
memory_tcmalloc_release_free_memory();
}
/* 刷盘 */
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
for (j = 0; j
struct saveparam *sp = server.saveparams+j;
time_t now = time(NULL);
char *reason;
int retval;
if (sp->lastsave
(now – sp->lastsave > sp->seconds ||
dirty >= sp->changes)) {
serverLog(LL_NOTICE,”Saving (%s)%s DB due to %s…”,
sp->forced?(“forced “):””,sp->kind,
sp->changes?”changes”:”time”);
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
reason = (sp->changes && sp->seconds) ?
“of DB changes exceed” : “of elapsed time”;
retval = rdbSaveBackground(server.rdb_filename);
if (retval == REDIS_OK) {
server.dirty = 0;
server.lastsave = time(NULL);
snprintf(buf,sizeof(buf),”%ld”,server.lastsave);
setGlobalKey(“redis:lastsave”,createStringObject(buf,strlen(buf)));
server.lastbgsave_status = REDIS_OK;
server.lastbgsave_time_start = server.unixtime;
server.lastbgsave_time_end = (time_t)-1;
if (sp->forced) serverLog(LL_WARNING,”Background save terminated by signal %d”, WTERMSIG(retval));
} else {
server.lastbgsave_status = REDIS_ERR;
if (sp->forced) {
serverLog(LL_WARNING,”Background save terminated by signal %d”, WTERMSIG(retval));
} else {
serverLog(LL_WARNING,”Background saving error”);
}
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
if (retval == REDIS_EIO) serverLog(LL_WARNING,”IO error background saving DB, can’t persist!”);
else if (retval == REDIS_EPERM) {
serverLog(LL_WARNING,”No permission to write on the disk, can’t persist! “\
“Check the permissions and ownership of the dump.rdb file and its parent directory.”);
} else {
serverLog(LL_WARNING,”Unknown error background saving DB”);
}
}
}
}
break;
}
}
}
/* Best effort handle of signals received by the parent while wting
* child processes in background. */
handleChildrenSignals();
}
3. 后台删除过期键
在Redis多线程过期机制中,后台线程是负责删除过期键的工作线程。后台线程的数量可以通过配置文件中的`maxmemory-samples`参数来设置,该参数也可作为过期键列表的长度。配置文件中的相关配置如下:
```bash################################## MEMORY MANAGEMENT ###################################
# Max number of fields per Redis hashhash-max-ziplist-entries 512
# Max bytes of strings encoded with Redis on-heap datastructures# (e.g. 'set foo "bar"') & hash keys. The limit is set (by default)
# at 512MB which is an insane amount. To use it a server with a lot# of memory is needed, but such a server would waste a lot of memory
# if not used.