Redis 是一个高性能的 key-value 存储系统,被广泛用于微服务架构中。如果我们想要使用 Redis 集群模式提供的高级特性,则需要对客户端代码进行改动,这带来了应用升级和维护的一些困难。利用 Istio 和 Envoy ,我们可以在不修改客户端代码的前提下实现客户端无感知的 Redis Cluster 数据分片,并提供读写分离、流量镜像等高级流量管理功能。
Redis ClusterRedis 的一个常见用途是用作数据高速缓存。通过在应用服务器和数据库服务器之间加入一个 Redis 缓存层,可以减少应用服务器对数据库的大量读操作,避免数据库服务器在大压力下响应缓慢甚至宕机的风险,显著加强整个系统的健壮性。Redis 作为数据缓存的原理如图所示:
在一个小规模的系统中,上图所示的单个 Redis 就可以很好地实现缓存层的功能。当系统中需要缓存的数据量较大时,一个 Redis 服务器无法承担所有应用服务器的缓存需求;同时单个 Redis 实例失效时也会导致大量读请求被直接发送到后端的数据库服务器上,导致数据库服务器瞬时压力超标,影响系统的稳定性。我们可以采用 Redis Cluster 来对缓存数据进行分片,将不同的数据放到不同的 Redis 分片中,以提高 Redis 缓存层的容量能力。在每个 Redis 分片中,还可以采用多个 replica 节点对缓存的读请求进行负载分担,并实现 Redis 的高可用。采用了 Redis Cluster 的系统如下图所示:
从图中可以看到,在 Redis Cluster 模式下,客户端需要根据集群的分片规则将不同 key 的读写操作发送到集群中不同的 Redis 节点上,因此客户端需要了解 Redis Cluster 的拓扑结构,这导致我们无法在不修改客户端的情况下将一个使用 Redis 独立节点模式的应用平滑迁移到 Redis Cluster 上。另外,由于客户端需要了解 Redis Cluster 的内部拓扑,也将导致客户端代码和 Redis Cluster 运维上的耦合,例如要实现读写分离或者流量镜像的话,就需要修改每个客户端的代码并重新部署。
这种场景下,我们可以在应用服务器和 Redis Cluster 之间放置一个 Envoy 代理服务器,由 Envoy 来负责将应用发出的缓存读写请求路由到正确的 Redis 节点上。一个微服务系统中存在大量需要访问缓存服务器的应用进程,为了避免单点故障和性能瓶颈,我们以 Sidecar 的形式为每个应用进程部署一个 Envoy 代理。同时,为了简化对这些代理的管理工作,我们可以采用 Istio 作为控制面来统一对所有 Envoy 代理进行配置,如下图所示:
在本文的后续部分,我们将介绍如何通过 Istio 和 Envoy 来管理 Redis Cluster,实现客户端无感知的数据分区,以及读写分离、流量镜像等高级路由策略。
部署 IstioPilot 中已经支持了 Redis 协议,但功能较弱,只能为 Redis 代理配置一个缺省路由,而且不支持 Redis Cluster 模式,无法实现 Redis filter 的数据分片、读写分离、流量镜像等高级流量管理功能。为了让 Istio 可以将 Redis Cluster 相关的配置下发到 Envoy Sidecar 上,我们修改了 EnvoyFilter 配置相关代码,以支持 EnvoyFilter 的 "REPLCAE" 操作。该修改的 PR Implement REPLACE operation for EnvoyFilter patch 已经提交到 Istio 社区,并合入到了主分支中,将在 Istio 后续的版本中发布。
在撰写本文的时候,最新的 Istio 发布版本 1.7.3 中尚未合入该 PR。因此我构建了一个 Pilot 镜像,以启用 EnvoyFilter 的 "REPLACE" 操作。在安装 Istio 时,我们需要在 istioctl 命令中指定采用该 Pilot 镜像,如下面的命令行所示:
$ cd istio-1.7.3/bin $ ./istioctl install --set components.pilot.hub=zhaohuabing --set components.pilot.tag=1.7.3-enable-ef-replace备注:如果你采用的 Istio 版本新于 1.7.3,并且已经合入了该 PR,则可以直接采用 Istio 版本中缺省的 Pilot 镜像。
部署 Redis Cluster请从 https://github.com/zhaohuabing/istio-redis-culster 下载下面示例中需要用到的相关代码:
$ git clone https://github.com/zhaohuabing/istio-redis-culster.git $ cd istio-redis-culster我们创建一个 "redis" namespace 来部署本例中的 Redis Cluster。
$ kubectl create ns redis namespace/redis created