Redis学习(三)Redis数据库
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Redis学习(三)Redis数据库,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含13406字,纯文字阅读大概需要20分钟。
内容图文
![Redis学习(三)Redis数据库](/upload/InfoBanner/zyjiaocheng/891/809aea26b69043ccab4526c4f8e87f23.jpg)
目录
服务器/客户端
Redis主要包括两种工作状态,服务器 redisServer 对象结构和客户端 RedisClient 对象结构,服务器将所有的数据库都保存在其成员的一个db数组中,db数组的每一个元素都是一个 redisDb 的redis数据库结构,代表一个数据库。
struct redisServer {
//...
//保存服务器所有数据库的数组
redisDb *db;
//服务器的数据库数量
int dbnum;
//...
};
在初始化服务器时,程序会根据服务器的dbnum属性来决定创建多少个数据库,dbnum属性由服务器配置的database选项决定,默认情况下为16,所以Redis服务器默认会创建16个数据库。
每个Redis客户端都有自己的目标数据库,每当客户端执行数据库的读/写命令的时候,目标数据库就会成为这些命令的操作对象。默认情况下,Redis客户端的目标数据库是0号数据库(db[0]),客户端可通过执行SELECT 1命令切换到1号目标数据库。
typedef struct redisServer {
//...
//记录客户端正在使用的目标数据库(指向服务器db数组的某个元素)
redisDb *db;
//...
} redisClient;
客户端的db属性记录了客户端当前的目标数据库,它是一个指向服务器中redisDb结构的指针,RedisClient.db 指针指向RedisServer.db数组中的其中一个元素,这个元素就是此客户端的目标数据库。而切换客户端的目标数据库其实就是修改它指向服务器db数组中元素的指针。
数据库键空间
Redis 是一个键值对(key-value pair)数据库服务器, 服务器中的每个数据库都由一个 redisDb
结构表示, 其中redisDb
结构的 dict
字典保存了数据库中的所有键值对, 我们将这个字典称为键空间(key space)。
typedef struct redisDb {
// ...
// 数据库键空间,保存着数据库中的所有键值对
dict *dict;
// ...
} redisDb;
键空间和用户所见的数据库是直接对应的:
- 键空间的键也就是数据库的键, 每个键都是一个字符串对象。
- 键空间的值也就是数据库的值, 每个值可以是字符串对象、列表对象、哈希表对象、集合对象和有序集合对象在内的任意一种 Redis 对象。
上图所示就是一个数据库键空间即一个dict字典,其中:
alphabet
是一个列表键,键的名字是一个包含字符串"alphabet"
的字符串对象,键的值则是一个包含三个元素的列表对象。book
是一个哈希表键,键的名字是一个包含字符串"book"
的字符串对象,键的值则是一个包含三个键值对的哈希表对象。message
是一个字符串键, 键的名字是一个包含字符串"message"
的字符串对象, 键的值则是一个包含字符串"hello?world"
的字符串对象。
数据库的键空间是一个字典, 所以所有针对数据库的操作 —— 比如添加一个键值对到数据库, 或者从数据库中删除一个键值对, 又或者在数据库中获取某个键值对, 等等, 实际上都是通过对键空间字典进行操作来实现的。
添加键
添加一个新键值对到数据库, 实际上就是将一个新键值对添加到键空间字典里面, 其中键为字符串对象, 而值则为任意一种类型的 Redis 对象。
如在键空间添加一个新的键值对,这个新键值对的键是一个包含字符串 "date"
的字符串对象, 而键值对的值则是一个包含字符串 "2013.12.1"
的字符串对象,添加完成后数据库的结构如下:
删除键
删除数据库中的一个键, 实际上就是在键空间里面删除键所对应的键值对对象。
如DEL book表示删除上述数据库键空间中的book键所对应的键值对,结果如下:
更新键
对一个数据库键进行更新, 实际上就是对键空间里面键所对应的值对象进行更新, 根据值对象的类型不同, 更新的具体方法也会有所不同。
对于字符串键: SET 键 新的值 ,对于哈希键 :HSET 键 新的值 等等。
对键取值
对一个数据库键进行取值, 实际上就是在键空间中取出键所对应的值对象, 根据值对象的类型不同, 具体的取值方法也会有所不同。
对于字符串键: GET 键,对于列表键的范围取值:LRANGE 键 0 -1,表示找到键之后接着取得该键所对应的列表对象值。
其他键操作
除了添加、删除、更新、取值操作之外,还有很多针对数据库本身的 Redis 命令 也是通过对键空间进行处理来完成的。
- 用于清空整个数据库的 FLUSHDB 命令, 就是通过删除键空间中的所有键值对来实现的。
- 用于随机返回数据库中某个键的 RANDOMKEY 命令, 就是通过在键空间中随机返回一个键来实现的。
- 用于返回数据库键数量的 DBSIZE 命令, 就是通过返回键空间中包含键值对的数量来实现的。
- 类似的命令还有 EXISTS 、 RENAME 、 KEYS , 等等, 这些命令都是通过对键空间进行操作来实现的。
读写键空间时的维护操作
当使用 Redis 命令对数据库进行读写时,服务器不仅会对键空间执行指定的读写操作,还会执行一些额外的维护操作,包括:
- 在读取一个键之后(读操作和写操作都要对键进行读取),服务器会根据键是否存在,以此来更新服务器的键空间命中(hit)次数或键空间不命中(miss)次数, 这两个值可以在 INFO stats 命令的
keyspace_hits
属性和keyspace_misses
属性中查看。 - 在读取一个键之后, 服务器会更新键的 LRU (最后一次使用)时间, 这个值可以用于计算键的闲置时间,使用命令 OBJECT idletime <key> 命令可以查看键
key
的闲置时间。 - 如果服务器在读取一个键时, 发现该键已经过期, 那么服务器会先删除这个过期键, 然后才执行余下的其他操作。
- 如果有客户端使用 WATCH 命令监视了某个键, 那么服务器在对被监视的键进行修改之后, 会将这个键标记为脏(dirty),从而让事务程序注意到这个键已经被修改过。
- 服务器每次修改一个键之后,都会对脏(dirty)键计数器的值增一, 这个计数器会触发服务器的持久化以及复制操作执行。
- 如果服务器开启了数据库通知功能, 那么在对键进行修改之后, 服务器将按配置发送相应的数据库通知。
过期键
Redis作为一个缓存数据库,对内存的限制有着很高的要求。其具体实现为键的设置时间过期功能,即对存储在 redis 数据库中的值可以设置一个过期时间,到时自动删除。这对Redis的性能提升是非常有意义的。
设置过期时间
通过EXPIRE命令或者PEXPIRE命令,客户端可以以秒或者毫秒级的精度为数据库的某个键设置生存时间(Time To Live,TTL)在经过指定的秒数或者毫秒数之后,服务器就会自动删除生存时间为0的键。
与EXPIRE命令和PEXPIRE命令相似,客户端可以通过EXPIREAT命令或者PEXPIREAT命令,以秒或者毫秒精度为数据库中的某个键设置过期时间(expire time)。过期时间是一个UNIX时间戳,当过期时间来临时,服务器就会自动删除这个键。
通过EXPIRE命令或者PEXPIRE命令实现秒级或者毫秒级的生存时间的设置功能:
- EXPIRE <KEY> <TTL> : 将键的生存时间设为 ttl 秒
- PEXPIRE <KEY> <TTL> :将键的生存时间设为 ttl 毫秒
- EXPIREAT <KEY> <timestamp> :将键的过期时间设为 timestamp 所指定的秒数时间戳
- PEXPIREAT <KEY> <timestamp>: 将键的过期时间设为 timestamp 所指定的毫秒数时间戳.
虽然有多种不同单位和不同形式的设置命令,但实际上EXPIRE、PEXPIRE、EXPIREAT、PEXPIREAT四个命令内部都是调用同一个函数实现,无论客户端执行的上述四个命令的哪一个,最终的执行效果都和执行PEXPIREAT命令是一样的。事实上,生存时间和过期时间是一个意思,过期时间即为当前时间+生存时间。
保存过期时间
数据库redisDb结构中的 expires 字典保存了数据库中所有键的过期时间,被称为过期字典。
typedef struct redisDb {
// 数据库键空间,保存着数据库中的所有键值对
dict *dict;
// 键的过期时间
dict *expires;
//...
} redisDb;
- 过期字典的键是一个指针,指向键空间dict中的某个键对象(某个数据库键,5大类中一种)。
- 过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间(一个毫秒级的UNIX时间戳)。
- 如果给定的键不存在于键空间,那么不能设置过期时间。
移除过期时间
persist命令就是pexpireat命令的反操作:persist命令在过期字典中查找给定的键,并解除键值在过期字典中的关联。如果对上述键空间执行:persist message、persist book,其键空间变更为如下状态:
过期键的判定
TTL或者PTTL命令可以返回秒级或者毫秒级别的键的剩余生存时间。
通过过期字典,程序可以通过以下步骤检查一个键是否过期:
- 检查给定键是否存在于过期字典中,如果存在则取出其过期时间;
- 检查当前UNIX时间戳是否大于键的过期时间,如果是则键过期执行过期删除;
这两种方法都可以进行过期判定,TTL命令返回值大于等于0,说明未过期,但实际中一般都采用后者,因为直接访问字典比执行一个命令要快。
过期键的删除策略
对于过期键的删除,有三种策略:定时删除、惰性删除和定期删除。
定时删除:在设置键的过期时间时,创建一个定时事件,当过期时间到达时,由事件处理器自动执行键的删除操作。
定时删除策略对内存是最友好的: 因为它保证过期键会在第一时间被删除, 过期键所消耗的内存会立即被释放。
这种策略的缺点是, 它对 CPU 时间是最不友好的: 因为删除操作可能会占用大量的 CPU 时间 —— 在内存不紧张、但是 CPU 时间非常紧张的时候 (比如说,进行交集计算或排序的时候), 将 CPU 时间花在删除那些和当前任务无关的过期键上, 这种做法毫无疑问会是低效的。
惰性删除 :放任键过期不管,但是在每次从 dict 字典中取出键值时,要检查键是否过期,如果过期的话,就删除它,并返回空;如果没过期,就返回键值。
惰性删除对 CPU 时间来说是最友好的: 它只会在取出键时进行检查, 这可以保证删除操作只会在非做不可的情况下进行 —— 并且删除的目标仅限于当前处理的键, 这个策略不会在删除其他无关的过期键上花费任何 CPU 时间。
惰性删除的缺点是, 它对内存是最不友好的: 如果一个键已经过期, 而这个键又仍然保留在数据库中, 那么 dict 字典和 expires 字典都需要继续保存这个键的信息, 只要这个过期键不被删除, 它占用的内存就不会被释放。
定期删除:每隔一段时间,对 expires 字典进行检查,删除里面的过期键。
定期删除是这两种策略的一种折中:
- 它每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,来减少删除操作对CPU时间的影响。
- 另一方面,通过定期删除过期键,它有效地减少了因惰性删除而带来的内存浪费。
Redis 使用惰性删除和定期删除两种策略来删除过期的键: 惰性删除策略只在碰到过期键时才进行删除操作, 定期删除策略则每隔一段时间, 主动查找并删除过期键。
Redis的这三种删除方式虽然可以在一定程度上避免内存的浪费和对CPU的影响,但仍然没有彻底的解决突发性的高并发问题;试想一下,如果很短的时间内,Redis的缓存中有巨大量数据新增(比如微博热搜、淘宝双11等),而且这些数据并没有超过限制的时间,超时命令机制无法删除它们,最终就会导致Redis内存块耗尽。解决这个问题,Redis采用的是内存淘汰机制。
Redis内存淘汰 (内存回收) 机制
我们可以通过配置redis.conf中的maxmemory这个值来开启内存淘汰功能,我们可以通过这个值来设置内存淘汰算法
- 客户端发起了需要申请更多内存的命令(如set)。
- Redis检查内存使用情况,如果已使用的内存大于maxmemory则开始根据用户配置的不同淘汰策略(就是配置的maxmemory这个值)来淘汰内存(key),从而换取一定的内存。
- 如果上面都没问题,则这个命令执行成功。
maxmemory为0的时候表示我们对Redis的内存使用没有限制。
Redis提供了下面几种淘汰策略供用户选择,其中默认的策略为noeviction策略:
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰(仅仅是超时的)
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰(剩余存活时间最短)
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(不仅仅是超时的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- noeviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,读正常进行,新写入操作会报错。
这些策略适用的场景:
- allkeys-lru:如果我们的应用对缓存的访问符合幂律分布(也就是存在相对热点数据),或者我们不太清楚我们应用的缓存访问分布状况,我们可以选择allkeys-lru策略。
- allkeys-random:如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略。
- volatile-ttl:这种策略使得我们可以向Redis提示哪些key更适合被eviction。
另外,对于过期键在持久化RBD、AOF和复制功能中的处理,后续Redis持久化之后的学习中会详细说明。
总结
- Redis 服务器的所有数据库都保存在
redisServer.db
数组中, 而数据库的数量则由redisServer.dbnum
属性保存。 - 客户端通过修改目标数据库指针, 让它指向
redisServer.db
数组中的不同元素来切换不同的数据库。 - 数据库主要由
dict
和expires
两个字典构成, 其中dict
字典负责保存键值对, 而expires
字典则负责保存键的过期时间。 - 因为数据库由字典构成, 所以对数据库的操作都是建立在字典操作之上的。
- 数据库的键总是一个字符串对象, 而值则可以是任意一种 Redis 对象类型, 包括字符串对象、哈希表对象、集合对象、列表对象和有序集合对象, 分别对应字符串键、哈希表键、集合键、列表键和有序集合键。
expires
字典的键指向数据库中的某个键, 而值则记录了数据库键的过期时间, 过期时间是一个以毫秒为单位的 UNIX 时间戳。- Redis 使用惰性删除和定期删除两种策略来删除过期的键: 惰性删除策略只在碰到过期键时才进行删除操作, 定期删除策略则每隔一段时间, 主动查找并删除过期键。
- 执行 SAVE 命令或者 BGSAVE 命令所产生的新 RDB 文件不会包含已经过期的键。
- 执行 BGREWRITEAOF 命令所产生的重写 AOF 文件不会包含已经过期的键。
- 当一个过期键被删除之后, 服务器会追加一条 DEL 命令到现有 AOF 文件的末尾, 显式地删除过期键。
- 当主服务器删除一个过期键之后, 它会向所有从服务器发送一条 DEL 命令, 显式地删除过期键。
- 从服务器即使发现过期键, 也不会自作主张地删除它, 而是等待主节点发来 DEL 命令, 这种统一、中心化的过期键删除策略可以保证主从服务器数据的一致性。
- 当 Redis 命令对数据库进行修改之后, 服务器会根据配置, 向客户端发送数据库通知。
参考文章:
《Redis设计与实现》
内容总结
以上是互联网集市为您收集整理的Redis学习(三)Redis数据库全部内容,希望文章能够帮你解决Redis学习(三)Redis数据库所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。