redis之key过期源码分析
简述
redis的所有数据结构都可以设置过期时间,当key过期后再查询该key返回null。
redis实现key自动过期是通过额外保存需要自动过期的key和该key的过期时间,然后通过主动删除和定时任务删除两种机制来将过期的key移除并回收内存。在redis4.0版本引入了异步删除的机制,对于删除对象大小大于64字节的key,先通过Unlink
方法软删除后放入回收队列中,由其他线程异步回收内存空间,减少主线程的在内存回收上消耗的时间。
1 | typedef struct redisDb { |
设置过期
redis添加自动过期的key时,先向hash结构数据redisDb.dict
添加该key和key对应的值。添加成功后,调用setExpire
方法向hash结构数据redisDb.expires
添加该key和key对应的过期时间,过期时间的单位为毫秒。
1 | void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) { |
自动过期
过期key的删除回收分为主动删除和定时删除两种策略:
- 主动删除:对key删改查操作时先判断该key是否已过期,如过期则删除回收该key
- 定时删除:主线程每100毫秒从
redisDb.expires
中随机选择20个key,删除其中已过期的key。如果过期的key的比例超过1/4则重复该操作直到达到时间上限(默认25毫秒)
主动删除
对于key的删改查操作,先查询hash表redisDb.expires
判断该key是否已经过期,如果key已过期,则默认调用dbAsyncDelete
方法异步删除回收该key的内存空间;若key未过期,继续进行删改查操作。
另外在redis主从架构下,从节点不处理过期机制,通过等待主节点的指令直接删除对应的key。因此当网络延迟较大时,存在主节点中已过期的key能在从节点查询出来的问题。
1 | robj *lookupKeyRead(redisDb *db, robj *key) { |
定时删除
databasesCron
方法由主线程每100毫秒调用一次,其中的activeExpireCycle
方法则是删除回收过期key的关键方法,其入参有两个值:ACTIVE_EXPIRE_CYCLE_FAST
和ACTIVE_EXPIRE_CYCLE_SLOW
:
ACTIVE_EXPIRE_CYCLE_FAST
:表示快速删除回收过期key,该场景下每次删除回收的时间上限为1毫秒,当主线程处理完外部请求后等待新请求前阻塞时会使用该参数;ACTIVE_EXPIRE_CYCLE_SLOW
:表示慢删除回收过期key,该场景下每次删除回收的时间上限为25毫秒,当主线程每100毫秒执行定时任务时使用该参数;
由于主线程每100毫秒会调用一次activeExpireCycle
方法回收过期key,因此存在极端情况下同一时刻redis中大量数据同时过期,会导致每100毫秒一次的定时任务activeExpireCycle
需要花费25毫秒的时间删除回收过期key,从而出现客户端请求等待阻塞的情况。
1 | void databasesCron(void) { |
异步删除
在redis4.0版本默认调用dbAsyncDelete
方法删除回收key,但实际上dbAsyncDelete
会判断该key的对象大小,如果key的对象大小超过64字节时才会真正使用异步删除逻辑,将该key放入BIO队列由其他线程删除回收内存空间;否则仍然使用同步删除逻辑直接回收内存空间。
1 |
|