您可以捐助,支持我们的公益事业。

金额: 1元 10元 50元

姓名:

邮件:

电话:

公司:

说明:

认证码: 验证码,看不清楚?请点击刷新验证码 必填



  要资料 文章 文库 Lib 视频 Code iProcess 课程 认证 咨询 工具 讲座吧   专家招募  
会员   
 
   
 
  
每天15篇文章
不仅获得谋生技能
更可以追随信仰
 
 
     
   
 订阅
  捐助
基于ngx_lua的动态服务路由方案
 
作者:钱曙光 来源:极客头条 发布于:2017-1-12
来自于要资料   249 次浏览     评价:      
 

如何做到服务的zero down_time的更新

在更新服务的时候,怎么能做到让自己的服务不断掉,又拍云做更新的时候,不允许有失败,如果说因为我们的失败而导致请求失败,即使你的请求非常少,首先从口碑上就很不好;另外一个原因:如果造成了事故,是要赔钱的。这也是我们做动态服务路由的重要原因。

说到服务路由,大家都会想到三个方面:

1.服务注册、服务发现、负载均衡,服务注册说的是服务提供者在起来的时候,得去服务发现注册一下,以表明我提供了的服务、端口、IP是多少,服务名又是什么;

2.服务发现就是一个集中管理服务的地方,上面记录了有哪些服务,它们在哪些地方;

3.负载均衡,因为有很多同样的容器提供了同样的一个服务,怎么在这些容器里做负载均衡,也是要考虑的。

服务发现有很多方案,ETC跟Consul算是后起之秀,比较常见。ZooKeeper是一个比较老牌的开源项目,比较成熟,对资源的要求比较高,相对比较强大一点。Consul不但支持KV存储,还有原生的服务监控、多数据中心、DNS功能等,所以我们选了Consul这个方案。

负载均衡也是有很多方案,比如说Nginx,LVS扩展起来非常难;再高级一点的有HA_PROXY,它可以做到高层的,也可以做到基层的,Nginx专注于做HTTP,后续也支持了TCP。从负载均衡出发,选择了Nginx。

如上图,我们把Nginx和Consul放在一张图里。为了突出服务这一块,我把一些跟服务不太有关系的都省略掉了。我们基于Mesos、Docker还有Marathon做了服务管理。其中有一个服务是特殊的,就是Registrator,它会通过Docker API在每个物理机上起一个容器,通过Docker API,把容器的状态定时的汇报给Consul,上面的Nginx是做负载均衡的,因为我们的服务目前来说都是基于Nginx直接到容器里面。

Consul里的服务如何更新到Nginx?

在这个图里面,Nginx到容器这一步是没有问题的,服务注册到配置文件也是没有问题的,但是从Consul到Nginx是有问题的,因为Consul有所有的信息,但是这些信息要如何通知给Nginx,一个新的服务起来了,或者是一个服务挂掉了,这些信息Consul知道了,怎么让Nginx把有一些有问题的给删掉,再把一些新写的给加进去,这就是我们要解决的一个问题。

面临的问题就是Consul里的服务如何更新到Nginx,如果解决了这个问题,刚才那个图就已经圆满。

市场上有很多方案来解决这个问题:

1. Consul_template

监听Consul里的key,会触发执行一个脚本,利用这个特性的服务,服务发生变动,会根据预先配置好的模板去重新生成配置,这个就是最后要执行的一个脚本。原理就是这样:

上图是一个例子,比如说一个模板是这样的,然后中间都是将来要被渲染的一些变量,如果K/v发生变动,模板化生成一份真实的配置文件,然后再执行一个本地的命令,Nginx -s reload,重新生成配置文件,Reload一下,这样新的服务就已经生效了。当然这样也有一些问题:

(1) 如果你频繁的Reload会有性能损耗;

(2) 长时间处于Shutting down的状态,如果连接里头有长连接,旧的进程会一直处于一个中间进程,这个时间是不定的,就是说你不知道到底什么时候Reload真正完成;

(3) 进程内缓存失效,我们会把数据库的一些信息,一些代码全部缓存进本地,这样缓存就全部失效了。

当然前面三点也不是非常的严重,毕竟Reload的操作不是特别的频繁。

最后一点是与设计初衷不符,这也是我们最关心的一点,它设计的初衷是做什么呢?就是方便运维不去影响当前的请求,就相当于我们拿Docker做虚拟机用一样走歪了,走歪了之后最后很可能会碰到很多奇怪的坑,所以当时没有用这个方案。

2. 内部DNS方案

DNS的方案也是比较常用的,比如我把之前是一个IP地址的Server,现在改成一个域名,只要把它解析掉一批IP就好了,这个听起来已经很完美了,而且Consul本身支持DNS,我们也不用维护另外的DNS了,只要把这个ID换成域名就好了。

这样做的话,我们感觉还不如做Reload,因为首先多了一层DNS解析时间,再怎么快都是需要解析时间的,第二个是有DNS缓存,这是最主要的原因,因为缓存的存在,没办法立即把一台有问题的机器切掉,如果你要缓解这个问题,就要把缓存设得短一点,但这样解析次数就多了。还有一个就是端口号会改变,物理机一般我们会配置同一个端口,在Docker里面也可以这么做,但对于一些对网络不是很敏感的应用,比如说一些强CPU的应用,我们会直接把容器的网络,用桥接的方式连接起来,而这时候端口是随机分配的,可能每个容器分配的都不一样,所以就不行。

那我们到底想要怎么样呢?我们想要的非常简单,就是要通过HTTP接口,动态修改Nginx的上游服务列表。这样的方案我们找了之后发现有一个现成的,叫ngx_http_dyups_module。

3. Ngx_http_dyups_module

它能干什么事情呢?可以通过GET接口查询当前的一些信息;POST可以更新上游;也能通过Ddelete删除上游。

上图是一个例子,这个例子有三个请求,也就是发了三个指令:

第一个,给8080这个服务端口发了请求之后,发现后面根本就没有任何的上游服务,所以它就502了;

第二个,通过一个Curl的请求把两个服务地址给加进来;

第三个,重新访问了一下,第三条指令跟第一条指令是一模一样,因为第二条已经把服务加进来了,所以这是一个正常的输出。

在这个过程里头没有任何的Reload的操作,也没有改配置,它就完成了一个功能。

这个模块写得非常好,我们用了一段时间,但一段时间后把它下掉了,主要原因不是因为它不好,主要是我们结合了一些自身的情况,发现了一些问题:

第一,导致依赖Nginx本身的负载均衡算法。如果我们内部用Ngx_lua写得比较多,用了这个模块之后,会导致我们非常依赖C模块,也就是自身的一些负载均衡算法,我们有自己特有的需求,比如说本机优先,就是优先访问本机的服务,这样听起来比较奇怪的负载均衡,如果要做这些事情的话,我们就要改C代码;

第二,二次开发效率低,C的开发效率远不及Lua;

第三,纯Lua的方案无法使用,我们做这样一个方案并不是说我云处理能用就行了,有一个项目能用就行了,做这个方案最好是其他一些项目都可以用。

造自己的轮子

基于以上这些原因,我们开始造自己的轮子。

这个轮子是这样的,有四个部分:

最基础的Nginx,我们希望用一些原生的指令和重试的策略;

Lua的模块;

lua_resty_checkups,这是我们Lua版的管理模块,实现了动态的upstream管理,这个模块实现了大概30%的功能,而且还有一些主动的健康检查功能,它的代码量大概也就是1500左右,那C模块估计至少有1万行;

luasocket,千万不能在Nginx在处理请求的时候用。

简单介绍一下lua_resty_checkups这个模板,它有几个功能:

动态upstream管理,基于共享内存实现worker间同步;

被动健康检查,这个是Nginx自身的一个特性;

主动健康检查,这个模块会主动给你的后端发心跳包,可以定时,15秒发一次,你后端的服务是不是存活。我们还可以有一些个性化的检查,然后heratbeat定时给上游发送心跳包检测服务是否存活;

负载均衡算法,本地优先可节约内网流量等。

以Host区分服务:比如说这两个curl往同一个地址去发,这两者之间是不一样的。

这个图简单讲一下,它是一个请求的流程,可以分为三个部分,最上面是接收请求,我们会加载一个worker代码,在worker代码执行完之后,会根据这个host找对应的列表,然后把这个请求代理给服务端。

这个跟dyups的C模块一样,也是通过HTTP接口动态更新upstream列表,加完之后,可以在管理页面看一下,就可以看到刚刚加进去的两个服务,这里面有server地址,一些健康检查的消息,还有它的状态变更的时间,以及它失败的次数,这是主动健康检查的一个记录。那为什么会有主动健康检查呢?我稍微介绍一下,大家平时用的就是一些被动的健康检查,就是说我这个请求发出去之后失败了才知道失败了,主动的就是我发心跳包,在请求之前,我就可以知道你这个服务是不是出问题了。

动态Lua加载,这个在做游戏的时候会经常用到,在一开始的时候,我们的程序里面跑了一些Lua的代码,给后端的程序做参数转化和做兼容用,比如有一个小调整不乐意去改,就拿前面的路由去做,首先我可以对请求做改写,因为我可以拿到整个的请求的,它的请求体可以做任意的事情,这样的话,我可以跟一些权限控制结合起来,还有一个就是可以做一些简单的参数检查。据我们的统计,我们大概有至少10%是重复的请求,那这些重复请求如果都去做的话就是无谓的消耗,我们会返一个304,表示结果跟之前的一样,用之前的结果就好了。在返304的时候,如果说我们是需要后端的服务去判断,势必会把整个请求给收下来,然后再往后面发,相当于是内网带宽要增加一些,这样其实就已经节省了带宽,可以不往后面发了,主要是这几个原因。

这是一个动态负载加载的例子,我如果把这段代码推到Slardar里面的话,它会执行,如果你进行一个删除操作,它会返403,也就是说可以立即通过这个代码禁掉这个操作,那还有什么功能呢?你可以想象到的功能都可以做,而且这个过程是动态的,如果代码加载,也可以从状态页里看到它的信息。

刚刚讲的都是这个项目的特性,接下来想简单介绍一下实现过程,有一些可能比较深入,我尽量把一些深入的地方带过去,动态upstream管理,分三个部分,三个步骤。

1. 动态upstream管理

启动时从Consul加载配置文件,如果你没有任何理由的挂了,挂了之后你刚起来时,你怎么知道你刚刚怎么了呢?所以得有一个地方去固化这些东西,而我们选的就是Consul,所以它启动的时候必须从Consul加载,启动之后一个就是监听管理的端口,还有一个就是要启动一个定时器,这个定时器做worker间同步的,定时从共享内存看一下有没有更新,有更新的话可以同步在自己的worker里头。

这是一个简单的流程图,最开始的时候从Consul加载,在完成fork之后,就到了worker进程,也就是刚刚你初始化加载的那些每个worker都有了,另外一部分启动定时器,一旦有更新就会进入到这个里面。

2. 负载均衡

我们主要用到了balance_by_lua_,一个请求过来,通过upstream的C模块,然后把这个请求往这里发,下图这个配置文件,刚刚也有一个类似的,就是在这里写了地址。通过balance_by_lua_指令,我们会把它拦到这个文件里,就可以在这个Lua文件里头用Lua代码选一个,这就是自身的一个checkups的选择的过程。

大概的流程见下图,可以先看下边部分,一开始的时候,checkups.select_peer是我们的模块,然后根据这个host再到当前的peer,就跳出去了,这样就实现了用Lua控制。上面部分是要知道它是成功还是失败的,如果它失败了,我要对这个状态进行反馈。

3. 动态Lua加载

这个主要是用到Lua的三个函数,分别是loadfile、loadstring和setfenv。loadfile是加载本地Lua代码,loadstring是从Consul或HTTP请求body加载代码,setfenv设置代码的执行环境,通过这三个函数就可以加载,具体的实践细节我就不再介绍。

这是我们做的轮子,主要用到checkups的模块和balance_by_lua_,它有这些优势:

首先,纯Lua实现,不依赖第三方C模块,二次开发非常高效,减少维护负担。

第二是可以用Nginx原生的Proxy,因为我们只在请求的选peer的那个阶段做,peer选完之后,发数据的那个阶段是直接走Nginx自己的指令的,所以它可以用到Nginx原生的Proxy指令。

最后,它适用于几乎任何的ngx_lua项目。

在微服务架构里,slardar能做什么?

我们目前也在把之前的一些服务改造成微服务模式。微服务其实就是源于一个比较大的服务,把它拆分成一些小的服务,它的扩容跟迁移也不一样,微服务的扩容可以只扩容其中一部分,如果需要的功能比较多,就扩得多一点,需要少的,就扩得少一点。

我们现在正在尝试的一个方案,这个方案背景是这样,我们有一些做图的需求,做图这个功能有很多,比如美化等各种需求,如果要对这个做图的服务进行优化是非常困难的,因为它功能太多了,如果我们把它拆成微服务就不一样了,比如说这个虚线上面的是我们现在的服务,这个是微服务的一个网关,下面是一些小的服务。

比如说美化,它的运算比较复杂,耗CPU比较多,我们肯定选择一些CPU比较好的机器;用GPU来做缩略图,这个性能可能提高几十倍;最后是一个中规中矩的做图,那就普通的一些就够了。

还有一些比较偏门的,比如说梯度,可能只要保证下服务可以用就行了,通过这个微服务的路由,我们根据后面的区分把之前的一个服务,以及它的参数拆成三个小的服务,这样通过三个步骤可以完成一个做图的服务。

当然我们在尝试的这个方案其实也有很多问题,比如一个服务原来用一个程序就可以做了,现在变成了三个,势必内网的带宽要增加了,中间的图片要被导来导去,这怎么办?我们现在想到的办法就是做一些本地优先的调度策略,就是说你做完之后,本地有一些水印的,那就优先用本地的。

套用大师的一句话:Talk is cheap,Show me the code。开源!

   
 订阅
  捐助
相关文章

阻碍使用企业架构的原因及克服方法
世界级企业架构的行业挑战
企业架构和SOA架构的角色将融合
什么最适合您的组织?
相关文档

企业架构与ITIL
企业架构框架
Zachman企业架构框架简介
企业架构让SOA落地
相关课程

企业架构设计
软件架构案例分析和最佳实践
嵌入式软件架构设计—高级实践
企业级SOA架构实践

 

相关文章


专家视角看IT与架构
软件架构设计
面向服务体系架构和业务组件
人人网移动开发架构
架构腐化之谜
谈平台即服务PaaS

相关培训课程


面向应用的架构设计实践
单元测试+重构+设计模式
软件架构师—高级实践
软件架构设计方法、案例与实践
嵌入式软件架构设计—高级实践
SOA体系结构实践

相关咨询服务
应用架构设计与构建

成功案例


锐安科技 软件架构设计方法
成都 嵌入式软件架构设计
上海汽车 嵌入式软件架构设计
北京 软件架构设计
上海 软件架构设计案例与实践
北京 架构设计方法案例与实践
深圳 架构设计方法案例与实践
嵌入式软件架构设计—高级实践
更多...   
 
 
实录 多租户架构数据库虚拟化
主讲:梁涛
Oracle研发中心,性能优化架构师
 
实录 模型驱动三维管理
 主讲:俎涛
火龙果软件工程创始人
 
 
 
每天2个文档/视频
扫描微信二维码订阅
订阅技术月刊
获得每月300个技术资源
 
 

关于我们 | 联系我们 | 京ICP备10020922号 京公海网安备110108001071号