百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

基于ngx_lua的动态服务路由方案

off999 2025-02-06 16:05 22 浏览 0 评论

如何做到服务的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来做缩略图,这个性能可能提高几十倍;最后是一个中规中矩的做图,那就普通的一些就够了。

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

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

相关推荐

在NAS实现直链访问_如何访问nas存储数据

平常在使用IPTV或者TVBOX时,经常自己会自定义一些源。如何直链的方式引用这些自定义的源呢?本人基于armbian和CasaOS来创作。使用标准的Web服务器(如Nginx或Apache...

PHP开发者必备的Linux权限核心指南

本文旨在帮助PHP开发者彻底理解并解决在Linux服务器上部署应用时遇到的权限问题(如Permissiondenied)。核心在于理解“哪个用户(进程)在访问哪个文件(目录)”。一、核心...

【Linux高手必修课】吃透sed命令!文本手术刀让你秒变运维大神!

为什么说sed是Linux运维的"核武器"?想象你有10万个配置文件需要批量修改?传统方式要写10万行脚本?sed一个命令就能搞定!这正是运维工程师的"暴力美学"时...

「实战」docker-compose 编排 多个docker 组成一个集群并做负载

本文目标docker-compose,对springboot应用进行一个集群(2个docker,多个类似,只要在docker-compose.yml再加boot应用的服务即可)发布的过程架构...

企业安全访问网关:ZeroNews反向代理

“我们需要让外包团队访问测试环境,但不想让他们看到我们的财务系统。”“审计要求我们必须记录所有第三方对内部系统的访问,现在的VPN日志一团糟。”“每次有新员工入职或合作伙伴接入,IT部门都要花半天时间...

反向代理以及其使用场景_反向代理实现过程

一、反向代理概念反向代理(ReverseProxy)是一种服务器配置,它将客户端的请求转发给内部的另一台或多台服务器处理,然后将响应返回给客户端。与正向代理(ForwardProxy)不同,正向代...

Nginx反向代理有多牛?一篇文章带你彻底搞懂!

你以为Nginx只是个简单的Web服务器?那可就大错特错了!这个看似普通的开源软件,实际上隐藏着惊人的能力。今天我们就来揭开它最强大的功能之一——反向代理的神秘面纱。反向代理到底是什么鬼?想象一下你...

Nginx反向代理最全详解(原理+应用+案例)

Nginx反向代理在大型网站有非常广泛的使用,下面我就重点来详解Nginx反向代理@mikechen文章来源:mikechen.cc正向代理要理解清楚反向代理,首先:你需要搞懂什么是正向代理。正向代理...

centos 生产环境安装 nginx,包含各种模块http3

企业级生产环境Nginx全模块构建的大部分功能,包括HTTP/2、HTTP/3、流媒体、SSL、缓存清理、负载均衡、DAV扩展、替换过滤、静态压缩等。下面我给出一个完整的生产环境安装流程(C...

Nginx的负载均衡方式有哪些?_nginx负载均衡机制

1.轮询(默认)2.加权轮询3.ip_hash4.least_conn5.fair(最小响应时间)--第三方6.url_hash--第三方...

Nginx百万并发优化:如何提升100倍性能!

关注△mikechen△,十余年BAT架构经验倾囊相授!大家好,我是mikechen。Nginx是大型架构的核心,下面我重点详解Nginx百万并发优化@mikechen文章来源:mikechen....

在 Red Hat Linux 上搭建高可用 Nginx + Keepalived 负载均衡集群

一、前言在现代生产环境中,负载均衡是确保系统高可用性和可扩展性的核心技术。Nginx作为轻量级高性能Web服务器,与Keepalived结合,可轻松实现高可用负载均衡集群(HA+LB...

云原生(十五) | Kubernetes 篇之深入了解 Pod

深入了解Pod一、什么是PodPod是一组(一个或多个)容器(docker容器)的集合(就像在豌豆荚中);这些容器共享存储、网络、以及怎样运行这些容器的声明。我们一般不直接创建Pod,而是...

云原生(十七) | Kubernetes 篇之深入了解 Deployment

深入了解Deployment一、什么是Deployment一个Deployment为Pods和ReplicaSets提供声明式的更新能力。你负责描述Deployment中的目标状...

深入理解令牌桶算法:实现分布式系统高效限流的秘籍

在高并发系统中,“限流”是保障服务稳定的核心手段——当请求量超过系统承载能力时,合理的限流策略能避免服务过载崩溃。令牌桶算法(TokenBucket)作为最经典的限流算法之一,既能控制请求的平...

取消回复欢迎 发表评论: