返回 登录
0

日均请求量百亿级数据处理平台的容器云实践

阅读4427

声明:本文为CSDN原创投稿文章,未经许可,禁止任何形式的转载。
作者:袁晓沛,目前在七牛云的主要工作是基于容器平台构建分布式应用,借助容器的优势,实现大规模分布式应用的自动化运维以及高可用,以PaaS服务的形式提供服务器后端应用,同时致力于让开发者从繁琐的后端运维工作中解放出来。曾参与盛大网盘EverBox、EMC备份服务Mozy后端存储的设计、开发工作,主要方向在分布式系统的架构设计、开发、性能调优以及后期运维优化。
责编:钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件qianshg@csdn.net,另有「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信qshuguang2008申请入群,备注姓名+公司+职位。

七牛云研发架构师袁晓沛日前结合七牛云自定义数据处理平台业务的容器化实践,从平台的业务特点、为什么容器化、如何实现容器化以及容器实践的具体效果等角度进行了分享。以下是对他演讲内容的整理:

数据处理业务简介

数据主要有三种处理方式:

  1. 官方数据处理:
    提供基础的数据处理服务,包括但不限于图片的转码、水印、原图保护、防盗链等,以及音视频的转码、切片和拼接等。

  2. 自定义数据处理:
    允许用户构建、上传自定义的私有数据处理服务,并无缝对接七牛云存储上的数据以及其他数据处理服务。

  3. 第三方数据处理:
    一个开放的应用平台,提供大量功能丰富的第三方数据处理服务,比如图片鉴黄、人脸识别、广告过滤、语言翻译、TTS 等。

图片描述

图 1

图1是数据处理的一个调用示例。无论是七牛云的官方数据处理,还是自定义数据处理,或者第三方数据处理,都是以这种形式调用的。最左边的是一个URL,代表一个文件,中间绿色的Facecrop是数据处理命令,右边的200×200是请求参数。

图片描述

图 2

如图2所示,从左边的人物肖像图原图到右边200×200的小图片,这个服务可以把人脸从原图中剪裁出来。通过管道操作,还可以把图片再存到存储中,以后直接使用这个小图,不需要再走数据处理计算。这是一个典型的数据处理调用。

官方数据处理的挑战

第一,请求量非常大。目前,系统每天有近百亿的数据处理请求量。年底,可能会在目前近千台的计算集群基础上翻好几倍,整个存量、增量都非常大;

第二,突发流量非常频繁。很多客户是从其它云迁到七牛云,首次把大量文件迁移到七牛存储后,往往会发起大量的数据处理请求,这会带来大量突发流量;

第三,CPU密集型计算。目前后端集群中,绝大部分的机器都是用来跑图片、音视频转码的,这些都是CPU密集型的计算,这意味着后台需要很多台机器,而且CPU的核数越多越好;

第四,IO操作频繁。IO分为磁盘IO和网络IO,数据处理前,往往需要先把原始文件从七牛存储中下载到本地磁盘,所以磁盘IO和网络IO都很频繁。

官方数据处理的架构演化

图片描述

图 3

图3是数据处理早期的一个架构图。当时比较简单,所有请求都经过FopGate,每个节点上都部署了很多图片、音视频或者文档处理的Worker,是具体做转码的计算服务。整个架构,Worker在网关上是静态配置,增加新的Worker时需要改网关配置重新加载,做起来会很麻烦。另外,请求进来时,对应要处理的数据也是通过网关服务进入,控制流和数据流放在一起,网关整体压力非常大,当时出了很多问题。

图片描述

图 4

图4是数据处理目前的主体架构。右图左上角增加了一个Discovery组件,收集Agent上报的信息,Agent和Worker把自己能做什么事都上报到Discovery。每增加新的主机Agent和Worker时,不再需要静态配置,只需指向的该Discovery服务。网关可以通过Discovery获取信息,根据进入的具体请求,将这个请求路由到不同的Agent,Agent再把请求转到Worker处理。Agent组件还有负载均衡的作用,请求会选择后端相应的节点执行,另外也起到下载文件的作用。下载文件的数据流不经过网关服务,Worker向Agent发起一个下载请求,然后这个Agent到存储上下载数据,放到自己的缓存中。

这个架构解决了一些问题,但是之前提及的挑战依然存在。主要的问题是FopGate在过载时依然会崩溃,每个主机会过载出问题,造成请求变慢或宕机。接下来讨论一下如何解决这些问题。

如何应对官方数据处理的挑战

系统测量

第一,测量FopGate的服务能力。按照线上的配置,针对同比例缩小的集群做性能测试,测试出FopGate单机最大的请求数和句柄数,根据实际的业务量,确定机器配置和数量。

第二,测量某种数据处理的资源使用范式。大多数据处理是CPU密集型计算,我们需要找到单个处理的资源使用规律。测试方式如下:取线上约10万或100万个请求,针对一台测试机器压力测试。比如测Image,测试这台机器的变量有以下三个:Image 实例数,并发处理数和队列长度。最终拿到的一个结果是,做图片剪裁、图片处理、图片转码等服务,一个进程一个CPU的逻辑核,同时处理一个任务,对它来说是最快的,多个任务同时处理反倒会让CPU的处理变慢。

第三,测量单实例的服务能力。一个程序实现得很好,可以把多核利用起来。针对这样的实例,分配多个CPU线程、调大并发、并压测这个的实例,试图用10万个请求看整体处理速度,发现整体处理速度并没有起多个实例,但每个实例限制CPU线程数、限制并发好。所以最终的结论是操作系统对于CPU的调度,比进程要好;相比起一个大实例、接受高并发请求,我们更倾向于运行多个实例、但每个实例限制并发。

增加队列

这个主要的考虑是提高服务质量,避免单实例过载。前面得到结论,一个图片处理的实例,一个核,并发量为1时最快。我们为每台节点机器加了一个排队机制,排队之后不争抢资源,整体处理速度反而变快。

另外,从运营角度讲,可以用队列长度来区分免费用户和付费用户。比如,可以将免费用户的排队长度设置得长一点,比如设成10,意味着最后一个请求要等前面9个请求处理完才能处理。而对于付费用户,可以将队列长度调短一点,只有3或者5,这样平均等待时间就变短。总原则是区别付费用户和免费用户,免费用户保证高可用,但是不能保证高质量和低延迟,因为资源有限;对于付费用户可以保证高质量,因为可以通过排队长度控制这个事情。

限流

为什么有了队列之后还要限流?两个原因:

第一,大量长链接影响FopGate性能。因为七牛云处理最多的是图片、音频和视频三种请求。图片请求往往比较快,高峰期时可能几秒、几十毫秒完成,但是视频和音频转码往往比较慢,可能需要好几分钟或者好几十分钟,它取决于一个具体的转码参数和转码时长。如果不限流,网关会维持大量的长链接,累积大量句柄,轻则影响的性能,严重的情况下会造成宕机。

第二,突发流量,导致队列过长。队列太长,有大量任务积压的情况下,会有任务在处理之前就超时。与其超时,还不如直接限流,拒绝处理不掉的请求。

限流手段有三种:

第一,并发HTTP请求限制。超过这个限制,直接拒绝请求并返回。但这并不是最好的方式,因为超时的请求已经解析处理过,这对于性能是有一定损耗的。最好的做法是用一个信号量控制TCP accept,比如控制信号量个数是1万个,1万个请求正在并发处理时就不accept 。这是最好的方式,但实现略复杂。

第二,单用户请求数限制。有些用户可能会在特殊情况下发起大量的请求,为了不让他影响别的用户,系统会限制单个用户的请求量。

第三,Cmd数限制。主要指具体某个操作,比如图片查看,要对Cmd数进行限制。因为不能让大量同样的Cmd把资源消耗殆尽,影响其他Cmd的操作。

合理协调IO和CPU

为什么要合理协调?因为数据处理的流程是:下载、写盘、处理、写盘、返回,涉及到网络IO和磁盘IO。整个优化方式总原则是就近计算,将下载和计算部署在一起。目前,新的架构中是混合部署的。本来可以设计,将下载部署在其他机器上,但这样会增加一次网络的路由次数。所以,从一台机器的Agent下载后,直接在本机处理,不会再经过一次网络路由。另外一种方式是挂载ramfs,直接把下载内容放在内存中。比如内存分8G,下载完成后直接放在内存中,要用的时候直接进内存读,这样可以节约磁盘IO的开销,也能整体加快单个请求处理速度。

前面这些是官方数据处理的一些挑战、架构演化以及我们采取的一些对策,后面讲一下自定义数据处理所面临的挑战。

自定义数据处理的挑战

第一,处理程序由客户提供,我们不知道客户在程序里做了什么事情,也不知道客户的程序会使用多少资源,这意味着我们至少要做两点:一是安全性,不能访问到别的资源,二是隔离性,单个程序不能无限制使用资源。

第二,业务规模不确定性,无法估算量有多大。这个带来的挑战是业务可伸缩性,需要客户可控。

基于这三个需求:安全性、隔离性、可伸缩性,Docker非常适合这个业务场景,整个自定义数据处理在14年开始基于Docker实现。

图片描述

图 5

图5是自定义数据处理的业务流程:

第一,用户要创建一个自定义数据处理,即注册一个UFOP;

第二,用户在本机上开发自定义数据处理程序。开发这个程序要遵循七牛UFOP范式;

第三,把这个程序提交一个构建版本;

第四,切换到这个版本,设置实例数。实例启动后,该UFOP就可以访问了。

这是迭代的,有一个箭头直接指回开发自定义数据处理程序,意味着升级程序、构建另一个新版本、切换到新版本实例的一个过程。

业务流程-注册

图片描述

图 6

图6是注册的过程,第一个qufopctl是客户端工具,第二个是reg注册,第三个ufop-demo是名字,最后-m 2是模式,支持私有和公有模式。

图片描述

图 7

图7是自定义数据处理后端注册的流程,qufopctl把前面描述的注册命令,发到ufop-controller,它会进一步走鉴权服务,鉴权成功之后通过Keeper服务存盘到DB。注册成功后,用户开始在本地实现UFOP应用。

图片描述

图 8

图8左边是一个最小的UFOP代码,简单来说就是在UFOP请求路由上加一个UFOP请求,拿到待处理文件URL后下载对应文件,然后获取文件的类型和长度,并作为结果返回。右上角是我们定义的一个描述性文件。2014 年,我们做的时候Docker还没有那么火,所以没有直接用Dockerfile,而是自己定义描述模式。第一行是引用源;第二行是构建脚本,把外面的程序改成可写;第三是执行,怎么执行这个程序。右下角是要打包成tar包的本地目录。

业务流程-构建

图片描述

图 9

如图9所示为构建命令,是把本地的程序上传到服务端,并构建一个版本。

图片描述

图 10

图10是构建的后端。整个后端的过程如下:qufopctl把程序tar包上传到Kodo,发起构建请求,再把构建请求转发到构建机器,接着把UFOP描述文件转化为Dockerfile,基于Dockerfile构建Docker image,最后推送到Docker Registry。

下面是构建后端我们踩过的一些小坑。

使用Debian镜像服务

原先使用AppRox,是Debian包下载、缓存服务,但实际上经常下载超时,而且下载出错后,自己没办法辨别本地文件存在问题,需要手动清除,比较糟糕。后来,搭建了一个镜像源。镜像源的做法是首次全量下载,全部下载完毕后,设置一个定时增量同步的时间,在凌晨,一天一次,每天更新的量只有几十兆,几分钟便可完成。从此,再也没有出现过这方面的问题。

避免Docker构建缓存

图片描述

图 11

Docker的构建缓存比较容易出错。图11第一个是错误用法:第一步,下载一个jdk的压缩包;第二步,创建一个目录,将压缩包解压到这个目录,并且把原来的压缩包删除。分了两条命令,但是容器其实会对每一条命令做缓存。

假如我们第一次运行这两条命令时是没有问题的。第二次运行时,我们觉得第二条命令中OPT目录不好,想到另外一个目录,于是对第二条语句做了更改,那么就会出问题。为什么?因为第一条命令没有任何变化,所以容器构建系统认为,没有任何的变化就不去执行,于是直接跑第二条命令,第二条在前一次执行时,已经将压缩包删除,因此会跑失败。这种错误很容易犯,解决方法是把压缩包的下载、目录的创建、解压缩和删除压缩包,都放在同一条命令里。当这条命令被修改后,每次都会重新执行,就不会出现这个问题了。

调整实例数

图片描述

图 12

图片描述

图 13

接下来是调整实例数,前面已经构建完成,程序已经上传到服务器端。qufopctl resize ufop-demo -n 3,这条命令会增加3个实例。

整个后端的过程如图13所示,qufopclt发送请求到ufop-controller,中心调度器Scheduler下面有很多个node,运行着我们实现的Daemon服务,所有容器都是由Scheduler下发给某个Daemon,这些Daemon启动对应的容器。Daemon会把所在机器剩余资源的情况,包括CPU、内存和磁盘,实时报给Scheduler,Scheduler会根据实例的规格要求找到相应满足要求的node。这些容器实例创建成功后,Daemon会把具体的端口信息返回到Scheduler,Scheduler再通过Keeper服务持久化,这样就结束了。Docker Registry是Docker的镜像仓库。

升级实例

图片描述

图 14

到前面这一步,UFOP已经运行起来并且可以使用,如图14所示为升级实例的过程。我们有一个Upgrade的命令,-r是一个实例,现在要做的是升级前两个实例,后端会发生什么事情呢?

图片描述

图 15

图15所示为一个灰度升级阶段。首先原始阶段有三个实例,假设每个版本都是V1,现在要升级两个实例,后端的做法是增加两个实例,实例4和实例5,这两个对应的版本是V2,图15中橙色部分。等V4和V5彻底被启动后,我们将老的实例删除,它的结果是原来的一个V1, 两个V2,整体灰度升级过程就是这样子。

有些细节值得注意:

第一,新实例WarmUp,往往刚开始很多内部组件没有初始化,像内存池、线程池、连接池需要初始化,所以初始请求处理得往往比较慢,但是又不能不发请求,因为不发请求永远热不起来。做法是设定预热时间段,定时间段的权重,保证在这个时间段预热好之后就承担正常的流量。

第二,老实例CoolDown。如果直接删除老实例,对服务的可用性没有影响,因为老的请求正在被处理。做法是:使用Docker StopWait,当用Docker Stop停止一个容器时,它默认的行为是先发一个SIGTERM,如果不指定StopWait时间,就会马上发一个SIGKILL信号,这样你的容器将被直接杀死。若一个容器正在处理请求,那么我们希望能有一段时间让我们将请求处理完后,优雅的关闭,否则会影响可用性。所以使用这个命令时,可以设置StopWait时间,若等待超过设置的时间,却发现容器没有优雅关闭,再发SIGKILL信号。另外在业务层面,老实例在CoolDown时,应该设成一个关闭中的状态,避免新的请求再打进去。

第三,保留足够的计算冗余。这个是如何考虑的呢?因为升级需要考虑步长,如果升级的步长大于计算的冗余实例数,意味着剩余的等待实例会承担更多的请求,多于预期的请求会影响服务的质量,因为请求的时间会变长。所以选用的升级步长应该小于冗余的实例数。

数据流

图片描述

图 16

刚才讲的是控制流,包括如何注册、如何创建一个新的实例。而这些数据流的请求过来是什么样的?这涉及到一些内部组件,即云存储的服务器。服务器后面有数据处理,自定义数据处理会从之前说的状态服务中获取某一个后端实例列表,由它做负载均衡传给后端。在后端数据流的维度上,每台机器还有一个Fetcher,它接收到请求后会把请求路由到本机,并执行实例。同时,它会做数据下载的工作,我们出于对用户隐私安全的考虑做了一些混淆,不允许他直接下载,而是转化成本地的。这是数据流的流程。右边有一个组件叫DiskCache,是一个缓存集群。

图片描述

图 17

图17是数据链路的V2版本,是一个思路,还没有完全实现。唯一的改动是在实例之间加了一个队列,当然这个队列是可选的。当前所有请求都放在这个队列中,由UFOP实例获取请求,处理完成后将结果返回,这是一步处理的过程。这个队列有什么作用?前面提到,调整实例可以做手动伸缩,没有说自动伸缩。而这个队列可以做自动伸缩。

自动伸缩设置

自动伸缩需要用户做一些配置,一个是默认的实例数,也就是第一次用户上传一个自定义数据处理的版本,上传完成后调整实例数,调整成10个,还要设置一个平均单实例待处理任务数的配置,假如,这个设置是10,当队列里面有小于100个待处理任务数时是不需要伸缩的,但是当队列里面的任务数大于100,比如到110就要伸缩了,因为单个实例平均待处理的任务数已经超过,这个时候再增加一个实例是最适合的选择。通过对队列的监控,可以达到自动伸缩。

自动伸缩的后端

图片描述

图 18

图18是自动伸缩的后端。最上面中间有一个Scaler组件,会实时监控队列长度,拿到每个队列的配置,即平均单任务待处理任务数。它根据这两个信息,可以实时地发出调整实例的请求,比如平均单任务处理数超过预想时,它可以自动增加一个实例,并告诉调度器,调度器会找相应的节点启动实例,启动后告诉Keeper,Keeper会把这些信息记录下来。回到前面的数据流,及时从里面获取信息,达到自动伸缩的目的。

如何应对自定义数据处理的挑战

解决方案:

第一,安全性。这一部分做法比较简单。自定义数据处理单个物理机上的容器数约几十个,这与CPU的核数有关。可以设置某个范围的端口不互访,以达到容器不能互访的目的,从而获得安全性;

第二,隔离性。可以限定某一个容器只用指定配额的CPU和内存,这取决于用户的配置;

第三,可伸缩性。首先实现了一个简单高效的容器调度系统,它是支持秒级伸缩的;其次是暴露伸缩API,用户可以手动伸缩达到伸缩目的;最后是利用队列长度,达到自动伸缩的目的。


2016年9月22日-23日,SDCC 2016大数据技术&架构实战峰会将在杭州举行,两场峰会大牛讲师来自阿里、京东、苏宁、唯品会、美团点评、游族、饿了么、有赞、Echo等知名互联网公司,共同探讨海量数据下的应用监控系统建设、异常检测的算法和实现、大数据基础架构实践、敏捷型数据平台的构建及应用、音频分析的机器学习算法应用,以及高可用/高并发/高性能系统架构设计、电商架构、分布式架构等话题与技术。
9月4日24点前仍处于最低六折优惠票价阶段,单场峰会(含餐)门票只需499元,5人以上团购或者购买两场峰会通票更有特惠,限时折扣,预购从速。(票务详情链接)。

评论