文章转载自公众号:小姐姐的味道
笔者曾经维护过千个 redis 实例,这些实例采用的简单主从结构,集群方案主要是客户端jar包。刚开始,个人并不是太喜欢redis cluster
,因为它的路由实在是太死板,运维复杂。
但官方在推这个东西,注定了它的应用越来越广泛,这在平常的交流中就能够发现。虽然有这样那样的缺点,但总抵挡不了权威推动的浪潮。随着redis cluster
越来越稳定,是时候和redis cluster
来一次灵魂交流了。
简介
redis cluster
是亲生的集群方案,目前,在高可用和稳定性方面,都有了很大的进步。据统计和观察,采用redis cluster
架构的公司和社区越来越多,已经成为事实的标准。它的主要特点就是去中心化,无需proxy
代理。其中一个主要设计目标就是达到线性可扩展性(linear scalability)。
仅仅靠redis cluster
服务器本身,并不能完成官方承诺的功能。广义上的redis cluster
应该既包含redis
服务器,又包含客户端实现比如jedis
等。它们是一个整体。
分布式存储无非就是处理分片和副本。 对redis cluster
来说,核心概念就是槽(slot),了解了它,基本就了解了集群的管理方式。
优缺点
当了解这些特性以后,运维上其实是更简单了。我们先看下比较明显的优缺点。
优点
1、不再需要额外的Sentinel
集群,为使用者提供了一致的方案,减少了学习成本。
2、去中心架构,节点对等,集群可支持上千个节点。
3、抽象出了slot
概念,针对slot
进行运维操作。
4、副本功能能够实现自动故障转移,大部分情况下无需人工介入。
缺点
1、客户端要缓存部分数据,实现Cluster
协议,相对复杂。
2、数据是通过异步复制的,不能保证数据的强一致性。
3、资源隔离困难,经常流量不均衡,尤其是多个业务共用集群的时候。数据不知道在哪里,针对热点数据,也无法通过专项优化
完成。
4、从库是完全的冷备,无法分担读操作,真是太太浪费了。需要做额外工作。
5、MultiOp
和Pipeline
支持有限,老代码要是进行架构升级,要小心了。
6、数据迁移是基于key
而不是基于slot
的,过程较慢。
基本原理
从槽到key
,定位过程明显就是一个双层的路由。
key的路由
redis cluster
和常用的一致性hash
没什么关系,它主要采用了哈希槽的概念。当需要在其中存取一个key
时,redis
客户端会首先对这个key
采用crc16
算法算出一个值,然后对这个值进行mod
操作。
crc16(key)mod 16384
所以,每个key
都会落在其中的一个hash
槽上。16384 等同于 2^14(16k),redis
节点发送心跳包时,需要把所有的槽信息放在这个心跳包里,所以要竭尽全力的优化,感兴趣的可以看下为什么默认的槽数量是 16384 。
服务端简单原理
上面谈到,redis cluster
共定义了 16384 个槽,所有的集群操作都是围绕着这个槽数据进行编码。服务端使用一个简单的数组存储这些信息。
对于判断有无的操作,使用bitmap
来存储是最节省空间的。redis cluster
就是使用一个叫做slot
的数组来保存当前节点是否持有了这个槽。
如图,这个数组的长度为 16384/8=2048 Byte
,那么就可以使用 0 或者 1 来标识本节点对某个槽是否拥有。
其实,只需要第一份数据ClusterState
也能完成操作,保存另外一个维度的Slot
数组,能够方便编码和存储。一个节点除了会将自己负责处理的槽记录在两个地方(clusterNode结构的slots和numslots),它还会将自己的slots
数组通过消息发送给集群中的其他节点,以此来告诉其他节点自己目前拥有的槽。
当数据库中的 16384 个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。
当客户端向节点发送相关命令时,接收命令的节点会计算出命令要处理的key
属于哪个槽,并检查这个槽是否指派给了自己。如果不是自己的,会指引客户端到正确的节点。
所以,客户端连接集群中的任意一台机器,都能够完成操作。
安装一个6节点集群
准备工作
假如我们要组装一个3分片的集群,每个分片有一个副本。那么总共需要的node
实例就有 3*2=6 个。redis
可以通过指定配置文件的方式启动,我们所做的工作就是修改配置文件。
复制6份默认的配置文件。
for i in {0..5}
do
cp redis.conf redis-700$i.conf
done
修改其中的配置文件内容,拿redis-7000.conf
来说,我们要启用它的cluster
模式。
cluster-enabled yes
port 7000
cluster-config-file nodes-7000.conf
nodes-7000.conf
会保存一些集群信息到当前节点,所以需要独立。
启动&关闭
同样的,我们使用脚本来启动它。
for i in {0..5}
do
nohup ./redis-server redis-700$i.conf &
done
为了演示,我们暴力把它关闭。
ps -ef| grep redis | awk '{print $2}' | xargs kill -9
组合集群
我们使用redis-cli
进行集群的组合。redis
将自动完成这个过程。这一系列的过程,是通过发送指令给每个节点进行组合的。
./redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
几个高级原理
节点故障
集群中的每个节点都会定期地向集群中的其他节点发送ping消息,以此来检测对方是否在线,如果接收ping
消息的节点没有在规定的时间内返回pong
消息,那么发送ping
消息的节点就会将接收ping
消息的节点标记为疑似下线(PFAIL)。
如果在一个集群里面,半数以上节点都将某个主节点 x 报告为疑似下线,那么这个主节点x将被标记为已下线(FAIL),将x标记为FAIL
的节点会向集群广播一条关于 x 的FAIL
消息,所有收到这条FAIL
消息的节点都会立即将 x 标记为FAIL
。
大家可以注意到这个过程,与 es 和 zk 的节点判断类似,都是半数以上才进行判断,所以主节点的数量一般都是奇数。由于没有最小组群配置,理论上会有脑裂(暂时并未遇到过)。
主从切换
当一个节点发现自己的主节点进入fail
状态,将会从这个节点的从节点当中,选出一台,执行slaveof no one
命令,变身为主节点。
新的节点完成自己的槽指派以后,会向集群广播一条pong
消息,以便让其他节点立即知道自己的这些变化。它告诉别人:我已经是主节点了,我已经接管了有问题的节点,成为了它的替身。
redis
内部对集群的这些管理,大量使用了已经定义好的这些指令。所以这些指令不仅仅供我们从命令行使用,redis
自己内部也用。
数据同步
当一台从机连接到master
之后,会发送一个sync
指令。master
在收到这个指令后,会在后台启动存盘进程。执行完毕后,master
将整个数据库文件传输到slave
,这样就完成了第一次全量同步。
接下来,master
会把自己收到的变更指令,依次传送给slave
,从而达到数据的最终同步。从redis 2.8
开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。
数据丢失
redis cluster
中节点之间使用异步复制,并没有类似kafka
这种ack
的概念。节点之间通过gossip
协议交换状态信息,用投票机制完成Slave
到Master
的角色提升,完成这个过程注定了需要时间。在发生故障的过程中就容易存在窗口,导致丢失写入的数据。比如以下两种情况。
一、命令已经到到master
,此时数据并没有同步到slave
,master
会对客户端回复ok。如果这个时候主节点宕机,那么这条数据将会丢失。redis
这样做会避免很多问题,但对一个对数据可靠性要求较高的系统,是不可忍受的。
二、由于路由表是在客户端存放的,存在一个时效问题。如果分区导致一个节点不可达,提升了某个从节点,但原来的主节点在这个时候又可以用了(并未完成failover)。这个时候一旦客户端的路由表并没有更新,那么它将会把数据写到错误的节点,造成数据丢失。
所以redis cluster
在通常情况下运行的很好,在极端情况下某些值丢失问题,目前无解。
复杂的运维
redis cluster
的运维非常的繁杂,虽然已经进行了抽象,但这个过程依然不简单。有些指令,必须在详细了解它的实现原理之后,才能真正放心的去用。
上图就是扩容会用到的一些命令。在实际使用的过程中,可能需要多次频繁地输入这些命令,且输入的过程中还要监视它的状态,所以基本上是不可能人工跑这些命令的。
运维的入口有两个。一个是使用redis-cli连接任意一台,然后发送cluster
打头的命令,这部分命令大多数是对槽进行操作。 在开始组合集群时,就是反复调用这些命令进行的具体逻辑执行。
另外一个入口是使用redis-cli命令,加上--cluster
参数和指令。这种形式主要是用来管控集群节点信息 ,比如增删节点等。所以推荐使用这种方式。
redis cluster
提供了非常复杂的命令,难于操作和记忆。推荐使用类似CacheCloud
的工具进行管理。
下面是几个例子。
通过向节点 A 发送 CLUSTER MEET
命令,客户端可以让接收命令的节点 A 将另一个节点 B 添加到节点 A 当前所在的集群里面:
CLUSTER MEET 127.0.0.1 7006
通过cluster addslots
命令,可以将一个或者多个槽指派给某个节点负责。
127.0.0.1:7000> CLUSTER ADDSLOTS 0 1 2 3 4 . . . 5000
设置从节点。
CLUSTER REPLICATE <node_id>
redis-cli —cluster
redis-trib.rb
是官方提供的Redis Cluster
的管理工具,但最新版本已经推荐使用redis-cli
进行操作。
向集群中添加新节点
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7007 --cluster-replicas 1
从集群中删除节点
redis-cli --cluster del-node 127.0.0.1:7006 54abb85ea9874af495057b6f95e0af5776b35a52
迁移槽到新的节点
redis-cli --cluster reshard 127.0.0.1:7006 --cluster-from 54abb85ea9874af495057b6f95e0af5776b35a52 --cluster-to 895e1d1f589dfdac34f8bdf149360fe9ca8a24eb --cluster-slots 108
类似的命令还有很多。
create:创建集群 check:检查集群 info:查看集群信息 fix:修复集群 reshard:在线迁移slot rebalance:平衡集群节点slot数量 add-node:添加新节点 del-node:删除节点 set-timeout:设置节点的超时时间 call:在集群所有节点上执行命令 import:将外部redis数据导入集群
其他方案概览
主从模式
redis
最早支持的,就是M-S
模式,也就是一主多从。redis
单机qps
可达到 10w+,但是在某些高访问量场景下,依然不太够用。一般通过读写分离来增加slave
,减少主机的压力。
既然是主从架构,就面临着同步问题,redis
主从模式的同步分为全同步和部分同步。当刚创建一个从机的时候,不可避免的要进行一次全量同步。等全量同步结束之后,进入增量同步阶段。这个和redis cluster
是没什么区别的。
这种模式还是比较稳定的,但要额外做一些工作。用户需要自行开发主从切换的功能,也就是使用哨兵去探测每个实例的健康状况,然后通过指令进行集群状态的改变。
当集群规模增大,主从模式会很快遇到瓶颈。所以一般会采用客户端hash
的方法进行扩展,包括类似于memcached
的一致性哈希。
客户端hash
的路由可能会很复杂,通常会通过发布jar
包或者配置的方式维护这些meta
信息,这也给线上环境增加了很多不确定性。
不过,通过加入类似ZK
主动通知的功能,将配置维护在云端,可以显著降低风险。笔者曾经维护过的几千个redis
节点,就是用这种方式进行管理的。
代理模式
代码模式在redis cluster
出现之前,非常流行,比如codis
。代理层通过把自己模拟成一个redis
,接收来自客户端的请求,然后按照自定义的路由逻辑进行数据分片以及迁移,而业务方不需要改动任何代码。除了能够平滑的进行扩容,一些主从切换、FailOver
的功能也在代理层完成,客户端甚至可以没有任何感知。这类程序又称为分布式中间件。
一个典型的实现如下图,背后的redis
集群甚至可以是混合的。
但这种方式的缺点也是显而易见的。首先,它引入了一个新的代理层,在结构上、运维上都显复杂。需要进行大量的编码,比如failover
、读写分离、数据迁移等。另外,proxy
层的加入,对性能也有相应的损耗。
多个proxy
一般使用lvs
等前置进行负载均衡的设计,如果proxy
层的机器很少而后端redis
的流量很高,那么网卡会成为主要的瓶颈。
Nginx
也可以作为redis
的代理层,比较专业的说法叫做Smart Proxy
。这种方式较为偏门,如果你对nginx
比较熟悉,不失为一种优雅的做法。
使用限制和坑
redis
的速度特别的快。一般越快的东西,出问题的时候造成的后果越大。
不久之前,写过一篇针对于redis
的规范:《Redis规范,这可能是最中肯的了》。规范和架构一样,适合自己公司环境的,才是最好的,但会提供一些起码的思路。
严格禁止的东西,一般都是前人踩坑的地方。除了这篇规范的内容,对于redis-cluster
,补充以下几点。
1、redis cluster
号称能够支持1k个节点,但你最好不要这么做。当节点数量增加到10,就能够感受到集群的一些抖动。这么大的集群证明你的业务已经很牛x了,考虑一下客户端分片吧。
2、一定要避免产生热点,如果流量全部打到了某个节点,后果一般很严重。
3、大key
不要放redis
,它会产生大量的慢查询,影响正常的查询。
4、如果你不是作为存储,缓存一定要设置过期时间。占着茅坑不拉屎的感觉是非常讨厌的。
5、大流量,不要开aof
,开rdb
即可。
6、redis cluster
的操作,少用pipeline
,少用multi-key
,它们会产生大量不可预料的结果。
以上是一些补充,更全还是参考规范吧 《Redis规范,这可能是最中肯的了》。。。
End
redis 的代码才那么一丁点,肯定不会实现非常复杂的分布式供能。 redis 的定位就是性能、水平伸缩和可用性,对于简单的、一般流量的应用,已经足够了。生产环境无小事,对于复杂的高并发应用,注定了是一个组合的优化方案。
以上就是W3Cschool字节宝
关于与亲生的Redis Cluster,来一次灵魂交流的相关介绍了,希望对大家有所帮助。