10 月 20 日 -22 日,由 InfoQ 主办的 QCon 2016 全球软件开发大会上海站已成功举办。作为云 CDN 行业的代表企业,又拍云受邀出席了本次大会。又拍云系统开发工程师叶靖在“微服务实践与架构演进之路”分论坛上,发表了“基于 Docker 的云处理服务:更新、扩容与自动容错”的主题演讲。(以下为演讲实录)
大家早上好,我叫叶靖,来自杭州又拍云科技有限公司。又拍云为客户提供场景化 CDN、云存储、云处理等服务,比如针对直播,我们提供又拍直播云解决方案。我在又拍云主要负责云处理这方面的业务,所以今天的分享也和云处理有关,主要介绍又拍云基于 Docker 的云处理服务平台,我将会着重分析我们平台是怎么做服务的更新,以及我们在处理能力不够的时候是怎么扩容的;或者是当我们一台服务出现故障时候,我们是怎样快速切掉的。
总结起来,其实我们想要解决的就是三个问题:
- 第一个问题:怎么做无宕机时间的更新——更新的时候,我的服务、我的请求不能出现任何的异常;
- 第二个问题:怎样做自动扩容以及故障恢复,换一种说法就是怎样可以方便地提升我的处理能力,以及可以快速摘除故障的机器;
- 第三个问题:如何方便的对请求进行改写,我们有一些需求是要对原始的请求进行一些请求的改写操作。
关键在路由
总结一下,要达到这三点,其实只要做一个东西就够了,那就是需要在云处理集群的最前面做路由。让这个路由支持无宕机时间的更新,让它支持方便进行扩容与故障摘除,也让它支持能够对请求进行改写。我今天主要是分享又拍云在做的一个东西。
首先从路由开始说起,说到服务路由,要涉及到三个概念:服务注册、服务发现和负载均衡,如下图:
服务注册就是我的容器需要主动上报我提供的服务的名字,以及所在 IP 端口这些信息;
服务发现是把服务注册的信息集中起来;
因为我们提供的容器非常多,还需要在容器之间做负载均衡。
服务发现有很多开源的工具可以用,比如 Consul、etcd 和 Apache Zookeeper。Consul 功能多一些,所以我们选型的时候选择它作为我们服务发现的工具。
在选型上,又拍云选了 Nginx,架构简图如图所示:
最下面是 Mesos,Mesos 上面跑的是容器,服务都是以容器提供的,有做图的,音频的,各种各样的服务,有一些是特殊的服务,比如可以用 Registrator 做注册服务,上报到 Consul,前面的 Nginx 主要是做负载均衡,通过 upstream 把请求代理到下面。在 Consul 中的服务地址改了以后,怎样快速反馈给 Nginx 呢?其实如果把这个解决了,这个图就可以非常好地运转起来。
这个方案要做得很简单,需要解决 Consul 的服务如何主动、方便更新到 Nginx。我们调研了一下,发现有这么几种解决方案:
第一种解决方案是用 Consul template,监听 Consul 中服务变动,当配置变动时,由模块重新生成配置,最后对 Nginx 进行 Reload 操作,让它生效。这里有一个例子,如图:
上图的框里面是一个配置的模块,中间两个括号括起来的都是一些需要被实例化的变量,当服务发生改变的时候,会生成真实的配置文件,然后用生成的真实的配置文件进行 reload 操作,Reload 是一个平滑的过程,不会对当前的请求造成任何影响,所以可以达到更新 Consul,并且把 Consul 的信息反馈给 Nginx 的目的,但是我们没有采用这个方案,因为缺点比较多。我们希望找到更好的办法,更优雅地改变 upstream,所以我们又想到第二个方案,用 DNS,这个方案非常好理解,用内部的 DNS 对 IP 进行解析,但是这个方案隐藏着更大的问题。
以上两个方案都不行,那我们想要什么方案?我们想让 Nginx 监听一个管理端口,可以通过对管理端口发指令,让它自己更新 Upstream,有没有这样的方案呢,其实是有的,下面是一个开源的 Nginx C 模块,它做的事情就是通过 HTTP 指令更新、操作、删除 upstream,如下图:
这里有个例子:
上图例子中,请求了 8080 端口,发现 502 了,因为这个时候,我们在 host 下面没有任何上游服务存在,最后给 127 的 8081 端口发送请求,要在 Nginx 动态加上两个 server 地址,他们对应的 host 就是 dyhost,第三个指令和第一个指令是完全一样的,但是结果不一样,它返回了正确的输出,并没有返回 502,说明这第二个指令中的两个地址已经生效了,这个已经完全达到了我们的目的,通过一个 HTTP 请求,改变 Nginx 的 upstram。
又拍云用了第三个方案大概两个月之后,感觉还是有一些问题困扰着我们,最终我们选择重新造轮子。为什么没有继续走下去,原因如下:
又拍云经验——造轮子
为了解决面临的问题,我们开始造自己的轮子。经过讨论,我们决定用 lua 建造一个符合我们需求的轮子。
介绍这个轮子之前,我简单介绍一下 Openresty 项目,它的原理就是把 Nginx、Nginx 的 lua 模块以及常用的 lua 代码,集合起来打包成一个项目,由一整个项目提供高性能 web 服务器。
下图是我们的动态负载均衡,它目前是已经开源的,由四部分组成,
- 第一部分就是 Nginx,官方的 Nginx,没有任何改动;
- 第二部分是 Nginx Lua 模块;
- 第三个部分是 lua resty checkups,它是把 Nginx upstream 模块常用的功能单独抽出来,用 lua 重新实现了一遍;
- 第四部分是 luacocket 是用于加载配置信息的。
我们造的这个轮子,有什么特点,有什么功能?
首先它是一个动态负载均衡,它的功能如下图:
第二个特点是我们以 host 区分服务,这里有一个例子,
如图我发了两个 CUR 请求,它的地址是一样的,都是 1.155 的 3130 端口,但是它的 host 不一样,最终这两个请求所到达的处理集群是不一样的,一个是拿图片的宽高信息,另一个是对图片进行缩略图等这些做图操作。
下图是我们请求的流程图:
从一开始接收请求到把 HTTP 里面的 host 解析出来,解析出来以后这里涉及到动态 lua 代码加载,当 host 解释出来以后,我们到 upstream 列表里面查后端有没有提供服务,如果有的话会进行转发。
在上图的状态里可以看到椭圆中那两个我们刚刚加进去的服务,因为有主动健康检查,如果加的是有问题的服务地址,会显示一个错误,比如我们第二个加进去的就是不存在的地址,然后这个服务就不会被 Nginx 选为可用的上游。
接下来介绍一下动态 lua 代码加载,为什么要做动态 lua 加载这个功能?
首先因为我们是一家 2B 的公司,需要方便地对客户的请求做改写;
需要执行简单的参数检查,节省带宽。
这些 lua 代码动态注入 Nginx 里,让 Nginx 执行。下图是动态 lua 加载的例子:
中间是 lua 的代码,它的意思是说我想要把 host 为 admin.upyun.com 的 DELETE 操作全部禁用,除此之外,任何你想象出来的操作都可以用 lua 代码实现,它的功能是非常强大的。我们 lua 代码也有状态页,当我们加入了 lua 代码,可以看到当前跑的 lua 代码的版本以及加载时间等信息。
上面内容讲的都是一些特性和功能,接下去主要介绍我们是怎么实现这些功能的,主要涉及到三个功能:动态的 upstream 管理怎么做、负载均衡和动态 lua 加载。
动态 upstream 是三个步骤:
- 启动的时候通过 luasocket 从 consul 加载配置文件;
- 第二步,加载完配置之后,Slardar 会启动一个管理端口,监听指令,
- 接着会启动一个定时器,进行 worker 间同步。
下图是一个 upstream 管理的流程图,最开始的时候,从 Consul 加载,一个是接收端口更新指令的,然后另外一个到定时器,这个定时器不断超时查看共享内存,,如果发现共享内存里的版本与当前进程不一致,就更新到进程内部。
在了解了怎样管理 upstream、怎样做 upstream 的同步后,我们还要在 upstream 之间做负载均衡,这个主要通过 lua 的指令实现,叫 balance by lua。
Nginx 的负载均衡可以分成两步:
- 第一步通过 upstream 的 C 模块选一个可用的后端服务的地址;
- 第二步,会把请求代理到上一步选的后端地址。
这个指令,它的作用就是作用在 upstream 选择这步的,比如我们写在 upstream 里面,就不会再进原来 C 模块的代码,而会到我们指令后面的 lua 代码里面来,这样可以在 lua 代码里面控制这些 upstream 的选择,因为我们有 lua 版的 checkups,所以这些操作可以很好的结合起来。
主要的代码就两步,如下图
第一步就是用我们 checkups 选一个可以用的 peer,根据什么选,就是根据当前请求的 host 选;第二步是从 Consul 或 HTTP 请求 body 加载代码。
目前在又拍云内部很多项目都已经用上了这套方案,我们有这么多优势,对于性能怎样,我们做了非常简单的对比,如图所示:
上图的对比有一个地方不一样,就是圈出来的地方,左边是用 lua 做 upstream 选择的,相应的 upstream 配置会有一些变化,右边是原生 Nginx 的配置。对这样的配置进行压测,压测的结果在 QPS 峰值上没有明显差距,所以用 lua 版方案和 C 方案在性能上的损耗其实可以忽略的,可以放心用它。
最近微服务非常火,我们也做了改造,如果我们把这样的架构改成微服务,Slardar 能做什么?我想我们服务肯定是多很多,IP 端口也多很多,那 Slardar 能做什么,举一个例子。
上图是作图服务拆分的示意图,如果我们不进行微服务改造的话就只有中间这个作图服务,它做了所有的工作、所有的缩略图操作。改造之后,这个大的作图,会被分解成一些小的作图模块,这些小的模块,各自提供服务,当然都是以容器的形式,比如做缩略图需要硬件加速,就可以把它抽出来了,可以做很多实例,水印的可能还是按原来老路子走,就是 CPU 做,也不需要太多实例,切开以后,发现中间大的服务,虽然是不需要任何功能的,因为它只要做调度,但是它需要从 Consul 里面拿到所有微服务的地址,并且还要去做一些负载均衡,因为地址有好多个,还有做容错,还得去做定时的更新,因为这个服务的地址,可能是会更新的,更新了以后要重新拉内部的地址,这样显得中间这个做图服务就会做了很多不该属于它做的工作。为了解决这样的问题,我们在中间又加了一层 slardar。
中间加了 slardar 后,上面的作图就不需要微服务的端口,直接给我们内网的 slardar,带上你需要访问的微服务的名字,这个名字带上以后,由内网的 slardar 做路由选择,里面会维护所有微服务的地址,它会做重试、做服务的更新,从这可以看出来,我们其实是把 Consul 里面一部分服务发现的工作,给它下移到我们 slardar 里面,Consul 可能会变成一个持久化存储的角色,可能是把服务发现的角色弱化掉了,这样的话,使内网服务的层级,看起来更加的清晰。
评论