当前位置: 首页 > news >正文

模块三 进阶微服务

25 | 微服务为什么要容器化?

上面讲解了微服务架构的基础组成以及在具体落地实践过程中的会遇到的问题和解决方案,这些是掌握微服务架构最基础的知识。从今天开始,我们将进一步深入微服务架构进阶的内容,也就是微服务与容器、DevOps之间的关系。它们三个虽然分属于不同领域,但却有着千丝万缕的关系,可以说没有容器的普及,就没有微服务架构的蓬勃发展,也就没有DevOps今天的盛行其道。

微服务带来的问题

单体应用拆分成多个微服务后,能够实现快速开发迭代,但随之带来的问题是测试和运维部署的成本的提升。相信拆分微服务的利弊你早已耳熟能详,我讲个具体的例子。微博业务早期就是一个大的单体Web应用,在测试和运维的时候,只需要把Web应用打成一个大的WAR包,部署到Tomcat中去就行了。后来拆分成多个微服务之后,有的业务需求需要同时修改多个微服务的代码,这时候就有多个微服务都需要打包、测试和上线发布,一个业务需求就需要同时测试多个微服务接口的功能,上线发布多个系统,给测试和运维的工作量增加了很多。这个时候就需要有办法能够减轻测试和运维的负担,我在上一讲给出的解决方案是DevOps。

DevOps可以简单理解为开发和运维的结合,服务的开发者不再只负责服务的代码开发,还要负责服务的测试、上线发布甚至故障处理等全生命周期过程,这样的话就把测试和运维从微服务拆分后所带来的复杂工作中解放出来。DevOps要求开发、测试和发布的流程必须自动化,这就需要保证开发人员将自己本地部署测试通过的代码和运行环境,能够复制到测试环境中去,测试通过后再复制到线上环境进行发布。虽然这个过程看上去好像复制代码一样简单,但在现实时,本地环境、测试环境以及线上环境往往是隔离的,软件配置环境的差异也很大,这也导致了开发、测试和发布流程的割裂。

而且还有一个问题是,拆分后的微服务相比原来大的单体应用更加灵活,经常要根据实际的访问量情况做在线扩缩容,而且通常会采用在公有云上创建的ECS来扩缩容。这又给微服务的运维带来另外一个挑战,因为公有云上创建的ECS通常只包含了基本的操作系统环境,微服务运行依赖的软件配置等需要运维再单独进行初始化工作,因为不同的微服务的软件配置依赖不同,比如Java服务依赖了JDK,就需要在ECS上安装JDK,而且可能不同的微服务依赖的JDK版本也不相同,一般情况下新的业务可能依赖的版本比较新比如JDK 8,而有些旧的业务可能依赖的版本还是JDK 6,为此服务部署的初始化工作十分繁琐。

而容器技术的诞生恰恰解决了上面这两个问题,为什么容器技术可以解决本地、测试、线上环境的隔离,解决部署服务初始化繁琐的问题呢?下面我就以业界公认的容器标准Docker为例,来看看Docker是如何解决这两个问题的。

什么是Docker

Docker是容器技术的一种,事实上已经成为业界公认的容器标准,要理解Docker的工作原理首先得知道什么是容器。

容器翻译自英文的Container一词,而Container又可以翻译成集装箱。我们都知道,集装箱的作用就是,在港口把货物用集装箱封装起来,然后经过货轮从海上运输到另一个港口,再在港口卸载后通过大货车运送到目的地。这样的话,货物在世界的任何地方流转时,都是在集装箱里封装好的,不需要根据是在货轮上还是大货车上而对货物进行重新装配。同样,在软件的世界里,容器也起到了相同的作用,只不过它封装的是软件的运行环境。容器的本质就是Linux操作系统里的进程,但与操作系统中运行的一般进程不同的是,容器通过Namespace和Cgroups这两种机制,可以拥有自己的root文件系统、自己的网络配置、自己的进程空间,甚至是自己的用户ID空间,这样的话容器里的进程就像是运行在宿主机上的另外一个单独的操作系统内,从而实现与宿主机操作系统里运行的其他进程隔离。

Docker也是基于Linux内核的Cgroups、Namespace机制来实现进程的封装和隔离的,那么Docker为何能把容器技术推向一个新的高度呢?这就要从Docker在容器技术上的一项创新Docker镜像说起。虽然容器解决了应用程序运行时隔离的问题,但是要想实现应用能够从一台机器迁移到另外一台机器上还能正常运行,就必须保证另外一台机器上的操作系统是一致的,而且应用程序依赖的各种环境也必须是一致的。Docker镜像恰恰就解决了这个痛点,具体来讲,就是Docker镜像不光可以打包应用程序本身,而且还可以打包应用程序的所有依赖,甚至可以包含整个操作系统。这样的话,你在你自己本机上运行通过的应用程序,就可以使用Docker镜像把应用程序文件、所有依赖的软件以及操作系统本身都打包成一个镜像,可以在任何一个安装了Docker软件的地方运行。

Docker镜像解决了DevOps中微服务运行的环境难以在本地环境、测试环境以及线上环境保持一致的难题。如此一来,开发就可以把在本地环境中运行测试通过的代码,以及依赖的软件和操作系统本身打包成一个镜像,然后自动部署在测试环境中进行测试,测试通过后再自动发布到线上环境上去,整个开发、测试和发布的流程就打通了。

同时,无论是使用内部物理机还是公有云的机器部署服务,都可以利用Docker镜像把微服务运行环境封装起来,从而屏蔽机器内部物理机和公有云机器运行环境的差异,实现同等对待,降低了运维的复杂度。

微服务容器化实践

Docker能帮助解决服务运行环境可迁移问题的关键,就在于Docker镜像的使用上,实际在使用Docker镜像的时候往往并不是把业务代码、依赖的软件环境以及操作系统本身直接都打包成一个镜像,而是利用Docker镜像的分层机制,在每一层通过编写Dockerfile文件来逐层打包镜像。这是因为虽然不同的微服务依赖的软件环境不同,但是还是存在大大小小的相同之处,因此在打包Docker镜像的时候,可以分层设计、逐层复用,这样的话可以减少每一层镜像文件的大小。

下面我就以微博的业务Docker镜像为例,来实际讲解下生产环境中如何使用Docker镜像。正如下面这张图所描述的那样,微博的Docker镜像大致分为四层。

  • 基础环境层。这一层定义操作系统运行的版本、时区、语言、yum源、TERM等。
  • 运行时环境层。这一层定义了业务代码的运行时环境,比如Java代码的运行时环境JDK的版本。
  • Web容器层。这一层定义了业务代码运行的容器的配置,比如Tomcat容器的JVM参数。
  • 业务代码层。这一层定义了实际的业务代码的版本,比如是V4业务还是blossom业务。

在这里插入图片描述
这样的话,每一层的镜像都是在上一层镜像的基础上添加新的内容组成的,以微博V4镜像为例,V4业务的Dockerfile文件内容如下:

FROM registry.intra.weibo.com/weibo_rd_content/tomcat_feed:jdk8.0.40_tomcat7.0.81_g1_dns
ADD confs /data1/confs/
ADD node_pool /data1/node_pool/
ADD authconfs /data1/authconfs/
ADD authkey.properties /data1/
ADD watchman.properties /data1/
ADD 200.sh /data1/weibo/bin/200.sh
ADD 503.sh /data1/weibo/bin/503.sh
ADD catalina.sh /data1/weibo/bin/catalina.sh
ADD server.xml /data1/weibo/conf/server.xml
ADD logging.properties /data1/weibo/conf/logging.properties
ADD ROOT /data1/weibo/webapps/ROOT/
RUN chmod +x /data1/weibo/bin/200.sh /data1/weibo/bin/503.sh /data1/weibo/bin/catalina.sh
WORKDIR /data1/weibo/bin

FROM代表了上一层镜像文件是“tomcat_feed:jdk8.0.40_tomcat7.0.81_g1_dns”,从名字可以看出上一层镜像里包含了Java运行时环境JDK和Web容器Tomcat,以及Tomcat的版本和JVM参数等;ADD就是要在这层镜像里添加的文件, 这里主要包含了业务的代码和配置等;RUN代表这一层镜像启动时需要执行的命令;WORKDIR代表了这一层镜像启动后的工作目录。这样的话就可以通过Dockerfile文件在上一层镜像的基础上完成这一层镜像的制作。

总结

今天我给你讲解了微服务拆分后相比于传统的单体应用所带来的两个问题,一个是测试和发布工作量的提升,另一个是在弹性扩缩容时不同微服务所要求的软件运行环境差异带来的机器初始化复杂度的提升,而Docker利用Docker镜像对软件运行环境的完美封装正好解决了这两个问题。

正是因为Docker可以做到一处通过、到处运行,所以对业务的价值极大,解决了以前应用程序在开发环境、测试环境以及生产环境之间的移植难的问题,极大提高了运维自动化的水平,也为DevOps理念的流行和业务上云提供了基础。

可见容器化改造对微服务是十分必要的,但Docker也不是“银弹”,同样会产生新的复杂度问题,比如引入Docker后旧的针对物理机的运维模式就无法适应了,需要一种新的针对容器的运维模式。接下来,将详细讲解微服务容器化后该如何运维。

26 | 微服务容器化运维:镜像仓库和资源调度

容器化技术解决了单体应用拆分为微服务后,所带来的服务测试和发布运维复杂度提升的问题,可以说容器化技术天生就是为微服务而生。但微服务容器化后又带来了一个新的挑战,那就是容器如何运维的问题。

为什么微服务容器化的运维又成了新问题?

对于大部分业务团队来说,在进行容器化以前,服务都是部署在物理机或者虚拟机上,运维往往有一套既有的运维平台来发布服务。我就以微博的运维平台JPool来举例,当有服务要发布的时候,JPool会根据服务所属的集群(一般一个业务线是一个集群)运行在哪个服务池(一般一个业务线有多个服务池),找到对应的物理机或者虚拟机IP,然后把最新的应用程序代码通过Puppet等工具分批逐次地发布到这些物理机或者虚拟机上,然后重新启动服务,这样就完成一个服务的发布流程。

但是现在情况变了,业务容器化后,运维面对的不再是一台台实实在在的物理机或者虚拟机了,而是一个个Docker容器,它们可能都没有固定的IP,这个时候要想服务发布该怎么做呢?

这时候就需要一个面向容器的新型运维平台,它能够在现有的物理机或者虚拟机上创建容器,并且能够像运维物理机或者虚拟机一样,对容器的生命周期进行管理,通常我们叫它“容器运维平台”。

根据我的经验,一个容器运维平台通常包含以下几个组成部分:镜像仓库、资源调度、容器调度和服务编排。所以,关于微服务容器化运维的内容,我会分为3期,今天先来看容器运维平台的镜像仓库和资源调度,后面两期会介绍容器调度、服务编排和微博容器运维平台的建设。

镜像仓库

Docker容器运行依托的是Docker镜像,也就是说要发布服务,首先必须把镜像发布到各个机器上去,这个时候问题就来了,这个镜像该放在哪?如何把镜像发布到各个机器上去?这时候你就要依靠镜像仓库了。

镜像仓库的概念其实跟Git代码仓库类似,就是有一个集中存储的地方,把镜像存储在这里,在服务发布的时候,各个服务器都访问这个集中存储来拉取镜像,然后启动容器。

Docker官方提供了一个镜像仓库地址:https://hub.docker.com/,对于测试应用或者小规模的业务可以直接使用。但对于大部分业务团队来说,出于安全和访问速度的需要,都会搭建一套私有的镜像仓库。那么具体该如何搭建一套私有的镜像仓库呢?下面我就结合微博的实践,和你聊聊这里面的门道。

1.权限控制
镜像仓库首先面临的第一个问题就是权限控制的问题,也就是说哪些用户可以拉取镜像,哪些用户可以修改镜像。

一般来说,镜像仓库都设有两层权限控制:一是必须登录才可以访问,这是最外层的控制,它规定了哪些人可以访问镜像仓库;二是对镜像按照项目的方式进行划分,每个项目拥有自己的镜像仓库目录,并且给每个项目设置项目管理员、开发者以及客人这三个角色,只有项目管理员和开发者拥有自己镜像仓库目录下镜像的修改权限,而客人只拥有访问权限,项目管理员可以给这个项目设置哪些人是开发者。

2.镜像同步
在实际的生产环境中,往往需要把镜像同时发布到几十台或者上百台集群节点上,单个镜像仓库实例往往受带宽原因限制无法同时满足大量节点的下载需求,这个时候就需要配置多个镜像仓库实例来做负载均衡,同时也就产生镜像在多个镜像仓库实例之间同步的问题了。显然通过手工维护十分繁琐,那有什么好的办法吗?

一般来说,有两种方案,一种是一主多从,主从复制的方案,比如开源镜像仓库Harbor采用了这种方案;另一种是P2P的方案,比如阿里的容器镜像分发系统蜻蜓采用了P2P方案。微博的镜像仓库是基于Harbor搭建的,所以这里我就以Harbor为例,介绍镜像同步机制。

Harbor所采取的主从复制的方案是,把镜像传到一个主镜像仓库实例上去,然后其他从镜像仓库实例都从主镜像仓库实例同步,它的实现就像下图所描述的一样。
在这里插入图片描述

除此之外,Harbor还支持层次型的发布方式,如果集群部署在多个IDC,可以先从一个主IDC的镜像仓库同步到其他从IDC的镜像仓库,再从各个从IDC同步给下面的分IDC,它的实现就像下图所描述的一样。
在这里插入图片描述

3.高可用性
既然Docker镜像是Docker容器运行的基础,那么镜像仓库的高可用性就不言而喻了。一般而言,高可用性设计无非就是把服务部署在多个IDC,这样的话即使有IDC出问题,也可以把服务迁移到别的正常IDC中去。同样对于镜像仓库的搭建,也可以采用多IDC部署,那么需要做到的就是不同IDC之间的镜像同步。以微博的镜像仓库为例,就像下图所描述的那样,镜像仓库会部署在永丰、土城两个内网IDC内,两个IDC内的镜像同步采用Harbor的双主复制策略,互相复制镜像,这样的话即使有一个IDC出现问题,另外一个IDC仍然能够提供服务,而且不丢失数据。
在这里插入图片描述

资源调度

解决了Docker镜像存储和访问的问题后,新问题又随之而来了,Docker镜像要分发到哪些机器上去?这些机器是从哪里来的?这其实涉及的是资源调度的问题。

服务部署的集群主要包括三种:

  1. 物理机集群。大部分中小团队应该都拥有自己的物理机集群,并且大多按照集群 - 服务池 - 服务器这种模式进行运维。物理机集群面临的问题,主要是服务器的配置不统一,尤其对于计算节点来说,普遍存在的一种情况就是几年前采购的机器的配置可能还是12核16G内存的配置,而近些年采购的机器都至少是32核32G内存的配置,对于这两种机器往往要区别对待,比如旧的机器用于跑一些非核心占用资源量不大的业务,而新采购的机器用于跑一些核心且服务调用量高的业务。

  2. 虚拟机集群。不少业务团队在使用物理机集群之后,发现物理机集群存在使用率不高、业务迁移不灵活的问题,因此纷纷转向了虚拟化方向,构建自己的私有云,比如以OpenStack技术为主的私有云集群在国内外不少业务团队都有大规模的应用。它的最大好处就是可以整合企业内部的服务器资源,通过虚拟化技术进行按需分配,提高集群的资源使用率,节省成本。

  3. 公有云集群。现在越来越多的业务团队,尤其是初创公司,因为公有云快速灵活的特性,纷纷在公有云上搭建自己的业务。公有云最大的好处除了快速灵活、分钟级即可实现上百台机器的创建,还有个好处就是配置统一、便于管理,不存在机器配置碎片化问题。

为了解决资源调度的问题,Docker官方提供了Docker Machine功能,通过Docker Machine可以在企业内部的物理机集群,或者虚拟机集群比如OpenStack集群,又或者公有云集群比如AWS集群等上创建机器并且直接部署容器。Docker Machine的功能虽然很好,但是对于大部分已经发展了一段时间的业务团队来说,并不能直接拿来使用。

源调度最大的难点不在于机器的创建和容器的部署,而在于如何对接各个不同的集群,统一管理来自不同集群的机器权限管理、成本核算以及环境初始化等操作,这个时候就需要有一个统一的层来完成这个操作。
这个对有历史包袱的团队,比如公司内网的物理机集群已经有一套运维体系来说,挑战不小,需要针对新的模式重新开发这套运维平台。以微博的业务为例,为了满足内部三种不同集群资源的统一管理,专门研发了容器运维平台DCP,来实现对接多个不同的集群。它的难点在于不仅对外要对接不同的云厂商,针对不同云厂商提供的ECS创建的API,统一封装一层API来实现机器管理;对内也要针对私有云上不同集群的机器进行管理,进行上下线和配置初始化等操作。

以DCP配置初始化操作为例,在创建完主机后,还需要在主机上进行安装NTP服务、修改sysctl配置、安装Docker软件等操作,这时候就需要借助配置管理软件来向主机上进行分发。因为微博内网的主机,之前都是通过Puppet进行分发的,考虑到稳定性并没有对这一部分进行修改;而针对阿里云上创建的主机,则使用的是编程功能更为强大的Ansible进行分发。

总结

镜像仓库帮我们解决的是Docker镜像如何存储和访问的问题,在业务规模较大时,各个业务团队都需要搭建自己的私有镜像仓库。类似Harbor这种开源解决方案能很好地解决权限控制、镜像同步等基本问题,关于高可用性的要求以及上云支持等业务场景,你可以参考我给出的解决方案,它是经过微博实际线上业务验证过的。

资源调度帮我们解决的是如何整合来自不同的集群的资源的问题,如果你的业务不止在内部私有云上部署,在公有云上也有部署,甚至是采用了多家公有云,那么资源的调度将会是非常复杂的问题,尤其是在公司内部已经存在一套对接内部集群的运维管理平台的情况下,是升级已有的运维平台以支持公有云,还是直接开发另外一套新的能够实现多云对接,这是一个很现实的问题。我的建议是单独开发一套新的运维平台先来接管公有云,然后逐步迁移内部集群的管理工作到新的运维平台中。

27 | 微服务容器化运维:容器调度和服务编排

镜像仓库解决的是Docker镜像存储和访问的问题,资源调度决定了Docker镜像可以分发到哪些机器上的问题。这两个问题解决后,你就该考虑如何在集群中创建容器,也就是容器如何调度的问题;容器创建后如何运作才能对外提供服务,也就是服务如何编排的问题。
两个问题解决后,你就该考虑如何在集群中创建容器,也就是容器如何调度的问题;容器创建后如何运作才能对外提供服务,也就是服务如何编排的问题。

容器调度

容器调度的问题,说的是现在集群里有一批可用的物理机或者虚拟机,当服务需要发布的时候,该选择哪些机器部署容器的问题。

比如集群里只有10台机器,并且已经有5台机器运行着其他容器,剩余5台机器空闲着,如果此时有一个服务要发布,但只需要3台机器就行了,这个时候可以靠运维人为的从5台空闲的机器中选取3台机器,然后把服务的Docker镜像下载下来,再启动Docker容器服务就算完成发布。但如果集群机器的规模扩大到几十台或者上百台时,要发布的服务也有几十个或者上百个的时候,由于每个服务对容器的要求,以及每台机器上正在运行的容器情况变得很复杂,就不太可能靠人肉运维了。

这时就需要有专门的容器调度系统了,为此也诞生了不少基于Docker的容器调度系统,比如Docker原生的调度系统Swarm、Mesosphere出品的Mesos,以及Google开源的大名鼎鼎的Kubernetes。下面我就结合微博的实践经验,给你讲讲容器调度要解决哪些问题。

1. 主机过滤

主机过滤是为了解决容器创建时什么样的机器可以使用的问题,主要包含两种过滤。

  • 存活过滤。也就是说必须选择存活的节点,因为主机也有可能下线或者是故障状态。
  • 硬件过滤。打个比方,现在你面对的集群有Web集群、RPC集群、缓存集群以及大数据集群等,不同的集群硬件配置差异很大,比如Web集群往往用作计算节点,它的CPU一般配置比较高;而大数据集群往往用作数据存储,它的磁盘一般配置比较高。这样的话如果要创建计算任务的容器,显然就需要选择Web集群,而不是大数据集群。

上面这两个过滤方式都是针对主机层次的过滤方式,除此之外,Swarm还提供了容器层次的过滤,可以实现只有运行了某个容器的主机才会被加入候选集等功能。

2.调度策略

调度策略主要是为了解决容器创建时选择哪些主机最合适的问题,一般都是通过给主机打分来实现的。比如Swarm就包含了两种类似的策略:spread和binpack,它们都会根据每台主机的可用CPU、内存以及正在运行的容器的数量来给每台主机打分。spread策略会选择一个资源使用最少的节点,以使容器尽可能的分布在不同的主机上运行。它的好处是可以使每台主机的负载都比较平均,而且如果有一台主机有故障,受影响的容器也最少。而binpack策略恰恰相反,它会选择一个资源使用最多的节点,好让容器尽可能的运行在少数机器上,节省资源的同时也避免了主机使用资源的碎片化。

具体选择哪种调度策略,还是要看实际的业务场景,通常的场景有:

  • 各主机的配置基本相同,并且使用也比较简单,一台主机上只创建一个容器。这样的话,每次创建容器的时候,直接从还没有创建过容器的主机当中随机选择一台就可以了。
  • 在某些在线、离线业务混布的场景下,为了达到主机资源使用率最高的目标,需要综合考量容器中跑的任务的特点,比如在线业务主要使用CPU资源,而离线业务主要使用磁盘和I/O资源,这两种业务的容器大部分情况下适合混跑在一起。
  • 还有一种业务场景,主机上的资源都是充足的,每个容器只要划定了所用的资源限制,理论上跑在一起是没有问题的,但是某些时候会出现对每个资源的抢占,比如都是CPU密集型或者I/O密集型的业务就不适合容器混布在一台主机上。

所以实际的业务场景,对调度策略的要求比较灵活,如果Swarm提供的spread和binpack满足不了的话,可能就需要考虑自行研发容器调度器了。

服务编排

1.服务依赖

大部分情况下,微服务之间是相互独立的,在进行容器调度的时候不需要考虑彼此。但有时候也会存在一些场景,比如服务A调度的前提必须是先有服务B,这样的话就要求在进行容器调度的时候,还需要考虑服务之间的依赖关系。

为此,Docker官方提供了Docker Compose的解决方案。它允许用户通过一个单独的docker-compose.yaml文件来定义一组相互关联的容器组成一个项目,从而以项目的形式来管理应用。比如要实现一个Web项目,不仅要创建Web容器比如Tomcat容器,还需要创建数据库容器比如MySQL容器、负载均衡容器比如Nginx容器等,这个时候就可以通过docker-compose.yaml来配置这个Web项目里包含的三个容器的创建。

Docker Compose这种通过yaml文件来进行服务编排的方式是比较普遍的算法,以微博的业务为例,也是通过类似yaml文件的方式定义了服务扩容的模板,模板除了定义了服务创建容器时的镜像配置、服务池配置以及主机资源配置以外,还定义了关联依赖服务的配置。比如微博的Feed服务依赖了user服务和card服务,假如user服务扩容的模板ID为1703271839530000,card服务扩容的模板ID为1707061802000000,那么Feed服务的扩容模板里就会像下面这样配置,它代表了每扩容10台Feed服务的容器,就需要扩容4台user服务的容器以及3台card服务的容器。

{"Sid":1703271839530000,"Ratio":0.4}
{"Sid":1707061802000000,"Ratio":0.3}
2.服务发现

容器调度完成以后,容器就可以启动了,但此时容器还不能对外提供服务,服务消费者并不知道这个新的节点,所以必须具备服务发现机制,使得新的容器节点能够加入到线上服务中去。

根据我的经验,比较常用的服务发现机制包括两种,一种是基于Nginx的服务发现,一种是基于注册中心的服务发现。

  • 基于Nginx的服务发现
    这种主要是针对提供HTTP服务的,当有新的容器节点时,修改Nginx的节点列表配置,然后利用Nginx的reload机制,会重新读取配置从而把新的节点加载进来。比如基于Consul-Template和Consul,把Consul作为DB存储容器的节点列表,Consul-Template部署在Nginx上,Consul-Template定期去请求Consul,如果Consul中存储的节点列表发生变化,就会更新Nginx的本地配置文件,然后Nginx就会重新加载配置。

  • 基于注册中心的服务发现
    这种主要是针对提供RPC服务的,当有新的容器节点时,需要调用注册中心提供的服务注册接口。在使用这种方式时,如果服务部署在多个IDC,就要求容器节点分IDC进行注册,以便实现同IDC内就近访问。以微博的业务为例,微博服务除了部署在内部的两个IDC,还在阿里云上也有部署,这样的话,内部机房上创建的容器节点就应该加入到内部IDC分组,而云上的节点应该加入到阿里云的IDC。

3.自动扩缩容

容器完成调度后,仅仅做到有容器不可用时故障自愈还不够,有时候还需要根据实际服务的运行状况,做到自动扩缩容。

一个很常见的场景就是,大部分互联网业务的访问呈现出访问时间的规律性。以微博业务为例,白天和晚上的使用人数要远远大于凌晨的使用人数;而白天和晚上的使用人数也不是平均分布的,午高峰12点半和晚高峰10点半是使用人数最多的时刻。这个时候就需要根据实际使用需求,在午高峰和晚高峰的时刻,增加容器的数量,确保服务的稳定性;在凌晨以后减少容器的数量,减少服务使用的资源成本。

常见的自动扩缩容的做法是根据容器的CPU负载情况来设置一个扩缩容的容器数量或者比例,比如可以设定容器的CPU使用率不超过50%,一旦超过这个使用率就扩容一倍的机器。

总结

你的业务团队在选择解决方案时,要根据自己的需要选择合适的方案,而不是理论上最好的。

比如Kubernetes解决方案在容器调度、服务编排方面都有成熟的组件,并且经过大业务量的实际验证。但是要考虑到Kubernetes本身的复杂性以及概念理解的门槛,对于大部分中小业务团队来说,在生产环境上使用Kubernetes都会显得大材小用,并且还需要部署并运维Kubernetes周边的一些基础设施,比如etcd等。

相比之下,Docker原生自带的解决方案Swarm和Compose就要简单得多,但是功能也比较有限,如果不能满足你的业务需求的话,也不好再二次开发。

在了解了镜像仓库、资源调度、容器调度、服务编排后你会发现,微服务容器化后最大的挑战其实来自于原有运维设施如何支持容器的运维,是在原有运维平台上升级还是完全采用新的容器运维平台,这才是关键,往往不能一蹴而就,需要逐步按照业务进行替换升级。但是考虑到微服务容器化后所带来的种种好处,采用新的运维模式势在必行。

28 | 微服务容器化运维:微博容器运维平台DCP

微服务容器化后如何运维的几个关键问题:镜像仓库、资源调度、容器调度、服务编排,这些问题的产生都是因为微服务部署的节点从一台台物理机或者虚拟机变成了一个个容器,运维模式发生了根本性的变化。此时,容器运维平台也就应运而生。

微博的业务从2013年就开始进行容器化,2015年为了应对春晚以及突发热点事件带来的峰值流量,开始引入阿里云;同时也为了适应业务的发展和运维方式的变化,在2015年底开始研发新的容器运维平台DCP。今天我就和你聊聊微博容器运维平台DCP,我会讲讲一个真实的容器运维平台是如何建设的,在建设过程中面临了哪些问题,以及对应的解决方案,希望可以让你对容器运维平台的架构有所了解,并提供一些经验可供借鉴。

DCP整体架构

首先我们先来看看DCP的架构设计,从下面这张架构图你可以看到,DCP的架构主要分为四个部分:基础设施层、主机层、调度层、编排层,对应的分别解决前面提到的容器运维平台建设的几个关键问题:基础设施层用于解决镜像仓库的问题,主机层主要解决如何进行资源调度的问题,调度层主要解决容器如何在资源上创建的问题,编排层主要解决容器如何运作以对外提供服务的问题。下面我们来看各层的详细设计。
在这里插入图片描述

基础设施层

DCP中基础设施层主要用于提供各种基础设施,以保证其他层功能的正常运行。通常来讲,主要包括以下几个基础组件:用于存放容器镜像的镜像仓库、提供监控服务的监控中心、实时监控系统容量以便于自动扩缩容的容量评估系统以及容器创建后,如何加入线上服务的服务发现组件,其中镜像仓库是DCP最核心的基础组件。

DCP以开源镜像仓库Harbor为基础搭建了私有的镜像仓库,不过由于微博业务的特征,为了应对随时可能到来的突发峰值流量的冲击,需要随时随地能够扩容服务池。但在内网冗余度不足的时候,也不得不借助公有云来实现,因此服务不仅在内网私有云上有部署,在阿里云上也有部署,这样的话从阿里云申请的主机也需要从镜像仓库中拉取镜像。此时,如果镜像仓库只在内网部署的话,就需要跨专线去拉取镜像,但如果上百台服务器同时拉取镜像,带宽占用很可能达到上百G,由于专线带宽是有限的,显然这样不可取。为此,正确的做法就像下图中那样,在阿里云机房也部署一套镜像仓库,并且通过Harbor的主从复制机制与内网的镜像仓库保持同步。同时,为了做到负载均衡,每个机房内部都部署了多个Harbor节点,内网节点访问内网镜像仓库会通过LVS进行负载均衡,阿里云上节点访问阿里云镜像仓库会通过SLB进行负载均衡,以满足镜像仓库的带宽需求。
在这里插入图片描述

主机层

DCP中主机层的功能主要是为了完成资源的调度,也就是针对不同的集群,完成主机的创建、成本的管理以及配置初始化工作,也叫Pluto层。前面提到过微博业务不仅在内网私有云上有部署,而且在阿里云上也有部署,为此Pluto需要适配不同底层提供的创建主机的API,进行成本核算并且进行配置初始化操作。Pluto层的架构你可以参看下图。

在这里插入图片描述

1.主机创建

Pluto在创建主机时,主要有两个来源,一个是内部物理机组成的共享池,一个是调用阿里云API创建ECS。其中共享池内的资源主要来源于两部分:一部分是冗余度高的服务池缩容部分主机加入到共享池;一部分是在线业务和离线计算互相补充,比如白天在线业务需要的机器多,而离线计算的任务主要运行在凌晨,这时候就可以在白天把离线计算的集群的部分机器加入到共享池给在线业务使用,而在晚上业务低峰期把在线业务的部分机器加入到共享池给离线计算任务使用。而使用阿里云创建ECS,主要是在共享池内的资源不足的情况下,比如有突发热点事件到来,各个服务池都需要紧急扩容,这时候共享池内的资源就不足以应对了。而使用阿里云API创建ECS会受到阿里云API的各种限制,下面我列举几个微博在使用阿里云创建机器时所遇到的问题,你就可以理解主机创建的复杂性所在了。

  • 由于阿里云API对单账户的调用有并发限制,所以实际业务在创建阿里云ECS上时,不能上百台同时创建,一般要控制在几十台的规模左右,如果这个时候业务需要创建上百台机器该怎么做呢?那就需要采取队列机制,来控制机器创建的速度。下面这张图就描述了微博在使用阿里云创建ECS时的解决方案,在实际创建ECS时,不会立即调用阿里云API,而是把节点创建任务先放到一个DB队列中,然后再通过一个线程定时从DB队列中获取创建任务,每次只创建几十台,这样的话就不会触发阿里云API对单账号调用的并发限制。

img

  • 除了有单账户调用的并发限制,还会有可用区的库存限制、安全组库存限制以及vSwitch库存限制,所以在实际使用阿里云API创建ECS时,当机器规模较大,如果直接指定使用某个可用区、安全组和vSwitch,就可能因为库存原因导致创建失败。微博一开始就使用了这种方案,但在突发峰值流量来临时,往往要创建几百台甚至上千台的阿里云ECS,为此经常会因为以上限制导致创建失败。后来针对可用区、安全组以及vSwitch都做了多可用区、多安全组以及多vSwtich配置,在出现库存不够时,就自动切换到别的地方来创建,极大提高了大规模ECS创建的成功率。

    img

2.成本管理

无论是从共享池内创建的机器,还是调用阿里云API创建的ECS,都是有成本的,为此必须对机器的数量以及使用时长进行记录,以便进行成本管理。

以阿里云的ECS为例,又分为按量付费、按月付费以及按年付费,可以按照以下方式来进行管理。

  • 按量付费。按照使用时长,以秒为单位计费,适合突发流量到来临时需要扩容部分机器时使用,所以需要记录每台ECS从调用API创建成功到销毁所使用的时长。
  • 按月付费。这种比较适合短期业务需要使用机器的场景,比如微博曾经在奥运会期间扩容过大量包月付费的机器,以应对奥运会期间带来的流量上涨。需要注意的是,这种机器到了月底会自动销毁,所以如果还有使用需要的话,需要及时续费。
  • 按年付费。这种比较适合需要长期在阿里云上部署的业务,比如有一些新的业务因为业务发展比较快,采用传统自采机器部署的话,由于采购周期比较长不适合业务发展,所以使用公有云更为合适。
3.配置初始化

主机创建完成后,还要进行一些基础软件的安装以及配置修改等工作,这就是配置初始化的过程。以阿里云创建的ECS为例,如果短时间内创建了上千台ECS,这个时候配置初始化的工作量会非常大,需要同时给上千台ECS下发配置文件并安装基础软件,同时还需要记录每台ECS的初始化状态到DB,以便查询是否初始化成功。下图描述了初始化的过程,DCP在进行主机配置初始化时,会通过Ansible向所有主机下发配置文件和基础软件,并通过自定义callback queue,把每台主机的初始化状态异步写入到DB中,避免上百台机器同时并发写入DB造成死锁。

img

调度层

DCP中调度层的主要功能是在可用的主机上创建容器。由于微博业务早在2013年就开始进行容器化,基于当时的背景考虑,就选择了Swarm作为容器调度的工具,并根据自己的业务特点在Swarm基础上进行二次封装,定制了自己的调度层Roam,使其具备支持跨IDC、高可用以及可扩展的特性。下面是Roam的架构,其主要工作原理是:

  • Swarm Manager和Swarm Client节点都向Consul中注册,并且有一个Active Manager和Standby Manager。任何一个IDC内的Active Manager如果down掉的话,Standby Manager就会注册到Consul中,成为新的Active Manager,以保证高可用性。
  • 当发起容器调度时,Roam根据IDC参数请求Consul,得到该IDC的Swarm Manager信息。
  • Roam访问该IDC内的Swarm Manager,Swarm Manager再访问Consul获取Swarm Client信息,并根据Roam传递的调度策略从Swarm Client中选择节点创建容器。

img

编排层

DCP中编排层的主要作用是对服务进行整合以对外提供服务,主要包括服务依赖、服务发现以及自动扩缩容,下面我来详细介绍每一部分的具体实现。

1.服务依赖

DCP通过模板来管理容器的创建,一个服务如果需要进行扩容、创建容器,就必须按照模板里定义的参数来执行,以下图描述的DCP里的一个扩容任务创建模板为例,通常来讲,模板里定义的参数主要包括几个部分:任务的名称、机器的配置、任务依赖、任务详细配置(包括调用阿里云API创建ECS时的可用区、安全组参数等),其中任务依赖的配置项是:

{"Sid":1707061842070000,"Ratio":0.2,"ElasticCount":0}
{"Sid":1703271821000000,"Ratio":0.3,"ElasticCount":0}

它的含义是执行这个扩容任务时,会自动执行ID为1707061842070000和1703271821000000的扩容任务,并且按照每扩容10台容器分别扩容2台和3台依赖容器的比例来执行。

img

2.服务发现

微博的业务场景主要包含两种服务,一种是HTTP服务,一种是Motan RPC服务,他们分别使用了不同的服务发现方式。

  • HTTP服务。考虑到传统的基于Nginx的配置Reload机制实现的服务发现方式,在高并发访问的情况下,会导致吞吐量下降10%左右,如果业务频繁变更的话,就会受到影响。为此,DCP在实际业务中基于Nginx和Consul研发了一种可行的解决方案nginx-upsync-module,并且已经开源。
  • Motan RPC服务。Motan RPC服务在启动时,会向注册中心Config Service注册服务,并且注册中心支持多IDC部署。像下图所描述的那样,正常情况下服务消费者会访问同一个IDC内的服务提供者,并且支持在故障的时候,可以切换到其他IDC。

img

3.自动扩缩容

DCP系统实现自动扩缩容主要依靠的是容量决策支持系统,由容量决策支持系统来实时监控系统的容量。如下图所示,一旦容量决策支持系统检测到某个服务需要进行扩容,就会创建扩容任务,Config Watcher会监控到扩容任务,并通知CronTrigger有调度策略变更。CronTrigger接到扩容任务,就会调用Scheduler来具体执行扩容。同时还可以通过API来修改、查询扩缩容的信息,也可以通过UI来操作。

img

总结

介绍了微博容器运维平台DCP的架构主要包括基础设施层、主机层、调度层以及编排层,并详细介绍了每一层的功能实现,以及各自承担的不同职能。下面这张图是一次完整扩容流程,包括了资源评估、配额评估、初始化、容器调度、部署服务、服务依赖、服务发现以及自动扩缩容等,DCP正是通过把这些过程串联起来,实现容器运维的。

img

思考题

在讲到服务编排时,我提到服务之间会存在依赖关系,比如服务A依赖服务B,假如此时服务A的流量上涨,需要对服务A进行扩容,这时候有两种方案:一种方案是通过自动扩缩容,服务A和服务B的扩容完全独立,分别按需自动扩缩容;一种方案是通过服务依赖,扩容服务A之前先扩容服务B,你认为这两种方案哪种更好?为什么?

29 | 微服务如何实现DevOps?

把一个大的单体应用拆分成多个微服务之后,每个服务都可以独立进行开发、测试和运维。但当拆分的微服务足够多时,却又仿佛陷入一个新的泥沼,无论是业务代码的开发还是测试和运维,工作量都比之前提升了很多。

采单体应用架构时,一个业务需求只需要修改单体应用的代码,然后针对这个单体应用进行测试,测试通过后再把单体应用的代码发布到线上即可。而拆分为微服务之后,一个大的系统被拆分为多个小的系统,一个业务需求可能要同时修改多个微服务的代码,这样的话多个微服务都需要进行测试,测试通过了都需要把代码发布到线上,显然工作量成倍增加。这时候就迫切需要一种新的开发、测试和运维模式来解决这个问题,这就是今天我要给你讲的微服务与DevOps。

什么是DevOps?

在介绍DevOps之前,先来回顾一下传统的业务上线流程:开发人员开发完业务代码后,把自测通过的代码打包交给测试人员,然后测试人员把代码部署在测试环境中进行测试,如果测试不通过,就反馈bug给开发人员进行修复;如果通过,开发就把测试通过的代码交给运维人员打包,然后运维人员再发布到线上环境中去。可见在传统的开发模式下,开发人员、测试人员和运维人员的职责划分十分明确,他们往往分属于不同的职能部门,一次业务上线流程需要三者之间进行多次沟通,整个周期基本上是以天为单位。你肯定会想假如能够把开发、测试和发布流程串联起来,就像生产流水线上那样,每个步骤完成后,就自动执行下一个步骤,无须过多的人为干预,业务的迭代效率不就能提升很多吗。

没错,DevOps的思想正是如此。在我看来,DevOps是一种新型的业务研发流程,业务的开发人员不仅需要负责业务代码的开发,还需要负责业务的测试以及上线发布等全生命周期,真正做到掌控服务全流程。DevOps就是下图中心的部分,集开发、测试和运维三者角色于一体。

img

而要实现DevOps,就必须开发完成代码开发后,能自动进行测试,测试通过后,能自动发布到线上。对应的这两个过程就是CI和CD,具体来讲就是:

  • CI(Continuous Integration),持续集成。开发完成代码开发后,能自动地进行代码检查、单元测试、打包部署到测试环境,进行集成测试,跑自动化测试用例。
  • CD(Continuous Deploy),持续部署。代码测试通过后,能自动部署到类生产环境中进行集成测试,测试通过后再进行小流量的灰度验证,验证通过后代码就达到线上发布的要求了,就可以把代码自动部署到线上。

其中CD还有另外一个解释就是持续交付(Continuous Delivery),它与持续部署不同的是,持续交付只需要做到代码达到线上发布要求的阶段就可以了,接下来的代码部署到线上既可以选择手动部署也可以选择自动部署。实际服务发布时,代码能否自动部署到线上本身并不是难点,关键在于是否需要人为判断整个发布过程是否正常,毕竟有些异常只有在真正的线上发布过程中才能被发现,人为介入相对来说要保险一些,所以只做到持续交付也可以算是实现了DevOps。

DevOps的关键是如何实现代码开发自测通过,自动部署到测试环境,验证通过后再自动部署到生产环境,小流量验证后再自动发布到线上去。在传统的采用物理机部署服务的时代,这个流程的很难自动化执行的最大原因就是代码环境的可移植性差,这是因为开发自己的环境,跟测试环境以及生产环境的软件配置往往存在很大差异,经常会出现开发在自己的环境中运行通过的代码,部署到测试环境就运行不了的问题。而容器化正好解决了代码环境的可移植性的问题,使得DevOps取得了突飞猛进的发展,并成为业界推崇的开发模式。那么具体该如何实现DevOps呢?下面我就以微博的业务实践为例,来给你详细讲解。

微博的DevOps实践

目前业界比较通用的实现DevOps的方案主要有两种,一种是使用Jenkins,一种是使用GitLab。微博就主要使用的是GitLab来实现DevOps,下面我就从微博一个服务的开发、测试到上线的具体流程,看看是如何实现DevOps的。

img

从上面图中你可以看到,一个服务的发布流程主要包含了三个步骤。

1.持续集成,这个步骤的主要作用是确保每一次代码的Merge Request都测试通过,可随时合并到代码的Develop分支,主要包括四个阶段:build阶段(开发分支代码的编译与单元测试)、package阶段(开发分支代码打包成Docker镜像)、deploy阶段(开发分支代码部署到测试环境)、test阶段(开发分支代码集成测试)。

2.持续交付,这个步骤的主要作用是确保所有代码合并Merge Request到Develop分支后,Develop分支的代码能够在生产环境中测试通过,并进行小流量灰度验证,可随时交付到线上。主要包括五个阶段:build阶段(Develop分支的代码编译与单元测试)、package阶段(Develop分支的代码打包成Docker镜像)、deploy阶段(Develop分支的代码部署到测试环境)、test阶段(Develop分支的代码集成测试)、canary阶段(Develop分支的代码的小流量灰度验证)。

3.持续部署,这个步骤的主要作用是合并Develop分支到Master主干,并打包成Docker镜像,可随时发布到线上。主要包括四个阶段:build阶段(Master主干的代码编译与单元测试)、package阶段(Master主干的代码打包成Docker镜像)、clear阶段(Master主干的代码Merge回Develop分支)、production阶段(Master主干的代码发布到线上)。

那么,上面这些流程是如何实现自动化的呢?在GitLab中可以通过一个叫“.gitlab-ci.yml”的文件来定义自动化流程都包含哪些阶段,以及每个阶段所具体执行的脚本,这样的话在提交代码Merge Request后会自动触发gitlab-ci.yml文件中定义的各个流程按顺序执行。

实现DevOps的关键点

上面我讲了具体业务中如何使用GitLab来实现DevOps,在具体实施时,每个阶段都有关键问题,只有解决了这些关键问题,才能真正实现DevOps。

1.持续集成阶段

持续集成阶段的主要目的是保证每一次开发的代码都没有问题,即使合并到主干也能正常工作,这里主要依靠三部分的作用。

  • 代码检查。通过代码检查可以发现代码潜在的一些bug,比如Java对象有可能是null空指针等,实际执行时可以在持续集成阶段集成类似Sonarqube之类的工具来实现代码检查。
  • 单元测试。单元测试是保证代码运行质量的第二个关卡。单元测试是针对每个具体代码模块的,单元测试的覆盖度越高,各个代码模块出错的概率就越小。不过实际业务开发过程中,为了追求开发速度,许多开发者并不在意单元测试的覆盖度,而是把大部分测试工作都留在了集成测试阶段,这样可能会造成集成测试阶段返工的次数太多,需要多次修复bug才能通过集成测试。尤其对于业务复杂度比较高的服务来说,在单元测试阶段多花费一些功夫,其实从整个代码开发周期角度来看,收益还是要远大于付出的。
  • 集成测试。集成测试就是将各个代码的修改集成到一起,统一部署在测试环境中进行测试。为了实现整个流程的自动化,集成自测阶段主要的任务就是跑每个服务的自动化测试用例,所以自动化测试用例覆盖的越全,集成测试的可靠性就越高。这里就要求开发和测试能及时沟通,在新的业务需求确定时,就开始编写测试用例,这样在跑自动化测试用例时,就不需要测试的介入了,省去了沟通成本。当然,业务开发人员也可以自己编写测试用例,这样的话就不需要专职的业务测试人员了。

除此之外,还有一个值得关注的问题,就是集成测试阶段业务代码部署的测试机器从何而来。在单体应用的时候,一般是开发把代码打包交给测试,测试人员再分配给自己的测试机中部署业务,然后进行集成测试。但是现在问题来了,由于拆分成了微服务,需要测试的服务变多了,如果同时有多个需求在测试,测试人员的测试机可能就不够用了,而出于成本考虑,一般公司都不会花费采购大量的测试机器。一个好的办法就是通过Kubernetes之类的容器平台对测试集群进行管理,当有业务代码正在执行集成测试时,就从测试集群中创建一个容器部署服务,完成测试后,再销毁容器,及时进行资源回收。这样测试机器不需要分配给某个具体的个人,实现按需使用,提高了测试集群的资源使用率。

2.持续交付阶段

持续交付阶段的主要目的是保证最新的业务代码,能够在类生产环境中可能够正常运行,一般做法都是从线上生成环境中摘掉两个节点,然后在这两个节点上部署最新的业务代码,再进行集成测试,集成测试通过后再引入线上流量,来观察服务是否正常。通常需要解决两个问题:

  • 如何从线上生产环境中摘除两个节点。这就需要接入线上的容器管理平台,比如微博的容器管理平台DCP就提供了类似下面的API,能够从线上生产环境中摘除某个节点,然后部署最新的业务代码。

    curl -s http://raptor.api.weibo.com/extension/v1/preview/run/ -d action=503&ip=11.75.21.155&service_pool=openapi_friendship-yf-docker&user=weibo_rd_user

  • 如何观察服务是否正常。由于这两个节点上运行的代码是最新的代码,在引入线上流量后可能会出现内存泄露等在集成测试阶段无法发现的问题,所以这个阶段这两个节点上运行最新代码后的状态必须与线上其他节点一致。实际观察时,主要有两个手段,一个是观察节点本身的状态,如CPU、内存、I/O、网卡等,一个是观察业务运行产生的warn、error的日志量的大小,尤其是error日志量有异常时,往往就说明最新的代码可能存在异常,需要处理后才能发布到线上。

3.持续部署阶段

持续部署阶段的主要目的把在类生产环境下运行通过的代码自动的发布到线上所有节点中去,这里的关键点就在于实际的线上发布阶段并不是想象中的那么直接。以微博API的业务为例,同样的服务也分为核心池和非核心池,核心池提供给移动端和PC调用,非核心池提供给其他内部业务调用,并且还按照机房分为不同的服务池,比如永丰机房服务池和土城机房服务池。实际发布的时候,考虑到线上服务的稳定性,并不是说按照一定的步长,自动把所有服务池都发布了,而是先发布非核心池以及土城机房的核心池,然后验证观察一段时间线上服务一切正常后,再继续发布永丰机房的核心池,以防止某些问题在服务发布的过程中才暴露出来,但又不至于影响线上所有的服务节点。所以这个阶段,持续部署一般并不要求那么完美,许多公司在这个阶段都采用了手动发布的方式以控制风险,或者只做到持续交付阶段,对于持续部署并不要求自动化。

总结

DevOps通过将开发、测试和运维流程自动化,以减轻微服务拆分后带来的测试和运维复杂度的提升,同时还提高了业务研发的效率。为了实现DevOps,需要实现持续集成、持续交付以及持续部署,可以采用Jenkins或者GitLab这些开源DevOps工具来搭建你自己的CI/CD流程,关键点在于如何把已有的自动化测试用例,以及现有容器管理平台集成到CI/CD流程当中去,以完成自动化的CI/CD流水线处理。

实际上DevOps你可以理解为一种新型的业务研发流程,也可以理解为一种新的技术思维,它摒弃了传统的开发、测试和运维严格区分的观念,把三者的角色融为一体,让服务的开发者负责从开发、测试到发布的整个生命周期,真正的承担起服务负责人的角色。更广义的DevOps除了包括CI/CD流程的自动化处理,还包括智能监控决策、在线自动扩缩容等,甚至还引入了人工智能技术走向另外一个新方向AIOps。

30 | 如何做好微服务容量规划?

单体应用拆分为微服务后带来的开发、测试和运维复杂度的提升,可以通过DevOps实现CI/CD流程的自动化来解决。除此之外,单体应用拆分为微服务还带来另外一个问题,也就是拆分出来后的多个微服务容量如何规划的问题。在单体应用时,只需要针对这个单体应用的访问量和实际接口性能来决定要不要给单体应用扩容,而拆分为众多的微服务之后,需要考虑每个服务的容量规划,它的复杂度主要来自下面几个方面。

  • 服务数量众多,纯靠人肉运维难以管理,比如微博Feed业务仅仅RPC服务就有将近40个。
  • 服务的接口表现差异巨大,有的接口属于访问量比较大,但接口响应时间比较短的轻接口;有的接口属于访问量比较小,但接口响应时间比较长的重接口。比如微博Feed业务中计数接口的平均耗时只有2~3ms,而微博Feed业务中Feed接口的平均耗时要超过200ms。
  • 服务部署的集群规模大小不同,需要扩容的机器数量差异很大。比如微博的AB测试服务集群只有大约20台机器,扩容只需要几台机器就满足了;而Feed服务则有上千台机器,往往扩容需要上百台机器。
  • 服务之间还存在依赖关系,在服务扩容的时候,还需要考虑依赖服务的容量是否足够。比如微博Feed业务扩容还依赖用户关系服务和Card服务,扩容时还需要考虑依赖的用户关系服务和Card服务容量是否有问题。

由此可见,单体应用拆分为微服务后,微服务的容量规划难度一下子增加了很多,再靠传统的人肉运维模式显然难以为继。延续上期DevOps的话题,其实微服务的容量规划也是广义DevOps要解决的问题之一,那么DevOps是如何解决的呢?答案就是容量规划系统,下面我就来聊聊容量规划系统该如何实现。

容量规划系统的作用是根据各个微服务部署集群的最大容量和线上实际运行的负荷,来决定各个微服务是否需要弹性扩缩容,以及需要扩缩容多少台机器

可见,容量规划系统实施的关键在于两点:一是如何评估集群的最大容量和线上实际运行的负荷,也就是如何做好容量评估;二是如何确定弹性扩缩容的时机以及机器数,也就是如何做好调度决策。下面我们分别来看这两个关键点,逐个击破。

容量评估

一般集群的容量评估都是通过线上实际压测来确定的,那么该如何进行线上压测呢?都需要注意哪些关键点呢?

1.选择合适的压测指标

一般在选取压测指标时,主要有两类:一类是系统类指标,比如机器的CPU使用率、内存占用量、磁盘I/O使用率以及网卡带宽等;一类是服务类指标,比如接口响应的平均耗时、P999耗时、错误率。但这些指标在实际压测时,都会存在一些问题。系统类指标比如CPU使用率并不能直接反映出服务压测时的健康状况,有时候CPU使用率不高的时候,接口耗时也可能有问题;而有时候CPU使用率较高时,接口耗时表现依然很正常。而服务类的指标比如接口响应的平均耗时也不能精确的反映服务的实际健康状态,一个最典型的场景就是在压测时,已经出现一定比例的慢请求,而在平均耗时上并不能看出有多大变化,这时候实际服务已经处于不健康的状态了,应该停止压测了。

根据我的经验,在压测时,除了观察以上这些指标以外,还可以观察接口的慢速比,也就是接口响应时间高于某个阈值的比例。比如微博在进行Feed接口压测时,选择的压测指标就是Feed接口响应时间大于1s的比例,压测的终止条件是Feed接口响应时间大于1s的比例超过1%。我的主要考虑是,当99%以上接口请求都在1s以内返回时,反馈到用户的直接使用感受是不容易感知到Feed刷新慢,反之则不然。对于大部分在线服务来说,接口慢速比不超过1%都是服务质量保证的底线了,因此可以作为一个通用的压测指标。

2.压测获取单机的最大容量

集群的最大容量就是单机的最大容量 × 集群内的机器数量,所以要获得集群的最大容量,就必须获得单机的最大容量。通常有两种方式来获取单机的最大容量,一种是单机压测,一种是集群压测。

  • 单机压测一般有两种方式,一种是通过日志回放等手段,模拟线上流量来对单机进行压测;一种是通过TCP-Copy的方式,把线上机器的流量拷贝过来对单机进行压测。
  • 集群压测是对整个集群进行压测,以获取单机的最大容量。一般做法是通过不断把线上集群的节点摘除,以减少机器数的方式,来增加线上节点单机的流量,从而达到压测的目的。

从我的经验来看,采用集群压测的方式要更合理一些,因为它是完全使用线上真实流量进行压测,获取的单机最大容量数值更精确。如果采用单机压测,通常为了避免产生“脏数据”,往往需要去掉一些上行的修改请求,所以不能完全模拟线上真实情况。不过使用集群压测的方式也有一个缺点,就是压测的时候会对线上用户的实际请求产生影响,如果压测出问题了,会直接影响线上服务,所以一般会选择在业务低峰期进行压测,最大限度减少对线上服务造成的影响。还有一点是,通常会在工作日进行压测,以便出现问题时,也能人为快速介入。

假设我们采用集群压测,不断地缩减线上节点的数量,并观察服务的慢速比指标,当慢速比达到1%时,就停止压测,这个时候就可以计算单机的最大容量了,一般做法是用压测停止时刻的单机平均QPS作为单机的最大容量。但是,采用QPS就真的合理吗?实际上并非如此,这是因为QPS并不能准确衡量单机的消耗,就像下面这两张图所展示的,左图的请求响应时间主要集中在100ms以下,没有超过500ms的;而右图的请求响应时间主要集中在50ms以上,没有低于10ms的。这两种请求分布对单机消耗差异很大,显然右边要对单机的消耗更大一些。在单机QPS都是100的情况下,左边的单机还能继续加大QPS,而右边的单机已经出现超过500ms以上的慢请求了。

img

所以,一个更合理的计算单机容量的方式是采用区间加权来计算,也就是把请求按照响应时间分成多个区间,每个区间分别赋予不同的权重,响应时间越长权重越高,比如0~10ms区间的权重是1,10~50ms区间的权重是2,50~100ms区间的权重是4,100~200ms区间的权重是8,200~500ms区间的权重是16,500ms以上的权重是32,那么上面两张图所描述的情况的单机容量分别是8×1+50×2+30×4+10×8+2×16=340和2×2+10×4+50×8+20×16+8×32=1020。因此单机的最大容量,也就是压测停止时刻采用区间加权方式计算得出。

3.实时获取集群的运行负荷

通过压测能够获取到单机的最大容量,再乘以集群内的机器数量就是集群的最大容量了,下一步获取集群实际运行的负荷,就可以判断集群是否需要扩容了。跟刚才计算单机容量的方式类似,集群的运行负荷也需要通过采用区间加权的方式来计算,但是因为集群的规模可能很大,超过上千台机器,显然通过计算每台单机运行的负荷再加在一起的方式效率不高。我在线上实际使用的方法是统计每台单机在不同耗时区间内的请求数,推送到集中处理的地方进行聚合,将同一个集群内的单机位于不同耗时区间内的请求进行汇总,就得到整个集群的请求在不同耗时区间内的分布了,再利用区间加权的方式就可以计算整个集群的运行负荷。

调度决策

在容量评估阶段,你可以获取集群的最大容量和集群的实际运行负荷,有了这两个数据后该如何做调度策略呢?我在实际线上业务使用的是水位线来进行调度决策。就像水库的水位线一样,要实时观测水库的蓄水量,如果因为长时间降水导致水库蓄水量超过警戒水位线就需要开闸泄洪;如果长时间干旱降水量太少,就需要关闸蓄水,以保持水库中的蓄水量始终在一个合理的水位线上。这样的话,任意时刻的水位线就是集群的最大容量除以集群的实际运行负荷,可以实时监控集群的水位线。

img

在调度决策时候,就可以根据水位线来做决定。你可以看到下面图中划分了两条线,一条是安全线,一条是致命线。当集群的水位线位于致命线以下时,就需要立即扩容,在扩容一定数量的机器后,水位线回到安全线以上并保持一段时间后,就可以进行缩容了。

img

那具体在执行扩缩容时,机器数量该如何决定呢?

1.扩容

在决定扩多少机器时,一般有两种方式,一种是按数量,一种是按比例。因为不同的集群内机器数量差别可能很大,所以一般采取按比例的方式,举个例子比如每一次扩容都增加30%的机器数量,再看扩容后的水位线是否处于致命线以上了。

2.缩容

在扩容完成后,集群的水位线保持在安全线以上一段时间后,就需要缩容,以节省机器成本。可以根据实际业务特点来决定多久后可以缩容,比如微博的业务一般突发流量维持在1个小时以内,因此集群的水位线在安全线以上超过1个小时之后,就可以缩容。而在缩容时也不是一次把所有扩容的机器都缩掉,而是采用逐步缩容的方式,每隔5分钟判断一次集群的水位线是否还在致命线以上,然后按照10%、30%、50%、100%的比例进行缩容,这样可以避免缩容太快导致集群水位线又降到致命线以下又得再扩容机器。

在实际根据水位线决定是否扩缩容时还需要防止网络抖动等原因造成的水位线瞬间抖动,这个时候集群的运行负荷会突然变大,导致水位线异常,此时如果加以处理的话就会触发扩容,而实际上并不需要扩容。为了防止瞬间抖动,可以每分钟采集一次系统的水位线,一共采集5个点,只有5个点里有3个点满足扩容条件,才真正触发扩容。

总结

微服务如何做好容量规划的问题,即做好容量评估和调度决策。容量评估方面,首先要通过压测获取集群的最大容量,并实时采集服务调用的数据以获取集群的实时运行负荷,这样就可以获取集群的实时水位线。而调度决策方面,主要是通过水位线与致命线和安全线对比来决定什么时候该扩缩容。而扩缩容的数量也是有讲究的,扩容的机器数一般按照集群机器数量的比例来,而缩容一般采取逐步缩容的方式以免缩容太快导致反复扩容。

在单体应用拆分为多个微服务后,如果不做好容量规划是很危险的事情,尤其是在微服务的调用量出现突发峰值流量时,再靠人为判断决策扩缩容往往为时已晚。根据我在微博的实践,也证明了通过容量规划实现微服务的自动扩缩容才是解决这个问题的最佳途径。

思考题

在计算集群的水位线时,经常会遇到集群内有些单机问题导致整个集群的实时运行负荷偏大,对此你有什么解决方案吗?

31 微服务多机房部署实践

为了实现高可用性,微服务一般要部署在多个机房,保证有一个机房因为各种不可抗力因素导致不可用时,可以把流量切换到其他可用机房来避免故障。但是,是不是只要部署到多个机房就万事大吉了呢?你有没有想过这几个问题呢?

  • 一切正常时用户请求该访问哪个机房?
  • 多个机房之间的数据如何同步?
  • 多个机房之间的数据如何确保持一致性?

你看多机房部署并非看似那么轻松,里面还有不少门道。接下来,我就以微博业务实践为例,跟你聊聊微服务实际进行多机房部署时是如何解决这些关键问题的。

多机房负载均衡

当服务部署在多个机房时,最简单的就是遵循用户就近访问的原则,比如北方用户访问联通机房,南方用户访问电信机房。微博的服务也是同时部署在联通和电信机房,你可以看下面这张图,访问时根据用户访问的IP,通过DNS解析到不同的机房,如果是北方用户就访问联通机房,南方用户就访问电信机房。并且为了实现负载均衡,还会在每个机房分别部署四层负载均衡器VIP以及七层负载均衡器Nginx。比如来自北方用户的请求通过DNS解析到联通机房下任意一个VIP,然后通过VIP把请求转发给联通机房下任意一个Nginx,Nginx再把请求转发给联通机房下任意一个Tomcat容器,通过这种方式来实现各个机房内高并发访问下的负载均衡。

img

当然这是最理想的情况,在实际部署时经常会遇到下面的情况:

  • 某个机房的流量比较大,但是该机房的服务器规模有限并不足以支撑线上流量。
  • 某个机房服务有问题,需要切一部分流量到另外一个机房。

因此在实际部署时,有时候并不能完全遵循就近访问的原则,而是要根据需要调配流量,达到各个机房流量均衡的目的。在实践中可以通过两种方法来切换流量:一种是在DNS解析时,把一部分北方用户的请求解析到电信机房的VIP或者把一部分南方用户的请求解析到联通机房的VIP;另一种是在Nginx转发请求时,把一部分电信机房的Tomcat容器配置到联通机房的Nginx的upstream里或者把一部分联通机房的Tomcat容器配置到电信机房的Nginx的upstream里。这两种方法的示意你可以看下面这张图。

img

多机房数据同步

想要实现服务部署到多机房供用户访问是有前提的,这个前提是每个机房的数据都是一样的,用户访问哪个机房都可以获取到一样的数据,这就要求多个机房之间的数据必须保持同步。对于微博这种高并发访问的服务来说,数据通常都会有两层存储即缓存层和数据库层,就像下图所展示的。缓存层是为了存储用户经常访问的数据,尤其是在高并发访问下可以用缓存cache住绝大多数用户请求,减少对数据库层的压力,这是因为数据库层要直接访问磁盘,相比缓存层直接访问内存来说,响应要慢得多。

img

如此一来,要保证多个机房的数据一致,不仅要保证数据库层的数据一致,还需要保证缓存层的数据一致,应该如何实现呢?

1.主从机房架构

主从机房数据同步方案如下图所示。主从机房架构是以一个机房为主机房,所有的写请求都只发给主机房的处理机,由主机房的处理机来更新本机房的缓存和数据库,其他机房的缓存也通过主机房的处理机来更新,而数据库则通过MySQL的binlog同步机制的方式实现数据同步。

img

上面这种架构把所有的写请求都发给主机房,由主机房来负责写所有机房的缓存和本机房的数据库,而其他机房的数据库则通过MySQL的binlog同步机制实现数据同步。显然这样做有一个很大的风险,那就是如果主机房出现问题,就没法更新缓存和数据库了,所以就有了第二种方案。

2.独立机房架构

这种架构的数据同步方案如下图所示,联通和电信机房都有写请求,并通过一个叫WMB的消息同步组件把各自机房的写请求同步一份给对方机房,这样的话相当于每个机房都有全量的写请求。每个机房的处理机接收到写请求后更新各自机房的缓存,只有一个机房会更新数据库,其他机房的数据库通过MySQL的binlog同步机制实现数据同步。

img

独立机房架构相比于主从机房架构的优势在于任意一个机房出现问题,都不影响别的机房的数据更新,因为每个机房的写消息都是全量的,所以每个机房可以更新自己的缓存,并从数据库主库同步数据。其实独立机房架构的关键点在于WMB消息同步组件,它可以把各个机房之间的写请求进行同步。下面我就详细讲讲WMB消息同步组建是如何实现的。

WMB消息同步组件的功能就是把一个机房的写请求发给另外一个机房,它的实现原理可以用下面这张图来描述,分为两个部分:

  • reship,负责把本机房的写请求分发一份给别的机房。
  • collector,负责从别的机房读取写请求,然后再把请求转发给本机房的处理机。

img

那么该如何实现WMB的消息同步功能呢?根据我的实践经验,主要有两种方案,一种是通过MCQ消息队列,一种是通过RPC调用。

  • MCQ消息队列实现

下面这张图是采用MCQ消息队列的实现方案,从图中你可以看到联通机房的写请求写入到联通机房的MCQ里,然后联通机房的reship就从联通机房的MCQ里读取,再写入到电信机房的MCQ里,电信机房的collector就可以从电信机房的MCQ里读取到写请求,再写入到电信机房的另外一个MCQ里,电信机房的队列机就会从这个MCQ里读取写请求,然后更新缓存。可见采用这种方案的一个缺点是流程比较长,需要多次与MCQ消息队列打交道,当有大量写请求到来时,不仅要扩容reship和collector确保有足够的处理能力,还需要扩容MCQ消息队列以确保能够承受大量读取和写入,一种更加简单的方案是采用RPC调用来实现。

img

  • RPC调用实现

下面这张图是采用RPC调用的实现方案,从图中你可以看到联通机房的写请求会调用联通机房的reship RPC,然后联通机房的reship RPC就会调用电信机房的collector RPC,这样电信机房的collector RPC就会调用电信机房的处理机RPC,从而实现把联通机房的写请求同步给电信机房的处理机进行处理。

img

多机房数据一致性

解决了多机房数据同步的问题之后,还要确保同步后的数据是一致的,因为在同步过程中,会因为各种原因导致各机房之间的数据不一致,这就需要有机制能确保数据的一致性。而且考虑到不同业务的特征对数据一致性的要求也不相同,类似金融类的业务要求多机房之间的数据必须是强一致的,也就是一个机房的数据必须时刻同另外一个机房的数据完全一致;而社交媒体类的业务则要求没那么高,只需要能达到最终一致即可。微博的服务主要是通过消息对账机制来保证最终一致性,下面我们来看下如何通过消息对账机制来保证最终一致性。

你可以先看下面这张图,系统会给每一次写请求生成一个全局唯一的requestId,联通机房的写请求一方面会调用联通机房的处理机RPC来修改缓存和数据库,另一方面还会调用联通机房的reship RPC,reship RPC再调用电信机房的collector RPC来同步写请求,电信机房的collector RPC最后会调用电信机房的处理RPC来更新缓存。在这整个过程的每一个环节,requestId始终保持向下传递,无论是处理成功或者失败都记录一条包含requestId和机房标记的处理日志,并写到Elasticsearch集群上去。然后通过一个定时线程,每隔1分钟去扫描Elasticsearch集群上的日志,找出包含同一个requestId的不同机房的处理日志,然后验证是否在各个机房请求都处理成功了,如果有的机房某一阶段处理失败,则可以根据日志信息重试该阶段直到成功,从而保证数据的最终一致性。

img

总结

微服务多机房部署时要面临的三个问题,一是多机房访问时如何保证负载均衡,二是多机房之间的数据如何保证同步,三是多机房之间的数据如何保证一致性,并给出了微博在多机房部署微服务时所采取的解决方案,对于大部分中小业务团队应该都有借鉴意义。可以说多机房部署是非常有必要的,尤其是对可用性要求很高的业务来说,通过多机房部署能够实现异地多活,尤其可以避免因为施工把光缆挖断导致整个服务不可用的情况发生,也是业务上云实现混合云部署的前提。

思考题

在讲解多机房数据同步实践的时候,我提到了微博采用了WMB消息同步组件的方案,除了这种方案你是否有了解过其他多机房数据同步的方案?它们是如何实现的?

32 | 微服务混合云部署实践

大多数业务在发展到一定阶段,要么出于高可用性的需要,把业务部署在多个机房以防止单个机房故障导致整个服务不可用;要么出于访问量大的需要,把业务流量分散到多个机房以减少单个机房的流量压力。此时,服务一般是部署在企业内部机房的机器上,并利用私有云技术把内部机房的机器管理起来。然而有些业务经常还有弹性需求,比如微博就经常因为热点事件带来突发的峰值流量,需要扩容成倍的机器,但内部机房的资源有限,并且从成本因素考虑也不会预留太多机器,这个时候就会自然想到公有云。类似AWS和阿里云这样的公有云厂商,一般都会采购上万台机器,专门对外售卖,这样公有云的用户就不用预留这么多的机器了,可以按需弹性使用,节省机器成本。

我们今天要聊的混合云部署,就是既在企业内部的私有云部署服务,又使用企业外部公有云部署服务的模式。和多机房一样,混合云部署同样要考虑几个关键的问题。

  • 跨云服务如何实现负载均衡?
  • 跨云服务如何实现数据同步?
  • 跨云服务如何实现容器运维?

下面我就结合微博的微服务混合云部署实践,帮你找到解决上面问题的答案。

跨云服务的负载均衡

多机房的负载均衡,它主要考虑用户的就近访问,把用户的请求分别路由到不同的机房。同样的道理,当服务上云后还需要考虑把一定比例的用户请求路由到云上部署的服务,就像下图那样,微博的服务不仅在私有云的两个机房永丰和土城有部署,在阿里云上也部署了服务。为了做到负载均衡,把用户的访问按照DNS解析到不同的机房,私有云机房部署了VIP和Nginx分别用作四层和七层的负载均衡,阿里云机房部署了SLB和Nginx分别用作四层和七层的负载均衡。

img

跨云服务的数据同步

为了做到高可用性,一般采用独立机房的部署架构,每个机房的写请求都通过WMB同步给别的机房,以保证任意一个机房都有全量的写请求,从而使得任意一个机房的处理机都会全量更新数据。那么当服务跨云部署后,该如何实现数据同步呢?根据我的经验,在公有云部署服务和内部私有云部署服务还是有一些不同的,主要体现在下面两个方面。

1.私有云与公有云之间的网络隔离

一般来讲,出于安全的需要,企业内部机房同公有云机房之间的网络是隔离的,为了实现互通,需要架设专门的VPN网络或者专线,就像下图描述的,微博在内部私有云和阿里云之间搭建了两条跨云专线,分别打通了内部的联通、电信机房与阿里云的联通、电信可用区,这样的话不仅实现了私有云和公有云之间的网络互动,双专线也保证了高可用性,即使一条专线断了,也可以通过另外一条专线实现数据同步。不过这样做需要保证专线的冗余度充足,任何一根专线的带宽能够承担所有跨云的流量,否则就很危险了,因为一旦一根专线断了,所有流量都通过另外一根专线的话,就会把专线打满,出现网络延迟影响服务。

img

2.数据库能否上云

数据库能否上云的关键取决于数据的隐私性。一般而言,企业都会考虑数据库里的数据放在公有云上是否安全,因为企业内部私有云部署的数据库与外网隔离,再加上有种种防护措施,一般情况下不会出现数据库数据外泄情况。而公有云厂商普遍采用了虚拟化技术,不同公司的虚拟机有可能部署在同一台物理机上,所以能否实现有效的数据隔离非常关键,尤其对于企业的核心业务数据,往往会出于安全隐私的考虑,并不敢直接放到云上部署。考虑到这一点,微博的服务在阿里云部署时,并没有部署数据库,只部署了缓存,当缓存穿透时需要访问内网数据库,你可以参考下面这张图。

img

综合上面两点考虑,微博在做跨云数据同步的时候,把内部的永丰机房和土城机房的写消息通过WMB同步给阿里云机房的WMB,阿里云机房的WMB把写消息转发给阿里云机房的处理机处理,进而更新阿里云机房的缓存,整个流程可见下图。其中阿里云机房主要用于承担下行的读请求,部署的缓存也不是跟内网机房完全一致,而是只部署了最核心的服务所依赖的缓存,这样可以将大部分阿里云机房的请求都在内部消化,减少到内网数据库的穿透,从而节省跨云专线的带宽使用。

img

跨云服务的容器运维

微服务容器化后,便具备了可移植性,不仅可以在内部私有云上部署,还可以在外部公有云上部署,这就要求有一套统一的容器运维平台不仅能对接内部私有云的基础设施,也能对接外部的公有云,这部分内容你可以在[第28期]容器运维平台DCP中找到。服务实现了混合云部署后,DCP在实施跨云的容器运维时又多了哪些关键点呢?

1.跨云的主机管理

跨云主机管理的关键点在于,如何对内部私有云的机器和公有云的ECS进行管理,在DCP里是按照“主机-服务池-集群”的模式进行管理的,这三个概念的含义分别是:

  • 主机:某一台具体的服务器,可能是私有云内创建的虚拟机,也有可能是公有云创建的ECS。
  • 服务池:针对具体某个服务而言,由这个服务部署的主机组成,可能包含私有云的主机,也可能包含公有云的主机,规模可能是几台也可能是上百台。
  • 集群:针对具体某个业务线而言,可能包含多个服务池,比如微博的内容业务线包含了Feed服务池,也包含了评论服务池等。

在实际扩容时,如下图所示,可能有三种情况。

  • 私有云内弹性扩容:当某个服务池的容量不足需要进行扩容时,如果该服务池所在的集群内的主机数量充足,则只需要在私有云内弹性扩容加入服务池即可。
  • 公有云弹性扩容:当某个服务池的容量不足需要进行扩容时,如果该服务池所在的集群内没有多余的主机可用时,就需要在公有云上弹性扩容,然后加入服务池。
  • 私有云和公有云同时弹性扩容:当某个服务池的容量不足需要进行扩容时,如果该服务池所在的集群内的主机数量不足时,就需要在同时在私有云和公有云上进行弹性扩容,最后都加入到服务池中去。

img

2.跨云服务发现。

在[第28期]我讲过DCP的服务发现主要有两种方式,一种是针对HTTP服务采用的nginx-upsync-module,一种是针对RPC服务的Config Service。除此之外,阿里云上部署的服务还可以直接使用SLB来做服务发现。比如下面这张图,红包飞依赖了用户关系服务,当阿里云上用户关系服务扩容后,可以直接添加容器的IP到SLB下,这样红包飞服务访问SLB就可以获得最新的用户关系服务的节点列表。

img

3.跨云弹性扩容。

当有流量上涨,超出了内部私有云机房部署所能承受的范围时,可以扩容阿里云机房的机器,然后把流量切换到阿里云机房,这个过程请看下面这张图。切流量也有两种方案:一是在DNS层切换,把原先解析到私有云机房VIP的流量,解析到阿里云机房的SLB,这时候阿里云机房部署的SLB、Nginx和Java Web都需要扩容;一种是在Nginx层切换,把原先转发到私有云机房Nginx的流量,转发到阿里云机房的Java Web,这个时候只需要扩容阿里云的Java Web。

这两种方案应对的业务场景不同,DNS层的切换主要是针对大规模流量增长的情况,这个时候一般四层VIP、七层Nginx和Java Web的容量都不足以应对,就需要在DNS层就把流量切到阿里云机房,在阿里云扩容SLB、Nginx和Java Web;而Nginx层的切换主要是针对私有云内某个机房的Java Web容量不足或者服务有问题的时候,需要把这个机房的一部分流量切换到其他机房,这个时候就可以只扩容阿里云机房的Java Web,然后从Nginx层把流量切换到阿里云机房。

img

4.跨云服务编排。

在进行服务编排时,如果服务跨云部署,就要考虑跨机房访问的问题了。就像下图所描述的那样,微博的Feed服务不仅依赖User RPC,还依赖Card RPC,这样的话如果Feed服务需要扩容的话,就需要先扩容User RPC和Card RPC。由于Feed服务在永丰、土城、阿里云三个机房内都有部署,任意一个机房内部署的Feed服务需要扩容时,就需要扩容同一个机房内的User RPC和Card RPC。

img

总结

今天我给你讲解了微服务混合云部署必须解决的三个问题:跨云服务的负载均衡、跨云服务的数据同步、跨云服务的容器运维,以及微博在微服务混合云部署时的实践方案,可以说正是由于采用了混合云部署,才解决了微博在面对频繁爆发的热点事件带来突发流量时,内部资源冗余度不足的问题。虽然云原生应用现在越来越流行,但对于大部分企业来说,完全脱离内部私有云并不现实,因为云也不是完全可靠的,一旦云厂商出现问题,如果没有内部私有云部署的话,那么服务将完全不可用。如果你的服务对高可用性要求很高,那么混合云的方案更加适合你。

思考题

微服务采用混合云部署的时候,如果公有云和私有云都都部署数据库的话,数据该如何保持同步?

http://www.dtcms.com/a/389095.html

相关文章:

  • Vue3 组件封装原则与实践指南
  • Git合并冲突
  • 部署K8S集群
  • K8S配置管理:ConfigMap与Secret
  • 奥威BI+ChatBI:数据智能时代的一体化解决方案
  • 微服务与云原生实战:Spring Cloud Alibaba 与 Kubernetes 深度整合指南
  • 从慕尼黑到新大陆:知行科技「智驾」与「机器人」的双行线
  • VINTF中manifest.xml和compatibility_matrix.xml的作用
  • AI时代云原生数据库一体机的思考
  • 配置manifest.xml和compatibility_matrix.xml
  • Prometheus高可用监控架构性能优化实践指南
  • 低代码平台与云原生开发理念是否契合?
  • 红队测试手册:使用 promptfoo 深入探索大语言模型安全
  • el-date-picker设置默认值
  • 结语:Electron 开发的完整路径
  • 数据结构系列之线性表
  • Vue2 生命周期钩子详解:beforeCreate、created、mounted、beforeDestroy 用法顺序与坑点指南
  • electron nodejs安装electron 以及解压打包
  • 每日一题:链表排序(归并排序实现)
  • 团体程序设计天梯赛-练习集 L1-032 Left-pad
  • AI的出现,能否代替IT从业者
  • 一个基于Java+Vue开发的灵活用工系统:技术实现与架构解析
  • 原神望陇村遗迹 解谜
  • 半导体制造常提到的Fan-in晶圆级封装是什么?
  • MySQL 专题(五):日志体系(Redo Log、Undo Log、Binlog)原理与应用
  • 锂电池取代铅酸电池作为及其老化率计算常用算法
  • FreeRtos面试问题合集
  • Codeforces Round 1051 Div.2 补题
  • tokenizer截断丢失信息,如何处理?
  • Mybatis学习笔记03-XML映射配置