2019 年 5 月 11 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙武汉站,又拍云首席布道师在活动上作了《 基于 OpenResty 的动态服务路由方案 》的分享。html
OpenResty x Open Talk 全国巡回沙龙是由 OpenResty 社区、又拍云发起,邀请业内资深的 OpenResty 技术专家,分享 OpenResty 实战经验,增进 OpenResty 使用者的交流与学习,推进 OpenResty 开源项目的发展。活动已前后在深圳、北京、武汉举办,后续还将陆续在上海、广州、杭州等城市巡回举办。node
邵海杨,又拍云首席布道师,运维总监,资深系统运维架构师,多年 CDN 行业架构设计、运维开发、团队管理相关经验,精通 Linux 系统及嵌入式系统,互联网高性能架构设计、CDN 加速、KVM 虚拟化及 OpenStack 云平台的研究,目前专一于容器及虚拟化技术在又拍云的私有云实践。git
如下是分享全文:github
今天和你们介绍一个基于 ngx_lua 的动态服务路由解决方案,它是整个容器化过程当中的组件,容器化在服务路由上有很大的挑战,又拍云经过本身的方案来实现了,而且已经稳定运行了三年左右。目前这个方案已经开源,若是你们后续也碰到同样的问题,能够直接使用这个方案。算法
在更新服务时,如何能作到让服务不断掉呢?又拍云作服务更新的时候,是不容许有失败的,若是由于咱们的更新失败致使请求失败,即便请求很是少,口碑上也会很差,并且若是形成了事故,是要赔钱的。这也是咱们作动态服务路由的重要缘由。数据库
服务路由主要包括如下几个部分:后端
服务发现有不少方案,可是它们的应用场景和语言都不太同样。Zookeeper 是一个比较老牌的开源项目,相对比较成熟,但对资源的要求比较高,是咱们最先使用的一个方案,包括咱们如今的 kafka、消息队列都是依赖 Zookeeper;etcd 和 Consul 是后起之秀,K8S 是依赖 etcd 的,etcd 在容器编排里面是依赖的;又拍云在服务注册和发现环节用了 Consul ,它是一站式的技术站,部署、可视化、维护等环节都比较方便,它不但支持 KV 存储,还有原生的服务监控、多数据中心、DNS 功能等。缓存
负载均衡也有不少方案, LVS 有一个优点是在作完前面两层后,若是性能很差能够再加一个 LVS,由于它在四层,更加底层,不会破坏原来的网络结构,可是它的扩展很是难。HA_PROXY 和 Nginx 各有千秋,HA_PROXY 对 HTTP 头部解析消耗的 CPU 更少,若是作纯转发,如 WAF 可使用 HA_PROXY,HA_PROXY 大概占 CPU 10% 左右 ,而 Nginx 作纯头部转发基本上是占 CPU 20%-25%,可是 Nginx 可扩展性更强,Nginx 能够作 TCP、UDP、HTTP 三种协议的转发和负载均衡,可是 HA_PROXY 只支持 TCP、HTTP。 HA_PROXY 最大的变化是它已经用 lua 重构,后续的发展也会与 lua 紧密结合,这至关因而又多了一种能力,它们也在拥抱 K8S 的生态圈。咱们的方案是选择了 Nginx ,由于它专一于作 HTTP ,扩展性好,支持 TCP。网络
如上图,咱们把 Nginx 和 Consul 放在一张图里。为了突出服务,这里把一些跟服务不太相关的都省略掉了。咱们基于 Mesos、Docker、 Marathon 作了服务管理。其中有一个特殊的服务是 Registrator,它会经过 Docker API 在每一个物理机上起一个容器,经过 Docker API,把容器的状态定时的汇报给 Consul。上面的 Nginx 作负载均衡,由于咱们的服务目前都是基于 Nginx 直接到容器里面。架构
在前面的图里,Nginx 到容器、服务注册到配置文件都没有问题,可是从 Consul 到 Nginx 会出现问题,由于 Consul 有全部的信息,可是这些信息如何通知给 Nginx 呢?一个新的服务起来,或者是一个服务挂掉,这些信息 Consul 知道后怎么让 Nginx 把这些有问题的服务删掉,再把一些新写的服务加进去,这就是咱们要解决的问题。
这里的问题就是 Consul 里的服务如何更新到 Nginx,若是解决了这个问题,Nginx +Consul+Registrator 的模式就圆满了。目前也有不少方案能够来解决这个问题:
一、方案一:Consul_template
监听 Consul 里的 key,触发执行一个脚本,利用这个特性的服务,服务发生变更,会根据预先配置好的模板从新生成配置,这个就是最后要执行的一个脚本。
上图是一个例子,有模板生成 upstream.conf,中间都是未来要被渲染的一些变量,若是 K/v 发生变更,模板化生成一份真实的配置文件,而后再执行一个本地的命令,Nginx -s reload,从新生成配置文件,Reload 一下,这样新的服务就生效了。
固然 Reload 也会有一些缺点:
二、方案二:内部 NDS 方案
DNS 的方案也是比较经常使用的,好比把以前是一个 IP 地址的 Server,如今改为一个域名,只要把它解析掉一批 IP 就行了,这个听起来已经很完美了,并且 Consul 自己支持DNS,咱们也不用维护另外的 DNS 了,只要把这个 ID 换成域名就行了。
可是咱们感受使用 DNS 方案还不如作 Reload,缘由是
咱们想要的是经过 HTTP 接口,动态修改 Nginx 的上游服务列表,咱们找到了现成的方案,叫 ngx_http_dyups_module。
ngx_http_dyups_module 能够经过 GET 接口查询当前的一些信息;POST 能够更新上游;也能经过 Delete 删除上游。
上图是一个例子,这个例子有三个请求:
在这个过程里没有任何 Reload 的操做,也没有改配置,它就完成了一个功能。
这个模块写得很是好,可是咱们用了一段时间后把它下掉了,主要缘由不是由于它很差,而是咱们结合了一些自身的状况,发现了一些问题:
基于以上这些缘由,咱们开始造本身的轮子。
这个轮子有四个部分:
一、lua-resty-checkups
简单介绍下 lua_resty_checkups 这个模板,它有几个功能:
二、服务区分
以 Host 区分服务:好比上图两个 curl 往同一个地址去发,这二者之间是不同的。
三、请求流程
简单介绍下请求的流程,它能够分为三个部分,最上面是接收请求,会加载一个 worker 代码,worker 代码执行完根据 host 找对应的列表,而后把这个请求代理给服务端。
四、动态 upstream 更新
这个跟 dyups 的 C 模块同样,也是经过 HTTP 接口来动态更新 upstream 列表,加完后能够在管理页面看到刚加进去的两个服务,这里会有 server 地址、一些健康检查的消息、状态变动的时间,以及它失败的次数,下图是一次主动健康检查的一个记录。
为何会有主动健康检查呢?你们平时用的就是一些被动的健康检查,也就是请求发出去以后失败了才知道失败了,主动的检查是发心跳包,在请求以前就能够知道服务是否是出问题了。
五、动态 lua 加载
动态 lua 加载在作游戏的时候会常常用到。一开始程序里面跑了一些 lua 的代码,给后端的程序作参数转化和作兼容,好比有一个小调整不乐意去改,就拿前面的路由去作,首先能够对请求作改写,由于我能够拿到整个请求,它的请求体能够作任意的事情。
此外,咱们还能够跟一些权限控制结合,作一些简单的参数检查。据咱们的统计,咱们至少有 10% 是重复请求,若是这些重复请求都去执行就是无谓的消耗,咱们会返 304,表示结果跟以前的同样,能够直接用以前的结果。在返 304 的同时,若是咱们须要后端的服务去判断,会把整个请求收下来,而后再日后面发,至关于内网带宽要增长一些,这样其实已经节省了带宽,能够不日后面发了。
这是一个动态负载加载的例子,若是把这段代码推到 Slardar 里面,它会执行,若是进行一个删除操做,它会返 403,便可以当即经过这个代码禁掉这个操做,那还有什么功能呢?你能够想象到的功能均可以作,并且这个过程是动态的,若是代码加载,也能够从状态页里看到它的信息。
前面介绍都是 Slardar 的特性,接下来简单介绍一下实现过程,一共分为三个部分: 动态 upstream 管理、负载均衡和动态 lua 代码加载。
一、动态 upstream 管理
启动时经过 luasocket 从 consul 加载配置文件,服务若是没有任何理由的挂了,挂了以后你刚起来时,你怎么知道刚刚怎么了呢?因此得有一个方式去固化这些东西,而咱们选的是 consul,因此它启动的时候必须从 consul 加载,启动以后要监听管理的端口,接收 upstream 更新指令,还要启动一个定时器,这个定时器作 worker 间的同步,定时从共享内存看一下有没有更新,有更新就能够同步在本身的 worker 里。
这是一个简单的流程图,最开始的时候从 consul 加载,在完成 fork 后到了 worker 进程,也就是刚刚初始化加载的那些 worker 都有了,另一部分启动定时器,一旦有更新就会进入到这个里面。
二、负载均衡
负载均衡咱们主要用到了 balance_by_lua_*,一个请求过来,经过 upstream 的 C 模块把这个请求往这里发,如图是配置文件,刚刚也有一个相似的,就是在这里写了地址。经过 balance_by_lua_* 指令,咱们会把它拦到这个文件里,就能够在这个 lua 文件里用 lua 代码选一个,这就是自身的一个 checkups 的选择的过程。
上图是大概的流程,能够先看下边部分,一开始的时候,checkups.select_peer 是咱们的模块,而后根据这个 host 再到当前的 peer 就跳出去了,这就实现了用 lua 控制。上面部分是要知道它是成功仍是失败的,若是它失败了,要对这个状态进行反馈。
三、动态 lua 加载
这个主要是用到 lua 的三个函数,分别是 loadfile、loadstring 和 setfenv。loadfile 是加载本地的 lua 代码,loadstring 是从 consul 或 HTTP 请求 body 加载代码,setfenv 设置代码的执行环境,经过这三个函数就能够加载,具体的实践细节这里就再也不介绍。
四、动态负载均衡 Slardar 的优点
这就是咱们造的轮子,主要用到 lua-resty-checkups 的模块和 balance_by_lua_* ,它有如下的优点:
咱们目前也在把以前的一些服务改形成微服务模式。微服务其实就是源于一个比较大的服务,把它拆分红一些小的服务,它的扩容跟迁移也不同,微服务的扩容能够只扩容其中一部分,扩容多少能够根据需求。
咱们如今正在尝试一个方案,这个方案背景是咱们有作图的需求,作图这个功能有不少,好比说美化、缩略、水印等,若是要对作图的服务进行优化是很是困难的,由于它功能太多了,若是咱们把它拆成微服务就不同了,好比上图虚线上面的是咱们如今的服务,这个是微服务的一个网关,下面是一些小的服务。好比说美化,它的运算比较复杂,耗 CPU 比较多,咱们确定选择一些 CPU 比较好的机器;用 GPU 来作缩略图,这个性能可能提升几十倍;最后是一个中规中矩的作图,那就普通的一些就够了。
还有一些比较偏门的,好比说梯度,可能只要保证服务能够用就好了,经过这个微服务的路由,咱们根据后面的区分把以前的一个服务,以及它的参数拆成三个小的服务,这样经过三个步骤能够完成一个作图的服务。
固然咱们在尝试这个方案其实也有不少的问题,好比一个服务原来用一个程序就能够作了,如今变成了三个,势必内网的带宽要增长了,中间的图片要被导来导去,这个怎么办呢?咱们如今想到的办法就是作一些本地优先的调度策略,即作完以后,本地有一些水印的,那就优先用本地的。
最后套用大师的一句话:Talk is cheap,Show me the code。目前咱们已经将 Sladar 项目开源,项目地址是:https://github.com/upyun/slardar 。
演讲视频及PPT: