最近项目上出现了一个无法理解的BUG,用户默认每天享有一定次数的权限,使用完毕则无法享用,第二天才能再继续。本质就是redis缓存过期嘛,让它凌晨12点失效就好了。
但是问题发生了,它就是没失效...深究其原因,竟是由于零界点没处理好的锅,服务器时间与request时间是有些许时间差的,key的expire到了一定数量处理也是需要时间哒,就这俩主因。加个存在判断,失效给他提前个10秒留个准备就好了~
所以吸取教训,好好了解一下这个失效机理,同时也提醒各位别范这种低级错误。
一。如何才能触发key的失效?
除了调用PERSIST命令外,还有没有其他情况会撤销一个主键的失效时间"color: #ff0000">二。Redis是如何管理和维护主键的
1).Redis的存储结构
typedef struct redisDb { dict *dict;//存储主键和值的映射 dict *expires;//存储主键和过期时间的映射 dict *blocking_keys; dict *ready_keys; dict *watched_keys; int id; } redisDb;
主要看前两个结构就好,
dict:用来维护一个 Redis 数据库中包含的所有 Key-Value 对(其结构可以理解为 dict[key]:value,即主键与值之间的映射)
expires:用于维护一个 Redis 数据库中设置了失效时间的主键(其结构可以理解为 expires[key]:timeout,即主键与失效时间的映射)。
设置了失效时间的主键和具体的失效时间全部都维护在 expires 这个字典表中
当我们使用 EXPIRE、EXPIREAT、PEXPIRE 和 PEXPIREAT 命令设置一个主键的失效时间时,
Redis 首先到 dict 这个字典表中查找要设置的主键是否存在,如果存在就将这个主键和失效时间添加到 expires 这个字典表。
2).消极方法
1.expireIfNeeded函数
触发:这个函数在任何访问数据的函数中都会被调用,Redis 在实现 GET、MGET、HGET、LRANGE 等所有涉及到读取数据的命令时都会调用它
意义:在读取数据之前先检查一下该key有没有失效,如果失效了就删除它。
2.propagateExpire函数(在上边一个函数中调用) 主要函数
触发:执行上一个函数时,它在其里边
意义:用来在正式删除失效主键之前广播这个主键已经失效的信息
操作:
(1).一个是发送到 AOF文件,将删除失效主键的这一操作以 DEL Key 的标准命令格式记录下来;
(2).发送到当前 Redis 服务器的所有 Slave,同样将删除失效主键的这一操作以 DEL Key 的标准命令格式告知这些 Slave 删除各自的失效主键。
以上我们了解了 Redis 是如何以一种消极的方式删除失效主键的,但是仅仅通过这种方式显然是不够的,因为如果某些失效的主键迟迟等不到再次访问的话,Redis 就永远不会知道这些主键已经失效,也就永远也不会删除它们了,这无疑会导致内存空间的浪费。所以有了下边的方法。
3).积极方法:(该方法利用 Redis 的时间事件来实现,即每隔一段时间就中断一下完成一些指定操作)
1.serverCron函数:
触发:它在 Redis 服务器启动时创建
作用:这个回调函数不仅要进行失效主键的检查与删除,还要进行统计信息的更新、客户端连接超时的控制、BGSAVE 和 AOF 的触发等等
2.activeExpireCycle函数:
触发:执行上一个函数时,它在其里边每秒的执行次数由宏定义 【REDIS_DEFAULT_HZ】 来指定,默认每秒钟执行10次。
操作:
a).遍历处理 Redis 服务器中每个数据库的 expires 字典表中,从中尝试着随机抽样【REDIS_EXPIRELOOKUPS_PER_CRON】(默认 值为10)个设置了失效时间的主键,
b).检查它们是否已经失效并删除掉失效的主键,
c).如果失效的主键个数占本次抽样个数的比例超过25%,Redis 会认为当前数据库中的失效主键依然很多,所以它会继续进行下一轮的随机抽样和删除,
d).直到刚才的比例低于25%才停止对当前数据库的处理,转向下一个数据库。
其他:activeExpireCycle 函数避免失效主键删除占用过多的CPU资源,所以其不会试图一次性处理Redis中的所有数据库,而是最多只处理 REDIS_DBCRON_DBS_PER_CALL(默认值为16)个库,有处理时间上的限制
三。Memcached 删除失效主键的方法与 Redis 有何异同"color: #ff0000">四。总结:
redis每秒执行10次过期检查,每次中,随机从某个库的expire表中抽取10个key,检测他是否失效,若失效则删除。当失效比例超过1/4,本次重新执行随机抽取10key,不计入10次中的1次,直到这一秒的10次都执行完。
问:
那么有人问了,万一,万一!失效的key做足够的多,1秒的这10次都没执行完又到下一秒了,咋整?
答:
redis有检测机制的,不会让它把CPU拖死的:
a.每次处理数据库个数的限制、
b.activeExpireCycle 函数在一秒钟内执行次数的限制、
c.分配给 activeExpireCycle函数CPU时间的限制、
d.继续删除主键的失效主键数百分比的限制,Redis 已经大大降低了主键失效机制对系统整体性能的影响,
所以由此也可得,设置失效时间的原则:尽可能避免在同一时间点的大批量key失效,它是需要处理时间的。
消极失效主要函数.propagateExpire函数:
void propagateExpire(redisDb *db, robj *key) { robj *argv[2]; //shared.del是在Redis服务器启动之初就已经初始化好的一个常用Redis对象,即DEL命令 argv[0] = shared.del; argv[1] = key; incrRefCount(argv[0]); incrRefCount(argv[1]); //检查Redis服务器是否开启了AOF,如果开启了就为失效主键记录一条DEL日志 if (server.aof_state != REDIS_AOF_OFF) feedAppendOnlyFile(server.delCommand,db->id,argv,2); //检查Redis服务器是否拥有Slave,如果是就向所有Slave发送DEL失效主键的命令,这就是 //上面expireIfNeeded函数中发现自己是Slave时无需主动删除失效主键的原因了,因为它 //只需听从Master发送过来的命令就OK了 if (listLength(server.slaves)) replicationFeedSlaves(server.slaves,db->id,argv,2); decrRefCount(argv[0]); decrRefCount(argv[1]); }
积极失效主要函数.activeExpireCycle函数:
void activeExpireCycle(void) { //因为每次调用activeExpireCycle函数不会一次性检查所有Redis数据库,所以需要记录下 //每次函数调用处理的最后一个Redis数据库的编号,这样下次调用activeExpireCycle函数 //还可以从这个数据库开始继续处理,这就是current_db被声明为static的原因,而另外一 //个变量timelimit_exit是为了记录上一次调用activeExpireCycle函数的执行时间是否达 //到时间限制了,所以也需要声明为static static unsigned int current_db = 0; static int timelimit_exit = 0; unsigned int j, iteration = 0; //每次调用activeExpireCycle函数处理的Redis数据库个数为REDIS_DBCRON_DBS_PER_CALL unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL; long long start = ustime(), timelimit; //如果当前Redis服务器中的数据库个数小于REDIS_DBCRON_DBS_PER_CALL,则处理全部数据库, //如果上一次调用activeExpireCycle函数的执行时间达到了时间限制,说明失效主键较多,也 //会选择处理全部数据库 if (dbs_per_call > server.dbnum || timelimit_exit) dbs_per_call = server.dbnum; //执行activeExpireCycle函数的最长时间(以微秒计),其中REDIS_EXPIRELOOKUPS_TIME_PERC //是单位时间内能够分配给activeExpireCycle函数执行的CPU时间比例,默认值为25,server.hz //即为一秒内activeExpireCycle的调用次数,所以这个计算公式更明白的写法应该是这样的,即 (1000000 * (REDIS_EXPIRELOOKUPS_TIME_PERC / 100)) / server.hz timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/server.hz/100; timelimit_exit = 0; if (timelimit <= 0) timelimit = 1; //遍历处理每个Redis数据库中的失效数据 for (j = 0; j < dbs_per_call; j++) { int expired; redisDb *db = server.db+(current_db % server.dbnum); //此处立刻就将current_db加一,这样可以保证即使这次无法在时间限制内删除完所有当前 //数据库中的失效主键,下一次调用activeExpireCycle一样会从下一个数据库开始处理, //从而保证每个数据库都有被处理的机会 current_db++; //开始处理当前数据库中的失效主键 do { unsigned long num, slots; long long now; //如果expires字典表大小为0,说明该数据库中没有设置失效时间的主键,直接检查下 //一数据库 if ((num = dictSize(db->expires)) == 0) break; slots = dictSlots(db->expires); now = mstime(); //如果expires字典表不为空,但是其填充率不足1%,那么随机选择主键进行检查的代价 //会很高,所以这里直接检查下一数据库 if (num && slots > DICT_HT_INITIAL_SIZE && (num*100/slots < 1)) break; expired = 0; //如果expires字典表中的entry个数不足以达到抽样个数,则选择全部key作为抽样样本 if (num > REDIS_EXPIRELOOKUPS_PER_CRON) num = REDIS_EXPIRELOOKUPS_PER_CRON; while (num--) { dictEntry *de; long long t; //随机获取一个设置了失效时间的主键,检查其是否已经失效 if ((de = dictGetRandomKey(db->expires)) == NULL) break; t = dictGetSignedIntegerVal(de); if (now > t) { //发现该主键确实已经失效,删除该主键 sds key = dictGetKey(de); robj *keyobj = createStringObject(key,sdslen(key)); //同样要在删除前广播该主键的失效信息 propagateExpire(db,keyobj); dbDelete(db,keyobj); decrRefCount(keyobj); expired++; server.stat_expiredkeys++; } } //每进行一次抽样删除后对iteration加一,每16次抽样删除后检查本次执行时间是否 //已经达到时间限制,如果已达到时间限制,则记录本次执行达到时间限制并退出 iteration++; if ((iteration & 0xf) == 0 && (ustime()-start) > timelimit) { timelimit_exit = 1; return; } //如果失效的主键数占抽样数的百分比大于25%,则继续抽样删除过程 } while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]