本文是针对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中的事务:

  1. 事务提交前,先检查命令语法是否正确
  2. 提交后的命令,一定会执行
  3. 由命令报错,也会执行完
  4. 不能回滚

和批量操作的区别,exec时,命令要么都执行要么都不执行(不关心成功与否)。

批量操作不会检查语法。

  1. multi,告诉redis 服务器开启一个事务。注意只是开启,而不是执行
  2. exec,告诉redis开始执行事务
  3. discard,告诉redis取消事务
  4. 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解决方案

  1. 对热key增加一定对规则,增加后缀,让它变成好几个key ,分散到不同节点上,减少一个节点到压力。

但是会出现数据一致性问题,更新一个key 变成更新多个key,工作量增加。

对热点key 单独做集群,做隔离,成本会增加。

  1. 采用读写分离,会有延迟

  2. 客户端本地缓存

  3. 进程内存cache

什么是大key

string中的数据大于10k

list,zset,set中数据大于5000个

发现大key

1redis-cli --bigkeys

用命令 llen,scard,zcard,strlen 看字节数、成员数分析

大key的删除

耗时,阻塞主线程

低访问段删除

list,set,zset,hash可以分批次删除

unlink 代替del 命令