您还未登录! 登录 | 注册 | 帮助  

您的位置: 首页 > 软件开发专栏 > 系统/运维 > 正文

微信月活9亿的高效运维之路

发表于:2018-01-03 作者:吴磊 来源:高效运维

今天的主题主要是关于弹性伸缩、扩容和缩容这些方面。

微信业务量增长的时候,我们比较关心的是效率问题。前期可能两三个月就涨了 1 倍的量,怎么能够保证我们的运营效率是跟得上的?后期主要关心的是成本问题。

我们在 2014 年以后增长有点放缓,所以主要的精力会在成本这个方面。

如上图,下面我主要分为四个方面来做分享:

  • 运营规范。
  • 云化管理。
  • 容量管理。
  • 自动调度。

运营规范

配置文件规范

先来看配置文件规范,我们前期花了比较多的精力。可能整个系统设计比较复杂,最开始都会有一些配置管理工程师专门来处理,后期我们把这块搞得比较规范了。

配置文件规范分为下面几项:

目录结构标准

这就是一个服务部署上线的时候怎么定义它的目录结构,大家应该先做好这些目录结构标准的定义。

跨服务的相同配置项管理

为什么提这一点?可能有些配置项你会在这个服务里需要这几个配置项,在另一个服务里也需要这几个配置项。

如果更改这个配置项的时候,你是不是把服务 A 发一遍,服务 B 也发一遍?我们有一套机制,每台机每个目录下都会有一个全局共用的配置项,我们会通过一些自动化的灰度的方式来控制这里的发布,这一块是单独拎出来的。

同一服务内不同实例的差异配置项管理

不确定大家运营时会不会碰到类似的问题,就算你用 Docker 了你镜像管理挺好的。

你部署个实例出来,可能你们的业务就需要在实例 1 上做一些调整,当然你也可以用脚本来管理。

但是我们觉得这是比较复杂的状态,我们会把这些差异性全部给它统一抽取出来,尽量做到所有的环境下它的配置文件的 MD5 都是一致的。

开发/测试/现网的差异配置项管理

前期也会有这个问题,开发环境我们一般不管,测试环境也是运维来负责管理,现网当然是运维管理的。

基本上测试和现网的差异只有路由的差异,其他的配置项我们都保证它是完全一致的。

做这么一个要求,大概实现的效果是不管你通过什么手段扩容,我们直接一个镜像打过去或者直接一个包拷过去就可以了,不会再有后续的脚本来改动它。

同一服务下同一版本的多个实例,在所有环境下配置文件的 MD5 都严格一致。

名字服务规范

名字服务这块比较重要,分为三层:

  • 接入层,类 LVS 实现。
  • 逻辑层,类 etcd 实现。
  • 存储层,路由配置自动化。

服务伸缩是运维工程,独立于研发的变更发布。

接入层和逻辑层,都是类似的实现,我们内部根据我们的业务特性做了一些业务开发。

存储层跟 QQ 有点不一样,QQ 好像是逻辑层和存储层都是用同一个名字服务实现,我们在存储层这里还没有做这方面的配置。

我们现在存储层跟逻辑层隔得比较开,在涉及到数据的方面,我们差不多可以认为是运维能够配置的方式,但是又不完全是能够配置,用那些辅助脚本来配置。

服务伸缩是运维工程,我们不希望在服务伸缩时还考虑别的因素,独立于研发的变更发布。

我们有个特点,研发是通过运维提供的变更系统,他在上面基本上不需要做什么操作,整条链就打通了,运维不用关心变更发布,只需要管好服务伸缩就好。

数据存储规范

数据存储规范如下:

  • 接入层,不带数据。
  • 逻辑层,带短周期 cache、带静态数据、禁止动态数据落地。
  • 存储层,带长周期 cache、基于 paxos 协议的数据落地。

接入层和逻辑层的服务伸缩,无需考虑数据迁移和 cache 命中率。

数据存储方面,单独拎一页出来会觉得这个场景是比较多人中招的,比如接入层肯定不会带什么数据的,逻辑层我希望它是不带数据的,而且我们也严格要求不带数据的。

经常出现这样的场景,他的逻辑层要自动伸缩,跑了一段时间上线了一些逻辑在上面保存了一些数据,在下面做缩容的时候是不是就中招了,会有这个问题,所以我们会定这个规范。

逻辑层是不带数据的,会有静态数据,包括用户发的消息、用户发的朋友圈,很明显应该放到存储层的。

接入层不带数据,其实历史上我们也有一次是带了数据的,在每次过年抢红包的时候,所有人都在摇的那一把,那个量是非常恐怖的。

摇红包那个点我们做设计是每个摇红包的请求真的打到我们接入层,不会有客户端摇五次才有一次请求上来。

我们的接入层的性能保证我们能抗住这个量,但是后面的逻辑层肯定撑不了这个量,1 秒钟几千万。

这个时候我们会做一些很特殊的逻辑,在接入层直接带上红包数据,当然这是比较特殊的场景,我们只在过年的时候这个规范是没有执行的。

运营规范小结

运营规范小结:

  • 阶段目标

服务可运维

  • 落实措施

变更系统拦截

全网扫描不规范的服务

这里说了几点,我们的目标简单来说就是服务可运维,可运维的意思就是你做扩容缩容的时候不需要做人工的操作,如果做人工的操作就是不可运维。

为了实现服务可运维,我们在变更系统里做了一些拦截,它接下来变更可能不符合我们运营规范或者之前有不符合我们运营规范的地方,我们都会拦下来,要求变更,实现我们的运维规范。

云化管理

接下来说一下云,大家用云也挺多的,近几年也比较火,可能大家用得比较多的是 Docker,微信这边我们没有用 Docker,后面也会说到为什么没有用的原因。

为什么上云

上云的两点原因:

微服务总数近 5k。

同物理机上多服务的资源抢占问题。

先说为什么要上云,在 2013 年、2014 年时,我们的微服务已经到差不多 5000 个。

我是近几年才知道这个叫微服务,我们之前实现方式已经是好多年都算微服务的方式了。

最开始说微服务这个事情的时候,我记得是 QQ 那边海量运营的一些课程都会提到,我觉得这个思路跟微服务是完全一致的。

我们运维这边在实现了一套新服务发布的时候,基本上不会给研发有什么限制说你这个服务不要搞太多。

所以整个系统搞下来那个量是比较夸张的,当然就会出现他的多个服务要在同一台机上部署,因为里面有 5000 个微服务,一定要同物理机部署。部署多个服务就会有资源抢占,基于这个因素,我们就上云。

哪部分上云

哪部分上云:

  • 接入层,独占物理机、 容量充足、 变更少。
  • 逻辑层,混合部署、 容量不可控、变更频繁。
  • 存储层,独占物理机、容量可控、变更少。

哪一块上云,刚才也说到,接入层因为要扛住比较猛的量,微信的用户也比较多,如果它有什么问题,可能接入层会雪崩。

所以我们主要是独占物理机,容量足,变更也少,暂时没有上云的需求。

在逻辑层这里,之前提到 5000 多个微服务,所以比较混乱,变更比较多,容量也不可控,所以这块上了云。

存储层这里,也没有上云,但是有单独的容量管理和数据迁移。

基于 Cgroup 的云化

基于 Cgroup 的云化:

  • 虚拟机型定制

VC11 = 1 个 cpu 核 + 1G 内存

VC24 = 2 个 cpu 核 + 4G 内存

  • 物理机分片

我们云的方式是直接 Cgroup,Cgroup 这个名字可能有些人不知道,我们用的是内核 Cgroup 这种机制。

在现网的时候,用类似简单的虚拟机型定制,比如 1 个 cpu+1G 内存。

前期我们也有一些流量因素的考虑,后来把它给取消掉了,我们系统架构上保证的那个流量好像是不怎么会成为问题的。

这里简单列了一下虚拟机分片的方式,怎么隔的方式都有,我们隔的这么随意也带来另外一个问题,系统运转过程中会有一些类似于磁盘碎片的东西存在,就好像你 Windows 跑了很久以后也会有磁盘碎片存在一样。

这个我们也有一些比较特殊的方法来处理。

线上未启用 Docker

我们没有用 Docker 的几点原因:

  • svrkit 框架 100% 覆盖全网,已实现标准化规范化。
  • 框架本身大量依赖 IPC 交互。
  • 自研非侵入式 vs Docker 侵入式。

Docker 太火了,我们差不多在 2014 年、2015 年的时候,线上也少量上线了一轮。

我们这个 svrkit 框架是我们微信内部自研的一套框架,100% 覆盖全网。

100% 什么意思?一点开源代码都没有,包括前面大家可能用得最多的 Nginx 做接入,这个我们也是在自研上切换的,包括后面的存储,前期用了 MySQL,后期也都换成自己的组件了。

现网用我们自己的框架 100% 覆盖了,我们的标准化和规范化都是比较好的。

可以说Docker解决的一些问题在我们这里不是问题:

  • 我们对 Docker 的需求不是很强烈。
  • 框架本身我们用了很多 IPC 交互,这些东西在 Docker 里都做了很严格的规范,如果我们用 Docker,可能会把这些 Docker 里的各种机制破坏掉。如果是这种情况,还不如不用 Docker。
  • 我们对 Docker 这种接入方式的实现还是有顾虑,早一两年有人跟我讨论比较多,Docker 主进程起来的时候,可能由于要更新 Docker 本身,会导致服务重启。

好像近期已经解决这个问题,这个在早期来看我们认为是一个比较严重的问题,我不希望说因为 Docker 本身的变更,对线上的服务有任何影响。

私有云调度系统

基于上面的点,我们自研了一套云化管理的 Docker 系统。

上图是我们私有云调度系统:

  • 基于 svrkit 框架自研。
  • 参考 borg/yarn/k8s/mesos 等主流调度系统的优点。
  • 覆盖 80% 的微服务。

基于我前面提到的 svrkit 框架+自研,调度系统当然也要用我们自己的调度系统覆盖,目前覆盖了 80% 的微服务,还差一点点 100% 覆盖。

私有云调度系统架构

这是我们的架构,熟悉的人一看就知道跟业界的事情没有什么区别,中间有一些虚拟化的坑,可以认为跟大家用得没有太大区别,不细讲了。

云化管理小结

云化管理小结:

  • 阶段目标

服务间资源隔离

服务伸缩页面化操作

  • 落实措施

部署系统拦截未上云业务

主动改造核心业务

在云化管理这一块,我们实现的一个目标就是资源隔离,Docker 服务之间不要因为一个服务异常搞的第二个服务也异常。

第二是服务伸缩页面化操作,有些服务还是很庞大的,一个服务下几千上万台都可能,这种时候不希望你上机器的,在页面上点就可以了。

我们为了落实这个目标,会把部署系统,把那些没有上云的拦住,他要走旧的部署方式上线,他需要走旧的流程才能走旧的商业模式。

前面这些运营规范和云化管理,相信大家多多少少都会有自己的实现方式,也都会有自己的一些总结。后面容量这块不确定大家都是怎么改的。

容量管理

如何支撑业务发展

这是一个业务量增长的曲线,也是我们容量的曲线。

一般来说你扩了一次容就一直平衡在这里,有一个点你发现撑不住了,要扩容,扩上去,就一直保持这个状态,跟容量曲线是不怎么匹配的。

第一个点是你这个容量低于现网的业务量的时候,这其实是容量不足的,能不能很快发现这个问题。

第二个点是处理效率,容量不足的时候把曲线打上去,扩容需要多长时间,如果是几天或者几分钟,这个效率是不一样的。

我们希望实现一个最优容量的方式,它跟业务增长完全是匹配的方式。

这个曲线并不很难实现,只要扩容够频繁,跟这个就是完全吻合的最优容量的曲线。

你发现它容量不足是分钟级的,扩容也是分钟级的,那你完全可以描出这么一套一模一样的曲线出来。

使用硬件指标评估容量

我们会说你怎么知道这个服务的容量不足的?一般我们第一个反应就是用硬件指标,包括 CPU 使用率是多少,磁盘空间多少,网卡容量、内存。这也能解决到问题,但它不能解决所有问题。

使用 CPU 评估容量

服务容量 = 当前峰值/经验 CPU 上限,这是一个使用 CPU 来算服务容量的方式。

这是一个简单的例子,比如你现在 CPU 当前峰值 40%,你自己的经验觉得这个能达到 80% 的 CPU,简单除下就是 50% 的容量。

硬件指标是否可靠

这东西靠谱吗?我这里画了两个示意图:

  • 有些类似左边这张图是靠谱的,容量基本上和 CPU 是保持增长的。
  • 有些类似右边这张窄口瓶图,CPU 涨到一定点的时候,容量就上不去。

真实案例

这是现网打流量的例子,人为把流量打上去,会发现有些服务怎么打都是在 80%;有些怎么都是 60%,上不去。

硬件指标的局限性

硬件指标我们认为有一些局限性:

不同服务它依赖硬件的类型,有些是被 CPU 限制,有些是被内存限制。

临界点的质量无法预测,当 CPU 接近 80% 或者 100% 这个点的时候,我们观察到有些服务的质量没办法预测,不稳定,有些服务能够跑到 80%,CPU 还能稳定服务很长时间,有些一到 80%,可能有一些请求就不返回了。

确实,线上服务多了以后,做的事情不大可控。我们觉得应该要通过压测,才能比较准确的得到容量的模型。

压测方式

压侧方式分两种:

  • 一种是模拟流量
  • 一种真实流量

环境也是分两种:

  • 测试环境
  • 现网环境

四种压测方式:

  • 模拟流量在测试环境打的时候,测试团队内部对质量的一些验证。这种测试方式是测试团队负责的,我们没有太参与。
  • 模拟流量打现网有点类似于全链路压测,比如淘宝在双十一经常这么干。对微信来说,我们只在过年这么干,微信过年好像跟淘宝双十一差不多,都是一个比较疯狂的状态,所以我们会在过年前用模拟的流量打一下现网,这个不是一个很常见的压测方式。
  • 真实流量往测试环境打,一般是我们要验证一些存储性能时会这么干,把线上的流量旁路打到测试环境里面,看一下这些存储性能的情况,可以在测试环境里比较好的模拟出来,不会影响到现网。
  • 真实流量打现网环境,我这里主要想说的,我们真的在现网压测,用真实的流量打现网环境是什么状况。

现网压测

现网压测实现起来非常简单,当你名字服务这块实现比较标准的话,都是统一的流程,就是压侧流程,不停的调其中一个服务的权重,观察它是什么后果就行了。

现网压测可能导致的异常

这样压有什么问题,大家都能想到,你压那个服务,什么时候停,怎么能保证不要搞出事来。

我们认为这里有以下三点比较关键:

  • 压测会不会引发故障,你能不能及时发现。
  • 压测有没有可能带来一些底层的问题,其实你的监控是发现不了的。
  • 真的出故障以后,你怎么能快速恢复。

服务的自我保护

你的各个服务有没有做好自我保护。我们引入了一个快速拒绝的概念,这个服务正常的时候,可能你的上线每分钟 100 万个请求也是能正常服务的。

上游服务给你 500 万的时候,你只能处理 100 万,你能不能把其他 400 万个请求拒绝掉,这是我们框架实现的能力。

上游服务的重试保护

上游服务重试保护是怎么样的?比如你在压其中一个实例的时候,你一直加大它的权重,导致它快速拒绝返回失败了,那你有没有一个机制走到其他的实例把整个流程跑完,这也是一个比较关键的点,这个我们也是在框架里支持了。

立体化监控

监控体系够不够完善,包括硬件的监控,刚才提到快速拒绝的监控,还有前端跟后端这种耗时的监控,还有刚才提到的前面这种,有没有发生失败,整条线都是我们需要关注的。

可以这么说,有了服务的自我保护、上游服务的重试保护、立体化监控,我们才敢压测,如果这三个点搞不定,压测这个事情做不了。

秒级监控

能不能快的发现你的异常?为了这个问题,我们把分钟级的监控升级成了秒级的监控,所有的异常差不多都是 10 秒钟之内就能发现。

整个实现的方式大家应该都差不多,每台机都有个采集的,之前是每分钟上报一次到队列,然后再汇总,然后再入库。

现在我们改了这个逻辑,6 秒钟做一次采集,然后数据转化,然后做一个秒级数据的汇总。上面还是分钟级的。

放量速率动态控制

这里还有一个速率的动态控制,这里比较复杂,不知道怎么解释这种情况。

左边这张图是我们比较早期压测的实现方式,它从一个点开始,可能用一个比较快速的调整它权重的方式,直到调用失败出来了,然后再回退,再继续往上压,不停往上不往下,用这种方式来接近你的容量上限。

这个点大家很明显看到一个问题,运维在压测的时候其实是给自己找事,每次压测都打上来,又掉下去,打上来。

失败曲线就像狗啃的一样,一直有抖来抖去的曲线。这种压测方式,运维自己都受不了。

后面我们调了一下,其实这个也跟我们服务框架提供的能力相关,我们整个服务框架会有一个入队/出队的机制,每个微服务本身都会有这样的机制。

这里队列的积压情况是比较关键的指标,我们会监控入队延迟,它出队,发现它一有积压,性能已经跟不上了,我们通过这种指标来调整压测的速率,大概实现的效果是右边这张图。

前期是比较快的权重,当观察到队列已经有积压有延迟,就开始放缓,一直达到一个点,可能最后那个点是有一点点压测失败,实现这么一个效果,整个过程中可能只有几秒钟,就得到性能模型,得到压测真实的值。

现网压测效果

这是我们真实的线上压缩的结果。这张图打出来的斜率是先涨得比较快,后期再慢慢增长,然后把这个压测点停掉了。

为了打出这张图的效果,我们还搞了蛮久的,可能大概一年多才达到这种现状,基本上不会给现网服务带来失败的。

容量管理小结

这个事情我们做完了,觉得有几方面的收益:

  • 服务的资源需求可准确量化,刚才提到几百微服务,每个是怎么算出来的。
  • 可确认服务的最优机型,怎么知道你这个微服务适合于哪种机型?有些服务根据我们压测会发现它有些场景跟别人不一样,我们只要把自动化压测的方式实现了,每个模型试一下,压出来你知道它最优的机型是怎么样的。

自动调度

业务增长的自动扩容

解决业务增长的自动扩容,因为你的业务量在涨,你的用户的请求包括各种请求量都是跟着指标在涨的。

你知道业务量怎么增长,知道微服务的增长曲线,前面的压测又知道每个实例的性能怎么样,你就能够准确知道我现在容量大概比例是在哪条线上。

我们一般让它跑在 50%、60% 这个区间内,66% 是一个容灾的预留,我们先部署三个 IDC 里面,你要想挂掉两个,另外一个也不会有什么异常的话,可能就是这两个限。

我们会留出一些时间给采购机器给我们,会把一些服务的流量控制在50%这个点。

异常情况的自动扩容

还有一些异常的情况:

  • 业务量的突增,这种是某些产品搞活动没有通知我们,我们会用一些粗暴的方式来解决,包括说 CPU,我们会给它定一个点,当你的 CPU 冲过这个点的时候,我们会自动发起扩容。
  • 程序性能下降,你业务没有太大有变化,但你程序变了,这其实也会导致容量不 ok。这个点我们是能够监测到这种情况的,就看我们压测的频率有多频繁,如果你每天都压测,你可以把压测的曲线描出来。

程序性能下降的合理性评估

我们差不多有些服务是 2015 年每天都在压,每一天他的性能是怎么样的,我们描出一条曲线出来。

可能会发现有一些时间点性能下降比较远,比如这里的例子是他发了个大版本,特性里面增加了一些处理逻辑,所以性能下降比较明显。

这个事情可能过了一两个月以后,有些同事出来解决 fix 它,这种性能监控我认为是前面的容量管理和智能交互这一块的副产品,这个副产品我们用得还比较多的,可以准确知道整个微服务性能变化怎么样。

性能管理闭环

能不能不要让它出现这种新的下降的点,我们每天都在压,能不能再快一点,直接在它上线的时候直接拦住。

比如说开发同学灰度上了一个台机,我们马上压测它灰度上线的这个机器,看它的性能怎么样,如果发现它的性能下降,就把他拉住。

几种业务形态

这是我们现网的一些业务形态,可能比较多的就是最上面这种图,它在晚上 9 点到 10 点迎来它的最高峰。中间这种图一般跟工作相关的如微信,工作时间段用得比较多的。

还有一种是最底下这种,比较典型的例子是微信活动,微信活动好像是晚上 22 点的时候会来一步。

还有一些比较古老的业务,漂流瓶,用得脑残粉还挺多的,他们会守着 0 点的时候。

削峰填谷

我们对这些业务曲线希望做什么?

  • 峰那么高,我们所有的设备都是为了应付最高的那个峰,这个峰能不能做点特殊处理,不给它们危险设备。
  • 晚上零点以后,这么低的业务量,设备挺浪费的。我们希望有一个削峰填谷的作用,原理还是很简单,就是把你的有些服务跟另外一些服务的峰值错开。有些服务高峰期过了,把它的资源放弃出来,至于谁去用它不管。

在线业务削峰

在线业务削峰:

  • 常规服务,高峰期后释放资源。
  • 错峰服务,获取资源。

离线计算填谷

离线计算填谷:

  • 离线任务的运行时段

01:00 ~ 08:00 不限任务

08:00 ~ 20:00 任务入队控制

  • 离线任务的资源占用

cpu.shares + memory.limit_in_bytes + blkio

离线任务的优先级最低

离线计算,凌晨那段时间确实都很闲。这个时候比较适合离线计算来调度,微信群之前离线计算的东西比较少,主要是语音输入等一些人工智能方面的东西。

最近可能有一些看一看、搜一搜的新功能,他们用的离线计算的调度还挺多的。

资源占用方面,这几个场所的配置基本上是 Cgroup 控制得比较严格一些。然后把离线任务的优先级调一下,用离线任务来把我们低峰的谷填平了。

自动调度小结

这在自动调度这块是我们实现的一个目标:

  • 全盘控制所有的在线服务,充分利用资源。
  • 离线任务这块不用单独申请计算资源,直接用我们在线上的一些 CPU 内存就好,存储单独去说。

吴磊,微信基础平台运维负责人,2010 年加入 QQ 邮箱运维团队,从微信第一个版本开始全程支撑从 0 到数亿同时在线的野蛮生长。目前负责消息收发、朋友圈等基础业务运维,专注自动化运维系统建设。