从淘宝到云端,阿里高可用架构演进实战

本文主要介绍近几年在阿里电商平台及阿里云上的高可用设计经验,分为两个部分:第一部分主要包括传统的淘宝店铺稳定性体系的建设及相关的基础链路设计、缓存和容灾方案的设计及部署;第二部分主要包括目前公有云高可用设计的主要思路、经典故障及应对措施等。前端

注:本文整理自阿里技术专家沐剑在 QCon 北京 2017上的演讲,由阿里技术公众号受权转载。数据库

  写在前面后端

你们好,我今天分享的题目是《高可用实践:从淘宝到上云的差别》,取这个标题是由于会涉及到两个方面内容,一方面以淘宝为例子,传统的 IDC 的时候,咱们稳定性是怎么作的,另外在云计算背景下,有不少创业公司是基于阿里云这样的公有云基础设施作研发,在公有云的环境下怎么作好咱们系统的高可用。数组

个人花名叫沐剑,2011 年加入淘宝作评价系统,2012-2015 年在店铺平台,负责店铺的前台浏览系统和后台的 RPC 服务,以及一些性能优化、双 11 保障的事情。到了 2015 年开始到了 TAE 团队,开始负责云端架构及总体高可用方案,TAE 的升级版 EWS 如今也在聚石塔上面帮大量 ISV 和创业公司解决运维部署、自动化监控和性能分析等等问题。缓存

去年我是做为阿里商家事业部双 11 做战项目研发的 PM。2017 年我开始接手商家营销团队。在阿里五六年的经验,其实就作了几件事,好比连续五年参加了双十一的核心备战,而后像去 IOE、异地多活,全链路压测、安全混合云、容器服务等项目参与设计和实施。安全

首先我会从淘宝店铺角度分享,之前在店铺是怎么样作双 11 保障的,后面是一些公有云相关的内容。性能优化

  淘宝店铺稳定性体系建设服务器

这是一个淘宝的店铺系统,这套系统是一个很是典型的高并发的浏览系统,在前几年的双 11 峰值有 20 万次的 Web 页面请求,平均一个页面对应了 20 次的 RPC 调用,这个时候对于整个系统的集合来讲每秒的 QPS 是 400 万次,这中间就会涉及到缓存、数据库以及其它二方的 RPC 调用,对于这样的系统来讲,在性能、稳定性和体验间要作一个平衡,既不能纯用太多的机器死扛这个访问量,又要保障用户的体验。网络

  基础链路设计数据结构

从请求链路来讲,首先 DNS 把 CDN 的 VIP 解析出来,分布在全球不一样的区域,CDN 回源到接入层分别通过 4 层和 7 层的负载均衡,近几年会发现 CDN 这个行业早已不只仅局限作 CSS/JS 等静态资源的缓存,也承担了一些动态加速和收敛的特性,因此咱们是经过 CDN 作域名收敛,收敛后会把这个流量发到统一接入层,而后到应用集群,后面再通过应用存储、Cache 这些服务。

当咱们在作稳定性的时候,会考虑性能和稳定性之间是什么关系,不少人认为这二者是冲突的,好比我要保障一个东西的性能很是高的时候,要牺牲掉不少别的东西,可能你要引入一个很是新的框架或者基础设施来提高性能,但它的稳定性多是不那么成熟的,可是从容量规划的角度看,只有这套系统性能足够好,才能承担像双 11 那样的大访问量。

店铺也是一套经历了不少年的系统,在应用层上的优化基本上已经作到极致了,咱们就转变思路,在操做系统层能不能作一些优化,这里借助了一个比较好的工具 perf,在操做系统层面告诉你系统调用的开销是集中在哪里,从 perf 上就能够定位到有一个百分比,能够看到是好比数组分配仍是 GC 产生了大量的开销。

最初咱们发现是异常带来的开销,就会看为何这个系统的异常会致使 20% 以上的 CPU 开销,最后用 BTrace 跟了一下异常的构造函数,发现是咱们依赖的开源的三方包里经过异常作控制流,每一次它处理结束的时候,就抛一个 EOFException 出来,这个就致使了很是大的开销,咱们就把开源包替换掉了。

当你依赖一些底层的东西的时候,若是对原理不太了解会给你带来一些意料以外的事情。JVM 里是有一个常量池存储字符串常量的地方,就是一个哈希表,若是说这个表的大小不足够大,就会从哈希查询变成链表查询,性能就会特别低。

再谈一个 warm up 的问题,当咱们应用刚刚启动的时候,尚未把字节码编译成 native code,延迟很是高,用户就获得一个有损的服务。咱们如今在内部的 JVM 作一个功能,会采集线上系统的调用,把热点方法收集下来作分析,在应用把真实流量挂上去以前,已经预先把全部的热点方法编译成 native code 保证这个性能。开源界也有其余的方案,好比 Azul 的 Zing 有个 ReadyNow,IBM 的 J9 有个 AOT,也是作相似的事情。

  缓存设计

谈到缓存,Cache 里有一些小技巧,在作双十一备战时发现一个店铺的基础服务平时天天平常就有 100 亿的调用量,当时是几十台机器估了一下可能要成倍增加,成本是很是高的,怎么解决这个问题,当时写了个富客户端,让业务方先去查咱们分布式 Cache,若是命中就直接返回来,若是不命中再走咱们的服务端查。这种状况下,只要你可以保证命中率足够高,好比 98% 的命中率,就意味着只有 2% 是须要后端服务器承担剩下的请求,用很是少的服务器去承担很是大的流量,这是成本和性能间的权衡。

在缓存方面,咱们不多会关心缓存的高可用是怎么部署的,它是一个偏运维的内容,我把缓存的部署简化成一个双机房的模型,由于它在高可用里是最简单的场景。

对于缓存来讲有两种经典部署模式,第一种叫共享集群部署,在 IDC 里个人应用是分机房部署的,Cache 集群也是分机房部署,对于应用服务器来讲,两边的 Cache 对他来讲逻辑上是一个集群,会往 IDC 1 的 Cache 写一半过去,往 IDC 2 也写一半过去,这种部署的好处在于,机房间网络断掉的时候,有一半的数据是在缓存的,保证一半的数据可以命中,不会直接死掉,另外对成本上相对比较友好,没有浪费任何一个 Cache 的节点,这个 Cache 自己是复用的。

可是也正如刚才说的问题,若是中间断掉了,有一半的命中率是有损的,因此就诞生了另外的一个部署模式,就是独立部署,无论你哪一个机房挂掉,命中率是基本不变的,两边同时保持了 98% 的命中率,可是它是成本不友好的,两边要同时部署,同时承担副本的做用,而且失效时,要同时失效另一个 IDC 2,这样才保证一致性。

在缓存上,我认为一切东西都是能够被缓存的,一般咱们认为缓存跟实际数据库里存在的东西多是不同的,有几毫秒的延迟或者怎么样,因此咱们设计一个系统的时候,对一致性要求很是高的时候,会倾向于不用缓存,用数据库扛这个流量。

但以 MySQL 为例,InnoDB 里有一个很重要的缓存 Buffer Pool,对于一个数据库,在冷库的状况下用一堆 SQL 去查它,和慢慢预热完再查它的时候效果是不同的,这个是咱们当初在作异地多活时面临的一个问题。例如我已经有一个机房,但愿创建一个新单元去承担这个机房的流量,当我建完这个单元,把全部的应用都部署好了后,把流量切 50% 过来会怎么样,假设这两个单元的机器数同样,这个单元会挂,由于这个数据库是冷的,缓存是空的,不能承担以前那个单元数据库所能承担的 QPS。

如今业界有不少叫 API 网关或者 CDN,他们在边缘节点也作了一层短暂的 Cache,可能只 Cache 50 或者 100 毫秒,可是当你系统受到攻击的时候能够拯救你后端的应用系统,攻击引起的命中率一般比较高,有这 50 毫秒的缓存,可能后端只有几百个 QPS 过来,那个流量你是能够承受的。

在高可用里两个很是经典的作法是限流和降级,在阿里双 11,有一位老兵说过一句话,他说当双 11 到来的时候,任何一个系统均可能出问题,你要作的是对你的上游限流,对你的下游限流。怎么理解,当上流的流量超过你的能力的时候就要限流,当下游好比 DBA 告诉你数据库压力很大了,那就对下游限流,只要保证住这个限流,你基本不会挂,每一个系统都作到这个的时候,整个系统都是可用的。当流量超出你掌控的时候,这个作法可让你成为这个暴风下的幸存者。

对限流降级的思考,第一限流降级考验的是什么问题,我认为本质上考验的是故障自恢复能力,在平时工做中会遇到机房断网或者停电,每半个月都会作断网演练,不告诉你发生什么,就把这个网切断,看你的应用 O 不 OK,通常是在晚上两三点,接到不少的机房报警,这个时候看你的架构设计的是否足够可用,若是足够可用就没问题,不会形成什么影响,继续睡觉,若是设计很差,就得爬起来当即处理。

而开关降级最大的做用,好比咱们发现一些线上的问题,第一反映是赶忙回滚,可是当你的系统很大的时候,特别像 Java 这种,一个系统启动要启动几分钟,你的回滚完成,20 分钟都过去了,这个过程对用户来讲都是有损的,而开关能够在一瞬间把全部的逻辑切到老的。这个是避免回滚时间致使的问题。开关有的时候能救命,若是没有这个开关的话,避免问题放大就只能回滚,因此开关是一个很大的价值所在。

  容灾设计

另一点很是重要的是,在设计一个技术方案的时候,就会把容灾的设计融入到方案里。好比在设计技术方案的时候,在最后一章单独有一个容灾设计,这个节点里任何服务挂掉的时候,你要保持什么样的方式保持这个服务是可用的。

在容灾设计时有几点必须考虑,好比我引了一个新 jar 包或者调了一个新的 RPC 的服务、引入了分布式的存储,之前没用过也不知道它稳不稳定,第一想法是它确定会挂,它挂了咱们怎么作,咱们当时在作前台系统的异步化的时候,由于 Redis 支持 map 的数据结构,因此咱们就是用 Redis 的 hmget 从这个 map 里拿出部分的 key 减小网卡的流量,但即便这个挂掉了,咱们还会走老的 Cache,只不过网卡流量会大一些,可是对用户的服务是无损的,因此这里要考虑若是它挂了怎么作降级,有什么样的恢复流程。

另外是发布计划,在新系统上线时就会关注这些问题,好比此次有没有作数据迁移,好比之前我是 8 个库不够用了我拆到 16 个库或者 32 个库,中间必定是有数据迁移的,涉及到数据迁移必定要有一套对帐系统保证这个数据是新数据和老数据是对得平的,否则必定有问题,由于咱们是作交易相关的,订单、金额绝对不能出问题。

另外是你的发布顺序是否是有依赖,若是出了问题的时候,谁要先回滚,这里是取决于技术设计。另外是否要经过客服公告的方式告诉外部用户说有 5 分钟的不可用,若是真的有用户打电话有疑问客服同窗能够向用户解释。

在高可用这个领域作久了会有一种直觉,这个直觉很重要,来源于你的经验转换成这种直觉,可是对于一个成熟的团队来讲,须要把这种直觉转化为产品或工具。有不少牛人他们的技能都只能叫手艺,你须要把这种手艺转换成产品和工具。

  公有云高可用设计

2015 年我去作云产品,这里给你们分享下咱们是怎么样帮客户包括咱们的系统在云上是作高可用的。

  经典故障案例

首先看两个经典故障案例,第一个是 Gitlab 生产数据库删了,它恢复了好久,Snapshot 等全都没有生效,作了五六层的备份也都没有什么用。这个事情说明第一咱们的故障要按期演练,好比中间件在作的线上故障演练,你说你的系统可用性好,我把这个主库断了,虚拟机挂掉几台试试,作这些演练就能够知道你这个容灾体系是否是可靠的,若是没有这个演练的话,当真正的故障发生时你才会发现这个东西是不 OK 的。

另一个很典型的问题,Gitlab 对备份的原理是不够了解的。好比当时用的 PostgreSQL 的一个版本,当时是有问题的,没有验证,开发人员对这个又不是特别了解的状况下就会出现这个问题,这就是为何要去了解你的依赖以及你依赖的依赖。

去年咱们作压测,有个应用一边压测一边在优化作发布,发现第一批发的起不来了,就只是改了一两行代码加日志,他就去看什么缘由,最后发现依赖的某个 jar 包依赖一个配置,而这个配置在压测中被降级了,一个 jar 包就把应用启动卡住了。若是在双十一当天或者在平时业务高峰期的时候发现这个问题是来不及修复的。因此这个时候,咱们就要求,依赖的二方 jar 包必须看一下里面是怎么实现的,依赖哪些东西。

反过来讲,别人依赖个人客户端就意味着他不只依赖着个人服务还依赖着个人缓存,这个缓存出了问题对他也有影响,咱们每一年双十一前有一个强弱依赖梳理,不只要梳理本身应用里面的,还有依赖的全部东西都梳理出来,中间任何一个节点挂掉了你应该怎么办,须要给一个明确答复。

第二个故障案例是今年发生的,AWS S3 敲错了一个命令把基础核心服务下线了,有一个对象索引服务和位置服务系统被 offline,后来也作了一些改进,每次敲的命令有一个静默期,让你有个反悔的机会,线上有个最小的资源保证服务。

这个给咱们带来的启示是什么,云服务自己也是会发生故障的,好比买了云数据库,咱们没有办法假设它是 100% 可用的,当它出现问题咱们怎么办,是给云厂商提工单说何时能恢复,仍是我本身可以有一个容灾的方案解决这个问题。从 2015 年开始,咱们愈来愈多地发现,对架构可用性最大的威胁是什么?在市政施工里必定概率就会莫名其妙搞出光缆被挖断等故障,咱们不得不考虑,当云服务自己出现问题咱们该怎么办。

  应对措施

因此咱们须要有一套面向云的高可用架构。在很早之前有厂商提出相似 SDN 的一个概念,叫 SDI——软件定义基础设施,过去咱们发现只有大厂能够作这个事情,设计一套很复杂的管理系统帮他实现,这里放一个路由器,这边放一台虚拟机,能够经过软件的控制流去控制这个东西。可是在云的时代,资源变得很容易得到。以阿里云为例子,能够用 API 随时建立新的虚拟机,新的负载均衡,或者是新的存储,均可以经过 API 随时建立随时销毁,这对于你的基础设施的灵活度很是有好处。

之前有的人会以为,性能问题或者容量问题应该经过性能优化的方式解决,经过一些黑科技方式解决,加机器会以为很 low。但我以为一个问题若是能简单用加机器来解决是很不容易的,意味着对你的整个架构的水平扩展性要求很是高,并且解决效率很高,加机器就解决了,而对一些中心化的系统来讲就比较麻烦,加机器都加不了,可能要把机器关掉升配置再从新拉起来。因此咱们说,在公有云上面,在资源如此容易得到的状况下要充分利用这个特性,要作一个可以作水平扩展的架构。

那么第一步要作什么,前两年很火的容器、微服务,本质上都是解决了是无状态的应用怎么作自动化的扩容这个问题。右边这个图上面,上面是一个负载均衡,中间是一个前端的服务,后端是一个无状态的后端服务,底层是 MQ、对象存储、数据库这些东西,若是咱们可以把前端和后端的无状态服务第一步先容器化,就能够作到当流量过来的时候,只要后端的存储没有问题,整套架构就是可以水平扩展的。

从去年公开的报道和故障来看,不少人有个误会是说云厂商的机器应该是不会挂的,我买了一台云厂商的虚拟机应该是随时可用的,即便不可用云厂商也要帮我解决热迁移的问题,热迁移在业界是很复杂的问题,不光涉及到磁盘存储的迁移,也涉及到内存是要作迁移的,可能还要用 RDMA。而且对于传统的 IDC 来讲,无论物理机仍是虚拟机都是有可能挂的,对云也不例外。

当咱们在使用公有云服务的时候,都是会挂的,这是个心理准备。不光是机器,包括负载均衡是否是也有可能挂,下面的消息队列或者数据库是否是也有可能会挂,当你基于任何东西均可能会挂的前提设计一个系统的时候,才能真正作到这个系统在任何状况下都不会受底层云服务的故障影响。

而对于不一样的云服务来讲是有不一样的容灾策略。好比一台虚拟机挂了,一般来讲负载均衡无论是 4 层仍是 7 层都会作健康检查,挂了健康检查不通自动会把流量切断。若是个人负载均衡挂了怎么办,若是 DNS 有健康检查那就方便了,若是没有的话可能就要设计一个旁路系统解决这个问题,发现这个已经不通了,就自动把它从 DNS 上摘掉。

无论是云服务发生故障仍是本身应用发生故障有个大原则是如何最快速解决问题,就是一个字,切。为何要作异地多活,为何要把流量往各个地方引,切流量是解决问题最快的,把坏流量切到好的地方立刻就解决了,若是你要等定位问题解决问题再上线客户就流失掉了。对淘宝来讲也是同样,当某一个单元低于咱们认为的可用性的时候,咱们会把这个单元的流量引到另一个可用的单元,固然前提是那个单元的容量是足够的。

弹性是否是万能的?全部的云服务都是弹性的,弹性其实不是万能的,容量规划仍然是有必要的,否则就不必作双十一备战了。这里有一个你须要付出的代价,弹性的过程每每是须要时间的,那么容量规划在这个环节中起到的做用就很重要,当真的逼不得已的时候,我要扩容了,怎么保证我扩完容以前系统不雪崩?就是要结合以前的限流,尽量保障每一个客户获得他应有的服务,可是也要保障系统的稳定性。

Region 和 Availability Zone 这两个,当实践的时候,购买虚拟机、负载均衡或者数据库,必定要选择多可能区的服务,好比阿里云买 SLB 是可选可用区的,有个主可用区和副可用区,若是一边挂了能够切换到另一边,RDS 也是同样的。

这幅图是一套典型基于公有云的一套架构,不叫异地多活,应该叫跨区域设计。左右两个大框是两个城市,左边是北京,右边是上海,每一个里面又有不一样的机房可用区去承担这个流量,假如北京的挂掉了,就切,原来是在 A 可用区,就切到 B 可用区,这时候 A 可用区没有流量进来,经过切换的方式可以把这个服务快速恢复。下面画了一个跨区域复制,咱们在异地多活项目里,涉及到了跨城市跨数据中心的复制,好比个人北京是提供写服务的,上海要提供读服务,就要经过这种方式同步数据过去。

相关文章
相关标签/搜索