

微服务 - Nginx网关 · 进程机制 · 限流熔断 · 性能优化 · 动态负载 · 高可用 - Sol·wa...
source link: https://www.cnblogs.com/Sol-wang/p/17370377.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

系列目录:
本文的前提需要了解一些 Linux 知识。
以下围绕 Nginx 1.23 的网关应用;参考官网:http://nginx.org/
本文没有对概念性方面做深入的阐述,常见的是一笔带过,而更多的...是对配置项的解释。
为什么需要网关
通常后台提供了不同的应用服务,甚至是集群,每种服务每个服务,需要维护不同的请求地址,甚至服务认证、跨域等动作,管理起来比较麻烦。因此,需要一个网关,介于客户端和应用服务之间,所有的外部请求都会先经过网关,网关再把请求分发到目标服务。网关对外提供唯一请求入口,作为对外联系的窗口,易于管理和维护请求。
一、Nginx 概述
Nginx 不仅是一个高性能的Web服务器,还具备访问代理、负载均衡、内容缓存等功能,用于客户端访问流量到后台应用服务器负载均衡和请求转发。其基于模块化的代码架构及可与其它有效集成的可编程特性,使其具有强大的扩展能力。Nginx以资源消耗低、高稳定、高性能的并发处理能力著称。
1.1 Nginx 特性
访问代理:
Nginx 可以通过访问路径、URL 关键字、客户端 IP等多种手段实现访问路由分配。
反向代理:
将接收到的请求再转到后端的目标应用服务器,并把响应数据返回给客户端。支持目前绝大多数的网络协议:HTTP/FastCGI/RPC/UDP/TCP等。
负载均衡:
通过自身的 upstream 模块支持多种负载均衡算法,使后端服务器可以非常方便地进行横向扩展,以应对高并发。
内容缓存:
Nginx支持静态站点和后端分离,可以把静态内容缓存起来,也可以将后端变化不大的响应结果缓存起来,使整体实现了更高速的相应能力。
可扩展性:
可定制的模块化架构方式,更多的语言(C/Perl/JavaScript/Lua)支持开发第三方模块并引入,增强可编程及扩展能力。
1.2 Nginx 进程
首先,进程是CPU管理的运行单元,CPU的单个核心也可以运行多个进程,只不过是交替运行着各个进程,称为时间片,这种方式速度很快,以至于看上去像在同时运行;多核CPU就能同时运行更多的进程。
Nginx是由多个进程运行,一个主进程Master和多个子进程Worker,主进程负责管理子进程,如:重启/重载/创建/销毁等,子进程负责处理具体的请求等业务功能。进程间共享内存数据,更多的进程带来更好的处理能力。
Nginx 进程运行示意图:

1.3 Nginx 重载
Nginx支持配置信息的重载,并以最新的配置内容运行,当Nginx在高速运行的时候,如何做到平稳过渡呢?
相关命令:nginx -s reload
重载过程:
Nginx Master process 负责 fork 出一个新的 Worker process,最新的Worker使用新的配置信息运行,这时候就销毁一个旧的worker,此时,Worker有新旧之分,新Worker用新配置运行,旧Worker依然用旧配置运行,Master继续fork出新的Worker。。。以同样的方式持续替换旧Worker,直到全部替换完成。整个过程中,Nginx 并没有停止运行,丝滑过渡。
二、安装配置
2.1 编译安装
安装前提:
yum install gcc -y # C语言编译器
yum install pcre pcre-devel -y # PCRE Library
yum install zlib zlib-devel -y # zlib Library
编译安装:
# 进入解压后的目录中 编译安装 [指定用户/组] [--with-追加自带模块名称]
./configure --prefix=/usr/local/nginx [--user=www --group=www] [--with-http_gzip_static_module]
make && make install
2.2 启动实例
进入主进程目录:/usr/local/nginx/bin
nginx # 启动
nginx -s stop # 停止,立即
nginx -s quit # 退出,处理完现有任务后
nginx -s reopen # 重启
nginx -s reload # 重载配置,交替更新工作进程
docker 运行 nginx 很简单:
拉取镜像:docker pull nginx
启动容器:docker run -d --name=ngx-a -p 80:80 nginx
浏览器打开主机IP显示 NGINX 欢迎页面。
影响 Nginx 的系统关联项
Firewall/UFW 防火墙:端口的开放
SELinux 权限的限制:请求后端的权限
2.3 配置文件结构
nginx 的配置文件默认存于 /etc/nginx/nginx.conf,其中通过 include 引入其它目录子配置文件。
- 全局块:针对 Nginx 实例的设置,资源及事件的设定。
- HTTP:从 Client 到 Nginx 的请求设置,请求过程中要处理的各项配置;
- Upstream:代理转发的下个目的地列表,后端服务组地址列表,连接与负载均衡的设定。
- Server:从 Nginx 到 Service 的设置,通常对应前后端某种服务或组;限制设定,代理设定,错误机制等。
- Location:路由匹配转发通讯,重定向等。
配置模板示例
###### 全局块
worker_processes auto; # 工作进程数
error_log /var/log/nginx/error.log notice; # 错误级别记录
events {
worker_connections 1024; # 单个工作进程,可承载的最大连接数
}
http {
###### MIME 配置
include /etc/nginx/mime.types; # 文件扩展名与文件类型映射表
default_type application/octet-stream; # mime.types 不包含时的默认设置
###### 请求日志配置
log_format log-format-a '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log log-format-a; # 访问日志路径 及引用格式
###### 后端可用服务配置
upstream backend-server-name {
server 192.168.1.101:80;
server 192.168.1.102:80;
}
server {
###### 请求匹配
listen 80; # 客户端请求的端口
server_name _; # 客户端请求的域名;区分不同服务(可多个空格区分,支持正则)
location / {
###### 重写配置
rewrite ^<规则>$ <目的地> break;
###### 转发到后端配置
proxy_pass http://backend-server-name$request_uri; # 转发到后台服务地址,来自于 upstream 项
proxy_http_version 1.1; # 指明版本(1.1默认为keep-alive长连接,1.0默认为短连接)
proxy_set_header Host $host; # 保持原来的请求域名
proxy_ignore_client_abort on; # 客户端断网时,是否中断对后端的请求
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 从远程客户端IP到服务端的层层代理转发IP,多IP追加空格分隔
###### Cookie 域名/路径
proxy_cookie_domain {backend-domain} {request-domain};
proxy_cookie_path {backend-path} {request-path};
}
###### 指定文件拒绝所有访问
location ~ ^/(\.user.ini|\.ht|\.git|\.svn|\.project|LICENSE|README.md){
deny all;
}
###### 限制的客户端
location \ {
deny 172.18.0.101; # 拒绝的ip
allow 172.18.1.10; # 允许的ip
}
###### 客户端缓存配置
location ~* \.(js|css|jpg|svg|gif|png)$ {
if (-f $request_filename) { # -f:只能是文件,因为这用-f判断了
expires 30d; # 缓存有效时长 30 天
break;
}
}
###### 防盗链配置
location ~* \.(gif|jpg|png|bmp)$ { # 指定格式禁止的请求来源:google/baidu
valid_referers none blocked *.ttlsa.com server_names ~\.google\. ~\.baidu\.;
if ($invalid_referer) {
return 403; # 状态码
#rewrite ^/ http://www.ttlsa.com/403.jpg;
}
}
###### (前端) 错误机制
error_page 404 /404.html; # 错误码 转向的 错误页
error_page 500 502 503 504 /50x.html; # 错误码 转向的 错误页
location = /50x.html { # 错误页 指向的 静态页面
root /usr/share/nginx/html;
}
}
}
2.4 前后端分离
把前端站点部署到 Nginx:
server {
listen 80;
server_name xxx.com;
# 前端配置
location / {
# 前端站点路径
root /home/vue/dist;
index index.html
}
# 后端配置
location /api {
proxy_set_header host $HOST;
proxy_pass http://192.168.1.101:8081;
}
}
2.5 负载均衡模式
在配置项 upstream 中,负责提供可用的服务地址列表,并可指定负载均衡的实现方式。
轮询 Round-Robin:将访问按序依次请求到后端各个服务器上,能确保平均负载
upstream backend-a {
server 192.168.1.101:80;
server 192.168.1.102:80;
}
权重 Weight:按百分比请求到后端服务器上,常用到硬件配置不同的场景
upstream backend-b {
server 192.168.1.101:80 weight=3;
server 192.168.1.102:80 weight=7;
}
最少连接 Least-Connect:处理请求少的后端服务优先接收新请求
upstream backend-c {
least_conn;
server 192.168.1.101:80;
server 192.168.1.102:80;
}
IP-Hash:相同的访问IP落到后端同个服务器上,所以支持会话保持,但不是绝对平均负载
upstream backend-d {
ip_hash;
server 192.168.1.101:80;
server 192.168.1.102:80;
}
第三方的会话保持 sticky_cookie_insert,同时支持负载均衡。
2.6 限流与熔断
限流:通过对并发/请求进行限速来保护系统,防止系统过载瘫痪而不能提供服务;为了更好控制整个系统的负载情况,即使阻止了某些请求,系统继续提供服务。
http_limit_conn:单个IP同时允许的连接限制
http {
# 连接限流定义
# - $binary_remote_addr:限制对象(客户端)
# - zone:限制自定义名称
# - 10:内存中用10兆空间存储连接记录
limit_conn_zone $binary_remote_addr zone={limits-name}:10m;
server {
location /search/ {
# 单个IP同时允许建立多少连接(并发限制)
limit_conn {limits-name} 1;
}
}
}
http_limit_req:单个IP请求频率的限制;次/每秒;
http {
# 请求限流定义
# - $binary_remote_addr:限制对象(客户端)
# - zone:定义限制(策略)名称
# - 10m:用十兆空间记录访问次数
# - rate:每秒10次的请求处理速率
limit_req_zone $binary_remote_addr zone={limits-name}:10m rate=1r/s;
# 请求限流定义
# - $server_name:限制对象,对指定服务器请求的限制
limit_req_zone $server_name zone={limits-name}:10m rate=10r/s;
server {
location /search/ {
# 引用以上定义的限流策略,做以下设定(漏桶方式)
# - burst:最多接收5个排队用户IP,处于等待处理状态(容量)
# - nodelay:超出排队之外的更多请求,拒绝并返回503(溢出)
limit_req zone={limits-name} [burst=5] [nodelay];
}
}
}
http_limit_rate:向客户端传输响应的速率限制;字节/每秒/每连接;0不限制
http {
server {
location /download/ {
# 带宽限制
limit_rate_after 5m; # 初始限速5m
limit_rate 500k; # 超出后限速500k
}
}
}
熔断:当后端服务发生指定频率错误后,Nginx触发熔断措施,不再请求此后端服务,直接返回默认内容到用户端。
upstream http_backend {
# 10s内出现3次错误,该服务器将被视为不可用(熔断)
server 192.168.1.101:8080 max_fails=3 fail_timeout=10s;
server 192.168.1.102:8080 max_fails=3 fail_timeout=10s;
}
当然也有容错机制,Nginx 默认自动转向其它服务再请求,相关配置:proxy_next_upstream
三、性能优化
3.1 全局优化
# 工作进程数
worker_processes auto; # 建议 CPU核心数|CPU线程数
# 最大支持的连接(open-file)数量;最大值受限于 Linux open files (ulimit -n)
# 建议公式:worker_rlimit_nofile > worker_processes * worker_connections
worker_rlimit_nofile 65535;
events {
use epoll; # 高效的IO多路复用(RedHat6+ 都支持 epoll)
multi_accept on; # 设置一个进程是否同时接受多个网络连接
worker_connections 10240; # 单个工作进程,可承载的最大连接数;
}
3.2 与客户端之间的优化
http {
###### 零拷贝技术
sendfile on; # 开启不读到(应用本身)内存,直接通过系统发出数据
#sendfile_max_chunk 2m; # 每个进程每次调用传输数量不能大于设定的值,默认0为无上限。
###### 网络传输
# on:累积到一定容量的数据再发,发送次数少
# off:有数据就发,发送次数多,占用网络资源
tcp_nopush on;
###### 长连接;用户端比较分散,keepalive 默认值已经足够,个人不建议重设
###### 响应数据 开启压缩模式
gzip on;
gzip_vary on; # 为兼容老浏览器,追加到Header的压缩标注
gzip_proxied any; # 所有代理请求都压缩,也可指定类型
gzip_comp_level 2; # 压缩等级1-9(比例)等级越大 压缩越高 越耗CPU
gzip_min_length 128; # 压缩前提,当返回内容大于指定时再压缩
gzip_types text/css text/xml image/svg+xml; # 指定压缩的文件类型;更多压缩项可参考 mime.types
}
3.3 与后端服务之间的优化
http {
upstream backend_A {
# ...
# 长连接;所有请求汇聚到后端服务器,并发时是有必要在此基础上重配 keepalive 的
# 以下 keepalive 需要 http 1.1 版本,并且 header connection close
keepalive 100; # 每个Worker与后端的连接池中,保持指定量的空闲连接数(QPS的10%)
keepalive_time 1h; # 每个长连接的(忙碌+空闲)总时长,超出后强制失效
keepalive_timeout 60s; # 每个长连接,最大空闲时长,超出后自动关闭长连接
keepalive_requests 1000; # 每次长连接支持的 Request 次数,超出后自动关闭
}
server {
location \ {
proxy_pass http://backend_A;
proxy_http_version 1.1; # 1.1 支持 keep-live
proxy_set_header connection ""; # 覆盖客户端的连接头
}
}
}
3.4 扩展优化
多级缓存:通过 Nginx 实现各种缓存的配置,浏览器缓存、CDN缓存、Nginx内存、代理缓存、后端服务应用缓存等。
资源静态化 ssi 模块:请求结果生成静态页,Nginx设置拦截,更多的请求直接读静态页后返回;减少后端请求,定时生成静态页。
静态资源同步 rsync:每台服务都安装,监控目录变化,把生成的静态页推送到多台服务器。
合并请求 concat 第三方模块:将多个静态资源文件 合并为一次请求加载完成,减少并发量;淘宝示例:??xxx.js,yyy.js,zzz.js
四、动态负载
自动更新 upstream 上的可用服务地址,Nginx 的商业版才提供,开源的可选第三方模块;这里后端集群管理用的是 Consul;所以这里使用 Consul 提供的配套工具 Consul-Template。
当后端集群 consul 上的应用服务有变动时,工具 consul-template 负责拉取 consul 最新的健康应用服务列表,并生成 nginx conf 后再重载 Nginx。
1、下载部署 consul-template
# 从官网 https://releases.hashicorp.com/consul-template/ 下载软件包
curl -O https://releases.hashicorp.com/consul-template/0.31.0/consul-template_0.31.0_linux_amd64.zip
unzip consul-template_0.31.0_linux_amd64.zip consul-template # unzip解压软件包并重命名
mv consul-template /usr/local/bin/ # 移动到特定目录
/usr/local/bin/consul-template -v # 验证安装效果,显示版本号
2、创建生成 nginx conf 的模板文件 ngx-server-conf.tmp,用于生成指定格式的 nginx.conf 文件;内容示例如下:
为便于集中管理配置文件,存放于 nginx 默认的配置目录下 /etc/nginx/conf.d/ngx-server-conf.tmp
########## 自动生成多个 upstream
{{range services}} {{$name := .Name}} {{$service := service .Name}}
upstream {{$name}} {
least_conn;
{{range $service}}server {{.Address}}:{{.Port}};
{{else}}server 127.0.0.1:65535; # force a 502{{end}}
} {{end}}
########## upstream end ##########
server {
listen 80;
server_name xxx.com;
location / {
root /usr/share/nginx/html/;
index index.html;
}
########## 自动生成多个 locattion
{{range services}} {{$name := .Name}}
location /api/{{$name}} {
proxy_pass http://{{$name}};
proxy_http_version 1.1;
}
{{end}} ########## location end ##########
}
更多的模板文件语法可参考官网说明:consul-template templating language
3、创建 consul-template 的配置文件 ctmp.hcl,用以设定运行参数。内容示例如下:
为便于集中管理配置文件,存放于 nginx 容器内的默认配置目录下 /etc/nginx/conf.d/ctmp.hcl
# 连接 consul 的配置
consul {
address = "172.18.0.3:8500" # consul node
}
# 生成 nginx config file 的配置
template {
source = "/etc/nginx/conf.d/ngx-server-conf.tmp" # consul-template 的配置模板文件
destination = "/etc/nginx/conf.d/default.conf" # 生成的 nginx-server 配置文件
command = ["nginx", "-s", "reload"] # 执行的命令,以重载 Nginx 配置
}
# 总结步骤:从 address 拉数据,通过格式 source 生成 destination 配置,最后 command 重载Nginx。
以上更多的配置项参考官网说明:consul-template configuration options
4、启动运行 consul-template
# nginx 容器中启动 consul-template
/usr/local/bin/consul-template -config /etc/nginx/conf.d/ctmp.hcl
# 验证效果:可查看生成的 nginx conf
cat /etc/nginx/conf.d/default.conf
效果:先查看当前 nginx default.conf;之后停掉后端一个应用服务,对比 nginx 前后两个 default.conf 的内容变化。
五、高可用
高可用 - 双机热备:主机和从机通过TCP/IP网络连接,正常情况下主机处于工作状态,从机处于监视状态,一旦从机发现主机异常,从机将会在很短的时间之内代替主机,完全实现主机的功能。
工具 Keepalived,安装到内网中每个装有 Nginx 的系统上:
多个 Keepalived 实例形成一个组,组成员有 Master/slave 之分,时时监控组内所有 Keepalived 实例的运行情况;
Keepalived 通过一个虚拟IP加入到 Master 网卡上,所以通过虚拟IP能够直接连接到 Master上,也就是其中一个 Nginx;
一个 Keepalived 成员宕机后,从 Slave 中选举出新的 Master,也就是把虚拟IP自动加入到新的 Master上,持续提供服务;
对外仅通过内网的虚拟IP完成与 Nginx 的连接,而不关心使用的 Nginx 在哪台机上。
5.1 配置运行 Keepalived
安装 Keepalived:yum install -y keepalived
配置文件:/etc/keepalived/keepalived.conf
配置模板:可仅留 global_defs,vrrp_instance,virtual_ipaddress
global_defs {
router_id <key> # 同组不重复的唯一标识
}
vrrp_instance <Instance-Name> {
state MASTER # MASTER/BACKUP;有主优先运行
interface <net-card-name> # 指定虚拟IP寄存的网卡
priority 100 # 相同角色的优先级,越大越优先
advert_int 1 # 间隔秒检测一次成员的运行状况
authentication { # 成员间的通讯凭证
auth_type PASS # 同组相同的方式
auth_pass 1111 # 同组相同的编码,保持成员互通
}
virtual_ipaddress { # 追加新IP,对外提供的通讯入口
192.168.17.200 # 同网段的、未被使用的、虚拟新IP
}
}
启动后,可在 Master 中的指定网卡中看到已追加的虚拟IP;不妨 ping 下你的虚拟IP...
再配置一台 Keepalived,注意这里配置的不同项为:router_id / state / priority
浏览器中访问虚拟IP,就直接访问了 Master 上的 Nginx;
关掉Master服务器后,Keepalived 将虚拟IP又添加到了备用服务器上了;
虚拟IP继续提供正常的 Nginx 服务,浏览器正常访问虚拟IP地址;
当 Master 修复启动后,Keepalived 又将虚拟IP自动切换到 Master 上,始终以 Master 优先使用。
注意
Keepalived 仅检测自己主进程的运行状况,并不是检测 Nginx 的运行状况;
所以:当 Nginx 错误,而 Keepalived 运行正常时,并不能达到 Master 转移的效果;
方案:用脚本定时检测 Nginx 的主进程,Nginx发生错误时主动 Kill Keepalived,达到 Master 转移的效果。
5.2 脚本检测 Nginx 服务
创建检测脚本文件 /etc/keepalived/check_nginx.sh 内容示例:
#!/bin/bash
# 检测 Nginx worker process 运行的个数
ckn=$(ps -C nginx --no-heading | wc -l)
# 当没有 Nginx worker process 运行时
# 制造keepalived异常,使得启用备用服务器
if [ $ckn -eq 0 ]; then
killall -9 keepalived
fi
并赋予用户可执行权限;如:chmod +x /etc/keepalived/check_nginx.sh
Keepalived 脚本检测配置内容示例:
# global_defs ...
vrrp_script check_nginx_status { ### 定义检测策略
user root # 负责检测的用户
interval 1 # 检测间隔秒
script /etc/keepalived/check_nginx.sh # 检测的可执行文件
}
vrrp_instance <Instance-Name> {
# ...
track_script {
check_nginx_status # 引用检测策略
}
# ...
}
在 Master 上停止 Nginx 运行 试试看...🥝🥝
以上是一个联动停止运行的方案,那是不是需要一个联动启动也更为方便,自行考量...
Recommend
-
40
简介 上一篇介绍了 Hystrix Dashboard 监控单体应用的例子,在生产环境中,监控的应用往往是一个集群,我们需要将每个实例的监控信息聚合起...
-
28
-
26
点击上方 “中间件兴趣圈” , 选择 “设为星标” 做积极的人,越努力越幸运!
-
31
作者:人月神话,新浪博客同名 简介:多年SOA规划建设,私有云PaaS平台架构设计经验,长期从事一线项目实践 今天准备谈下微...
-
33
限流、熔断与降级 限流、熔断与降级,此三者都是流量过大时,通过一定的方式去保护系统的手段,是应对海量服务的三大“神器”
-
10
聊一聊限流、降级、熔断小时候村里一到夏天,全村都开空调,村里总闸的保险丝就会因为用电量太大,自动熔断了,直接停服。股市也有一些极端情况开启熔断处理,不到万不得已,不会熔断。在...
-
6
重新整理 .net core 实践篇————熔断与限流[三十五] 简单整理一下熔断与限流,跟上一节息...
-
8
Envoy 可以作为 Sevice Mesh 微服务框架中的代理实现方案,Rainbond 内置的微服务框架同样基于 Envoy 实现。本文所描述的全局限速实践也是基于 Envoy 已有的方案所实现。 Envoy 全局限速
-
7
微服务网关zuul限流、熔断、路由规则详解 | 小张哥blog 最近在研究灰度方案。 灰度主要涉及两个方面,一个是网关,一个是配置...
-
6
微服务1:微服务及其演进史
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK