Redis的部署历程从单机部署、主从模式、哨兵模式、集群路径进行演变,如果你还不了解这四种模式以及优缺点的话,这篇文章会很适合你进行阅读认识。《一文读懂Redis的四种模式,单机、主从、哨兵、集群》
介绍
什么是Redis集群
对于高可用的部署,或者说在Kubernetes里进行部署,假如说现有的业务以已经使用了redis里多个db,那么Redis集群只有db0可以使用,这样使用起来代码变更代价也是很高的,所以要根据现有的业务模式进行合理选择。
关于Redis Cluster,先来看看它的架构
Redis Cluster 数据分片
Redis Cluster 不使用一致散列,而是一种不同形式的分片,其中每个键在概念上都是我们所谓的散列槽的一部分。
Redis 集群中有 16384 个哈希槽,要计算给定键的哈希槽是多少,我们只需取密钥的 CRC16 模数 16384。
Redis 集群中的每个节点都负责哈希槽的一个子集,例如,您可能有一个包含 3 个节点的集群,其中:
- 节点 A 包含从 0 到 5500 的哈希槽
- 节点 B 包含从 5501 到 11000 的哈希槽
- 节点 C 包含从 11001 到 16383 的哈希槽
这允许在集群中轻松添加和删除节点。例如,如果我想添加一个新节点 D,我需要将一些哈希槽从节点 A、B、C 移动到 D。同样,如果我想从集群中删除节点 A,我可以移动 A 提供的哈希槽到 B 和 C。当节点 A 为空时,我可以将其从集群中完全删除。
由于将哈希槽从一个节点移动到另一个节点不需要停止操作,因此添加和删除节点或更改节点持有的哈希槽百分比不需要任何停机时间。
Redis Cluster 支持多键操作,只要涉及到单个命令执行(或整个事务,或 Lua 脚本执行)的所有键都属于同一个哈希槽。用户可以使用称为散列标签的概念强制多个键成为同一个散列槽的一部分。
Redis Cluster 规范中记录了哈希标签,但要点是如果键中的 {} 括号之间有一个子字符串,则只有字符串内的内容会被哈希,因此例如this{foo}key和another{foo}key 保证在同一个哈希槽中, 并且可以在具有多个键作为参数的命令中一起使用。
Redis Cluster 主从模式 为了在主节点子集出现故障或无法与大多数节点通信时保持可用,Redis 集群使用主从模型,其中每个哈希槽具有从 1(主节点本身)到 N 个副本(N -1 个额外的从节点)。
在我们包含节点 A、B、C 的示例集群中,如果节点 B 发生故障,集群将无法继续,因为我们不再有办法为 5501-11000 范围内的哈希槽提供服务。
然而,当集群创建时(或稍后),我们为每个主节点添加一个从节点,这样最终的集群由主节点 A、B、C 和从节点 A1、B1、C1 组成. 这样,如果节点 B 出现故障,系统就能够继续运行。
节点 B1 复制 B,并且 B 失败,集群会将节点 B1 提升为新的 master 并继续正常运行。
但是需要注意的是,如果节点 B 和 B1 同时发生故障,Redis Cluster 将无法继续运行。
Redis 集群一致性保证 Redis Cluster 无法保证强一致性。实际上,这意味着在某些情况下,Redis Cluster 可能会丢失系统已向客户端确认的写入。
Redis Cluster 会丢失写入的第一个原因是因为它使用异步复制。这意味着在写入期间会发生以下情况:
您的客户端写入主 B。 主 B 向您的客户端回复 OK。 主设备 B 将写入传播到其从设备 B1、B2 和 B3。 如您所见,B 在回复客户端之前不会等待来自 B1、B2、B3 的确认,因为这对 Redis 来说是一个令人望而却步的延迟惩罚,因此如果您的客户端写入某些内容,B 会确认写入,但会在之前崩溃能够将写入发送到其从站,其中一个从站(未收到写入)可以提升为主站,永远失去写入。
这与大多数配置为每秒将数据刷新到磁盘的数据库发生的情况非常相似,因此您已经能够推理出这种情况,因为过去使用不涉及分布式系统的传统数据库系统的经验。同样,您可以通过强制数据库在回复客户端之前将数据刷新到磁盘来提高一致性,但这通常会导致性能低得令人望而却步。在 Redis Cluster 的情况下,这相当于同步复制。
基本上,需要在性能和一致性之间进行权衡。
部署Redis
在Kubernetes中部署Redis集群面临挑战,因为每个Redis实例都依赖于一个配置文件,该文件可以跟踪其他集群实例及其角色。为此,我们需要结合使用Kubernetes StatefulSets和PersistentVolumes。
PV的建立 需要建立6个PersistentVolume,pv是集群级别的基础资源,声明pv时候,使用apply 进行创建是不需要指定命名空间的。 执行与结果:
1 | $ cat redis-pv.yaml |
1 | $ kubectl apply -f redis-pv.yaml |
此时查看到pv已经就绪
1 | $ kubectl get pv |
statefulset的建立 这里可以自定义redis的conf配置,并且带上了namespaces,使pod会在db空间内生成。
1 | cat redis-sts.yaml |
1 | $ kubectl apply -f redis-sts.yml -n db |
此时查看pod状态,为Runing
1 | $ kubectl get pods -l app=redis-cluster -n db |
查看存储声明pvc,和存储卷pv
1 | $ kubectl get pvc -n db |
此时pod内的redis已经跑起来了。
service的建立 建立一个service使其可以被外部使用,Service是一种抽象的对象,它定义了一组Pod的逻辑集合和一个用于访问它们的策略,一个Serivce下面包含的Pod集合一般是由Label Selector来决定的。我们后端运行了6个副本,这些副本都是可以替代的,因为前端并不关心它们使用的是哪一个后端服务。尽管由于各种原因后端的Pod集合会发生变化,但是前端却不需要知道这些变化,也不需要自己用一个列表来记录这些后端的服务,Service的这种抽象就可以帮我们达到这种解耦的目的。
1 | cat redis-svc.yml |
1 | $ kubectl apply -f redis-svc.yml -n db |
查看它
1 | $ kubectl get svc -n db |
部署Redis Cluster
下一步是形成Redis集群。为此,我们运行以下命令并键入yes
以接受配置。前三个节点成为主节点,后三个节点成为从节点。
组成集群
1 | $ kubectl exec -it redis-cluster-0 -n db -- redis-cli --cluster create --cluster-replicas 1 $(kubectl get pods -l app=redis-cluster -n db -o jsonpath='{range.items[*]}{.status.podIP}:6379 ') |
验证集群部署
1 | $ kubectl exec -it redis-cluster-0 -n db -- redis-cli cluster info |
查看role
1 | for x in $(seq 0 5); do |
1 | $ for x in $(seq 0 5); do echo "redis-cluster-$x"; kubectl exec redis-cluster-$x -n db -- redis-cli role; echo; done |
测试 Redis 集群
在生产使用之前,做好测试工作是必要的。进行连接和故障模拟,对于连接使用将部署一个简单的Python应用程序,而对于故障模拟将删除一个节点并观察集群行为。
部署命中计数器应用
将一个简单的应用程序部署到我们的集群中,并在它前面放置一个负载均衡器。此应用程序的目的是在将计数器值作为 HTTP 响应返回之前增加一个计数器并将该值存储在 Redis 集群中。
1 | cat app-deployment-service.yml |
应用
1 | $ kubectl apply -f app-deployment-service.yml -n db |
此时,我们可以开始使用浏览器点击 IP 来为点击计数器生成一些值。 获取ip
1 | $ kubectl get svc -n db |
也可以通过kubectl get svc hit-counter-lb -o json|jq -r .spec.clusterIP
获取。 现在直接使用curl
进行模拟点击
1 | $ curl 172.21.15.161 |
可看到redis集群正常工作。
模拟节点故障
从前面的信息当中我们可以得知redis-cluster-0为集群其中的一个master节点,我们可以直接对其进行删除来模拟主节点宕机的情形:
1 | $ kubectl describe pods redis-cluster-0 -n db | grep IP |
数据有没有丢失呢? 再次验证一下
1 | $ kubectl exec -it redis-cluster-0 -n db -- redis-cli get hits |
可以看到数据还在,并且模拟点击时数值也是正常的变化。
集群如何愈合
当我们创建集群时,我们创建了一个 ConfigMap,它反过来创建了一个脚本/conf/update-node.sh,容器在启动时调用该脚本。此脚本使用本地节点的新 IP 地址更新 Redis 配置。使用 confic 中的新 IP,集群可以在新 Pod 以不同 IP 地址启动后修复。
在这个过程中,如果我们继续加载页面,计数器继续递增,随着集群收敛,我们看到没有数据丢失。
结论
Redis 是一个强大的数据存储和缓存工具。由于 Redis 存储数据的方式,Redis Cluster 通过提供分片和相关的性能优势、线性扩展和更高的可用性来扩展功能。数据会自动在多个节点之间拆分,从而允许操作继续进行,即使部分节点出现故障或无法与集群的其余部分进行通信。
有关 Redis Cluster 的更多信息,请访问官方教程或规范文档。
那么更多,可参考文献:Deploying Redis Cluster on Top of Kubernetes