服务器之家:专注于服务器技术及软件下载分享
分类导航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|数据库技术|

服务器之家 - 数据库 - Redis - Redis 2.8-4.0过期键优化过程全纪录

Redis 2.8-4.0过期键优化过程全纪录

2019-11-23 20:32白馨 Redis

这篇文章主要给大家介绍了关于Redis 2.8-4.0过期键优化的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

前言

之前 白馨(陌陌-技术保障部存储工程师 )在Redis技术交流群里,总结了一下Redis从2.8~4.0关于过期键相关的fix记录,非常有帮助,但有些东西未尽详细,本文将进行详细说明。

先从一个问题来看,运行环境如下:

Redis: 2.8.19
db0:keys=10000000,expires=10000000
主从结构

从下图中可以看到,在从节点get hello非空,在主节点get hello为空,之后从节点get hello为空,经排查主从同步offset基本正常,但出现了主从不一致。

Redis 2.8-4.0过期键优化过程全纪录

原因先不说,本文来探讨下Redis2.8-4.0版本迭代中,针对过期键的fix,看看能不能找到答案。

一、过期功能回顾

当你执行了一条setex命令后,Redis会向内部的dict和expires哈希结构中分别插入数据:

?
1
2
dict------dict[key]:value
expires---expires[key]:timeout

例如:

?
1
2
3
4
5
6
7
127.0.0.1:6379> setex hello 120 world
OK
127.0.0.1:6379> info
# 该数据库中设置为过期键并且未被删除的总量(如果曾设置为过期键且删除则不计入)
db0:keys=1,expires=1,avg_ttl=41989
# 历史上每一次删除过期键就做一次加操作,记录删除过期键的总数。
expired_keys:0

二、Redis过期键的删除策略:

当键值过期后,Redis是如何处理呢?综合考虑Redis的单线程特性,有两种策略:惰性删除和定时删除。

1.惰性删除策略:

在每次执行key相关的命令时,都会先从expires中查找key是否过期,下面是3.0.7的源码(db.c):

下面是读写key相关的入口:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
robj *lookupKeyRead(redisDb *db, robj *key) {
 robj *val;
 
 expireIfNeeded(db,key);
 val = lookupKey(db,key);
 ......
 return val;
}
 
robj *lookupKeyWrite(redisDb *db, robj *key) {
 expireIfNeeded(db,key);
 return lookupKey(db,key);
}

可以看到每次读写key前,所有的Redis命令在执行之前都会调用expireIfNeeded函数:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int expireIfNeeded(redisDb *db, robj *key) {
 mstime_t when = getExpire(db,key);
 mstime_t now;
 if (when < 0) return 0; /* No expire for this key */
 now = server.lua_caller ? server.lua_time_start : mstime();
 if (server.masterhost != NULL) return now > when;
 /* Return when this key has not expired */
 if (now <= when) return 0;
 /* Delete the key */
 server.stat_expiredkeys++;
 propagateExpire(db,key);
 notifyKeyspaceEvent(NOTIFY_EXPIRED,
  "expired",key,db->id);
 return dbDelete(db,key);
}

从代码可以看出,主从逻辑略有不同:

(1) 主库:过期则expireIfNeeded会删除过期键,删除成功返回1,否则返回0。

(2) 从库:expireIfNeeded不会删除key,而会返回一个逻辑删除的结果,过期返回1,不过期返回0 。

但是从库过期键删除由主库的synthesized DEL operations控制。

2.定时删除策略:

单单靠惰性删除,肯定不能删除所有的过期key,考虑到Redis的单线程特性,Redis使用了定期删除策略,采用策略是从一定数量的数据库的过期库中取出一定数量的随机键进行检查,不为空则删除。不保证实时删除。有兴趣的同学可以看看activeExpireCycle中具体实现,还是挺有意思的,下图是个示意图

Redis 2.8-4.0过期键优化过程全纪录

?
1
if (server->masterhost == NULL) activeExpireCycle();

(1)主库: 会定时删除过期键。

(2)从库: 不执行定期删除。

综上所述: 

主库:

(1) 在执行所有操作之前调用expireIfNeeded惰性删除。

(2) 定期执行调用一次activeExpireCycle,每次随机删除部分键(定时删除)。

从库:

过期键删除由主库的synthesized DEL operations控制。

三、过期读写问题

Redis过期删除策略带来的问题。我们只从用户操作的角度来讨论。

1、过期键读操作

下面是Redis 2.8~4.0过期键读操作的fix记录

(1) Redis2.8主从不一致

2.8中的读操作中都先调用lookupKeyRead函数:

?
1
2
3
4
5
6
7
8
9
10
robj *lookupKeyRead(redisDb *db, robs *key) {
  robj *val;
  expireIfNeeded(db,key);
  val = lookupKey(db,key);
  if (val == NULL)
    server.stat_keyspace_misses++;
  else
    server.stat_keyspace_hits++;
  return val;
}

•对于主库,执行expireIfNeeded时,过期会删除key。lookupKey返回 NULL。

•对于从库,执行expireIfNeeded时,过期不会删除key。lookupKey返回value。

所以对于过期键的读操作,主从返回就会存在不一致的情况,也就是开篇提到的问题。

(2) Redis 3.2主从除exists之外都一致

https://github.com/antirez/redis/commit/06e76bc3e22dd72a30a8a614d367246b03ff1312

3.2-rc1读操作中同样先调用了lookupKeyRead,实际上调用的是lookupKeyReadWithFlags函数:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
robj *lookupKeyReadWithFlags(redisDb *db, robj *key) {
  robj *val;
  if (expireIfNeeded(db,key) == 1) {
    if (server.masterhost == NULL) return NULL;
    if (server.current_client && //当前客户端存在
      server.current_client != server.master && //当前客户端不是master请求建立的(用户请求的客户端)
      server.current_client->cmd &&
      server.current_client->cmd->flags & REDIS_CMD_READONLY) { //读命令
        return NULL;
       }
  val = lookupKey(db,key,flags);
  if (val == NULL)
    server.stat_keyspace_misses++;
  else
    server.stat_keyspace_hits++;
  return val;
  }

可以看到,相对于2.8,增加了对expireIfNeeded返回结果的判断:

•对于主库,执行expireIfNeeded时,过期会删除key,返回1。masterhost为空返回NULL。

•对于从库,执行expireIfNeeded时,过期不会删除key,返回1。满足当前客户端不为 master且为读命令时返回NULL。

除非程序异常。正常情况下对于过期键的读操作,主从返回一致。

(2) Redis 4.0.11解决exists不一致的情况

https://github.com/antirez/redis/commit/32a7a2c88a8b8cca8119b849eee7976b8ada8936

Redis 2.8-4.0过期键优化过程全纪录

3.2并未解决exists这个命令的问题,虽然它也是个读操作。之后的4.0.11中问题才得以解决.

2、过期键写操作

在具体说这个问题之前,我们先说一下可写从库的使用场景。

(1).主从分离场景中,利用从库可写执行耗时操作提升性能。

作者在https://redis.io/topics/replication 中提到过:

For example computing slow Set or Sorted set operations and storing them into local keys is an use case for writable slaves that was observed multiple times.

在 https://github.com/antirez/redis/commit/c65dfb436e9a5a28573ec9e763901b2684eadfc4 举了一个更具体的例子:

For instance imagine having slaves replicating certain Sets keys from the master. When accessing the data on the slave, we want to peform intersections between
such Sets values. However we don't want to intersect each time: to cache the intersection for some time often is a good idea.

也就是说在读写分离的场景中,可以使用过期键的机制将从库作为一个缓存,去缓存从库上耗时操作的结果,提升整体性能。

(2). 迁移数据时,需要先将从库设置为可写。

比如下列场景:线上Redis服务正常,但可能遇到一些硬件的情况,需要对该机器上的Redis主从集群迁移。迁数据的方式就是搭建一个新的主从集群,让新主成为旧主的从。

进行如下操作:

•(1)主(旧主)从(新主)同步,rdb传输完毕90s之后,设置从库(新主)可写。

•(2)在主库(旧主)完全没有业务连接后,从库(新主)执行slaveof no one。

这种场景下,为了保证数据完全同步,并且尽量减少对业务的影响,就会先设置从库可写。

接着我们来做一个测试:

3.2版本主库执行的操作,主库的过期键正常过期。

Redis 2.8-4.0过期键优化过程全纪录

3.2版本可写从库执行以下操作,从库的过期键并不会过期。

Redis 2.8-4.0过期键优化过程全纪录

4.0rc3版本可写从库执行以下操作,从库的过期键却能够过期。

Redis 2.8-4.0过期键优化过程全纪录

其实可写从库过期键问题包含两个问题:

•(1)从库中的过期键由主库同步过来的,过期操作由主库执行(未变更过)。

•(2)从库中的过期键的设置是从库上操作的。

redis4.0rc3之前,存在过期键泄露的问题。当expire直接在从库上操作,这个key是不会过期的。作者也在https://redis.io/topics/replication 提到过:

However note that writable slaves before version 4.0 were incapable of expiring keys with a time to live set. This means that if you use EXPIRE or other commands that set a maximum TTL for a key, the key will leak, and while you may no longer see it while accessing it with read commands, you will see it in the count of keys and it will still use memory. So in general mixing writable slaves (previous version 4.0) and keys with TTL is going to create issues.

过期键泄露问题在https://github.com/antirez/redis/commit/c65dfb436e9a5a28573ec9e763901b2684eadfc4中得到了解决。

四.总结

1、针对过期键读操作

(1) Redis2.8主从不一致 

(2) Redis3.2-rc1主从除exists之外都一致: https://github.com/antirez/redis/commit/06e76bc3e22dd72a30a8a614d367246b03ff1312

(3) Redis4.0.11主从一致:

https://github.com/antirez/redis/commit/32a7a2c88a8b8cca8119b849eee7976b8ada8936

2、针对过期键的写操作:

Redis2.8~4.0都只返回物理结果。

3、从库中对key执行expire操作,key不会过期。

Redis4.0 rc3解决从库中设置的过期键不过期问题 https://github.com/antirez/redis/commit/c65dfb436e9a5a28573ec9e763901b2684eadfc4

4、如果slave非读写分离、上述迁移使用,基本本文问题不会出现。还有就是Redis 4非常靠谱,后面也会有文章介绍相关内容。(付磊)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对服务器之家的支持。

原文链接:https://mp.weixin.qq.com/s/frLDENsPQrv5sZ2PBVzoHg

延伸 · 阅读

精彩推荐
  • RedisRedis 6.X Cluster 集群搭建

    Redis 6.X Cluster 集群搭建

    码哥带大家完成在 CentOS 7 中安装 Redis 6.x 教程。在学习 Redis Cluster 集群之前,我们需要先搭建一套集群环境。机器有限,实现目标是一台机器上搭建 6 个节...

    码哥字节15752021-04-07
  • RedisRedis集群的5种使用方式,各自优缺点分析

    Redis集群的5种使用方式,各自优缺点分析

    Redis 多副本,采用主从(replication)部署结构,相较于单副本而言最大的特点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。...

    优知学院4082021-08-10
  • Redis详解三分钟快速搭建分布式高可用的Redis集群

    详解三分钟快速搭建分布式高可用的Redis集群

    这篇文章主要介绍了详解三分钟快速搭建分布式高可用的Redis集群,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    万猫学社4502021-07-25
  • RedisRedis Template实现分布式锁的实例代码

    Redis Template实现分布式锁的实例代码

    这篇文章主要介绍了Redis Template实现分布式锁,需要的朋友可以参考下 ...

    晴天小哥哥2592019-11-18
  • Redis《面试八股文》之 Redis十六卷

    《面试八股文》之 Redis十六卷

    redis 作为我们最常用的内存数据库,很多地方你都能够发现它的身影,比如说登录信息的存储,分布式锁的使用,其经常被我们当做缓存去使用。...

    moon聊技术8182021-07-26
  • Redis如何使用Redis锁处理并发问题详解

    如何使用Redis锁处理并发问题详解

    这篇文章主要给大家介绍了关于如何使用Redis锁处理并发问题的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习...

    haofly4522019-11-26
  • Redisredis缓存存储Session原理机制

    redis缓存存储Session原理机制

    这篇文章主要为大家介绍了redis缓存存储Session原理机制详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    程序媛张小妍9252021-11-25
  • Redis关于Redis数据库入门详细介绍

    关于Redis数据库入门详细介绍

    大家好,本篇文章主要讲的是关于Redis数据库入门详细介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览...

    沃尔码6982022-01-24