运维高手三:nginx负载均衡常见架构及问题解析
Nginx 作为负载均衡是基于代理模式的基础之上,所以在看本篇文章时,你需要对 Nginx 的代理、负载均衡的基本原理及 Nginx 负载均衡配置有基础的了解。
Nginx 负载均衡应用架构
关于 Nginx 负载均衡应用架构在企业应用中主要有两种类型。
分层入口代理架构
第一类是分层入口代理架构(属于相对传统架构),我们可以对整个后台网站服务系统架构做一个分层。大体可以分为用户请求入口层,以及为用户请求提供逻辑处理的服务层和为用户提供真正相关数据的数据层。
如图所示,我们可以发现入口层是最接近用户请求的,所以在这一层中,Nginx 扮演着重要的角色——入口网关,并承担 7 层负载均衡(LB)的功能。如图所示入口层的 Nginx 之前还有一套 LB,LB层的主要功能是为了保证 Nginx 本身的高可用、或承担 TCP/IP 负载均衡功能,所以这里整个入口层的负载均衡模式是一个 4层LB+7层LB(Nginx),这套架构中把与业务服务的相关功能则由 Nginx来处理。
哪一些业务服务相关功能交给 Nginx 做合适呢?比如在入口层我们会放一些和用户相关的信息,也比如动静分离(及实现分离网站页面的静态元素和动态元素),我们知道静态元素没有必要下沉到数据层获取,可以直接通过 Nginx 实现动态和静态的分流并由 Nginx 直接处理。另外,用户的访问控制、反爬虫等规则也是在入口层的 Nginx 实现的。
服务层同样也需要 Nginx , 来负载均衡实现上层请求应答上的高可用。
分层架构的最后一层是数据层,数据层中 Nginx 同样可以实现负载均衡,但数据层中通常使用的Nginx 较少见,为什么呢?因为这一层更追求数据调用的效率,比如 Memcache、MySQL 等数据库调用更多是基于底层的协议请求,而非更上层的 HTTP 请求。
但如果如果追求 HTTP 的可靠性、和应用性,是可以借助 Nginx 的负载均衡实现的,如:可Redis 使用 Nginx 做反向代理,通过 Nginx 把前端发送的 HTTP 请求转换成 Redis 协议的请求方式去请求Redis,这样就完成了 Redis 的反向代理。这种方式,企业可以很好控制Redis的监控、数据的一致性保障、及基于 Hash 算法稳定性保障。
总结一下,Nginx在分层架构里扮演了一个7层应用层负载均衡角色。随着软件架构和系统架构是不断演进变化,我们发现企业开始采用K8s、Docker这种轻量化、虚拟化部署;还有很多企业更加倾向于微服务架构,支持set化等。在这样的架构下,传统的分层负载均衡模式,促使改进去支持服务注册和发现。这个就是介绍的第二种Nginx负载均衡模式。
服务注册发现代理架构
如图所示,图中重点列出了 Nginx 在注册发现场景中扮演的角色。同样,我们可以看到整个流量还是通过 Nginx 来做入口网关的,但是重要的一点是 Nginx 需要支持动态的发现和注册后端服务。
注册是指后端的应用程序(如图中的 App1~App4)需要去往前端的中心存储节点里面注册它的应用服务,当注册上报后,Nginx 动态发现并动态生成发现的配置,然后对入口网关代理负载均衡进行分流调整,我们可以发现在基于 K8s 和 Docker 这种部署模式业务入口网关就应用了这种架构。
两种负载均衡应用架构说完了,我们会发现两种架构最大的区别是后面一种支持后端服务的注册与发现。
nginx负载均衡常见问题
解决办法
知道了问题发生的原因,我们如何来解决呢?
第一种方式的解决办法是在 Nginx 负载均衡的基础上添加了一个转发到后端的标准 head 信息,把用户的 IP 信息通过 X-Forwarded-For 方式传递过去。
如何解决域名携带
第二个问题,也就是 Nginx 作为负载均衡是如何把请求域名信息携带到后端的?这样的问题是什么样的场景呢?首先我们来看一张示意图。
http {
…
upstream app_servers {
server ip1:port1;
server ip2:port2;
server ip3:port3;
}
server {
…
location / {
proxy_set_header Host $host ;
proxy set_header Host www.vipumi.com;
proxy_pass http://app_servers;
}
….
}
}
我们通过 proxy_set_header 配置方式加入 Host 头部字段,如果用户的请求携带了域名,就把这个域名的 head 头以标准的 HTTP 方式将头信息传递到后端,然后让后端获取 Host 头信息,这样访问就不会受影响。
然后,如果用户没有携带头信息,而后端又要求指定域名,那么我们就可以在 proxy_set_header下,将 Host 头信息指定成一个固定的域名,保证满足后台对 Host 头信息的要求,这样就可以解决域名携带问题。
负载均衡导致session丢失问题
另外一个是 session 丢失问题,我们知道,session 是用户的一个会话信息,服务端和用户端通信需要一个访问认证,以及对鉴权处理的时候,给客户端分配一串 session 信息,这个 session 信息会在客户端以 cookie 的方式承载到服务端中进行校验。如果加入 Nginx 负载均衡,会默认开启一个轮询策略。假如这样的一种场景,用户请求到 Nginx,第一次请求会分发给 App server 1,第二次请求分发给 App server 2,但用户的 session 保留在 App1 上,此时这条请求再去请求 App2 的话,由于App2 上没有 session 信息,就导致会话丢失,用户需要重新登录。我们看到 Nginx 作为负载均衡 + Java 后台应用中遇见的一种问题,我们该怎样解决呢?
Session 保持
第一种方式是做 Session 保持,就是把负载均衡策略基于原有轮询数的基础上,改用 ip_hash、URL_hash 来解决。ip_hash 就是基于用户 IP 来做 hash,一个用户的请求统一分发到一台机器上。URL_hash 用于用户请求固定页面时,将用户请求固定到具体 后端 上,就保证了 Session 不会被丢失。
2. Session复制
第二种方式是 Session 复制。所谓 Session 复制是在后台应用的基础上,让 Session 之间可以以传播的方式进行复制。也就是 App1 上如果有一个Session,那么它可以复制给 App2、App3,无论怎样轮询,三个 App 上都会有同样的Session 信息,不至于因为轮询导致丢失会话失效。
3. Session 共享
Session 共享是由程序完成的,我们把 Session 信息不放在本地,通过应用程序把 Session 信息放入共享的 K/V 存储中,这样就不会产生 Session 丢失情况了。
我们可以看到,如果 Nginx 解决 Session 丢失问题基于 Session 保持来解决,所以怎样配置 Nginx呢?
http {
…
upstream app_servers {
ip_hash;
server ip1:port1;
server ip2:port2;
server ip3:port3;
}
server {
…
location / {
proxy_pass http://app_servers;
}
….
}
}
http {
…
upstream app_servers {
hash $request_uri;
server ip1:port1;
server ip2:port2;
server ip3:port3;
}
server {
…
location / {
proxy_pass http://app_servers;
}
….
}
}
真实的 Realserver 状态检测
最后一个常见问题是如何检测后端的 Realserver 状态,我们首先需要了解Nginx 默认真实检测后台服务状态是怎么实现的.
生产环境中 一个 Nginx 作为负载均衡如果发现后端某一个节点出现问题 那么它会把这个节点剔除,并检测下一个节点,那么 Nginx 是如何检测到节点有问题呢?默认情况下,Nginx 基于 TCP 端口和连接方式检测,也就是在服务 ping 不通,或无法建立 TCP 链接,以及端口服务完全不可用的状态下,才会认为这个服务不可用。
你可以想一想,在 Nginx 的这种检测机制下通常是有遗漏的,如果响应状态或返回状态有问题,对于Nginx 代理层而言,它并没有做到一个有效的容错,只是检测底层 TCP 连接方式,是不是也存在一定的局限性.
这个时候,你可能会想是否可以加入一个 proxy_next_upstream 就可以把后端节点剔除呢?proxy_next_upstream 虽然能够检测到服务端返回到前端的状态码,但是无法做到真正自动摘除故障节点。
那么怎么做呢?这里就需要依赖一个第三方模块了,这个第三方模块就是nginx_upstream_check_module,它是由淘宝技术团队开发并开源的,提供了更加细致的对后台服务真实状态的检测。你可以把这个模块编译到 Nginx 中,或是使用淘宝的 Tengine,Tengine 也是基于 Nginx 1.6 版本开发开源的。我们接下来看下具体配置:
check interval=3000 rise=2 fall=5 timeout=1000 type=http; //定义检查间隔、周期、时
check_keepalive_requests 100; //一个连接发送的请求数
check_http_send “HEAD / HTTP/1.1\r\nConnection: keep-alive\r\n\r\n”; //定义健康检查
check_http_expect_alive http_2xx http_3xx; //判断后端返回状态码
通过这样的配置,我们定义了检测间隔、周期和超时时间;定义了一个连接发送的请求数;然后是定义健康检查方式,比如检查 head 头的请求信息;最后判断后端返回状态码,如果是非 200 或 300 的话,就认为后台服务不健康,需要把这个问题服务除掉,避免用户请求到问题节点。
未完待续
回复“资料”有惊喜哟!