Redis数据库

一、Redis服务器中的数据库

Redis服务器将所有数据库都保存在服务器状态redis.h/redisServer结构的db数组中,db数组的每个想都是一个redis.h/redisDb结构,每个redisDb结构代表一个数据库:

struct redisServer {
    //一个数组,保存着服务器中的所有数据库
    redisDb *db;
};

在初始化服务器时,程序会根据服务器状态的dbNum属性来决定应该创建多少数据库:

struct redisServer{
    //服务器的数据库数量
    int dbnum;
};

dbnum属性的值由服务器配置的database选项决定,默认情况下,该选项的值为16,所以Redis服务器默认认为创建16个数据库,如图所示:

如上图所示,redisServer结构 代表Redis服务器,它有一个db数组,数组里装的元素是redisDB结构。

2、切换数据库

redisClient结构 代表Redis客户端,它有一个db指针,该指针指向redisServer.db数组中的其中一个数据库。

typedef struct redisClient{
    //记录客户端当前正在使用的数据库
    redisDb *db
    
}redisClient;

切换数据库就是通过改变这个指针的指向实现的。——这就是SELECT命令的实现原理

3、数据库键空间

Redis是一个键值对(key-value pair)数据库服务器,服务器中的每个数据库都有一个redis.h/redisDb结构表示,其中,redisDb结构的dict字典保存了数据库中的所有键值对,我们将这个字典称为键空间(key space):

typedef struct redisDb{
    //数据库键空间,保存数据库中所有键值对
    dict *dict;
}redisDb;

键空间和用户所见的数据库是直接对应的:

  • 键空间的键也就是数据库的键,每个键都是一个字符串对象
  • 键空间的值也就是数据库的值,每个值可以使任意一种Redis对象

因为数据库的键空间是一个字典,所以所有针对数据库的操作,比如添加一个键值对到数据库,或者从数据库中删除一个键值对,或者从数据库中获取某个键值对等,实际都是通过对键空间字典进行操作实现的。

当执行下列命令之后:

set message "hello world"
rpush alphabet "a" "b" "c"
hset book name "Redis in Action"
hset book auther "Josiah L. Carlson"
hset book publisher "Manning"
set message "hello world"

dict键空间的情况如下图所示:

读写键空间时的维护操作
当使用Redis命令对数据库进行读写时,服务器不仅对键空间执行指定的读写操作,还执行一些额外的维护操作,其中包括:

  • 在读取一个键之后,服务器会根据键是否存在来更新服务器的键空间命中(hit)次数或者键空间不命中(miss)次数
  • 在读取一个键后,服务器会更新键的LRU时间,这个值可以用于计算键的闲置时间,使用OBJECT idletime命令可以查看key的闲置空间
  • 如果服务器在读取一个键时发现该键已经过期了,那么服务器会先删除这个过期键,然后才执行余下的其他操作。
  • 如果客户端使用WATCH命令监视了某个键,那么服务器在对监视的键进行修改之后,会将这个键标示为脏(dirty),从而让事务程序注意到这个键已经被修改过。
  • 服务器每次修改一个键之后,都会对脏(dirty)键计数器的值增1,这个计数器会触发服务器的持久化以及复制操作
  • 如果服务器开启了数据库通知功能,那么在对键进行修改之后,服务器将按配置发送相应的数据库通知。

4、设置键的生存时间或过期时间

4.1、设置Redis键过期时间

  Redis提供了四个命令来设置过期时间(生存时间)。

  ①、EXPIRE <key> <ttl> :表示将键 key 的生存时间设置为 ttl 秒。

  ②、PEXPIRE <key> <ttl> :表示将键 key 的生存时间设置为 ttl 毫秒。

  ③、EXPIREAT <key> <timestamp> :表示将键 key 的生存时间设置为 timestamp 所指定的秒数时间戳。

  ④、PEXPIREAT <key> <timestamp> :表示将键 key 的生存时间设置为 timestamp 所指定的毫秒数时间戳。

  PS:在Redis内部实现中,前面三个设置过期时间的命令最后都会转换成最后一个PEXPIREAT 命令来完成。

  另外补充两个知识点:

  一、移除键的过期时间

  PERSIST <key> :表示将key的过期时间移除。

  二、返回键的剩余生存时间

  TTL <key> :以秒的单位返回键 key 的剩余生存时间。

  PTTL <key> :以毫秒的单位返回键 key 的剩余生存时间。

4.2、Redis过期时间的判定

  在Redis内部,每当我们设置一个键的过期时间时,Redis就会将该键带上过期时间存放到一个过期字典中。当我们查询一个键时,Redis便首先检查该键是否存在过期字典中,如果存在,那就获取其过期时间。然后将过期时间和当前系统时间进行比对,比系统时间大,那就没有过期;反之判定该键过期。

4.3、过期删除策略

  通常删除某个key,我们有如下三种方式进行处理。

①、定时删除

  在设置某个key 的过期时间同时,我们创建一个定时器,让定时器在该过期时间到来时,立即执行对其进行删除的操作。

  优点:定时删除对内存是最友好的,能够保存内存的key一旦过期就能立即从内存中删除。

  缺点:对CPU最不友好,在过期键比较多的时候,删除过期键会占用一部分 CPU 时间,对服务器的响应时间和吞吐量造成影响。

②、惰性删除

  设置该key 过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。

  优点:对 CPU友好,我们只会在使用该键时才会进行过期检查,对于很多用不到的key不用浪费时间进行过期检查。

  缺点:对内存不友好,如果一个键已经过期,但是一直没有使用,那么该键就会一直存在内存中,如果数据库中有很多这种使用不到的过期键,这些键便永远不会被删除,内存永远不会释放。从而造成内存泄漏。

③、定期删除

  每隔一段时间,我们就对一些key进行检查,删除里面过期的key。

  优点:可以通过限制删除操作执行的时长和频率来减少删除操作对 CPU 的影响。另外定期删除,也能有效释放过期键占用的内存。

  缺点:难以确定删除操作执行的时长和频率。

     如果执行的太频繁,定期删除策略变得和定时删除策略一样,对CPU不友好。

     如果执行的太少,那又和惰性删除一样了,过期键占用的内存不会及时得到释放。

     另外最重要的是,在获取某个键时,如果某个键的过期时间已经到了,但是还没执行定期删除,那么就会返回这个键的值,这是业务不能忍受的错误。

4.4、Redis过期删除策略

  前面讨论了删除过期键的三种策略,发现单一使用某一策略都不能满足实际需求,聪明的你可能想到了,既然单一策略不能满足,那就组合来使用吧。

  没错,Redis的过期删除策略就是:惰性删除和定期删除两种策略配合使用。通过这两种策略的配合,服务器可以很好地使用CPU时间和避免浪费内存空间之间取得平衡。

  惰性删除:Redis的惰性删除策略由 db.c/expireIfNeeded 函数实现,所有键读写命令执行之前都会调用 expireIfNeeded 函数对其进行检查,如果过期,则删除该键,然后执行键不存在的操作;未过期则不作操作,继续执行原有的命令。

命令调用expireIfNeed函数的流程如下:

定期删除:由redis.c/activeExpireCycle 函数实现,函数以一定的频率运行,每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。

  注意:并不是一次运行就检查所有的库,所有的键,而是随机检查一定数量的键。

  定期删除函数的运行频率,在Redis2.6版本中,规定每秒运行10次,大概100ms运行一次。在Redis2.8版本后,可以通过修改配置文件redis.conf 的 hz 选项来调整这个次数。

  

  看上面对这个参数的解释,建议不要将这个值设置超过 100,否则会对CPU造成比较大的压力。

  我们看到,通过过期删除策略,对于某些永远使用不到的键,并且多次定期删除也没选定到并删除,那么这些键同样会一直驻留在内存中,又或者在Redis中存入了大量的键,这些操作可能会导致Redis内存不够用,这时候就需要Redis的内存淘汰策略了。

4.5、内存淘汰策略

①、设置Redis最大内存

  在配置文件redis.conf 中,可以通过参数 maxmemory <bytes> 来设定最大内存:

  

  不设定该参数默认是无限制的,但是通常会设定其为物理内存的四分之三。(这里有个疑惑:为啥作者不考虑将此参数设定为百分比呢?)

②、设置内存淘汰方式

  当现有内存大于 maxmemory 时,便会触发redis主动淘汰内存方式,通过设置 maxmemory-policy ,有如下几种淘汰方式:

  1)volatile-lru   利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used ) 。

  2)allkeys-lru   利用LRU算法移除任何key (和上一个相比,删除的key包括设置过期时间和不设置过期时间的)。通常使用该方式

  3)volatile-random 移除设置过过期时间的随机key 。

  4)allkeys-random  无差别的随机移除。

  5)volatile-ttl   移除即将过期的key(minor TTL) 

  6)noeviction 不移除任何key,只是返回一个写错误 ,默认选项,一般不会选用。

  在redis.conf 配置文件中,可以设置淘汰方式:

  

5、AOF、RDB和复制功能对过期键的处理

5.1、RDB持久化对过期键的处理

save命令或者bgsave命令创建以新的RDB文件时,程序会对数据库中的键进行检查,过期的键不会保存到新创建的RDB文件中。

若服务器中包含三个键k1、k2、k3,并且k2过期了,那么当服务器启动时:
(1)若服务器以主服务器模式运行,那么程序只会将k1和k3载入到数据库,k2会被忽略。
(2)若服务器以从服务器模式运行,那么k1、k2、k3都会载入到数据库。

5.2、AOF持久化对过期键的处理

当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键产生任何影响。

5.3、复制

当服务器运行在复制模式下时,从服务器的过期键删除动作由主服务器控制:

  • 主服务器在删除一个过期键之后,会显式地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键;
  • 从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将过期键删除,而是继续像处理未过期的键一样来处理过期键;
  • 从服务器只有在接到主服务器发来的DEL命令之后,才会删除过期键。

举个例子,有一对主从服务器,它们的数据库中都保存着同样的三个键message、xxx和yyy,其中message为过期键,如图所示

如果这时有客户端向从服务器发送命令GET message,那么从服务器将发现message键已经过期,但从服务器并不会删除message键,而是继续将message键的值返回给客户端,就好像message键并没有过期一样。

假设在此之后,有客户端向主服务器发送命令GET message,那么主服务器将发现键message已经过期:主服务器会删除message键,向客户端返回空回复,并向从服务器发送DEL message命令,如图所示:

从服务器在接收到主服务器发来的DEL message命令之后,也会从数据库中删除message键,在这之后,主从服务器都不再保存过期键message了,如图所示:

5、数据库通知

数据库通知是Redis2.8版本新增加的功能,这个功能可以让客户端通过订阅给定的频道或者模式,来获知数据库中键的变化,以及数据库中命令的执行情况。
举个例子,以下代码展示了客户端如何获取0号数据库中针对message键执行的所有命令:

127.0.0.1:6379>SUBSCRIBE __keyspace@0__:message
Reading messages . . . (press Ctrl-C to quit)
1) "subscribe"            //订阅信息
2) "_ _keyspace@0_:message"
3) (integer) 1
1) "message" //执行SET 命令
2) "_ _keyspace@0_: message"
3) "set"
1) "message" //执行EXPIE命令
2) " keyspace@0_:message"
3) "expire"
1) "message" //执行DEL 命令
2) "_ _keyspace@0_: message "
3) "de1"

 

根据发回的通知显示,先后共有SET、EXPlRE 、DEL 三个命令对键message进行了操作。

这一类关注"某个键执行了什么命令"的通知称为键空间通知(key-space-notification),除此之外,还有另一类称为键事件通知(key-event-notification)的通知,它们关注的是"某个命令被什么键执行了" 。
以下是一个键事件通知的例子,代码展示了客户端如何获取0 号数据库中所有执行DEL 命令的键:

127.0 . 0.1:6379> SUBSCRIBE_ _keyevent@0_ _:de1
Reading messages. . . (press Ctrl-C to quit)
1) "subcribe"                      //订阅信息
2) "_ _keyevent@0_ _:del"
3) (integer) 1

1) "message"                     //键key执行了DEL命令
2) "keyevent@0_ _:del"
3) "key"

1) "message"                     //键number执行了DEL命令
2) "_ _keyevent@0_ _:del"
3) "number"

1) "message"                     //键message执行了DEL命令
2) "keyevent@0_ _:del"
3) "message"

根据发回的通知显示,key、number、message三个键先后执行了DEL 命令。
服务器配置的notify-keyspace-events选项决定了服务器所发送通知的类型:

想让服务器发送所有类型的键空间通知和键事件通知,可以将选项的值设置为AKE。
想让服务器发送所有类型的键空间通知,可以将选项的值设置为AK。
想让服务器发送所有类型的键事件通知,可以将选项的值设置为AE。
想让服务器只发送和字符串键有关的键空间通知,可以将选项的值设置为K$。
想让服务器只发送和列表键有关的键事件通知,可以将选项的值设置为E1。

关于数据库通知功能的详细用法,以及notify-keyspace-events选项的更多设置,Redis 的官方文档已经做了很详细的介绍,这里不再赘述。

 

转自:

https://www.cnblogs.com/ysocean/p/12422635.html

https://blog.csdn.net/weixin_43809223/article/details/109254567

https://blog.csdn.net/sinat_37205087/article/details/90700600

https://blog.csdn.net/yangyang_01/article/details/80494171

http://t.zoukankan.com/lukexwang-p-4710333.html

原文地址:https://www.cnblogs.com/duanxz/p/14747257.html