Redis简介
Redis是一个速度非常快的非关系数据库(non-relational database),它可以存储键(key)与5种不同类型的值(value)之间的映射(mapping),可以将存储在内存的键值对数据持久化到硬盘,可以使用复制特性来扩展读性能,还可以使用客户端分片来扩展写性能。
Redis与其他数据库和缓存服务器的对比
Redis 数据结构
SDS
SDS(simple dynamic string)简单动态字符串SDS的API都是二进制安全的(binary-safe),所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据,程序不会对其中的数据做任何限制,过滤,或者假设数据在写入时是什么样的,它被读取时就是什么样。
链表
字典
- 使用Murmurhash2算法来计算键的哈希值
- 使用链地址法来解决键冲突
- rehash:随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让hash表的负载因子(load factor)维持在一个合理的范围内,当hash表中保存的键值对数量太多或者太少时,程序需要对hash表的大小进行相应的扩展或者收缩,扩展或者收缩hash表的工作就是通过rehash(重新散列,hash函数还是原来的函数)操作来完成的;负载因子 = hash表已经保存节点数量 / hash表大小。
- 渐进式rehash:rehash动作不是一次性,集中式地完成的,而是分多次,渐进式地完成的。
跳跃表skiplist
跳跃表skiplist是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的,如下图所示:- 层(level): 节点中用L1,L2,L3等字样标记节点的各个层,L1代表第一层,L2代表第二层,一次类推。每个层都带有两个属性:前进指针和跨度,前进指针用于访问位于表尾方向的其他节点;而跨度则记录了前进指针所指向节点和当前节点的距离。
- 分值:各个节点中的1.0,2.0,3.0是节点所保存的分值,在跳跃表中,节点按照各自所保存的分值从小到大排列。
整数集合
整数集合(intset)是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现,结构定义如下图所示:- 有序性和唯一性:整数集合的每个元素都是contents数组的一个数组项(item),每个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复键
-
encoding属性决定了contents数组真正的类型,数组中的几个元素算作一个元素的问题,如下图所示:
- 升级:每当要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素的类型都要长时,整数集合需要先进行升级(upgrade),然后才能将新元素添加到整数集合里面。
压缩列表ziplist
压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值,如下图所示:- 有连锁更新的问题:在特殊情况下产生的连续多次空间扩展操作称之为“连锁更新(cascade update)”。
对象
Redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,包含如下的5中对象类型:- 通过这五种不同类型的对象,Redis可以在执行命令之前,根据对象的类型来判断一个对象是否可以执行给定的命令;
- 内存回收机制:Redis的对象系统基于引用计数技术实现了内存回收机制;
- 对象共享机制:Redis使用引用计数技术实现了对象共享机制。
Redis使用对象来表示数据库中的键和值,每当我们在Redis数据库中新创建一个键值对时,至少会创建两个对象,一个对象用作键值对的键(键对象,键总是一个字符串对象),另一个对象用作键值对的值(值对象)。
数据结构 | 对象类型 |
---|---|
字符串对象
字符串对象的编码可以是int,raw或者embstr,这三种字符串编码在一定条件下会进行转换。
如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性中(将void转换为long),并将字符串对象的编码设置为int。
-
如果字符串对象保存的是一个字符串的值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串SDS来保存这个字符串的值,并将对象的编码设置为raw,如下图所示
-
如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用embstr(embstr编码是专门用于保存端字符串的一种优化编码方式)编码的方式来保存这个字符串值;这种编码和raw编码一样,都是用redisObject结构和sdshdr结构来表示字符串对象,但raw编码会调用两次内存分配函数来分别创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中一次包含redisObject和sdshdr两个结构,如下图所示:
-
字符串对象对应的命令实现:
列表对象
列表对象的编码可以是ziplist或者linkedlist;ziplist编码使用的压缩列表作为底层实现,linkedlist使用的是双端链表作为底层实现,这两种编码会在一定的条件自动进行转换,两种列表编码如下表对比所示:
ziplist 编码 | linkedlist 编码 |
---|---|
哈希对象
哈希对象的编码可以ziplist或者hashtable;hashtable编码的哈希对象使用字典作为底层实现;这两种编码可以在一定的条件下相互转换;下表显示了两种编码的对比:
ziplist 编码 | hashtable 编码 |
---|---|
集合对象
集合对象的编码可以是intset或者hashtable,intset编码的集合对象使用整数集合作为底层实现;这两种编码在一定条件下可以相互转换,对比如下图所示:
intset 编码 | hashtable 编码 |
---|---|
有序集合
有序集合的编码可以是ziplist或者skiplist(跳跃表),skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset结构同时包含一个字典和一个跳跃表,如下代码所示:
typedef struct zset {
zskiplist *zsl;
dict *dict;
}
这两种编码在一定条件下可以相互转换,对比如下表所示:
ziplist 编码 | skiplist 编码 |
---|---|
redis常用命令
TYPE命令
对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以是字符串对象,列表对象,哈希对象,集合对象或者有序集合对象中一种,因此在对一个数据库键执行TYPE命令时,命令返回的结果为数据库键对应的值对象的类型,而不是键对象的类型,其中格式:type 键名称 ,对应的输出如下图所示:
OBJECT ENCODING命令
对象的编码就是对象使用了什么数据结构作为对象的底层实现,格式为:OBJECT ENCODING 键名称,对应的输出下图所示:
OBJECT IDLETIME命令
OBJECT IDLETIME命令可以打印出给定键的空转时间,空转时间=当前时间 - 键的值对象最后一次被命令程序访问的时间
对象通用命令
- DEL命令
- EXPIRE命令
- EXPIREAT 命令
- RENAME命令
- SORT命令
- TYPE命令
- OBJECT命令
- TTL命令:TTL命令以秒为单位返回键的剩余生存时间
- PTTL命令:PTTL命令则以毫秒为单位返回键的剩余生存时间
- SAVE命令:SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求
- BGSAVE命令:BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求
- FLUSHALL命令:清除当前数据库中的所有键值对
- CLIENT list命令:列出目前所有连接到服务器的普通客户端,命令输出中的fd域显示了服务器连接客户端所使用的套接字描述符
- AUTH命令:如果authenticated的值为0,那么表示客户端未通过身份验证;如果authenticated的值为1,那么表示客户端已经通过了身份验证,AUTH命令可以是客户端的状态authenticated的值从0到1
- PING命令
- SLAVEOF命令:通过向从服务器发送SLAVEOF命令,可以让一个从服务器去复制一个主服务器,命令SLAVEOF <master_ip> <master_port>
- EXISTS命令
- INFO replication: 客户端向主服务器发送INFO replication命令得到的副本信息回复
- redis-sentinel命令:redis-sentinel /path/to/your/sentinel.conf 或者 redis-server /path/to/your/sentinel.conf --sentinel
- SUBSCRIBE命令:订阅
- PSUBSCRIBE命令:订阅一个或多个模式
- PUBLISH命令
- 事务命令:MULTI/EXEC
- WATCH命令:WATCH命令是一个乐观锁,它可以在EXEC命令执行之前,监视任意数量的数据库键,并在EXEC命令执行时,检查被监视的键是否至少有一个已经被修改过了,如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。
- EVAL命令:EVAL命令可以直接对输入的脚本进行求值
- EVALSHA命令:根据脚本的SHA1校验和来对脚本进行求值
- SCRIPT LOAD命令:加载脚本
- SETBITS/GETBITS/BITCOUNT/BITOP命令:位数组
- CONFIG SET命令
- SLOWLOG GET命令:查看服务器所保存的慢查询日志
- MONITOR命令
select命令
每个Redis客户端都有自己的目标数据库,每当客户端执行数据库写命令或者数据库读命令的时候,目标数据库就会成为这些命令的操作对象;默认情况下,Redis客户端的目标数据库是0号数据库,但客户端可以通过执行select命令来切换目标数据库;数据库对应的结构如下:
redisServer | redisDb | 示例图 |
---|---|---|
数据库持久化
RDB持久化
RDB持久化功能是将Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失,如下图所示:AOF持久化
AOF(append only File)持久化功能,与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的,如下图所示:参考
- redis设计与实现(第二版)
- 源码解析:https://github.com/huangz1990/redis-3.0-annotated
- 测试用例:https://yq.aliyun.com/articles/62845
- 源码对应的测试用例讲解:https://yq.aliyun.com/articles/62845
- Mac redis: https://www.cnblogs.com/xrhou12326/p/7103106.html