Redis键值存储设计

程序员和产品经理的关系就像孙悟空和唐僧的关系,唐僧说我想去取
经,孙悟空说我帮你打怪;唐僧谁打怪但是不能伤害白骨精,孙悟空说
那你自己去打怪;唐僧说我不会打怪但是我就想取取经但是不能伤害白
骨精......算了,还是看看redis吧。

数据库存储结构

服务端数据库结构

redis默认为16个数据库,其数据库在redis中的存储结构如下:

Struct redisServer {
  redisDb *db; // 指向数据库的指针
}

图片

客户端数据库结构

客户端可以通过“select 数据库序号”来切换数据库;客户端的存储结构如下:

Struct redisClinet {
  redisDb *db; // 存储当前操作的数据库
}

图片

键空间存储结构

Struct redisDb 
  dict *dict // 键值对空间,保存所有的键值对
}

基本数据类型键值存储

先从基本的不带失效时间的键值对存储结构说起,比如向数据库里写入了数据:

redis> set message hello
redis> push ap "a" "b" "c"
redis> hset book name "redis"
redis> hset book auth "zhangsan"

那么在redis内部存储结构如下图中红框部分:
图片

键空间其实是存储在redisClient中的redisDb中dict结构中,如上图所示能看出每一个数据库对应一个独立的数据库的键空间。

带失效时间的数据类型存储

上面说了基础数据类型的键空间存储,下面来说下带失效时间的键空间存储。如一个key设置了失效时间,如以下为message设置了一个5秒的失效时间:

redis> expire message 5

那么通过get命令在5秒之内获取message是可以获取到值的,在5秒之后就获取不到值了。
还有几种设置过期时间的方法:

redis>expire message 5 // 设置message键值过期时间为5秒
redis>expireat message 5000 // 设置毫秒
redis>pexpire message 时间戳 // 设置过期时间的秒级时间戳
redis>pexpireat message 时间戳 // 设置过期时间的毫秒级时间戳

执行设置过期时间命令的底层原理:
无论执行哪个命令,在redis底层都会转换为pexpireat命令来执行:

expire命令首先会将秒转换成毫秒,也就是转换成了expireat命令;
expireat命令会获取当前毫秒级时间戳,然后加上设置的过期毫秒数,即得到了过期的毫秒级时间戳,即转换成了执行命令pexpireat;
pexpireat直接设置了当前key的毫秒级过期时间;
pexpire命令直接会把设置的秒级时间戳转换成毫秒级时间戳后,执行pexpireat命令,设置当前key的过期时间;
综上,也就是expire、pexpire、expireat这三个命令最终都会转换成pexpireat命令来设置过期时间。

下面来看下redis底层是如何存储键所对应的失效时间的:

Struct redisDb {
  dict *expires
}

在expires中存储这对应键值的过期时间戳。我们来看下结构图,下图就是给message这个可以设置了过期时间:
图片

上面红色字体就是为message设置了失效时间后的,失效时间的存储结构。

通过上面不难看出,整个的redis底层数据结构存储大概就是上图的样子。

移除过期时间

通过persist key命令可以移除过期时间。

执行命令后,如果当前key没有设置过期时间获取不存在,则返回0,否则返回1。

查询剩余过期时间

ttl key或pttl key两个命令可以获取剩余的过期时间。

原理:

pttl:获取当前key的毫秒级过期时间的时间戳t,获取当前毫秒级时间戳t1,剩余的过期时间t2=t-t1,直接返回t2就是剩余的过期时间。

ttl:获取当前键以毫秒级的过期时间的时间戳,然后执行的方法同pttl。

过期键的删除策略

redis内部采用的是定时删除和惰性删除的策略。

惰性删除:这个很简单,对redis进行读写操作时,在执行实际请求之前首先会执行expireIfNeeded函数,通过这个函数会判断该请求的key是否存在,如果不存在或key值未过期会执行实际的请求操作,如果key存在并且已过期,会删除该key,然后执行实际的请求操作。

定时删除:redis服务器会定期执行serverCron函数,这个函数在执行的时候就会调用avtiveExpireCycle函数,在这个函数中会在指定时间内做以下操作:遍历默认16个数据库,当遍历每一个库时,会默认处理20个key,也就是说遍历0号库时,每次会从redisDb.expire中取出一个key判断是否过期,如果redisDb.expire.size()=0时,跳过这个0号库,去处理1号库,如果redisDb.expire.size()大于0,每次会取出一个key查看是否过期,如果过期就会删除这个键空间对应的key和value,如果没有过期,那么就进行下一次遍历,直到遍历20次或执行avtiveExpireCycle函数的时间已达到执行上限,如果遍历0号库20次后时间未达到上限,则继续处理1号库,以此类推。直到时间已经达到上限,则退出activeExpireCycle函数,但是会记录处理到了哪个数据库,下次再次执行avtiveExpireCycle函数的时候会从上次执行到的数据库从新再次处理。

最后

欢迎大家一起交流,欢迎转载,感谢支持!

发布了7 篇原创文章 · 获赞 6 · 访问量 1010

猜你喜欢

转载自blog.csdn.net/qq_20878967/article/details/105167897