Redis使用对象来表示数据库中的键和值,新建键值对时,至少会创建2个对象(键和值对象)。每个对象都由redisObject结构体表示。
键总是一个字符串对象
编码可以是:
对于浮点数值,其保存方式是字符串,每次对浮点数操作时,都要转换。
如果一个字符串对象保存的是整数值,且可以用long类型来表示时使用。
会保存在对象结构的ptr属性里(将void*转成long),并将编码设为int
若保存的是一个字符串值,且长度小于32字节,将用embstr编码来保存。
若保存的是一个字符串值,且长度超过32字节(32bit),则用SDS保存字符串值。
int和embstr编码的字符串条件满足的情况下会转换成raw(如append)。
因为redis没有为embstr编写任何相应的修改程序,所以每次对embstr修改都会转换成raw编码。
(现在显示的是quicklist,不会显示ziplist,版本变化)
编码可以是
现在直接是quicklist,quicklist就相当于是块状链表,一个quicklist里面就像链表一样连着多个ziplist。
编码可以是:
使用ziplist编码的条件:
不满足就转换成hashtable
编码可以是:
当使用hashtable时,每个键的值都被设置为null
使用intset编码的条件:
编码可以为:
使用ziplist编码条件:
仅对包含整数值的字符串共享。
Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含从0到9999的所有整数值,当服务器需要用到值为0到9999的字符创对象时,会共享这些对象。
简单动态字符串
//链表节点
struct sdshdr {
int len;//记录所保存字符串(字节的形式)的长度,末尾的`\0`不会计算
int free;//保存buf中未使用的字节数
char buf[];//用于保存的字节数组
}
'\0'
字符,这样跟C库兼容。//链表节点
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void * value;
}
//链表结构
typedef struct list {
listNode *head;
listNode *tail;
unsigned long len;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
}
特点:
渐进式rehash
typedef struct intset {
uint32_t encoding;//元素(占用字节)类型
uint32_t length;//元素数量
int8_t contents[];//保存元素的(字节)数组
} intset;
是集合键的实现之一,当一个集合只包含整数元素,且元素不多时,就会使用此实现。(较多时使用hashtable),实际是用数组实现,里面从小到大排列
当新类型比整数集合现有元素类型长时,会进行元素类型升级,无论怎样的情况都不会进行降级。
列表键和哈希键的底层实现之一。
/* Redis database representation. There are multiple databases identified
* by integers from 0 (the default database) up to the max configured
* database. The database number is the 'id' field in the structure. */
typedef struct redisDb {
dict *dict; /* The keyspace for this DB */
dict *expires; /* Timeout of keys with a timeout set */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
过期时间的设置:expire, pexpire,expireat,pexpireat底层都是pexpireat实现
expire字典保存了数据库中所有键的过期时间。
数据库主要由:
Redis本身使用的是惰性删除和定期删除策略
内存友好
设置键过期的同时,创建一个定时器,在键过期时立即删除
CPU友好
放任键过期不管,获取键时,检查是否过期。过期则删除,否则返回该键
折中
隔一段时间,检查并删除过期键。检查多少数据库和删除多少键由算法决定。
生成RDB文件时过期键不会被保存。
RDB文件是一个经过压缩的二进制文件。通过save和bgsave命令创建
过期键不会被保存。
AOF文件载入时,会根据AOF文件创建一个伪客户端然后一条条执行指令。
appendfsync选项:
利用bgaofrewrite
为解决AOP文件体积膨胀问题,可以创建一个新的AOF文件替代现有AOF文件,同时保存的数据库状态相同。
实际上并不需要对现有AOF文件分析,只要根据现有数据库状态导出即可。
将 RDB 的文件和局部增量的 AOF 文件相结合。RDB 可以使用相隔较长的时间保存策略,AOF 不需要是全量日志,只需要保存前一次 RDB 存储开始到这段时间增量 AOF 日志即可
Redis是事件驱动程序,需处理以下2类事件:
Redis基于Reactor模式开发了自己的网络事件处理器。采用IO多路复用的方式来同时监听多个套接字。
serverCron函数默认每100毫秒执行一次,工作包括:
服务器的启动:
新版复制功能采用psync来执行复制时的同步操作,psync具有完整重同步和部分重同步2种模式。
由以下3个部分构成:
利用复制偏移量来判断主从是否不一致。
利用复制积压缓冲区(默认1MB)来实现部分重同步,如果偏移量不在复制积压缓冲区,则要进行完全重同步。
利用运行ID判断是否是原来的主服务器
集群通过分片(sharding)进行数据共享,并提供复制和故障转移功能。
根据cluster-enabled配置是否为yes决定是否开启服务器的集群模式。
集群的整个数据库被分成16384(二进制:11111111111111)个槽,数据库中的每个键都属于这16384中的一个。当每个槽都有节点在处理时,集群处于上线状态(OK),反之下线状态(fail)
利用char数组中为0或1来判断槽被那个节点处理(桶排序思想)
利用CRC16(key) & 16383
来计算给定key属于哪个槽
利用redis-trib进行分片,将属于某个槽的所有键值对从一个节点转移到另一个节点
集群里从节点用于复制主节点,主节点下线时,选出一个从节点代替主节点继续处理请求。
集群中的节点通过发送和接收消息来进行通信,常见消息包括MEET,PING,PONG,PUBLISH,FAIL五种。
都会让客户端转向
区别在于:
利用multi、exec、watch来实现。
不支持事务回滚机制,即使执行出错,后续指令继续执行。
所有对数据库进行修改的命令,执行后都会利用multi.c/touchWatchKey
函数对watched_keys字典进行检查,看是否有客户端正在监视刚刚被命令修改的键,如果有的话,将被修改键的客户端的redis_dirty_cas标识打开,表示客户端的事务安全性已被破坏。
watch命令实际实现了一个乐观锁
使用字符串对象表示位数组,即利用SDS保存位数组
通过执行monitor指令,客户端可以实时地接收并打印出服务器当前处理的命令请求相关信息。