本文是针对redis6.0.16进行分析。
基本数据结构
server.h
1struct redisServer {
2 pid_t pid;
3 pthread_t main_thread_id;
4 char *configfile;
5 char *executable;
6 chat **exec_argv;
7 int dynamic_hz;
8 int config_hz;
9
10 mode_t umask;
11 ...
12 redisDb *db; //代表创建的数据库
13}
1typeof struct redisDbz{
2 dict *dict;
3 dict *expires;
4 dict *blocking_keys;
5 dict *ready_keys;
6 dict *watched_keys;
7 int id;
8 long long avg_ttl;
9 unsigned long expires_cursor;
10 list *defrag_later;
11} redisDb;
dict保存了数据库中所有的键值对,也称键空间
dict很重要,其实redis相当于大的dict
dict.h
1typedef struct dict {
2 dictType *Type;
3 void *privdata;
4 dictht ht[2]; // 2个 一个做rehash用
5 long rehashidx
6 unsigned long iterators;
7} dict
8
9typedef struct dictht{
10 dictEntry **table;
11 unsigned long size
12 unsigned long sizemask;
13 unsigned long used;
14} dictht;
15
16typedef struct dictEntry {
17 void *key;
18 union {
19 void *val; //指向 redisobject
20 uint64 t u64;
21 int64 t s64;
22 double d;
23 } v;
24 struct dictEntry *next
25} dictEntry
key 保存实际的key值
val 它指向redisobject 数据结构
dictEntry *next 当hash值一样时,形成一个列表,指向下一个 dictEntry;
redisobject 真实的数据类型
server.h
1//实际的数据结构
2typedef struct redisobject {
3 unsigned type:4;
4 unsigned encoding:4;
5 unsigned lru:LUR_BITS;
6 int refcount;
7 void *ptr;
8} robj;
type 对应的数据类型,string hash list set zet
encoding内部实际保存类型数据类型,很重要,例如list的ziplist,quicklist;
lru 缓存淘汰机制时使用
refcount 引用计数,主要用于内存回收
ptr指向真实数据存储
redis数据类型以及底层结构和原理
string: int,简单动态字符串 sds
hash: ziplist,hashtable
list: ziplist,quicklist(双向循环链表)
set :intset ,hashtable
zset: ziplist,skiplist(跳表)
string
redis 的string 自己构建了简单动态字符串,sds。
sds内部又可以转为int,embstr,raw
string
- 值为int ->int
- 小于等于44字节的 -> embstr
- 大于44字节 -> raw
embstr是连续的内存,只要请求一次内存
raw它是另外的空间,需要额外的再请求一次内存获得到。
为啥不用c的string
1struct sdshdr{
2 unsigned int len; //buf中已使用的长度
3 unsigned int free; //buf中未使用的长度
4 char buf[];//柔性数组buf
5}
- 效率高 (保存了长度)
- 防止数据溢出
- c语言的2个字符串内存可能是紧挨着的,如果一个字符串要变长
- sds它可以先判断是否足够长的len,不满足长度len,就直接扩充
- 空间预分配
- 分配更大的空间,如果不够大,再申请内存,减少内存分配次数
- 惰性空间释放
- 字符串缩短后,不会立即释放,可能后面还需要变长
针对不同数据大小,分配不同的类型,减少空间
sdshdr5,sdshdr8,sdshdr16,sdshdr32,sdshdr32。
hash
redis的hash的底层是ziplist或者hashtable
1type `key`
2
3object encode `key` # 具体的数据类型
ziplist
zlbytes:32位无符号整形,表示整个ziplist所占的空间大小,包含了zlbytes所占的4个字节
zltail:32位无符号整型,表示整个list中最后一项所在的偏移量,方便在尾部做pop操作
zllen:16位,表示ziplist所存储的entry个数
entry:不定长
zlend: 8位,ziplist的尾部表示,值固定是255。
entry有3部分组成,prelen,encoding,value
- prelen:前一个entry的长度
- encoding:当前编码类型和长度
- value:真实的字符串和数字
ziplist优点: 连续的内存空间,所以利用率高,访问效率高。 缺点:更新效率低。
list
redis 的list 底层分类ziplist和quicklist
quicklist:
- 更新效率高 o(n)
- 缺点:增加内存的开销。
quicklist 是有 ziplist组成的双向链表
count是所有ziplist中entry个数,len是 节点数量
set
redis的set是无序的,自动去重。底层是intset或者dict
当数据用整形并且数据元素小于配置中set-max-intset-entries是用inset 否则 dict
zset
redis的zset是有序的,,自动去重的数据类型,底层是由 ziplist 和 skiplist实现的,当数据较少时用ziplist来存储。
ziplist可以由配置文件中通过zset-max-ziplist-entries 和zset-max-ziplist-value来配置。
redis中的事务
redis中的事务:
- 事务提交前,先检查命令语法是否正确
- 提交后的命令,一定会执行
- 由命令报错,也会执行完
- 不能回滚
和批量操作的区别,exec时,命令要么都执行要么都不执行(不关心成功与否)。
批量操作不会检查语法。
- multi,告诉redis 服务器开启一个事务。注意只是开启,而不是执行
- exec,告诉redis开始执行事务
- discard,告诉redis取消事务
- watch,监视某一个键值对,它的作用是在事务执行之前如果监视的键值被修改,事务会被取消。
io多路复用
开启多线程
io-threads 4
6.0默认还是单线程处理 io 读写
c代表客户端命令
redis 6.0开启io多线程, io读写都在io线程中,计算是在work线程中。work线程页会执行io读写。
1strace -ff -o log /xxx/redis-server /xxx/redis.conf
2# 跟踪系统调用 strace, 会把系统调用输出到 log.`pid` 的文件中,可以看到io 读写 和计算操作。
持久化机制
rdb,生成某一个时刻多快照,然后保存到二进制文件中
aof 记录每一条写命令,追加到文件中,打开可以看到具体到操作记录
混合模式,2者结合
手动触发: save:阻塞的状态,直到rdb持久化完成,线上环境 谨慎使用
bgsave :它会fock一个子进程,用来执行持久化,主进程继续响应客户端的请求。
自动触发:在m秒内,发生改变key的个数就会触发bgsave。
hot key,big key 发现和解决
hot key的问题
热key 占用大量cpu资源,使其效率降低,影响其他业务。
热key 所在节点访问大,容易造成网络瓶颈
热key解决方案
- 对热key增加一定对规则,增加后缀,让它变成好几个key ,分散到不同节点上,减少一个节点到压力。
但是会出现数据一致性问题,更新一个key 变成更新多个key,工作量增加。
对热点key 单独做集群,做隔离,成本会增加。
-
采用读写分离,会有延迟
-
客户端本地缓存
-
进程内存cache
什么是大key
string中的数据大于10k
list,zset,set中数据大于5000个
发现大key
1redis-cli --bigkeys
用命令 llen,scard,zcard,strlen 看字节数、成员数分析
大key的删除
耗时,阻塞主线程
低访问段删除
list,set,zset,hash可以分批次删除
unlink 代替del 命令