最近受客户启发,在全球业务扩张情况下,有状态数据的全球同步是技术架构中非常重要的一个环节,也是满足全球不同地区用户体验的一个重要前提;同时多区域多活架构也可以极大提升全站可用性保障。本篇从公开技术资料解析Netflix 在 AWS 上实现多区域多活架构的演进,后续继续深度探讨其中的技术细节。
背景
客户都不希望自己的用户在社交媒体抱怨 “XXX挂了,无法访问”,那通常在线服务的“故障率”,主要由那两点决定的呢?(1)部署运营的规模大小(2)变更的频率。这反应了“任何组件”都会出故障的概率问题,哪怕规模很小,但变更太频繁,那代码错误的概率也依然会提高;Netflix 从 2013 年遭遇一次区域级的 AWS ELB 故障后,开始反思如何保障 Netflix 的服务可以维持至少 99.99% 的可用性。Netflix 的整体采用了微服务架构,除了在 AWS 一个区域的多可用区部署之外,他们考虑在多个AWS区域部署,虽然区域级故障是极少概率会发生,但Netflix快速的变更有时会导致区域级的核心服务不可用;因此,Netflix决定实施全站的弹性和快速恢复能力;其中被采用了基本原则是隔离性和冗余:任何一种区域内的故障不会影响另外一个区域的服务;任何网络分区事件不会影响另外一个区域的服务质量。
多区域多活技术前提和挑战
要实现服务在 AWS 多区域多活架构,比如US-West-2和US-East-1,必须满足:
服务必须无状态:所有的数据和状态复制依赖底层数据层处理
区域内服务必须访问本区域内的资源:这些资源包括S3,SQS等等;这意味着,数据在多个区域冗余存储
用户的访问路径中不能有跨区域的服务调用:也就是说任何情况下,保证一个用户请求要么一直访问区域A或完全切换到区域B,跨区域数据复制应该是异步
要实现这样的架构,技术上有三点必须考虑的挑战:(1)高效的工具可以正确路由用户的流量到特定的区域(2)流控(Traffic Shaping)和限流/降级(Load Shedding)能力,以应对突发的“群体”事件(3)状态和数据异步跨区域同步。接下来让我们聚焦到数据同步这层。
第一代架构-多区域NoSQL存储数据同步
Netflix 多区域多活首先遇到的挑战就是如何在多区域复制用户数据和状态信息;(1)持久化层,他们采用了Apache Cassandra 作为可扩展和高弹性的NoSQL存储解决方案,(2)内存缓存层,基于Memcached扩展的EVCache 解决方案。
所有响应用户请求的状态数据都存储在 Cassandra 集群中,Cassandra本身是支持多数据中心双向异步复制功能,在实现服务层的多区域多活架构之前,Netflix 就已经构建并运维跨US-East-1和US-West-1两个区域的Apache Cassandra 集群,也就是说,某个区域的数据变更最终会复制到另外一个区域;多活的架构让我们对数据复制的延迟有了比较敏感的要求,所以 Netflix 做了一个测试来评估美国境内这两个区域的数据复制延迟是否在一个可接受范围内。该测试在一个区域写入了 100万条记录,然后在另外一个区域 500ms 后读刚刚写入的 100万条记录,结果发现所有的记录都正常被读出;该测试结果给了Netflix 团队非常大的信心在可接受延迟内的数据跨区域一致性功能上,Apache Cassandra 可以满足他们的应用场景。
但对于 Netflix 的应用而言,对用户请求的响应时间要求非常短,因此对数据层的复制延迟提出更高的要求,期望能降低到 10ms以内;通常架构在 Cassandra 之前 Netflix 还有一层内存缓存层 Memcached,里面存储两类数据:
Cassandra 的数据缓存
独立计算并只存储在 Memcached 的数据
如果要实现一个 Memcached 的多区域多活架构,一个非常大的挑战就是如何保证缓存数据和数据源的一致性;Netflix 避免了实现多区域多活的 Memcached 方案,而是简化到在他们的 Memcached 客户端 EVCache Client 中增加了一个远程缓存失效机制。即当 EVCache 客户端在一个区域写入数据时,它会同时发送一个消息给另外一个区域的 SQS 消息队列,从而另外一个区域的 EVCache 会根据消息使该键值缓存数据失效,那如果该区域有后续的读操作,会触发缓存数据重新计算或从 Cassandra 更新缓存数据。
3.2 第一代架构-小结
第一代架构主要依赖 Apache Cassandra 的多区域数据异步复制能力,内存缓存 Memcached 层,主要是改造了 EVCache 客户端,增强了一个区域内存数据变化的消息通知到另外一个区域,简单高效,另外一个区域依靠服务逻辑重新计算缓存值或者从 Cassandra 中更新内存数据。由于数据仅在美国境内的两个 AWS 区域之间复制,在 Netflix 的场景下,数据复制延迟(Cassandra)控制在 500ms 以内。
==扩展阅读==,分布式 NoSQL 数据库在大规模企业级微服务场景中有广泛的应用,比如电商,流媒体,社交,工具 APP 等等。
目前 AWS上用户用的比较多的分布式 NoSQL 数据库有:
Apache Cassandra
http://cassandra.apache.org/
https://aws.amazon.com/cn/blogs/big-data/best-practices-for-running-apache-cassandra-on-amazon-ec2/
Amazon Dynamodb https://aws.amazon.com/cn/dynamodb/全托管的NoSQL数据库,支持多区域多活,零运维管理
https://aws.amazon.com/cn/blogs/aws/amazon-prime-day-2019-powered-by-aws/ 支撑Amazon Prime大促流量的幕后功臣
MongoDB on AWS
https://aws.amazon.com/cn/quickstart/architecture/mongodb/
第二代架构-美国境内多区域缓存复制
Netflix用户体验,通常包括视频流中的内容分发,用户交互界面,个性化内容推荐,还有快速获取用户喜欢的播放列表等等。Netflix有成百上千的微服务,这种微服务架构使得团队可以聚焦特定功能需求,保持松耦合团队和服务依赖,同时围绕整体业务目标齐头并进;这些微服务绝大多数是无状态的,状态数据保存在上段提到的内存 Memcached 或 NoSQL 数据库 Cassandra 中。在第二代架构演进中,背后是 Netflix 的业务全球扩张及就近服务最终用户的需求。
为什么要做多区域的内存数据复制呢?(1)流量切换时,新区域的缓冲数据冷启动问题,冷启动会导致响应变慢,及给数据库带来突发的压力(2)有些缓冲数据的计算代价非常昂贵,这种类型的缓冲数据一旦在本地写入就需要复制到其他区域的缓冲,比如会员的观看历史,评价及个性化推荐等等。除了功能性需求之外,Netflix团队还考量了非功能性需求:
(1)多区域缓存数据强一致性,有些情况下,不同地区的状态数据不一致不会影响用户浏览和视频流体验,比如不一致的个性化推荐信息;非关键数据,Netflix采用了最终一致性,即业务可以容忍一段时间的缓冲数据不一致;
(2)新的全球复制架构不影响本地缓冲操作的性能和可靠性,即使跨区域复制缓慢而低效,异步复制保障整个复制系统可以容忍短暂的不可用而不影响本地缓冲操作;
(3)复制延迟,多快算快?会员访问站点流量从一个区域切换到另一个区域的频率有多高?Netflix团队避免了设计一个完美的系统,而是在可接受的延迟时间下容忍部分不一致情况;
我们先稍微展开下 EVCache 在单个区域的架构,一个 EVCache 集群包含(1)EVCache 服务器,上面跑着 Memcached 和一个 Java Sidecar 应用;一个EVCahce 集群可以包含多个 EVCache 服务器;该 Java 边车应用主要职责是和Eureka 交互,监控 Memcached 进程,收集和提交性能数据给 Servo 组件;(2)EVCache 客户端,定制的面向 Java 应用访问 EVCache 的客户端,数据通过 Ketama 一致性 Hash 算法自动进行分片存储, EVCache 在写入的时候,对多个集群采取双写策略。
那跨区域的 EVCache 复制是怎么实现的呢?同区域的 EVCache 集群是通过双写来实现数据在不同的 EVCache 集群的同步,一个 SET 操作的跨区域复制架构示意图如下,对于应用调用方而言,跨区域复制是透明无感知的:
应用通过 EVCache 客户端发送了一个SET操作到本地缓存,EVCache 客户端操作本地缓存的同时将这次缓存操作的元数据(不包含数据本身)写到本地消息系统 Kafka 中;Region A 本地的一个 Replication Relay 服务监听到消息并利用消息中的元数据从缓存中读取新的键值数据;在另外一个区域B中,运行着本地的一个 ReplicationProxy,负责跟区域A中的 Replication Relay 服务通信,该代理接收到SET消息将数据同样利用SET命令写入区域B的本地缓存,这样区域B的应用可以在本地读取到最新的数据。
这仅仅是针对SET操作原语的一个简单示意,对于其他的操作原语比如DELETE,TOUCH或Batch操作,原理类似,不过在ReplicationRelay这层,有些操作不需要读取内存最新数据;该架构中,仅仅只有ReplicationRelay和Replication Proxy感知到跨区域复制,其它组件是被安全隔离在跨区域复制功能之外的。
4.1 跨区域复制组件的弹性扩展和故障隔离
Replication Relay、Replication Proxy和Kafka消息队列都独立于EVCache集群和EVCache应用,这三个组件的故障并不会影响应用的本地缓存操作。由于用户访问存在波峰波谷,这三个组件各自都支持弹性扩展。如果发送跨区域之间的网络异常导致跨区域消息传输延迟,那Kafka的消息会堆积,但本地应用的内存GET/SET操作不受影响。
基于Kafka的跨区域消息复制架构生产环境跑了1年时间左右,支持高峰期每秒150万次消息通信;但那个时间段,Netflix 团队也遇到不少的技术挑战,比如间歇性的端到端的跨区域数据复制延迟波动,延迟上升的原因很多比如ReplicationProxy应用集群的弹性伸缩异常,跨区域基于互联网的网络拥塞等等。网络层面,在 VPC 内不同机器的PPS的性能是有差异的,Netflix的经验是使用2台+的高网络性能机器提升整个 PPS的容量;另外为了观察跨区域网络连接的性能,Netflix 团队增加了非常多的两点之间的监控,比如应用到Kafka,Relay集群读取 Kafka,Replay 集群到远程的Proxy集群,从 Proxy 集群到本地的缓冲之间等等。
在 Kafka 队列中保存的复制消息,仅仅包含Key相关的元数据,极大提升了Kafka集群的效率,降低了消息队列的存储消耗;另一方面,有些缓冲的操作原语并不需要Key对应的数据值,比如DELETE操作,我们仅仅发送一条DELETE消息给另外一个区域;再比如某些缓冲数据的SET操作,我们也仅仅会发送一条DELETE消息给到另外一个区域,使另外一个区域执行一次内存非命中操作,而不是将数据从一个区域直接复制过去。简言之,Netflix团队基于EVCache的客户端,给数据分级,同时基于对操作原语和操作上下文的理解,降低不必要的跨区域数据传输。
4.2 一些优化措施和效果
针对不同的缓冲集群,Netflix 尝试在延迟和吞吐上根据业务需求作不同的权衡。绝大多数的集群跨区域复制延迟99%的应用侧端到端的延迟小于1秒,部分应用可以接受一点延迟,利用批处理消息复制,提示整体的吞吐率;对于这样大数据量的内存复制延迟,99%的情况下控制在400ms以内。
还有一个显而易见的优化是,利用持久化连接,减少每次数据传输时重新建立TCP连接的三次握手时间。同时在Relay集群和Proxy集群之间,Netflix团队尽量在一次请求中填充多条消息来适配TCP窗口大小。这些网络优化措施,可以支撑整个EVCache跨区域复制高峰处理100万RPS。
4.3 继续跟进的技术挑战
Kafka 的弹性伸缩并不是一件容易的事情;当缓存需要更多的复制队列容量,Netflix 团队需要手工加入分区,并配置消费者匹配新的线程数量,同时扩展 Relay 集群来适配。这套流程增加了重复消息传输风险,延迟全局的最终一致性,而且不够高效。
Kafka 的监控尤其是丢失消息这块不是很精确,Netflix团队对比了Kafka brokers收到的消息总数,和Relay集群复制的消息总数,发现经常有差异;也就是说,由于软件缺陷,导致消息并没有成功保存到Kafka分区中,或者没有被Replay接受到;另外,Netflix团队还观察到,有时Kafka某个分区的处理性能非常低,虽然平均队列处理延迟还可接受,但还需要监控最大延迟时间。
在复制目标区域,如果有 EVCache 实例出现故障,那么会增加复制延迟,因为 Proxy 集群尝试写入出错的缓存实例,目标区域的 Proxy 处理延迟会连锁影响复制源区域的 Relay 集群处理,因为 Relay 集群在等待 Proxy 集群的确认消息。
第三代架构:全球联通架构
2013 年 Netflix 实现了美国境内用户的多区域双活架构,2012 年 Netflix 在欧洲构建了一个独立的区域服务欧洲用户,也就是说,欧洲用户在欧洲的订阅及观看历史没有和美国的订阅和观看历史合并在一起,因为 Netflix 统计出相对而言,横跨大西洋旅行的用户占少数,因此隔离欧洲和美洲用户信息有助于隔离互相之间的故障影响。但是,独立的欧洲全球存在单点风险,因此Netflix 团队实施了全球所有区域的多活架构。
为了在美国区域可以服务欧洲用户的请求,Netflix必须首先将EU-West-1孤岛的 Cassandra 集群数据复制到 US-East-1 和 US-West-2。对于 Netflix 团队有两种选择(1)将欧洲用户数据复制到美国区域 Cassandra 集群中的不同的逻辑空间,这样欧洲用户数据和美国用户数据存储在同一个集群的不同分区中(2)直接合并欧洲用户和美国用户数据,但合并逻辑比较复杂;
最终 Netflix 团队选择渐进式实现用户数据合并,第一种情况,合并的结果是刚好是Key值不冲突,Key对应的两边孤岛数据恰好合并而不互相影响,首先团队扩展了 Astyanax Cassandra 客户端,支持数据同时写入同一个集群的两个逻辑空间;其次,云数据团队还开发了一个新的工具,支持从一个逻辑区搬迁数据到另外一个逻辑区。第二种情况,两边的缓冲数据的Key值有重叠,那就需要深度分析,最好要保留那一份数据。
EVCache 本身已经支持全量的双向跨区域复制,所以,具体的复制同步策略留给应用团队来决定具体的缓冲数据集如何同步。
另外一块是个性化数据集,该数据集是在某一个区域计算好,然后复制到其他所有区域,因此个性化数据更新只会发生在该用户的 “Home” 区域;如果发生该用户的流量被重定向到另外一个区域进行处理,那个性化信息不会发生变化。因为个性化推荐信息是定期,时间驱动的预处理逻辑,因此个性化数据的复制同步策略在 Netflix 全球联通项目的第一阶段是可以接受的;未来 Netflix 也计划允许这种个性化数据预处理的计算可以根据资源来进行灵活调度。
多区域多活架构测试验证:混沌工程之 Chaos Kong
在 Netflix 很早就引入混沌工程方法来验证测试系统的可靠性和自愈能力,猴子军团利用随机算法,从 Netflix 生产环境挂掉一台 AWS 服务器,来检测系统是否有足够弹性恢复。Netflix 团队的理念是服务器故障是在所难免的,他们期望这些故障发生在上班时间,这样他们的工程师能够及时修复。所以,Netflix 的工程师都接受服务器可能发生故障这样的上下文信息,而且开发的服务要能够从服务器故障中自动恢复,这样假定真的发生了这样的故障,对于系统和团队而言,反而不是多大的事情。基于这样的成功实践,Netflix 进一步将基础设施故障从单个虚拟机扩大到整个 AWS 区域,这就是 Chaos Kong 的设计初衷,模拟并演练某个区域级故障,帮助我们发现系统的脆弱点并修复。
如上图所示,这是一次 Chaos Kong 的真实演练,对于Netflix的流媒体播放服务,在同样的8个小时的时间窗口,查看整个用户流量和每个区域的流量变化情况;可以看出当区域 US-West-2 整个不可用的时候,流媒体播放服务请求大量被引流到了 US-East-1 区域,US-East-1 区域充当了救世主的角色。对于最终用户而言,我们更关注是图中上半部分的所有用户流量的聚集指标,是非常健康的稳定状态,这表明我们的系统拥有足够的弹性进行故障转移。在最后一段时间,流量又从 US-East-1 回流到 US-West-2,而用户侧请求没有受到任何影响。
混沌工程演练可以给到团队足够的信心来处理各种故障,找出系统的脆弱点,并及时修复。
总结
服务全球用户的企业,为了提升用户体验,技术层天然要求就近提供服务,同时为了提升服务可用性避免区域级的基础设施或自建服务的故障,实现一个跨区域多活的架构是一个自然的选择;但从单区域多可用区多活走向多区域多活架构,有非常多的技术挑战;Netflix 的故事给我们的启发是,根据自己的业务特性,先完善单区域的多可用区架构,再细分状态数据特性和拆解用户请求路径,由易到难,逐步优化,仔细验证(混沌金刚)。
参考资料:
https://medium.com/netflix-techblog/
https://aws.amazon.com/cn/blogs/china/tag/%E6%B7%B7%E6%B2%8C%E5%B7%A5%E7%A8%8B/AWS 云上混沌工程
客户案例系列:
云架构师自我修养系列:
近期原创: