快手基于 Kubernetes 与 Istio 的容器云落地实践

作者简介

快手基于 Kubernetes 与 Istio 的容器云落地实践

张夏

快手 资深技术专家

大家好,我是来自快⼿的张夏,今天非常荣幸能有机会分享和交流快⼿在Kubernetes、CI/CD,以及 Service Mesh 的一些实践。

今天分享的内容,主要有以下⼏个方⾯:

  • 基于 Kubernetes 容器云建设

  • 利用 Helm 应⽤发布与管理

  • 采用 Gitlab CI/CD 构建 CI/CD

  • 使用 Istio 进⾏行微服务治理

随着近几年微服务化与 DevOps 的兴起,容器不止在国内外大型互联网公司中深受追捧,即使对于传统⾏业或一些规模相对较小的企业,对于容器化也是摩拳擦掌、跃跃欲试。

容器落地需要解决2个方面的问题: 首先,容器技术本身存在的问题和不足,另外,新技术如何与公司现有系统快速对接。

那么我主要针对以上2个方⾯,来说明下容器化落地要面临和解决的问题。

1. 基于 Kubernetes 的容器云建设

快手基于 Kubernetes 与 Istio 的容器云落地实践

首先,对于容器技术本身来说,国内的 IDC 环境的不稳定众所周知,跨 IDC 的网络抖动有时比较厉害,跨机房容灾就是我们一个难点;接下来,我们面对的是如何在保证服务质量前提下,提供计算资源的利⽤率。容器化之后,大家还吐槽的⼀个问题是 debug 起来比较困难了,容器化之前直接ssh 登录到机器上可以随心所欲、为所欲为,但现在容器化后限制了想象。

另外,容器的生命周期是短暂和不确定的,比如内核问题导致系统崩溃,容器漂移到别的机器上,如果数据保留在本地,就会导致容器服务迁移但数据没有迁移的困境等。

对于容器化落地,对于非初创公司,⼀般都是投⼊了⼤量的⼈⼒物⼒造就了祖传的⼯具和平台系统,引⼊容器之后呢,不大可能会从头推倒重来,更快速的对接现有系统平台才是正道。但⼈间正道是沧桑,新⽼技术对接并不会⼀帆风顺,如何更多快好省的对接,使容器技术在公司快速的落地,是我们需要考虑的问题。

术业有专攻,开发⼈员⼀般并不关⼼容器化技术的⼀些细节,学习成本越少越好,越简单易⽤、傻⽠式越好。

上⾯介绍完容器落地难点之后呢,我来介绍下快⼿在容器化之路的一些实践。期望可以多、快、好、省的踏上容器化之路。

 

首先,我会⾸先介绍下基于 Kubernetes 的容器云中的⼀些实践,避免走⼀些弯路;接下来我会介绍下基于 Gitlab CI 的CI/CD,作为有历史包袱的公司,即使采⽤相同的技术栈,做出的 CI/CD 也五花八门,甚至一个公司内部不同部⻔也会有不同的 CI/CD 方案,⽐如某鹅厂。

第三部分,会介绍下 Kubernetes 服务编排领域的唯一开源⼦项目 Helm 进⾏应⽤编排和应用级别的配置管理,最后会介绍下近⼀年快被炒糊了的 ServiceMesh 落地的⼀些经验。

快手基于 Kubernetes 与 Istio 的容器云落地实践

我之前经常被问到⼀个问题之⼀就是: 为什么要自己开发容器云,⽽不直接用⼤大⼚提供的⼀站式容器云⽅案。当你真正打算落地业务的时候,发现现有的容器云方案各种水土不服,定制开发也会非常的麻烦。

我们不生产容器,我们只是容器的搬运⼯。下⾯介绍下搭建私有容器云过程的一些实践,为即将容器化或正在容器化的同学们提供一个参考。

我们先看⼀下容器云的架构,分为几个部分来阐述:

快手基于 Kubernetes 与 Istio 的容器云落地实践

使用的技术如下:

高可用/容灾:Haproxy/DPVS

数据备份:etcd

网络:Calico/Flannel

DNS:CoreDNS
Proxy模式:IPVS
存储驱动模式:direct – LVM
CI/CD:Gitab CI/CD on Kubernetes
服务编排:Kubernetes + HELM等等
Service Mesh:Istio
应用配置管理:Helm
应用商店:应用HELM模板

快手基于 Kubernetes 与 Istio 的容器云落地实践

首先,看下容器镜像构建实践,构建容器镜像,刚开始使用容器时,被用户吐槽 Dockerfile 学习成本高,构建、拉取镜像慢等问题,这些问题 Docker 不能背锅,像造手机一样,都是友商提供的硬件,手机⼚商要做的事情就是把积木堆好。我们也是一样,通过充分的利⽤ Docker 的镜像分层复⽤功能和微镜像就可以很好地解决这个问题。

关于镜像层数,最需要做的是减少镜像层数,细⼼的同学可能注意到,构建一个 Docker 镜像时,对于 Dockerfile 里面的每命令行,⾄少要分为3个步骤:首先,启动一个临时容器,执行 Dockerfile 命令行中的命令,然后执⾏ docker commit命令通过容器⽣成一个镜像,最后销毁临时容器。

之前场景在 docker后台进程⽐较忙的时候,创建容器、commit、销毁容器3个步骤,⼤概需要⼗几秒时间。简单的说,减少一个  Dockerfile 命令行,可以节省约十⼏秒的时间,量化一下,一个Dockerfile ⾥有30个命令⾏,如果可以合并为10⾏以内呢,可以节省20*10秒的构建时间。

我们如何减少镜像层数的呢? 我们把 Docker 镜像进行逻辑的分层,目的是更好的复用。

接下来谈⼀下微镜像Alpine,Alpine Linux 是⼀款独立的⾮商业性的通⽤ Linux 发行版,Alpine Linux 围绕 musl libc 和 busybox 构建,尽管体积很小,Apline 提供了完整的 Linux 环境,其存储库中还包含了⼤量的软件包备选,它采⽤自有的名为 apk 的包管理器。

⽤Alpine跑了JDK8的镜像结果发现,JDK还是无法执⾏行。后来翻阅文档才发现Java是基于GUN Standard C library(glibc),⽽Alpine是基于MUSL libc(mini libc),所以Alpine需要安装glibc的库,以下是官方给出wiki:

https://wiki.alpinelinux.org/wiki/Running_glibc_programs。

至于如何安装,可以参考:https://github.com/sgerrand/alpine-pkg-glibc 。

快手基于 Kubernetes 与 Istio 的容器云落地实践

用Helm部署与管理复杂Kubernetes应用,基于GO模板语言,实现应用快速部署到K8s集群。Helm 是K8s服务编排唯一开源项目,做为K8s包管理工具,用于部署复杂K8s应用,处理复杂的服务间依赖。另外,可以用 Helm 查看发布历史与某一次发布的具体配置。

快手基于 Kubernetes 与 Istio 的容器云落地实践

Helm采用了模板+配置的形式,针对不同的环境,无需修改模板,通过修改配置就可以发布到不同环境之中。

快手基于 Kubernetes 与 Istio 的容器云落地实践

可以通过开源的 ChartMuseum 管理 Helm Chart 包。

快手基于 Kubernetes 与 Istio 的容器云落地实践对应用可能发布多个版本,希望通过HELM看到历史发布的信息,一个应用发布了三次,第三次有问题,需要回滚,但不知道回滚第一次还是第二次合适,需要查看历史版本配置的功能。

这就是我们刚才提的,如果用K8S处理的话,处理依赖的话,K8S用INITCONANIER,或者POSTSTORT、PRESTOP去做两个埋点,比如说在升级的时候,无论是蓝绿或者是灰度,做的时候考虑升级服务一定不能影响现在正在提供的服务,我们可以通过埋点做这个事情。

快手基于 Kubernetes 与 Istio 的容器云落地实践

即使没有状态的服务,升级/回滚也不是无损的,因为要删除容器。我们看一下QoS,线下无所谓,预发布环境,线下环境,没有关系我们直接用K8S两个类型,可以提高自然利用率,但是一旦是我们线上环境,上容器化,新上经验不是很多的情况,建议都是用容器的方式做,定额分配资源,不会造成资源竞争。

快手基于 Kubernetes 与 Istio 的容器云落地实践

相对来讲的话,只要分配给资源,K8S集群统一调度,不会与资源竞争。我们看一下服务发现,环境变量注入的方式不太推荐,早期采用了 Linux 环境变量方式,为每个 Service 生成对应的 Linux 环境变量,并在POD启动时,自动注入这些变量。

快手基于 Kubernetes 与 Istio 的容器云落地实践

DNS我们推荐,通过ADD-ON方式引入DNS功能,通过把SERVICE NAME作为DNS域名,为了更简单把GUESTBOOK的例子摘出来的。大家可以看一下。

快手基于 Kubernetes 与 Istio 的容器云落地实践

看一下七层服务发现,基于内部相互的去寻找另外一个服务,但是七层我们想集群外部,比如说容器化了,希望访问到别的容器服务,或者是别的集群服务,或者是我访问,或者外面的非容器内想访问容器内部的服务。

快手基于 Kubernetes 与 Istio 的容器云落地实践

自动化运维只是简单举一个例子,K8S做了很多自动化的事情,我们可以利用起来,举一个例子,拿 Deployment 的例子讲,我们看一个服务正不正常,可以通过监控,POD的数量,配制好 Probe 探针LIVENESS与READNESS 再通过简单的判断 AVAILABLE 的 POD 数量是否等于预期的即可,大大简化了运维与监控的成本。

快手基于 Kubernetes 与 Istio 的容器云落地实践

2. 利用Gitlab CI/CD构建CI/CD

快手基于 Kubernetes 与 Istio 的容器云落地实践

其实目标很简单,希望能够CI/CD希望把公司的一些工具,比如说需要大中台,把工具能够串联起来,从提交代码开始,到最后发布能够串联起来,CI/CD干了这些事情。

快手基于 Kubernetes 与 Istio 的容器云落地实践

接下来讲一下CI/CD过程,这个是开发提交代码,提交代码之后是非常粗略的,后面可以详细的,代码比如说做编译,打包,走各种测试,组建测试完了之后,预发布环节有流量的话,希望这个服务上线之前有审批,最后去发布,包括上生产环境。

Gitalab CI/CD用起来非常简单和方便,不是很熟悉的人两三天就可以学的,提供很多的模板,现在这个公司大面积推CI,我们的业务很多,包括扩张非常厉害,几千人的规模,新业务老业务非常杂,把这些所有都谈了一遍,他们不需要做什么,提交代码就可以了,剩下的事情由我们来做。

快手基于 Kubernetes 与 Istio 的容器云落地实践

 

快手基于 Kubernetes 与 Istio 的容器云落地实践

快手基于 Kubernetes 与 Istio 的容器云落地实践

下面这个流程相对完整一点,但是不够完整,差得比较多,我们可以看一下,开发从提交代码之后,跑一些单元测试,代码风格检查,代码覆盖率,每个公司要求不一样,对代码的质量,有的公司要求代码覆盖率低于80%,有的90%,过了之后才可以往下走。

前面单元测试这些代码覆盖率,代码扫描过了之后,可以看看下面再往下走的时候,我们开始关心镜像,其实前面还有编译打包,跟别的方式做的不一样,包是不存的,Istio管代码,代码不会丢。因为编译环境不丢,代码环境不丢,我可以随时随地弄一个包出来,存在镜像里面,有镜像的名称,如果一旦线上有问题的时候,从后向前能够推导我们代码和版本做结合。

快手基于 Kubernetes 与 Istio 的容器云落地实践

快手基于 Kubernetes 与 Istio 的容器云落地实践

这个是我们一个例子,做到一键式发布,开发提交代码,把镜像的名称,Gitlab 都打到了刚才上面提的HELM包里面去,这里面都有关联,包括后面的经费,每一个业务线的经费,包括产品的后关联,我们从上面可以看到,我们想集群。

第二图我们有多少数,版本里面选的那个其实就是我们用CI阶段自动构建起来的HELM包出来的,这个包并不是一个真正的含镜像的包,只是含镜像的Gitlab,还是在镜像中心里面,包括如果说我们做环境变量的替换到原来的配置的话,也是可以做的,这些都达到我们HELM包里面的。

快手基于 Kubernetes 与 Istio 的容器云落地实践

3. 基于Helm应用发布/配置管理

部署需要选一点东西,需要选择测试集群或者是预发布集群,但是升级和回本更简单的,我们选了一次,比如说现在想做升级,那我们直接点击升级,选择对应的包的回本,都是CI/CD打出来的,回滚其实也是类型,因为采用HELM做的,有一个历史的的信息。

这里面有一个小的问题,就是说,我们其实用HELM大家都知道,实际上我们没有HELM的顾虑,前面也提到的IDC环境并不是特别稳定,所以说就是不建议集群跨机房,一个集群只能在一个机房,基于这个结论之后,我们在比如说一个服务可能是说,这个服务高可用,可能只部署了一个机房,需要布多个机房,涉及多个集群,这种情况之后,无论是部署从升级回滚考虑一致性的问题。

快手基于 Kubernetes 与 Istio 的容器云落地实践

第一个机房不成功的,第二个失败的,我们强制多回滚的状态。为什么呢?我们回滚第一个成功,第二个没有成功,第二个是没有rollback信息的,第三个有,然后把我简单说一下,他们做什么管理,做了一个开源HELM,我们用按照序列号激增的一个办法,把K8S部署得一堆文件包起来的,部署在一个地方。

大家可以看到,这个是具体服务,是我们上线的具体服务,发布了很多次,因为是新服务,做得不太好,所以总上线,总改,能有一个历史完整信息,可以看到发布这么多次,如果用HELM来看的话,有什么不一致,都可以看得到的。环境变量不一致,不需要自己关心的我们引用这一套方案之后呢,节约了成本,更简单了,不用考虑太多的事情,只关心自己的代码就可以了,既然服务网格的话,一定涉及到服务通信,所以Service mesh。

快手基于 Kubernetes 与 Istio 的容器云落地实践

4. 使用serviceMesh做服务治理

下面简单介绍了Service Mesh是什么,以及我们为什么会选择Istio。

快手基于 Kubernetes 与 Istio 的容器云落地实践

快手基于 Kubernetes 与 Istio 的容器云落地实践

我们可以了解一下,首先我们现在用的功能最常见的功能是什么?公司用gRPC这个功能,是我们最常用的,后面有一些实践基于gRPC做的。

快手基于 Kubernetes 与 Istio 的容器云落地实践

下面罗列了Istio如何支持多个不同的gRPC服务访问,方式非常多,参见如下:

 

快手基于 Kubernetes 与 Istio 的容器云落地实践

快手基于 Kubernetes 与 Istio 的容器云落地实践

服务容器化或网格化,不是一蹴而就,所以在相当长的一段时间内,需要考虑物理机服务、容器化服务以及网格内部服务相互服务发现场景。下来罗列了个场景下服务发现方式:

快手基于 Kubernetes 与 Istio 的容器云落地实践

下面我们再来看下多集群下Pod如何调度: 多集群Pod调度,同一个集群内部,Kubernetes提供了非常丰富的调度策略,后期可根本需求进行相关调度开发。同一个机房多个集群,采用调度的时候,业务通过指定要调度到的集群地址就可以。跨机房多个集群,分两种情况,同类型的业务,可以采用Kubernetes。

快手基于 Kubernetes 与 Istio 的容器云落地实践

快手基于 Kubernetes 与 Istio 的容器云落地实践

混合环境的流量分发相对来说比较复杂的,同样分为多种情况:Istio环境和非Istio场景。具体分发策略在PPT中有说明。由于时间关系,今天就讲这么多,谢谢大家。

以上为快手资深技术专家张夏在 DevOps 国际峰会 2018 · 深圳站的分享。

一文理解微服务架构下的系统可用性如何保证?

从2005年Peter Rodgers博士提出微web服务,到2014年ThoughtWorks首席科学家Martin Fowler与James Lewis共同提出微服务概念至今已多年,这期间也是互联网及互联网+发展的高速期,消费市场变化莫测,消费者也变得越来越挑剔,很多公司和产品由于无法跟上市场的快速变化而纷纷倒下。越来越多的互联网巨头甚至传统行业都开始对自己的遗留系统进行微服务改造,通过把系统拆分为更加灵活、有业务边界上下文、松散耦合、可独立部署的服务来应对快速变化的消费市场。


微服务架构面临的挑战

通常情况下,对于复杂业务或遗留系统,我们可以通过领域驱动设计(DDD:Domain-Driven Design)有效的解决限界上下文划分、服务边界定义以及组织结构调整等问题。除了这些,我们的开发团队还面临着其他的挑战:复杂的分布式系统、数据一致性、容错设计、限流设计、舱壁设计等问题。那么如此复杂的系统如何来保证系统“质量”呢?

长久以来,“测试金字塔”都是敏捷开发团队保证项目交付质量的守则,而“测试金字塔”也确实从不同的维度涵盖了方法调用、业务逻辑、用户行为等方面。为了确保在进行复杂的调用和被调用时,服务之间能有一定程度上的一致性和快速反馈,我们会第一时间想到“契约测试”,“测试金字塔”也演化成了另一个样子。

一文理解微服务架构下的系统可用性如何保证?

下图,我们聚焦于微服务架构的业务服务层,在API测试之外在基础服务的调用方和提供方之间增加了契约测试:

一文理解微服务架构下的系统可用性如何保证?

在微服务和前后端分离日趋流行的今天,契约测试的确可以在系统频繁演进、重构的情况下保证服务间调用的可用性,而在“聚合服务层”通过API测试,可以暴露服务的组合过程中的问题。“尽早测试”可以让团队在初期发现更多的问题,降低后期修复成本,同时让服务与服务之间具有“感知力”,任何与契约不符的业务变更都能被测试所感知。但是,既然契约测试是保证服务调用方和提供方的一致性,更直接说,是另一种对API的验证,那么契约测试只能覆盖到业务逻辑维度,如果想更好开发或改造微服务系统,就需要相对深入的了解微服务有哪些特性:

一文理解微服务架构下的系统可用性如何保证?

我们可以看到,这个简单的图中提到了一些微服务的特性(基于Spring Boot):客户端负载均衡、微服务容错保护、API服务网关、分布式链路跟踪等,我们不对这些进行解释,但毫无疑问,契约测试无法覆盖和测试到这些特性,同时也无法模拟例如网络延迟、CPU满载、请求异常、依赖故障、硬件故障等场景。对于一个不具备容错能力的脆弱系统,即使我们可以对服务解耦、独立部署,可对于用户来说,体验到的可能是一次又一次的“灾难”。我们在质量活动中,总会听到这样的声音:“不要动这个功能,会弄坏其他功能”、“客户根本不会这么操作“、”这个缺陷没有意义,你这样会把系统弄挂”。我们总是担心系统某些脆弱的环节挂掉,担心某次操作让整个系统宕机。遗憾的是,墨菲定律告诉我们,“如果事情有变坏的可能,不管这种可能性有多小,它总会发生。

之前一个发生在身边的项目经历大概是这样的:一个团队提供基础服务,并承诺服务的功能、性能、弹性都没有问题。在集成联调时,由于对认证服务的调用超过负荷,对整个服务系统造成阻塞,导致雪崩效应,几乎所有客户端应用大面积瘫痪;另一个案例,由于没有对一个内容服务进行熔断保护,导致整个网站首页无法加载。而发生在世界各地的IT灾难也不少,某航空公司,由于调度和跟踪系统出现问题,导致去年6月份七天内将近3000个航班取消,损失3500万美金;“信息灾难总是普遍而没有偏见地发生在各个领域和任何时候”。

既然我们没有办法避免灾难的发生,最好的办法就是“探索系统故障边界,验证系统灾难恢复能力”。以往的“机房”时代的一些故障演练一般通过断网、断电模拟单点故障,来测试系统的恢复能力,而新型的分布式服务时代消除了单点故障,但也引入了更多复杂的问题,我们需要可靠性更强、容错性和扩容性更高的系统。一种解决方案就是,我们需要一种有策略的、有方法的实践方案对系统进行一定程度的“随机破坏”,通过让系统”感染“,来提升系统的”免疫力“。Netflix开发出Chaos Monkey来对系统进行随机试验来了解系统是否具有高可用性和容错性,而由此便诞生出”混沌工程“。


什么是混沌工程?混沌工程原则是什么?

混沌工程是一种可试验的、基于系统的方法来处理大规模分布式系统中的混乱问题。通过不断试验,了解系统的实际能承受的韧性边界并建立信心,通过不同的试验方法和目的,观察分布式系统的行为和反应。一句话——以试验的方法尽早揭露系统弱点

混沌工程类似于“故障演练”,不局限于测试,而更像是工程实践。为什么这么说,通常的测试用例会有“期望结果”和“实际结果”,通过将两个结果比较,或者对用户行为的预期,来判断测试通过或失败。而混沌试验类似于”探索性测试“,试验本身没有明确是输入和预期结果,通过对系统和服务的干预,来观察系统的”反应“。我们将混沌工程原则融入在试验过程中:在生产环境小规模模拟系统故障并定期自动化执行试验,通过试验结果与正常结果进行比对,观察系统”边界“。

一文理解微服务架构下的系统可用性如何保证?

通过“测试金字塔”和混沌试验,从业务逻辑和系统高可用性两个维度对微服务系统进行观察和测试,两种方案结合形成了一种更全面的实践,我称之为“服务级质量内建实践”(BQIS——Build Qualify in Services)。不论企业是在微服务改造期还是中台战略部署期,混沌实践能够有效避免生产环境灾难,提升系统的容错率和可用性。


如何引入混沌工程?

在众多服务化改造案例中,Netflix无疑是最成功的公司之一,该公司的很多试验工具也都集成在Spring Cloud中,成为微服务框架的标准。而Chaos Monkey就是Netflix进行混沌试验一个重要工具。作为国内的电商巨头,服务化和中台战略的先行者阿里,近期也开源了他们自己的混沌试验注入工具ChaosBlade。

“混沌工程”的引入受限于组织文化的接受程度,任何一种工程实践和方法论的落地都无法一蹴而就。但是我们依然可以通过裁剪,在组织的安全范围内进行逐步尝试。不管是在线上环境还是测试环境,我们都需要先搞清楚,目前的混沌工具都为我们提供了哪些方法。

Spring Cloud是时下最流行的分布式微服务架构下的一站式解决方案之一,它方便快速的将Spring Boot的微服务有效的管理起来,并提供了包括负载均衡、全链路监控、服务网关以及众多基于Netflix的开源工具。除此之外,鉴于Netflix在服务化演进中的成功案例,我们来了解下Netflix开源的混沌工程试验框架Chaos Monkey究竟是什么?

在Spring Boot工程中,对需要进行试验的服务application.yml文件的Chaos Monkey进行配置:

一文理解微服务架构下的系统可用性如何保证?

从配置文件中我们可以很容易看到,Chaos Monkey的三种袭击方式——延时、异常和进程终止,同时我们也可以设置一个数值范围,在对服务进行延时攻击时生成随机延时。默认攻击方式为延时攻击,当同时开启异常攻击时,进程攻击则不会发生。Level:N表示第N个请求将被攻击,N=1时,表示每个请求都会被攻击,当同时开启异常攻击时,与N值无关,表示每个请求都将被攻击。

ChaosBlade提供的攻击也很丰富,使用方式对开发人员来说更友好:

一文理解微服务架构下的系统可用性如何保证?

通过命令行对CPU、硬盘、网络进行试验,也可以对相应的服务进行类似的例如延时攻击试验:

一文理解微服务架构下的系统可用性如何保证?

利用性能测试工具例如Jmeter或Gatling,对于API进行测试,例如POST /product?des=phone,通过性能测试报告对比API性能指标以及Hystrix监控分析相关服务“反应”,同时也可以通过Grafana和CloudWatch监控各个系统参数来和“稳定基线数据”进行比较和观察。


混沌试验示例

试验一:

1.确定目标和范围,观察CPU利用率基线(CPU平均利用率低于20%)

一文理解微服务架构下的系统可用性如何保证?

观察API延时基线:(可以看到API-1和API-2平均延时低于300ms,API-3在300ms-700ms之间)

一文理解微服务架构下的系统可用性如何保证?

2.设计实验数据和方案,我们对几个实例进行CPU满载攻击:

一文理解微服务架构下的系统可用性如何保证?

观察CPU利用率:

一文理解微服务架构下的系统可用性如何保证?

观察API延时:(API请求延时变化明显,API-3延时更加严重)

一文理解微服务架构下的系统可用性如何保证?

试验二:测试服务熔断机制

示例项目架构:Eureka服务发现注册,一个PROVIDER-SERVICE且有两个实例,一个CONSUMER-SERVICE,也可以通过zipkin之类的分布式跟踪系统也可以看到服务调用关系。

一文理解微服务架构下的系统可用性如何保证?

1.对PROVIDER-SERVICE的实例2进行延时攻击

一文理解微服务架构下的系统可用性如何保证?

2.查看PROVIDER-SERVICE instance 2的延时是否生效通过postman测试,可以看到API响应为30s:

一文理解微服务架构下的系统可用性如何保证?

3.客户端请求相同的API

一文理解微服务架构下的系统可用性如何保证?

观察结果:负载均衡生效,请求成功,熔断器关闭

4.杀掉instance 1之后请求相同API

一文理解微服务架构下的系统可用性如何保证?

观察结果:请求超时,熔断器关闭

5.连续且请求相同API

一文理解微服务架构下的系统可用性如何保证?

观察结果:部分请求被立刻拒绝,加速服务失败的判定,熔断器开启

6.杀掉instance 2请求API:

一文理解微服务架构下的系统可用性如何保证?

观察结果:fallback机制生效,返回相应逻辑。

这些只是混沌工程的简单使用方法,在实际项目中需要根据项目架构、业务复杂度、调用场景等设计试验细节。


总结

从业务的横切面到对微服务系统的纵切面观察,“契约测试”固然重要,但并不能代表微服务质量保证的全部。“蛮力”可以从某种意义上解决很多问题,但并不能催化出更高阶解决方案。同样,也只有了解到微服务的实现方式和原理才能够更好的理解系统并实施更有效的质量解决方案。

Netflix对混沌工程的成熟度从“复杂度”和“接受度”两个方面给出了定义,可以看到,混沌工程或试验不单单是方法论的引入,更是实践上的渗透。用“Immersion”解释更加确切,与敏捷实践类似,这样的“服务级质量内建试验”对团队来说开始无疑是一种挑战,但随着越来越多的问题更快、更早的可视化给团队,使组织和客户对我们构建和改造中的系统越来越有信心。通过小规模实践到大规模改造,混沌工程不是为了测试,更不是为了引入工具, 混沌工程会像一种文化,将扩散于范围更广的团队和组织。

突发热点事件下微博高可用注册中心vintage的设计&实践

当前微博服务化采用公有云+私有云的混合云部署方式,承载了每天百亿级的流量,vintage 作为微博微服务的注册中心,为管理 10w 级微服务节点以及在流量激增情况下的服务快速扩缩容,面临了极大挑战。例如:复杂网络条件下 vintage 服务的高可用保障、解决大批量节点状态变更触发的通知风暴、异常情况下 vintage 节点的自适应和自恢复能力、vintage 多云部署场景下节点数据同步和一致性保障等。

vintage 3.0 优雅的解决了上述问题,具备了良好的扩展性,可用性达到 6 个 9。在 12 月 8 日北京 ArchSummit 全球架构师峰会上,来自微博研发中心的边剑,以挑战和问题切入与大家分享了 vintage 设计原则与方案,以及线上应用的最佳实践。

 一、突发热点对微博注册中心的要求与挑战 
1.1 突发热点事件
突发热点事件下微博高可用注册中心vintage的设计&实践

突发热点是微博面临的最常见、最重要的挑战,左图显示的就是最近的一个热点事件,右图是热点发生时的流量图,我们可以看出热点事件发生时,在短时间内会引发微博流量的数倍增长。那么面对如海啸般的突发流量,如何应对就显得尤为重要。

根据峰值流量模型不同,应对策略多种多样,但通常总结为下面三种措施:

  1. 常备充足的 buffer:该方式更佳适用于可提前预测的流量高峰。如:双十一购物节,微博的春晚保障等。而极热事件往往都具备突发性,很难提前预料,并做好充足的准备。同时极热事件发生时的流量往往高于日常峰值的数倍,且不同极热事件之间流量峰值和流量模型不尽相同,因此也很难准确评估 buffer 的数量。

  2. 采用服务降级的方式,保障核心业务:通过对系统接口逻辑中,非核心业务的逐步剪枝,来缓解核心接口整体压力,保障业务接口的功能运行及性能稳定,这类方式在很多年前是比较流行的。但此类方式通常会伴随系统功能受限,页面内容不完整等是用户体验下降的情况发生。此外在极端峰值场景下,还可能会出现降无可降的情况。因此这种方法也不是目前最好的选择。

  3. 弹性扩容: 为了克服前两种措施带来的不足,经过多年应对突发流量的经验积累,微博目前使用混合云的部署方式。通过对后端微服务资源的快速扩容,在短时间内迅速为整个系统构建起坚实的大坝。

1.2 突发热点事件对微博注册中心的要求与挑战

突发热点事件下微博高可用注册中心vintage的设计&实践

众所周知,微服务的运行必须依赖注册中心的支持。这就对微博注册中心提出了 4 点要求:

  1. 要支持每秒百级别以上的微服务扩容

  2. 秒级别的微服务变更通知延迟

  3. 能承载 10w+ 级别微服务节点

  4. 公司级多语言微服务注册 & 服务发现支持

然而即使我们满足了以上四个要求,但时常还会遇到网络抖动,流量极端峰值时专线带宽拥塞等情况。那么如何在网络状态不好的条件下,保障微博注册中心的可用性,实现微服务正常扩容就成为了微博注册中心在设计时必须要面临的挑战。

 二、vintage 高可用设计 & 实践 
2.1 设计目标
突发热点事件下微博高可用注册中心vintage的设计&实践

针对上面提到的要求和挑战,设计微博注册中心时在功能上要实现公司级别的注册中心及配置管理平台,支持多 IDC,支持多语言,具备 10w+ 级微服务承载能力,可用性上需满足 6 个 9。在服务性能方面,要具备每秒对百级别以上微服务的扩容能力。同时要求通知的平均延迟低于 500ms。而在伸缩性上,微博注册中心自身必须具备十秒级节点扩容,以及分钟级 IDC 注册中心搭建。

2.2 选型对比
突发热点事件下微博高可用注册中心vintage的设计&实践

根据设计目标,对业界比较流行的注册中心进行了调研。ZooKeeper,Etcd 无法较好满足微博对多机房支持要求。Consul 虽然满足了多机房的支持,但在网络 QoS 发生时由于其本身的 CP 模型设计,在服务可用性方面不能提供很好的保障。而 Eureka 虽然在多机房和可用性方面都满足微博的要求,但 Eureka 目前仅支持以长轮询的方式订阅服务,在通知延迟方面不能很好的支持,同时 在社区支持方面 Eureka 已暂停了新版本的研发。

综合考虑上述指标 ZooKeeper,Etcd,Consul,Eureka 均都无法很好的满足微博对注册中心提出的要求。因此我们决定在现有的 vintage 系统基础上进行升级改造,来满足具备公司级注册中心的能力。

2.3 vintage 架构设计
突发热点事件下微博高可用注册中心vintage的设计&实践
vintage 部署拓扑

先从系统拓扑部署了解下新版 vintage 系统的整体设计。vintage 服务采用多 IDC 的部署方式,通过 Gossipe 协议,实现集群节点间的自动发现。IDC 间主主通信实现多机房间数据同步。IDC 内采用一主多从的部署方式,基于 Raft 实现了可支持分区多主的选举协议,主从节点角色之间实现读写分离。

vintage 系统采用典型三层结构设计,将网络层,业务层,存储层抽象分离。

  1. 最上层是 Restful 风格实现的 API 接口,提供微服务注册,订阅及健康检测等功能。同时通过 307,308 模块完成对上下行流量的主,从节点分流。

  2. 中间的业务层包括命名和配置两大核心业务的功能实现。在内部功能方面,Node discovery 模块负责集群的全局节点自动发现,timer 和 service state Machine(微服务状态机)共同配合完成对微服务状态的高效管理。同时在 vintage 中的任何注册,注销及微服务元数据更新,节点状态变更等行为都将被抽象为一个独立事件并交由 event handler 进行统一处理。Transfer 和 repair 模块用于整个集群的节点间数据同步及一致性修复功能。而为应对网络问题对注册中心及整个微服务体系可用性产生的影响,vintage 内部通过 partion hanlder 实现网络分区状态的感知并结合多样化的系统保障策略予以应对。

  3. 最下面的存储层:采用基于树形结构的多版本数据存储架构,实现了对众多微服务及其所属关系的有效管理,支持微服务的灰度发布。event notify 模块将数据变更事件实时通知到业务层,配合 watch hub 完成对消息订阅者的实时推送。在数据可靠性方面,采用 aof+snapshot 方式实现本地的数据持久化。

2.4 高可用实践

突发热点事件下微博高可用注册中心vintage的设计&实践

了解整个系统后,将通过网络分区,通知风暴,数据一致性,高可用部署四个方面介绍 vintage 在高可用方面的实践,以及 AP 模型注册中心在数据一致性保障方面的措施。

网络分区

场景一:vintage 内部网络分区

突发热点事件下微博高可用注册中心vintage的设计&实践如上图,网络分区将 vintage 集群一分为二,同时每个分区中的节点数量均少于 n/2+1 个(n 为该 IDC 子集群 vintage 服务的节点总数)。在该场景下基于传统 Raft 协议的注册中心,由于对数据强一致性的要求,将无法提供正常的读写服务。而 Etcd 和 Consul 在解决这个问题时,也仅是通过 stale read 的方式满足了对数据读取的需要,但仍然无法实现在该场景下对微服务注册的需要。

微博注册中心为实现在网络分区下注册中心服务高可用,对原有 Raft 协议选举机制进行改造,实现了可对任意分区情况下的多主选举支持:

  • 解决了分区发生后处于少数分区内 rpc-server 与 rpc-client 的注册及服务发现能力。实现任何粒度分区下 vintage 服务的高可用。

  • 对故障节点的承受能力大幅提升,由原 Raft 协议的 n/2-1 个节点,提升至最大 n-1 节点。

同时在该场景下网络分区后,微服务会将自己的心跳汇报到所属分区的主节点。而由于分区存在,分区间节点数据无法通信。对于(vintage)IDC 集群中的其他主节点来说就出现了微服务心跳丢失的现象,而心跳是作为服务健康状态的重要识别手段。心跳超时的微服务节点将会被标识为不可用状态,从而不会被调用方访问。网络分区时往往会出现大批量的微服务心跳超时,未能妥善处理将直接影响业务系统服务的稳定性及 SLA 指标。而此时 partion handler 模块将会优先于心跳超时感知到网络分区的发生,启动 freeze 保护机制(freeze:注册中心内部对微服务状态的保护机制,基于乐观策略冻结当前 vintage 系统中的微服务节点状态,禁止节点不可用状态的变更,同时对于接受到心跳的微服务节点,支持其恢复可用状态)。

分区恢复后,各节点间通信恢复正常。vintage 会进行再次选举,从分区时的多个主节点中选举出新的主节点。vintage 中的数据变更都会抽象为一个独立事件,系统基于向量时钟实现了对全局事件的顺序控制。在多主合并过程中通过 repair 模块完成节点间数据的一致性修复,保障 IDC 内集群最终一致性。

场景二:client 与 vintage 间网络分区

突发热点事件下微博高可用注册中心vintage的设计&实践

图中绿色和黄色区域分别代表两个不同 IDC 中的 vintage 子集群。红色和紫色代表 IDC1 内的 rpc-server 和 rpc-clinet。此时红色的 rpc-server 与 IDC1 中的 vintage 主节点出现了网络分区,而紫色的 rpc-client 与 IDC1 的 vintage 集群整体存在网络隔离现象。rpc-server 和 rpc-client 之间网络通信正常。虽然这是一个相对复杂且极端的分区场景,但对于微博注册中心的高可用设计要求是必须要解决的问题。下面我们来看看 vintage 是如何解决的。

在该场景下 rpc-server 可通过 DNS 发现的方式获取该 IDC 内 vintage 子集群的全部机器列表,并使用 proxy 模式将注册请求发送至子集群中任意可达的从节点。并通过从节点代理,将注册请求转发至主节点完成服务注册。rpc-server 会定期检测自己与主节点的连接状态,一旦发现连接恢复,将自动关闭 proxy 模式,并通过向主节点发送心跳维护自身状态

rpc-client 可通过跨 IDC 服务发现方式,在 IDC2 中订阅所关注的 rpc-server 服务信息,其自身还会定期将获取到的 rpc-serve 服务列表保存至本地 snapshot 文件。防止即使 rpc-client 与全部的 vintage 服务均出现网络隔离,也可保证自身正常启动及调用。从而实现了 client 与 vintage 服务分区时,微服务注册与服务发现的高可用。

vintage 服务同样支持跨 IDC 的微服务注册。但从实际访问及运维模型考虑,要求每个 rpcserver 节点在同一时刻仅可属于一个 IDC 集群

通知风暴

突发热点事件下微博高可用注册中心vintage的设计&实践

在微博场景下,根据调用方对每种微服务的订阅数量的不同,通常会出现百级至千级别的通知放大。同时老版本 vintage 的服务订阅是基于 sign(md5 后服务列表指纹)对比 + 全量数据拉取的方式,根据微服务自身规模的大小,会出现十级至百级别的消息体放大(二次放大现象)。当出现大量(百级别)微服务同时扩缩容,网络 QoS 或大面积微服务机器故障。经过两次放大后,通知规模可达到百万甚至千万级别,严重占用机器带宽资源。带宽不足导致大量心跳阶段性丢失,微服务的状态极易在‘不可用’和‘正常’之间频繁转换,由此带来更大量的更变事件,出现风暴叠加,导致网络拥塞。更将引发下面两个问题:1. 注册中心业务接口调用失败;2. 大面积微服务心跳汇报失败,被错误标示为不可用状态,影响线上业务微服务的正常调用。

针对通知风暴严重危害,改造后的 vintage 系统使用了“梳”和“保”两种策略应对

突发热点事件下微博高可用注册中心vintage的设计&实践

“疏”主要采用的三种策略

  1. 分流及负载均衡:首先互联网公司通常根据用户的地域和运营商等信息,将请求分流至不同 IDC,并在核心 IDC 内部部署全量的核心服务,保障 IDC 内完成对用户请求的响应。vintage 根据微博流量分配及业务服务多机房部署的特点,在公司几大核心机房均有部署。实现了多机房微服务上行请求的流量分离。同时根据注册中心写少读多的特点在 IDC 内采用一主多从的部署结构,除保障了服务的 HA 外,也对下行流量实现了负载均衡。

  2. 快速扩容:对于通知风暴造成的下行流量,可通过对从节点快速扩容的方式提供充足的服务吞吐及带宽。

  3. 增量事件,避免消息体二次放大:vintage 内部将全部的数据变更,统一抽象为独立事件并在集群数据同步,一致性修复和服务发现 & 订阅功能上均采用增量通知方式,避免了消息体放大问题。将整体消息量有效压缩 2-3 个数量级,降低带宽的使用。而发生事件溢出时,系统也会先对全量数据压缩后再进行传递。

“保”是通过系统多重防护策略,保障通知风暴下 vintage 及微服务的状态稳定。其主要采用的三种策略:

  1. 首先使用 partion handler 实现对 vintage 集群内分区和微服务与集群节点分区网络问题的综合监控。结合 freeze 机制有效避免了微服务节点状态的频繁变更从而同样避免了在网络 QoS 引发的通知风暴。

  2. 其次对于业务系统来说,系统整体服务状态相对稳定。基于这点 vintage 为服务增加了保护阈值的设计(默认 60%)。通过设置保护阈值,可有效控制通知风暴发生时 vintage 内部异常服务数据变更的规模。更保障业务系统的整体稳定,避免发生服务雪崩。

  3. 最后对于带宽枯竭问题,vintage 增加了对带宽使用率的检测和限制。当超过机器带宽消耗>70%,会触发系统 304 降级,暂停全部通知推送,避免 vintage 节点带宽枯竭保障集群角色心跳维护,节点发现,数据同步等内部功能对带宽的基本要求,维护整个集群的稳定运行。

数据一致性

突发热点事件下微博高可用注册中心vintage的设计&实践

不难看出 vintage 是基于 AP 模型设计的注册中心。该模型下注册中心内节点间数据的一致性问题,成为了 vintage 必须要处理的核心问题。vintage 选择了最终一致性作为集群的数据一致性模型

接下来介绍 vintage 的最终一致性实现机制。

首先来回顾下 vintage 集群的部署拓扑。vintage 采用多 IDC 部署,各 IDC 间分治独立,IDC 间通过主主节点完成数据同步,IDC 内主从角色读写分离。在该拓扑下,vintage 通过集群内主从,集群间主主的数据同步 + 差异对比修复相结合的方式,实现节点间数据的最终一致性。(1) 数据同步:各 IDC 内均由主节点负责上行请求处理,从节点通过拉取主节点新增的变更事件完成数据同步(2) 同时 vintage 内部对全部存储的数据构建了对应的 merkle Tree,并随数据变化进行实时更新

突发热点事件下微博高可用注册中心vintage的设计&实践

vintage 会定期触发集群一致性检测逻辑。以主节点中数据为标准,IDC 内采用主从,IDC 间则是主主方式使用 merkle tree 特点由根节点逐层对比,精确定位数据差异节点,并完成一致性修复。

:通过线上运行统计,通常仅有<0.5% 的检测结果出现数据不一致现象。利用 merkle Tree 的特点有效优化了数据对比及修复过程中对网络带宽不必要的消耗:

  1. 大于 99.5% 一致性检测仅需对根节点数据对比即可确认数据一致性。

  2. 支持逐层节点对比,可准确定位不一致节点,并针对该节点进行数据修复。

高可用部署

服务的高可用通常与部署方式必密不可分,vintage 作为公司级别的注册中心本身必须具备极高的可用性和故障恢复能力。

vintage 在部署上主要考虑以下 4 点:

  1. 适度冗余度部署:冗余度方面,vintage 服务采用 2 倍冗余部署,冗余的节点在提升注册中心自身可用性的同时,也为业务系统在突发峰值流量时的快速扩容反应提供了良好的弹性空间。

  2. 与网络消耗型业务隔离:作为带宽敏感型服务,vintage 在机器部署时会与网络消耗类业务尽量隔离

  3. 多机架部署,同机架内多服务部署:考虑到网络分区和设备故障的因素,vintage 采用多机架部署方式,同机架内通常部署至少两个服务节点。

  4. 多 IDC 间数据互备:vintage 天然实现了 IDC 间的数据互备,同时通过冗余一套 IDC 子集群用于全集群的数据灾备。极端情况时可在分钟级实现 IDC 维度的任意子集群搭建,并通过系统内部接口优先从数据灾备的子集群中完成数据恢复。

2.5 高性能

在介绍 vintage 高可用后,再来看看 vintage 系统在高性能方面如何实现 10w+ 节点的支撑及平均百毫秒级别的通知延迟。

  1. List+Map 数据结构实现了支持 10w 节点高性能定时器。

  2. watch 变更实时推送机制。

    突发热点事件下微博高可用注册中心vintage的设计&实践

了解下微服务在 vintage 系统中的生命周期,这是一张 vintage 内部维护微服务生命周期的状态机。其中 initial,working,unreachable 为 vintage 内部对管理微服务的三个状态,分别用于说明节点处于初始注册状态,可用状态,以及不可用状态。

微服务节点通过调用 register 接口完成注册,vintage 会将节点状态设置为 initial。注册成功后微服务节点会周期性(例:5s)向 vintage 发送心跳,汇报自身健康状态,并不断更新自身心跳过期时间。vintage 在收到第一个心跳请求后,会将节点状态变为 working。vintage 会定期对全部 working 状态微服务节点进行健康检测,当发现节点心跳超时,该节点状态将被变更至 unreachable,并通过 watch 接口将变更时间推送给订阅方。而 unreachable 状态可通过再次发送心跳,转变为 working 状态。在 working To unreachable 的变更过程中如果触发了 vintage 节点状态保护机制,或出现网络分区,状态变更会被冻结。微服务节点下线时可通过调用 unregister 接口实现注销,完成整个生命周期。

vintage 每个 IDC 中仅由主节点负责全部的上行请求并通过心跳方式维护该 IDC 内全部微服务的健康检测及状态变更。而心跳作为周期性的高频请求,当单 IDC 内节点数达到一定量级时,对主节点的处理能力带来极大的挑战。并直接影响到单 IDC 集群的整体吞吐和服务节点承载能力。这就需要一个高效定时器来完成上述的挑战。

突发热点事件下微博高可用注册中心vintage的设计&实践

从图中可以看出,在设计上要求单 IDC 集群同样可承担 10w 级的服务节点,支持频繁的 expire 更新,同时要求对节点状态变更精度达到 10ms 级别。

首先对现有的 heartbeat 汇报及超时检测模型分析后,得出以下几个特点:

heartbeat 汇报请求时间,为节点 expire 的续租起始时间。

由于 heartbeat 续租时间固定,节点过期时间可根据续租时间固定排序。

健康检测与心跳独立处理。采用周期性触发机制实现对已排序数据 expire 的超时判断。

通过对特点的分析,并参考了目前比较流行的一些定时器处理算法。不难看出链表,最小堆以及时间轮这些主流定时器的时间复杂度上均会受到节点数的直接影响。那是否有一种数据结构可以做到 update 和 trigger 都是 o(1) 的时间复杂度呢?

突发热点事件下微博高可用注册中心vintage的设计&实践

定时器(timer)由 List+Map 共同组成用于维护 vintage 内部所有 working 状态的微服务节点的健康监测和心跳续租。

List 是一个根据 expire 升序排列的有序双向链表,链表中的元素为微服务节点的状态对象,包括节点 ID 和超时时间。Map 保存了微服务的 ID 及状态对象的引用。当 vintage 收到某个节点的心跳请求,会根据节点 ID 从 Map 中获取该微服务节点状态对象,由于心跳续租时间固定,完成 expire 字段更新后无需排序,可直接将节点对象插入在链表的尾部。

timer 会定期触发微服务的超时检测,根据链表 expire 升序的特点,每次检测的顺序都是由首都到尾部,发现首节点 expire 小于当前时间,触发过期操作,将节点从列表中删除,并调用 callback 函数,通知存储模块将节点状态更新为 unreachabl。依次向后检查链表中的各节点,直到出现第一个未过期的节点为止。

突发热点事件下微博高可用注册中心vintage的设计&实践

当服务状态变更,节点的注册与注销时都会将数据记录到存储中。vintage 实现了一套基于树形结构的多版本数据存储。依赖于树形存储结构,vintage 在实现对数据存储能力外,还可将微服务依照 /IDC/ 部门 / 业务线 / 服务 / 集群 / 服务节点 的方式分层管理。同时借助于多版本实现了从数据到事件的转换,并通过 Event notify 模块将变更事件实时回调通知 watch hub,完成对新增事件的订阅推送。

watch 推送机制

突发热点事件下微博高可用注册中心vintage的设计&实践

client 注册 watchhub

client 向 vintage 订阅服务变化过程。会首先向 vintage 的 watch hub 进行注册,watch hub 会为每个订阅方生成一一对应的 watcher 对象,并根据订阅目标的 path(ID),将相同路径的 watcher 合并至一个 watchers 列表中,保存在 watch hub 的 map 索引结构。

突发热点事件下微博高可用注册中心vintage的设计&实践

存储层数据变更通知

当 storage 完成数据存储后,会将数据变更的信息转换为新增事件通过 Event notify 模块回调通知 watch hub。watch hub 通过该 event 中的 path 在 map 索引找到订阅的 watchers 对象,并将事件写入全部 watcher 的 event queue 中。由 watch 函数完成事实数据推送。

:若 path 是多层结构时,watch hub 通过逆向递归的方式,将该事件依次插入多个 event 队列,实现 watch hub 的 path 递归功能。

系统伸缩性

为保障微服务的快速扩容,vintage 服务自身必须具备快速的扩容能力 vintage 会通过 Docker 化部署及 node discovery 节点自动发现能力,实现秒级别的扩容。并通过整合 Jpool+Dcp 体系,目前可实现分钟级 IDC 注册中心搭建。

多语言支持

在多语言支持方面,vintage 系统通过 HTTP Restful API 满足了公司级跨语言注册中心的服务支持。同时也在不断扩展多语言官方 SDK,目前已实现了 Java,Go 的支持。

 三、总结 

新版 vintage 系统已在线上运行了一年多,经历了微博春晚保障,多个核心机房网络升级和数不胜数的突发热点事件应对。

vintage 系统使用多 IDC 部署方式支持公司混合云战略,承担十万级别微服务注册与服务发现。同时系统可用性达到 99.9999%,通知变更平均延迟< 200ms,p999 延迟低于 800ms。vintage 注册中心在可用性和性能上,也满足了业务在突发热点时的应对保障,将常备业务机器的 buffer 由 40% 降低至 25%。

嘉宾介绍
突发热点事件下微博高可用注册中心vintage的设计&实践
边剑:新浪微博技术专家,专注于高可用架构,有多年高并发系统架构设计和研发经验,现就职于微博研发中心 – 平台架构部,主要从事微博平台公共服务及中间件系统的设计及优化,作为核心技术成员参与微博服务化建设。