redis

redis.py

StrictRedis实现了大部分的官方命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis.py

1
2
3
4
5
6
import redis
conn = redis.Redis(host='127.0.0.1', port=6379)
# 可以使用url方式连接到数据库
# conn = Redis.from_url('redis://@localhost:6379/1')
conn.set('name', 'LinWOW')
print(conn.get('name'))

部署方式

使用容器部署

1
docker run --name some-redis -d redis:4.0 redis-server --appendonly yes

常用命令

1
2
3
4
config get requirepass #查询是否设置访问密码
config set requirepass mypassword #设置访问密码
auth mypassword #认证密码
keys * #查询所有键值

配置文件

可以在部署redis的时候,手动指定redis的配置文件路径,在启动redis后,通过redis-cli,可以使用命令config get *获取所有配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
bind 0.0.0.0 #监听地址,可以用空格隔开后多个监听IP

protected-mode yes #redis3.2 之后加入的新特性,在没有设置bind IP和密码的时候,redis只允许访问127.0.0.1:6379,可以远程连接,但当访问将提示警告信息并拒绝远程访问

port 6379 #监听端口

tcp-backlog 511 #三次握手的时候server端收到client ack确认号之后的队列值,即全队列长度

timeout 0 #客户端和Redis服务端的连接超时时间,默认是0,表示永不超时

tcp-keepalive 300 #tcp 会话保持时间300s

daemonize no #默认redis-server不作为守护进程运行的,而前台运行,如果想在后台运行,就把它改成 yes,当redis作为守护进程运行的时候,它会写一个 pid 到 /var/run/redis.pid 文件

supervised no #和OS相关参数,可设置通过upstart和systemd管理Redis守护进程,centos7后都使用systemd

pidfile /var/run/redis_6379.pid #pid文件路径

loglevel notice #日志级别

logfile "/path/redis.log" #日志路径

databases 16 #设置数据库数量,默认:0-15,共16个库

always-show-logo yes #在启动redis 时是否显示redis的logo

save 900 1 #在900秒内有一个键内容发生更改就出就快照机制
save 300 10
save 60 10000  #60秒内如果有10000个健以上的变化,就自动快照备份

stop-writes-on-bgsave-error yes #yes时因空间满等原因快照无法保存出错时,禁止redis写入操作,建议为no

rdbcompression yes #持久化到RDB文件时,是否压缩,"yes"为压缩,"no"则反之

rdbchecksum yes #是否对备份文件开启RC64校验,默认是开启

dbfilename dump.rdb #快照文件名

dir ./ #快照文件保存路径,示例:dir "/apps/redis/data"

#replicaof <masterip> <masterport>  #指定复制的master主机地址和端口,5.0版之前的指令为slaveof
#masterauth <master-password> #指定复制的master主机的密码

replica-serve-stale-data yes #当从库同主库失去连接或者复制正在进行,从机库有两种运行方式:
1、设置为yes(默认设置),从库会继续响应客户端的读请求,此为建议值
2、设置为no,除去指定的命令之外的任何请求都会返回一个错误"SYNC with master in progress"。

replica-read-only yes #是否设置从库只读,建议值为yes,否则主库同步从库时可能会覆盖数据,造成数据丢失

repl-diskless-sync no #是否使用socket方式复制数据(无盘同步),新slave连接连接时候需要做数据的全量同步,redis server就要从内存dump出新的RDB文件,然后从master传到slave,有两种方式把RDB文件传输给客户端:
1、基于硬盘(disk-backed):为no时,master创建一个新进程dump生成RDB磁盘文件,RDB完成之后由父进程(即主进程)将RDB文件发送给slaves,此为推荐值
2、基于socket(diskless):master创建一个新进程直接dump RDB至slave的网络socket,不经过主进程和硬盘

基于硬盘(为no),RDB文件创建后,一旦创建完毕,可以同时服务更多的slave,但是基于socket(为yes), 新slave连接到master之后得逐个同步数据。当磁盘I/O较慢且网络较快时,可用diskless(yes),否则使用磁盘(no)

repl-diskless-sync-delay 5 #diskless时复制的服务器等待的延迟时间,设置0为关闭,在延迟时间内到达的客户端,会一起通过diskless方式同步数据,但是一旦复制开始,master节点不会再接收新slave的复制请求,直到下一次同步开始才再接收新请求。即无法为延迟时间后到达的新副本提供服务,新副本将排队等待下一次RDB传输,因此服务器会等待一段时间才能让更多副本到达。推荐值:30-60

repl-ping-replica-period 10 #slave根据master指定的时间进行周期性的PING master 监测master状态

repl-timeout 60 #复制连接的超时时间,需要大于repl-ping-slave-period,否则会经常报超时

repl-disable-tcp-nodelay no #是否在slave套接字发送SYNC之后禁用 TCP_NODELAY,如果选择"yes",Redis将合并多个报文为一个大的报文,从而使用更少数量的包向slaves发送数据,但是将使数据传输到slave上有延迟,Linux内核的默认配置会达到40毫秒,如果 "no" ,数据传输到slave的延迟将会减少,但要使用更多的带宽

repl-backlog-size 512mb #复制缓冲区内存大小,当slave断开连接一段时间后,该缓冲区会累积复制副本数据,因此当slave 重新连接时,通常不需要完全重新同步,只需传递在副本中的断开连接后没有同步的部分数据即可。只有在至少有一个slave连接之后才分配此内存空间。

repl-backlog-ttl 3600 #多长时间内master没有slave连接,就清空backlog缓冲区

replica-priority 100 #当master不可用,Sentinel会根据slave的优先级选举一个master,此值最低的slave会当选master,而配置成0,永远不会被选举,一般多个slave都设为一样的值,让其自动选择

#min-replicas-to-write 3  #至少有3个可连接的slave,mater才接受写操作
#min-replicas-max-lag 10  #和上面至少3个slave的ping延迟不能超过10秒,否则master也将停止写操作

requirepass foobared #设置redis 连接密码,如果有特殊符号,用" "引起来

rename-command #重命名一些高危命令,示例:rename-command FLUSHALL "" 禁用命令

maxclients 10000 #Redis最大连接客户端

maxmemory #redis使用的最大内存,单位为bytes字节,0为不限制,建议设为物理内存一半,8G内存的计算方式8(G)*1024(MB)1024(KB)*1024(Kbyte),需要注意的是缓冲区是不计算在maxmemory内。

appendonly no #是否开启AOF日志记录,默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了,但是redis如果中途宕机,会导致可能有几分钟的数据丢失(取决于dumpd数据的间隔时间),根据save来策略进行持久化,Append Only File是另一种持久化方式,可以提供更好的持久化特性,Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。默认不启用此功能

appendfilename "appendonly.aof" #AOF文件名,是文本文件,存放在dir指令指定的目录中
appendfsync everysec #aof持久化策略的配置,no表示不执行fsync,由操作系统保证数据同步到磁盘,always表示每次写入都执行fsync,以保证数据同步到磁盘,everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。

no-appendfsync-on-rewrite no #在aof rewrite期间,是否对aof新记录的append暂缓使用文件同步策略,主要考虑磁盘IO开支和请求阻塞时间。默认为no,表示"不暂缓",新的aof记录仍然会被立即同步,Linux的默认fsync策略是30秒,如果为yes 可能丢失30秒数据,但由于yes性能较好而且会避免出现阻塞因此比较推荐。

auto-aof-rewrite-percentage 100 # 当Aof log增长超过指定百分比例时,重写AOF文件, 设置为0表示不自动重写Aof 日志,重写是为了使aof体积保持最小,但是还可以确保保存最完整的数据

auto-aof-rewrite-min-size 64mb #触发aof rewrite的最小文件大小

aof-load-truncated yes #是否加载由于其他原因导致的末尾异常的AOF文件(主进程被kill/断电等),建议yes

aof-use-rdb-preamble yes #redis4.0新增RDB-AOF混合持久化格式,在开启了这个功能之后,AOF重写产生的文件将同时包含RDB格式的内容和AOF格式的内容,其中RDB格式的内容用于记录已有的数据,而AOF格式的内存则用于记录最近发生了变化的数据,这样Redis就可以同时兼有RDB持久化和AOF持久化的优点(既能够快速地生成重写文件,也能够在出现问题时,快速地载入数据)。

lua-time-limit 5000 #lua脚本的最大执行时间,单位为毫秒

cluster-enabled yes #是否开启集群模式,默认是单机模式

cluster-config-file nodes-6379.conf #由node节点自动生成的集群配置文件名称

cluster-node-timeout 15000 #集群中node节点连接超时时间,超过此时间,会踢出集群

cluster-replica-validity-factor 10 #在执行故障转移的时候可能有些节点和master断开一段时间数据比较旧,这些节点就不适用于选举为master,超过这个时间的就不会被进行故障转移,计算公式:(node-timeout * replica-validity-factor) + repl-ping-replica-period 

cluster-migration-barrier 1 #集群迁移屏障,一个主节点至少拥有一个正常工作的从节点,即如果主节点的slave节点故障后会将多余的从节点分配到当前主节点成为其新的从节点。

cluster-require-full-coverage yes #集群请求槽位全部覆盖,如果一个主库宕机且没有备库就会出现集群槽位不全,那么yes情况下redis集群槽位验证不全就不再对外提供服务,而no则可以继续使用但是会出现查询数据查不到的情况(因为有数据丢失)。建议为no

cluster-replica-no-failover no #如果为yes,此选项阻止在主服务器发生故障时尝试对其主服务器进行故障转移。 但是,主服务器仍然可以执行手动强制故障转移,一般为no

#Slow log 是 Redis 用来记录超过指定执行时间的日志系统, 执行时间不包括与客户端交谈,发送回复等I/O操作,而是实际执行命令所需的时间(在该阶段线程被阻塞并且不能同时为其它请求提供服务)slow log 保存在内存里面,读写速度非常快,因此可放心地使用,不必担心因为开启 slow log 而影响 Redis 的速度

slowlog-log-slower-than 10000 #以微秒为单位的慢日志记录,为负数会禁用慢日志,为0会记录每个命令操作。

slowlog-max-len 128 #最多记录多少条慢日志的保存队列长度,达到此长度后,记录新命令会将最旧的命令从命令队列中删除,以此滚动删除

127.0.0.1:6379> SLOWLOG len  #查看慢日志的记录条数
(integer) 14
127.0.0.1:6379> slowlog get  #查看慢日志的记录
1) 1) (integer) 14
2) (integer) 1544690617
3) (integer) 4
4) 1) "slowlog"
127.0.0.1:6379> SLOWLOG reset #清空慢日志
OK

原理

数据结构

简单动态字符串SDS
struct sdshdr{
	int len;//buf中字符数组的长度
	int free;//buf中未使用的字节数量
	char buf[];
}

空间预分配,修改sds时,如果修改后的sds的长度小于1MB,那么程序分配和len相同大小的未使用空间,否则,分配1MB的未使用空间; 惰性空间释放,当缩短字符串时,将释放的空间当做未使用字节。

链表
1
2
3
4
5
6
7
8
typedef struct list {
	listNode * head;
	listNode * tail;
	unsigned long len;
	void *(*dup)(void *ptr);
	void (*free)(void *ptr);
	int (*match)(void *ptr,void *key);
}list;
字典
typedef struct dictht{
	dictEntry **table;
	//哈希表大小
	unsigned long size;
	//哈希表大小掩码,总是size-1,和size-1与,等于和size取余,位运算效率高
	unsigned long sizemask;
	//哈希表已有节点数量
	unsigned long used;
}dictht;
typedef struct dictEntry{
	void *key;
	union {
		void *val;
		unit64_tu64;
		int64_ts64;
	} v;
	struct dictEntry *next;
}
typedef struct dict {
	dictType *type;
	void *privdata;
	dictht ht[2];
	int rehashidx;
}dict;

hash冲突时,使用链地址方法解决冲突,并且将新的键值对插入链表头,时间为O(1)

为了维护负载因子,需要对哈希表进行rehash,如果是扩展操作,ht[1]为第一个>=ht[0].used*2的2^n (2的n次方幂),如果是收缩,ht[1]大小为第一个大于等于ht[0].used的2^n ;将ht[0]中的值重新计算hash放到ht[1];释放ht[0],将ht[1]和ht[0]互换。 扩容条件:没有执行BGSAVE或者BGREWRITEAOF,并且哈希表的负载因子大于等于1;执行BGSAVE或者BGREWRITEAOF,且负载因子大于等于5 缩容条件:负载因子小于0.1

跳跃表

平均O(logN)、最坏O(N)的查找

使用场景

  • 有序集合键
  • 集群节点中内部数据结构
整数集合intset

保存整数,int16_t、int32_t或者int64_t,保证集合中不会出现重复元素

typedef struct intset{
	uint32_t encoding;
	uint32_t length;
	int8_t contents[];
}intset;
压缩列表

为了节约内存,格式如下

zlbytes,zltail,zllen,entry1,entry2,…,entryN,zlend

对象

typedef struct redisObject {
	// 对象类型,可选值是REDIS_STRING、REDIS_LIST、REDIS_HASH、REDIS_SET、REDIS_ZSET
	unsigned type:4;
	// 编码,每种类型都提供了至少俩种编码
	unsigned encoding:4;
	void *ptr;
	// ...
}robj;
redis> set msg "hello world" #键是字符串对象,值是字符串对象
redis> rpush numbers 1 3 5#键是字符串对象,值是列表对象
redis> hmset profile name Tom age 25 career Prohrammer #键是字符串对象,值是哈希对象
redis> sadd fruits apple banana #键是字符串对象,值是集合对象
redis> zadd price 8.5 apple 5.0 #键是字符串对象,值是有序集合对象
字符串对象

编码可以是int、raw、embstr,其中embstr是专门用于保存短字符串的优化编码

相关命令

set get append incrbyfloat incrby decrby strlen setrange gettange

列表对象

列表的编码可以是ziplist或者linkedlist

哈希对象

编码可以是ziplist或者hashtable

集合对象

编码可以是intset或者hashtable

有序集合对象

编码可以是ziplist或者skiplist

对象空转时长
typedef struct redisObject {
	// ...
	// 最后一次被访问的时间,object idletime可以查看
	unsigned lru:22;
	// ...
}robj;

服务器中的数据库

切换数据库,select 1

读写键空间时的维护操作
  • 在读取一个键之后,如果键存在,会增加命中次数,keyspace_hits,否则,增加不命中次数,keyspace_misses
  • 更新键的lru
  • 如果这个键已经过期,那么会删除这个过期的键
  • 如果有客户端watch了这个键,服务器会在对被监视的键进行修改之后,将这个键标记为脏,
  • 每次修改一个键之后,会对脏键的计数器的值增加1,这个计数器会增加持久化以及复制操作
  • 如果服务器开通了数据库通知功能,那么对键修改之后,服务器将按配置发送通知
设置键的过期时间
1
2
3
4
5
6
7
8
9
expire key 5#设置key有效期为5秒
ttl key#查看key的剩余有效期
pttl key
expireat key 1377257300#设置绝对的过期时间

pexpire key 5 #设置5毫秒的有效时间
pexpireat key 1377257300000

persist key#移除一个键的过期时间

redis数据库中,有一个过期字典,存放过期的键和过期时间,

删除策略
  • 定时删除,设置键的过期时间时,创建定时器,键以过期,就立马删除,比较消耗cpu,定时器需要使用时间事件,查找一个事件的时间复杂度O(N)较高
  • 惰性删除,放任不管,每次检查的键是否过期,如果过期,就删除,对cpu友好,对内存不友好
  • 定期删除,每隔一段时间,删除过期键。可以通过限制删除操作的时长和执行频率,来平衡
AOF、RDB和复制对过期键的处理

生成RDB文件,不会保存过期的键 载入RDB文件,主服务器会忽略过期的键,从服务器全部载入

在AOF模式时,当过期的键被都行删除或定期删除,程序会向aof文件追加del命令,aof重写的时候,忽略过期的键

当服务器在复制模式下,主服务器删除一个过期键,向从服务器发送del命令;从服务器在执行客户端发送的读命令时,不会将过期的键删除;从服务器接到主服务器的del命令,才会删除过期的命令

数据库通知

键空间通知:某个键执行了什么命令 subscribe __keyspace@0__:message 键事件通知:某个命令被什么键执行了 subscribe __keyevevnt@0__:del

notify-keyspace-events选项决定了服务器发送通知的类型

  • AKE
  • AK
  • AE
  • K$
  • EI

RDB持久化

持久化

save:阻塞redis服务器进程,直到rdb文件创建完成 bgsave:派生子进程,创建rdb文件

如果开启aof,优先使用aof文件还原数据库状态,当aof出于关闭的时候,才会使用rdb还原数据库状态

bgsave的时候,服务器会拒绝save命令和bgsave,同时bgrewriteaof和bgsave不会同时执行

服务器中,saveparam数据保存着描述和修改数的参数,用于定期保存数据库,更具一定时间内数据库有修改就进行保存 dirty计数器记录距离上一次成功执行save命令或者bgsave之后,服务器对数据库状态进行了多少次修改; lastsave记录着上一次成功执行save或者bgsave的时间 serverCron默认每个100毫秒执行一次,其中一项就是检查save设置是否满足,如果满足,就执行bgsave命令

文件结构

redis db_version databases eof check_sum

其中database部分保存着每个非空数据库,每个非空数据库在文件中的部分,都可以分为

SELECTDB db_number key_value_pairs

对于不带过期时间的key_value_pairs,其结构如下

type key value

带有过期时间的键值对存放结构如下

EXPIRETIME_MS ms TYPE key value

AOF持久化

aof是将执行的写命令写到aof文件中

redis服务器有aof_buf缓冲区,当默认的配置中appendfsync参数配置如下时:

  • always 将aof_buf缓冲区的内容写入并同步到aof文件
  • everysec 将aof_buf缓冲区的内容写入aof文件,如果上次同步超过1秒,就同步,这个是默认配置
  • no 写入aof文件,有操作系统决定同步

读取aof过程

  • 创建一个伪客户端,
  • 从aof文件中读取命令
  • 使用伪客户端执行读取的命令
  • 循环以上

长时间redis运行下来,会导致aof文件体积很大,为了解决这个问题,使用aof重写功能,新的aof文件会去除冗余,比如多个写同样对象的命令合并成一个写命令

实际上,aof重写的时候,会读取当前数据库的状态,生成写命令,忽略已过期的键,使用子进程写,不会阻塞主进程,不需要使用锁,为了避免aof重写时数据库更新,redis设置了aof重写缓冲区,当开启了aof重写时,redis执行了写命令后,会将这个命令发送给aof缓冲区和aof重写缓冲区,当子进程完成aof重写后,父进程将aof重写缓冲区内容写入新的aof文件,并替换原有的aof文件

事件驱动

文件事件

  • 使用io多路复用监听多个套接字,按照套接字执行的任务关联不同的事件处理器
  • 当被监听的套接字准备好accept、read、wrte、close时,与操作相对应的文件事件就会产生,就会调用之前关联好的事件处理器
文件事件处理器的构成

套接字、io多路复用程序、文件事件分派器,事件处理器

io多路复用将产生事件的套接字放到一个队列,有序、同步、每次一个向文件事件分排气传送套接字,

io多路复用底层实现
  • select
  • epoll
  • evport
  • kqueue
文件事件处理器
  • 连接应答处理器
  • 命令请求处理器
  • 命令回复处理器
  • 复制处理器

时间事件

  • 定时事件
  • 周期性事件

一个时间事件主要包括

  • id
  • when
  • timeProc

实现

服务器将所有的时间事件都存放一个无序列表,无序指的是不按照时间的时间排序,

serverCron事件

  • 更新服务器各类统计信息,比如时间、内存、数据库占用
  • 清理数据库中的过期键值对
  • 关闭和清理失效的连接客户端
  • 尝试aof或rdb持久化
  • 主服务器对从服务器的同步
  • 对集群定期同步和连接测试

redis.conf中的hz选项可以配置运行频率

客户端

通用属性

套接字描述符fd

  • -1,伪客户端,读取aof和处理lua脚本
  • 大于-1的整数,普通客户端

CLIENT list列出所有的客户端

名字,默认是空,可以使用client setname hello设置名字

标志,flags,

  • REDIS_MASTER 客户端是一个主服务器
  • REDIS_BLOCKED 客户端正在被列表命令阻塞
  • REDIS_MULTI REDIS_DIRTY_CAS 客户端正在执行事务,但事务的安全性已经被破坏
  • REDIS_SLAVE REDIS_PRE_PSYNC 客户端是从服务器

输入缓冲区 客户端发送的命令都会先缓存到这个区

1
2
3
typedef struct redisClient {
	sds querybuf;
}

输出缓冲区

1
2
3
4
5
6
7
8
typedef struct redisClient{
    // 固定缓冲区,保存比较小的回复,默认大小16KB
	char buf[REDIS_REPLY_CHUNK_BYTES];
	int bufpos;

	// 可变缓冲区由reply列表或多个字符串组成
	list *reply
}

authenticated,配置文件中的requirepass设置是否需要密码认证

  • 0 客户端未认证
  • 1 客户端已认证

obuf_soft_limit_reached_time,软性限制时间,输出缓冲区大小超过软性限制,记录此时间,当时长超过限制,关闭客户端

client-output-buffer-limit <class> <hard limit> <soft limit> <soft seconds>

1
2
3
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60

服务器

命令执行器

redisCommand

name 命令名字,比如set
proc 实现函数指针
arity 命令参数个数
sflags 标识,读命令、写命令、是否允许载入时使用
flags sflags的二进制标识
calls 服务器总共执行次数
milliseconds 执行命令总耗时

预备操作

  • cmd指针是否指向NULL
  • 检查参数是否正确
  • 检查客户端是否经过认证
  • 打开maxmemory设置,就会检查内存占用
  • 如果 bgsave出错、服务器打开stop-writes-on-bgsave-error、服务器执行一个写命令,那么服务器将拒绝这个命令
  • 如果服务器正在载入数据,那么标识为I的命令才能执行
  • 。。。

调用命令的实现函数

后续操作

  • 添加慢查询日志
  • 写入命令耗时
  • 写入aof
  • 发送给从服务器

初始化服务器

initServerConfig

  • 设置服务器运行id
  • 默认运行频率
  • 默认配置文件路径
  • 运行架构
  • 默认端口号
  • rdb持久化和aof持久化条件
  • lru时钟
  • 创建命令表

载入配置项

redis.conf

initSever 初始化服务器数据结构

  • server.clients
  • server.db
  • server.pubsub_channels
  • server.lua
  • server.slowlog

还原数据库状态

执行事件循环

复制

1
reids>slaveof 127.0.0.1 6379

旧的复制

同步sync

  • 从服务器向主服务器发送sync
  • 主服务器执行bgsave,并生成缓冲区记录从现在开始的命令
  • 主服务器将rdb文件发送给从服务器,从服务器载入
  • 主服务器将缓冲区的写命令发送给从服务器,从服务器执行这些命令

命令传播

问题:sync比较消耗资源,从服务器断链后,需要重新sync

新的复制

psync

  • 完整重同步,用于初次同步,和sync一样
  • 部分重同步,从服务器断连后,主服务器将断连期间的命令发送给从服务器
部分重同步

主服务器的复制偏移量 主服务器的复制积压缓冲区,默认大小为1MB的FIFO队列 服务器的运行id

哨兵

当主服务器下线的时候,会从从服务器中挑选一个升级为主服务器,哨兵会向从服务器发送新的复制指令,让这些从服务器重新设置主服务器,等原主服务重新上线后,将其设置为从服务器

1
redis-server /sentinel.conf --sentinel

集群

配置文件中的cluster-enabled中的参数决定是否开启集群

将节点加入集群

1
cluster meet <ip> <port>

实现过程,类似TCP三次握手

  • 节点A为节点B创建一个clusterNode结构,添加到自己的clusterState.nodes字典里面
  • 向节点B发送一条MEET消息
  • 节点B为节点A创建一个clusterNode结构,存放到自己的clusterState.nodes字典里面
  • 节点B回复节点A一条PONG消息
  • 节点A向节点B返回一条PING消息

redis集群通过分片的方式保存数据库中的键值对,整个集群被分为16384个槽,当这些槽都有节点在处理的时候,集群出于上线状态,否则,集群是下线状态

手动执行命令分配槽,只有将所有的槽分配完,集群的状态才是ok,注意不要在redis终端执行以下命令,直接在shell里面执行以下语句

1
redis-cli cluster addslots {0..5000}

计算键属于哪个槽,使用CRC-16校验和,与16383,可以通过以下命令获取

1
cluster keyslot <key>

当计算后发现当前的槽不在本节点,就会返回客户端,让客户端去正确的节点去执行 redis单点和集群客户端是不一样的,如果用单点客户端访问集群,客户端是无法再去寻找集群中的其他节点,只会在客户端显示错误和相关信息

在集群中,节点会用clusterState结构中slots_to_keys跳跃表来保存槽和键之间的关系

redis集群可以将任意数量的已经指派给某个节点的槽指派给另一个节点,通过redis-trib负责重分片,步骤如下

  • redis-trib向目标节点发送cluster setslot <slot> importing <source_id>,将目标节点准备从源节点导入属于slot的键值对
  • redis-trib对源节点发送cluster setslot <slot> migrateing <target_id>,让源节点准备好属于槽slot的键值对迁移至目标节点
  • redis-trib向源节点发送cluster getkeysinslot <slot> <count>,获取键名
  • redis-trib向源节点发送migrate <target ip> <target port> <key name> <timeout>迁移键值对
  • 重复以上俩个步骤
  • redis-trib向集群中的任意一节点发送cluster setslot <slot> node <target_id>将槽指派给目标节点,发送个整个集群

当一个槽正在迁移的时候,需要处理这个槽中的某个键,而这个键已经被迁移到新的节点中,那么将返回一个ASK错误,指引客户端转向新的节点。当客户端向新的节点发送命令时,先发送ask命令,新的节点发现节点正在导入槽,那么节点将破例去处理。

副本

cluster replicate <node id> 将当前的节点变成node id的副本,在配置文件中slaveof ip port,即将此节点配置为ip:port中的副本,5.0之后的版本参数为replicaof ip port

故障转移

订阅与退订

redisServer中的字典pubsub_channels记录着所有订阅的频道和订阅的客户端

慢查询

slowlog-log-slower-than 慢查询的时间阈值,单位是微妙 slowlog-max-len 保存多少条慢查询日志

slowlog get查看慢查询日志 slowlog reset清除所有的慢查询日志