数据库操作

数据库服务器: redisServer结构 - db、dbnum属性
服务器上数据库: redisDb结构 - dict、expires属性
客户端:redisClient结构 - db属性

img
  1. 属性db是一个数组,保存着服务器上所有的数据库。
  2. 属性dbnum为服务器上数据库数量,默认值为16。
    切换数据库:redisClient结构中db属性记录了客户端的目标数据库(本质上它是一个redisDb结构的指针)。

切换数据库

使用 SELECT <目标数据库号码>,本质就是改变db指针内容。

键空间

键空间,即:redisDb结构的字典dict属性,保存数据库中所有键值对.
对键的增删改查操作都是通过对健空间字典进行操作实现的。
img

读取键空间时维护操作

  1. 根据键是否存在,来更新服务器的键空间命中次数或不命中次数,可以使用INFO stats命令查看keyspace_hits属性和keyspace_misses属性。
  2. 更新键的LRU(最后一次使用)时间,这个值可以用于计算键的闲置时间。可以使用OBJECT idletime命令查看key的闲置空间。
  3. 检查键是否过期,过期会删除该键,然后才执行余下的操作。
  4. 当有客户端使用WATCH命令监控了某个键,当该键被修改后,要标记该键,让事务程序可以识别出。
  5. 每次修改键后,服务器会对dirty计数器的值+1;该计数器可以用来触发服务器持久化以及复制操作。
  6. 如果开启数据库同通知功能,需要按配置发送相应的数据库通知。

过期时间

设置过期时间

一般主要包括4种处理过期方,其中expire都是以秒为单位,pexpire都是以毫秒为单位的。

1
2
3
4
EXPIRE key seconds  //将key的生存时间设置为ttl秒
PEXPIRE key milliseconds  //将key的生成时间设置为ttl毫秒
EXPIREAT key timestamp  //将key的过期时间设置为timestamp所代表的的秒数的时间戳
PEXPIREAT key milliseconds-timestamp  //将key的过期时间设置为timestamp所代表的的毫秒数的时间

虽然有多种不同单位和不同形式的设置命令,但是实际都是使用PEXPIREAT命令实现的。
命令将的转换如下:

img

保存过期时间

通过过期字典保存 - redisDb结构的字典expires属性

img

计算并返回剩余生存时间

使用TTL/PLLT,通过计算键的过期时间和当前时间的差来实现。

过期键

过期键删除策略

定时删除:占用太多CPU时间,影响服务器的响应时间和吞吐量
惰性删除策略:浪费太多内存,有内存泄露的危险
定期删除:难点在于确定删除操作执行的时长和频率

Redis过期键删除策略: 惰性删除 + 定期删除

惰性删除

惰性删除由expireIfNeeded()函数实现,所有读写数据库命令在执行前都会调用该函数。

img

定期删除

定期删除由activeExpireCycle()函数实现,在规定时间内,分多次遍历各个数据库,每次都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中过期键。
img
注:

  1. 全局变量current_db会记录当前activeExpireCycle函数检查的进度,并在下次调用时使用。
  2. 当current_db变量被重置为0,说明所有数据都被检查了一遍。

RDB对过期键的处理

  • 生成RDB文件
    在执行SAVE/BGSAVE命令创建一个新的RDB文件时,已过期的键不会被保存到文件中。
  • 载入RDB文件
    主服务器模式运行,会检查,过期键不会被载入到数据库中。
    从服务器模式运行,不会检查,所有键都会被载入到数据库中。但是由于后期数据要进行主从同步操作,因此过期键对载入RDB文件的从服务器也不会造成影响。

AOF对过期键的处理

  • AOF重写
    执行BGREWRITEAOF命令所产生的重写AOF文件不会包含已经过期的键。
  • AOF写入
    当一个过期键被删除后,服务器会追加一条DEL命令到现有AOF文件的末尾,显示删除过期键。

主从服务器对过期键的处理

  • 当主服务器删除一个过期键后,它会将所有从服务器发送一个DEL命令,显示删除过期键。
  • 从服务器要等待主节点发来DEL命令,才能删除过期键;否则即使发现过期键,也不会删除它。即:一切行动听主服务器指挥。

数据库通知

分类

  • 键空间通知: 关注”某个键执行了什么命令“
  • 键事件通知: 关注“某个命令被什么键执行了”

发送通知

  • 服务器可以通过配置notify-keyspace-events选项来决定发送通知的类型。
    AKE:所有类型所有通知
    AK:所有类型键空间通知
    AE:所有类型键事件通知
    K$:字符串键的键空间通知
    E1:列表键的键事件通知 等等
  • 由notifyKeyspaceEvent()实现,参数type是要所发送的通知类型,程序会判断是否是服务器配置notify-keyspace-events选项,从而决定是否发送通知。

Redis 真的是单线程吗?

redis6.* 版本,引用了IO Threads概念,处理数据还是单线程的,但是IO读写引入了另外两个线程,提高了在多核机器的CPU利用率。

单线程基本模型

Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有到达服务端的命令都不会立刻执行,所有的命令都会进入一个队列中,然后逐个执行,并且多个客户端发送的命令的执行顺序是不确定的,但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。

为什么不采用多进程或多线程处理?

1.多线程处理可能涉及到锁2.多线程处理会涉及到线程切换而消耗CPU

Redis不存在线程安全问题?

Redis采用了线程封闭的方式,把任务封闭在一个线程,自然避免了线程安全问题,不过对于需要依赖多个redis操作(即:多个Redis操作命令)的复合操作来说,依然需要锁,而且有可能是分布式锁

单线程处理的缺点?

1.耗时的命令会导致并发的下降,不只是读并发,写并发也会下降2.无法发挥多核CPU性能,不过可以通过在单机开多个Redis实例来完善

Redis的单线程为什么这么快?

  1. 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1);

  2. 数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;

  3. 采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

  4. 使用多路I/O复用模型,非阻塞I/O;
    采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 I/O 的时间消耗)

  5. Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;