深入分析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 1
save 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 hash
hash-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.

数据运维技术 » 深入分析Redis多线程过期机制(redis过期 多线程)