1、原理篇:集群核心概念
在上一篇文章 快速上手redis主从复制和哨兵 中,我们介绍了redis主从复制和哨兵两种高可用机制。
虽然引入了哨兵,但主从复制中每个节点都存储全量数据,因此最大能存储多少数据受限于内存最小的那个节点,形成木桶效应,因此需要对redis做数据分片。
客户端分片:
旧版redis使用使用客户端分片,即由客户端决定每个键交由那个数据库节点存储;
弊端:如果集群增删节点,则需要手动迁移数据,且为了保证迁移过程中的数据一致性需要将集群暂时下线;
redis3.0开始支持集群。
集群特点:
拥有和单机实例同样的性能;
在网络分区后提供一定的可访问性以及对主库故障恢复的支持;
redis集群并不支持处理多个key的命令(如mget),这是因为在不同的节点间移动数据会达不到像单机redis那样的性能,在高负载的情况下可能会导致不可预料的错误;
只能使用0号数据库,如果用select切换则报错;
支持数据分片和主从复制;
1.1、插槽
redis cluster没有使用一致性hash,而是有一个哈希槽的概念,默认有16384个插槽。
redis将每个键键名的有效部分使用CRC16算法计算出散列值,然后对16384取余。
什么是有效部分?
如果键名中包含{至少一个字符},则有效部分为大括号里面的内容,比如{user001}:username和{user001}:password这两个键的有效部分都是user001,那么它们会被分配到一个结点上,可以使用涉及多键的命令(比如mget)去处理它们;
如果键名中不包含{至少一个字符},则整个键名都是有效部分;
如果有多个大括号则算法会在匹配第一个大括号就停止,然后进行判断。比如键a{b}c{d}的有效部分为b,而a{}c{d}的有效部分为整个键名;
集群中的每个主节点负责分配一部分插槽,cluster slots命令用于查看插槽分配情况,而从节点只负责备份主节点的插槽。
增删节点怎么办?
比如现在有A、B、C三个主节点,假如想增加D节点,那么只要将A、B、C节点中的部分槽迁移到D即可,如果想删除B节点,则只需要将B节点的槽迁移到A、C节点即可。
增删节点或者是修改某个节点槽的数量都不会导致集群不可用。
1.2、集群的主从复制模型
redis集群是支持主从复制的,目的是为了提高可用性。
比如当前集群有三个主节点A、B、C和三个从节点A1、B1、C1,则有以下情况:
如果从节点B1挂掉了,则不影响,整个集群可用;
如果主节点B挂掉了,集群会选举B1为新的主节点,整个集群仍然是可用的,待B重启后会变为B1的从节点;
但如果B和B1都挂掉了,那集群就不可用了,比如执行set命令时,会报错:(error) CLUSTERDOWN The cluster is down;
主从节点只要不是都挂,则不需要开启持久化,如果都挂,那必须要开启持久化了。
如果你的redis cluster只用于缓存,那么为了最大程度提升性能,不需要开启持久化。
如果你的redis cluster用来做数据库,那么就要看情况了,如果主从同时挂掉的几率不大且你对数据丢失的容忍度还行的话,就不需要开启持久化,否则需要。
1.3、集群一致性
redis集群不保证强一致性,导致不一致的可能原因有两个:
主从节点间是异步复制的;
某个主节点发生了网络分区,比如集群有A、B、C三个主节点以及对应的从节点A1、B1、C1,此时主节点B与集群形成网络分区,在网络分区期间某个客户端往B节点写数据,如果网络分区很短暂,那数据就不会丢失;但如果在网络分区期间集群重新选举了B1节点为主节点,那么数据就会丢失了;
形成网络分区的节点的超时时间是可以通过cluster-node-timeout这个配置项配置的,如果没有超时,则客户端可以正常向分区节点写数据,如果超时则不能写入。
2、实战篇:搭建集群
集群至少需要3个节点才能正常运行,下面演示一下在单个centos系统上配置一个3主3从的redis集群。
集群重要配置如下。
cluster-enable yes
cluster-node-timeout 5000
cluster-config-file nodes.conf复制
集群文件夹结构如下图所示。
6381节点配置文件内容如下,其它节点类似(关于模板配置文件见本文第3节-redis模板配置文件)。
include ../run/redis-cluster.conf
port 6381
pidfile usr/local/redis-cluster/run/redis-6381.pid
logfile usr/local/redis-cluster/run/redis-6381.log
dir usr/local/redis-cluster/6381/
cluster-config-file /usr/local/redis-cluster/run/nodes-6381.conf复制
启动各个节点,用redis-cli连接任意节点执行info cluster命令可以查看集群状态,cluster_enable为1表示可用。
但此时还不能写数据,比如在任意节点执行set age 10命令时,会报错:(error) CLUSTERDOWN Hash slot not served。
这是因为此时每个节点还是独立状态,下面需要将各节点联系在一起,即初始化集群。
如果是旧版本的redis,可以使用src/redis-trib(ruby语言编写,安装Ruby见本文第4节-安装Ruby)来初始化集群,初始化集群的命令如下。
src/redis-trib.rb --replicas 1 127.0.0.1:6381 127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384 127.0.0.1:6385 127.0.0.1:6386
复制
replicas 1表示每个主节点的副本个数为1。
如果执行上述命令提示WARNING: redis-trib.rb is not longer available!,则表示当前版本的redis已经不支持redis-trib.rb,具体的提示信息如下图所示。
根据该提示,我们知道了应该使用redis-cli来代替redis-trib.rb。
首先在redis-cli中执行cluster help命令,查看与集群cluster相关的命令有哪些,如下所示。
ADDSLOTS <slot> [slot ...] -- 为当前节点分配槽位
BUMPEPOCH -- Advance the cluster config epoch.
COUNT-failure-reports <node-id> -- 根据<node-id>返回失败报告的数量
COUNTKEYSINSLOT <slot> - 根据槽位返回key数量
DELSLOTS <slot> [slot ...] -- 在当前节点删除指定槽位
FAILOVER [force|takeover] -- 将当前复制节点提升为主节点
FORGET <node-id> -- 从集群中移除节点
GETKEYSINSLOT <slot> <count> -- 返回当前节点存储在插槽中的键名
FLUSHSLOTS -- 删除当前节点的槽位信息
INFO - 返回关于集群的信息.
KEYSLOT <key> -- 根据key返回hash槽
MEET <ip> <port> [bus-port] -- 将节点连接到工作集群
MYID -- 返回当前节点id
NODES -- 返回节点看到的集群配置。输出格式:<id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>
REPLICATE <node-id> -- 配置当前节点为<node-id>的复制节点
RESET [hard|soft] -- 重置当前节点 (默认: soft).
SET-config-epoch <epoch> - Set config epoch of current node.
SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- 设置槽位状态.
REPLICAS <node-id> -- 返回指定主节点的副本节点
SLOTS -- 返回关于槽范围映射的信息。每个系列都是由:start, end, master and replicas IP addresses, ports and ids复制
初始化集群步骤:
a)使用cluster meet命令将各个节点联系在一起,如下图所示。
节点之间的握手使用的是Gossip 协议(P2P 网络核心技术)。
再执行cluster nodes命令,可以看到六个节点联系在一起了,如下图所示。但此时这六个节点都是主库,并且还没有分配插槽。
b)使用cluster addslots命令为所有主库分配插槽。
由于cluster addslots命令不支持批量添加插槽,所以我写了一个shell脚本利用for循环批量分配插槽,shell脚本如下。
for ((i=$3; i<=$4; i ++))
do
6381/src/redis-cli -h $1 -p $2 cluster addslots $i > dev/null
done复制
现在让6381、6382、6383为主库,我们将16384个插槽分配给这三个主库,具体分配情况如下:
6381占0~5460号插槽,共5461个
6382占5461~10920号插槽,共5462个
6383占10921~16383号插槽,共5461个
执行如下命令即可分配插槽:
./cluster-addslots.sh 127.0.0.1 6381 0 5460
./cluster-addslots.sh 127.0.0.1 6382 5461 10920
./cluster-addslots.sh 127.0.0.1 6383 10921 16383复制
然后使用cluster slots命令查找插槽分配情况,如下图所示。
c)使用cluster replicate命令设置从库。
执行如下命令,使得:
6384为6381的从库;
6383为6382的从库;
6384为6383的从库;
6381/src/redis-cli -h 127.0.0.1 -p 6384 cluster replicate 6381的节点id
6381/src/redis-cli -h 127.0.0.1 -p 6385 cluster replicate 6382的节点id
6381/src/redis-cli -h 127.0.0.1 -p 6386 cluster replicate 6383的节点id复制
最后使用cluster nodes命令查看集群节点信息,即可看到有三个主库和三个从库,如下图所示。
集群搭建好后,我们使用redis-cli往集群写一些数据试试,但如果不幸运的话写第一个数据就会报错,如下图所示。
这是因为redis-cli连接的是6381这个节点,但user:1这个key会被分配到6382节点,因此写入错误。
解决办法是,redis-cli支持以集群模式连接到某一个节点,并且支持自动重定向,如下图所示。
关于集群的搭建就介绍到这里了。另外,关于集群节点的增删以及在不停掉集群的情况下如何迁移数据,本文就不往下介绍了,有兴趣的朋友可自行百度了解。
3、redis模板配置文件
redis-5.0.5版本默认配置文件(去除注释)如下。
################################## INCLUDES ###################################
################################## MODULES #####################################
################################## NETWORK #####################################
bind 127.0.0.1
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300
################################# GENERAL #####################################
daemonize no
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile ""
databases 16
always-show-logo yes
################################ SNAPSHOTTING ################################
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir ./
################################# REPLICATION #################################
replica-serve-stale-data yes
replica-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
replica-priority 100
################################## SECURITY ###################################
################################### CLIENTS ####################################
############################## MEMORY MANAGEMENT ################################
############################# LAZY FREEING ####################################
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
replica-lazy-flush no
############################## APPEND ONLY MODE ###############################
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes
################################ LUA SCRIPTING ###############################
lua-time-limit 5000
################################ REDIS CLUSTER ###############################
########################## CLUSTER DOCKER/NAT support ########################
################################## SLOW LOG ###################################
slowlog-log-slower-than 10000
slowlog-max-len 128
################################ LATENCY MONITOR ##############################
latency-monitor-threshold 0
############################# EVENT NOTIFICATION ##############################
notify-keyspace-events ""
############################### ADVANCED CONFIG ###############################
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
stream-node-max-bytes 4096
stream-node-max-entries 100
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes
########################### ACTIVE DEFRAGMENTATION #######################复制
将默认的配置文件做如下修改(如何修改具体看个人情况),使其变为所有节点通用的模板配置文件:
注释bind
protected-mode改为no
注释port
daemonize改为yes
注释pidfile
注释logfile
注释三个save
注释dir
如果是集群模式,则还需要:
新增cluster-enabled yes;
然后再针对单个节点用include指令导入公共模板(注意使用相对路径,否则启动会报错),然后再做一些本节点的特殊配置,如下所示:
include ../run/redis_useful.conf
port 6379
pidfile /var/run/redis_6379.pid
logfile /var/log/redis_6379.log
dir /usr/local/redis-5.0.5/复制
4、安装Ruby
wget https://cache.ruby-lang.org/pub/ruby/3.0/ruby-3.0.1.tar.gz
tar -xvzf ruby-3.0.1.tar.gz
./configure
make
make install复制