nginx一些配置

负载均衡和反向代理

一般来说负载均衡我们比较关心一下几点:

  • 上游服务器配置: 使用 upstream server 配置上游服务器
  • 负载均衡算法: 配置多个上游服务器时的负载均衡机制
  • 失败重试机制: 配置当超时或上游服务器不存活时,是否需要重试其他上游服务器
  • 服务器心跳检查: 上游服务器的检查心跳/心跳检查

nginx 作为负载均衡器/反向代理服务器如下图所示:
nginx 作为负载均衡器/反向代理服务器

upstream 配置

  • 给 nginx 配置上游服务器,即负载均衡到的真是处理业务的服务器,通过 http 指令下配置 upstream 即可。

upstream dao{
    server 192.168.61.1:9080 weight=1;
    server 192.168.61.1:9090 weight=2;
}

upstream server 的主要配置如下:

  • IP 地址和端口:配置上游服务器的 IP 和端口
  • 权重: weight 用来配置权重,默认都是 1,权重越高分配给这台服务器的请求就越多(如上配置中每三次请求其中一个是转发给 9080 ,其余两个转发给9090),需要根据服务器实际处理能力设置权重.
  • 配置如下 proxy_pass 来处理用户请求

location /{
    proxy_pass http://dao
}

当访问 nginx 时,会将请求反向代理到 dao 配置的 upstream server。

负载均衡算法

负载均衡是用来解决用户请求到来时如何选择 upstream server 进行处理,默认采用 round-robin(轮询),除此之外还支持其它的负载均衡算法,如下所示。

  • round-robin: 是 nginx 默认的负载均衡算法,即以轮询的方式将请求转发到上游服务器,通过配合 weight 配置实现服务器权重的轮询。
  • ip_hash:根据客户 IP 进行负载均衡,即相同的 IP 将负载均衡到同一个 upstream server,配置如下:

upstream dao{
	ip_hash;
    server 192.168.61.1:9080 weight=1;
    server 192.168.61.1:9090 weight=2;
}

  • hash key [consistent]:对某一个 key 进行哈希或者使用一致性哈希算法进行负载均衡。使用 hash 算法存在的问题是,当添加或者删除一台服务器时,将导致很多 key 被重新负载均衡到不同的服务器(这样会导致服务端会出现问题);因此,建议考虑使用一致性哈希算法,这样就算添加或删除一台服务器,也只有少算的 key 被重新负载均衡到不同的服务器。
  • 哈希算法:以请求的 uri 进行负载均衡,当然也可以使用 nginx 变量,因此可以实现很复杂的算法。

upstream dao{
	hash $uri;
    server 192.168.61.1:9080 weight=1;
    server 192.168.61.1:9090 weight=2;
}

  • 一致性哈希算法:consistent_key 动态指定

upstream  nginx_local_server{
	hash $consistent_key consistent;
    server 192.168.61.1:9080 weight=1;
    server 192.168.61.1:9090 weight=2;
}

如果 location 指定了一致性哈希 key,此外会优先考虑请求参数 cat(类目),如果没有,则再根据请求的 uri 进行负载均衡。


location / {
	set $consistent_key $arg_cat;
	if($consistent_key = ""){
		set $consistent_key $request_uri;
	}
}

不过我们更倾向于通过 lua 设置一致性哈希 key。

set_by_lua_file $consistent_key “lua_balancing.lua”;

lua_balancing.lua 代码如下:


 local consistent_key = args.cat
 if not consistent_key or consistent_key == '' then
 	consistent_key = ngx_var.request_uri
 end
local value = balancing_cache:get(consistent_key)

if not value then 
   success,err = balancing_cache:set(consistent_key,1,60)
else
    newval,err=balancing_cache:incr(consistent,1)
end

如果某一个分类请求量太大,上游服务器可能处理不了这么多请求,此时可以在一致性哈希 key 后加上递增的计数以实现类似轮询的算法。


if newval > 5000 then 
	consisitent_key = consistent_key .. '_' .. newval
end

  • least_conn: 将请求负载均衡到最少活跃连接的上游服务器。如果配置来的服务器较少,则将转而使用基于权重的轮询算法。
  • Nginx 商业版还提供了 least_time:基于最小平均响应时间进行负载均衡。

失败重试

主要有两部分配置: upstream server 和 proxy_pass。


upstream dao{
	server 192.168.61.1:9080 max_fails=2 fail_timeout=10s weight=1;
	server 192.168.61.1:9090 max_fails=2 fail_timeout=10s weight=1;
}

通过配置上游服务器的 max_fails 和 fail_timeout,来指定每一个上游服务器,当 fail_timeout 时间内失败了 max_fails 次请求,则认为该上游服务器不可用/不存活,然后将摘掉该上游服务器,fail_timeout 时间后会将该服务器加入到存活上游服务器列表进行重试。


location /test{
	proxy_connect_timeout 5s;
	proxy_read_timeout 5s;
	proxy_send_timeout 5s;
	proxy_next_upstream error timeout;
	proxy_next_upstream_timeout 10s;
	proxy_next_upstream_tries 2;
	proxy_pass http://dao;
	add_header upstream_addr $upstream_addr;
}

然后进行 proxy_next_upstream 相关配置,当遇到错误时,会重试下一台上游服务器。

健康检查

nginx 对上游服务器的健康检查默认采用的是惰性策略,nginx 商业版提供了 health_check 进行主动健康检查,也可以集成 nginx_stream_check_module 模块进行主动健康检查。
nginx_stream_check_module 支持 TCP 心跳和 HTTP 心跳来实现健康检查。

  • TCP 心跳检查

upstream dao{
	server 192.168.61.1:9080 weight=1;
	server 192.168.61.1:9090 weight=2;
	check interval=3000 rise=1 fall=3 timeout=2000 type=tcp;
}

该处使用 TCP 进行心跳检测,下面对参数进行一些解释说明

  • interval: 检测间隔时间,此处配置了每隔3秒检测一次
  • fall: 检测失败多少次后,上游服务器被标识为不存活
  • rise: 检测成功多少次后,上游服务器被标识为存活,并可以处理请求
  • timeout: 检测请求超时时间配置
  • HTTP 心跳检查

upstream dao{
	server 192.168.61.1:9080 weight=1;
	server 192.168.61.1:9090 weight=2;
	check interval=3000 rise=1 fall=3 timeout=2000 type=http;
	check_http_send "HEAD /status HTTP/1.0\r\n\r\n";
	check_http_expect_alive http_2xx http_3xx;
}

HTTP 心跳检查有如下两个需要额外配置。

  • check_http_send: 即检查时发的 HTTP 请求内容。
  • check_http_expect_alive: 当上游服务器返回匹配的响应状态码时,则认为上游服务器存活。

其他配置

  • 域名上游服务器
	
	upstream dao{
		server wwjd.fun;
		server xu.wwjd.fun
	}
	

在 nginx 社区版中,是在 nginx 解析配置文件的阶段将域名解析成 IP 地址记录到 upstream 上,当这两个域名对应的 IP 地址发生变化时,该 upstream 不会更新。nginx 商业版才支持动态更新。
不过,proxy_pass http://wwjd.fun 是支持动态域名解析的。

  • 备份上游服务器
upstream dao{
	hash $uri;
    server 192.168.61.1:9080 weight=1;
    server 192.168.61.1:9090 weight=2 backup;
}

将 9090 端口上游服务器配置为备上游服务器,当所有上游服务器都不存活时,请求会转发给备上游服务器。
如通过缩容上游服务器进行压测,要摘掉一些上游服务器进行压测,单位了保险起见会配置一些备上游服务器,当压测的上游服务器都挂掉时,流量可以转发到备上游服务器,从而不影响用户请求处理。

  • 不可用上游服务器
upstream dao{
	hash $uri;
    server 192.168.61.1:9080 weight=1;
    server 192.168.61.1:9090 weight=2 down;
}

9090 端口上游服务器配置为永久不可用,当测试或者机器出现故障时,暂时通过该配置临时摘掉机器。

长连接

这里主要说如何配置 nginx 与上游服务器的长连接,可以通过 keepalive 指令配置长连接数。


upstream dao{
    server 192.168.61.1:9080 weight=1;
    server 192.168.61.1:9090 weight=2 backup;
	keepalive 100;
}

通过该指令配置每个 worker 进程与上游服务器可缓存的空闲连接的最大数量。当超过这个数量时,最近最少使用的连接将被关闭。 keepalive 指令不限制 worker 进程与上游服务器的总连接。
如果想跟上游服务器建立长连接,可别忘了下面的配置


location / {
	# 支持 keep-alive
	proxy_http_version 1.1;
	proxy_set_header Connection "";
	proxy_pass http://dao;

}

如果是 http/1.0,则需要配置发送 “Connection:Keep-Alive”请求头。上游服务器不要忘记开启长连接支持。
然后我们看看 nginx 是如何实现 keepalive 的(ngx_http_upstream_keepalive_module),获取连接时的部分代码。


ngx_http_upstream_get_peer(ngx_peer_connection_t *pc,void *data){
	//1.首先询问负载均衡使用哪台服务器(IP和端口)
	rc = kp->original_ get_ peer(pc, kp->data) ;
	
	cache = &kp->conf->cache;
	//2.轮询“空闲连接池”
	for (q = ngx_ queue_ head (cache) ;q!= ngx_ queue_ sentinel (cache) ;q = ngx_ queue_ next(q) )
	{
		item = ngx_ queue_ data(q, ngx_ http_ upstream_ keepalive_ cache_ t, queue) ;
		C = item->connection;
		//2.1.如果“空闲连接池”缓存的连接IP和端口与负载均衡到的IP和端口相同,则使用此连接
		if (ngx_ memn2cmp((u_ char *) &item->sockaddr, (u_ char *) pc->sockaddr,item->socklen, pc->socklen) == 0) {
			//2.2从“空闲连接池”移除此连接并压入“释放连接池”栈顶
			ngx_ queue_ remove (q) ;
			ngx_ queue_ insert_ head(&kp->conf->free,q) ;
			
			goto found;
		}
	}
	// 3. 如果 “空闲连接池” 没有可用的长连接,将穿件短连接
	return NGX_OK;
}

释放连接时的部分代码如下。


ngx_ http_ upstream free_ keepalive_ peer (ngx_ peer_ connection_ t *pc,void *data, ngx_ uint_ t state)
{
	//当前要释放的连接
	c = pc->connection;
	//1.如果“释放连接池”没有待释放连接,那么需要从“空闲连接池”腾出一个空间给新的连接使用(这种情况存在于创建连接数超出了连接池大小时,这就会出现震荡)
	if (ngx_ queue_ empty (&kp->conf->free)) {
		q = ngx_ queue_ last (&kp->conf->cache) ;
		ngx_ queue_ remove(q) ;
		item = ngx_ queue_ data (q,  ngx_ http_ upstream_ keepalive_ cache_ t,queue);
		ngx_ http_ upstream keepalive_ close (item->connection) ;
	} else {
		//2. 从“释放连接池”释放一个连接
		q = ngx_ queue_ head (&kp->conf->free) ;
		ngx_ queue_ remove(q) ;
		item = ngx_ queue_ data(q, ngx_ http_ upstream_ keepalive_ cache_ t,queue) ;
	}
	 //3.将当前连接压入“空闲连接池”栈顶供下次使用
	ngx_ queue_ insert_ head (&kp->conf->cache, q) ;
	item->connection = c;
}

总长连接数是“空闲连接池”+“释放连接池”的长连接总数。首先,长连接配置不会限制 Worker 进程可以打开的总连接数(超了的作为短连接)。另外,连接池一定要根据实际场景合理进行设置。

  • 空闲连接池太小,连接不够用,需要不断建连接。
  • 空闲连接池太大,空闲连接太多,还没使用就超时。

另外,建议只对小报文开启长连接。

http 反向代理示例

反向代理除了实现负载均衡之外,还提供如缓存来减少上游服务器的压力。

  • 全局配置

proxy_buffering             on;
proxy_buffer_size           4k;
proxy_buffers               512 4k;
proxy_busy_buffers_size     64k;
proxy_temp_file_write_size  256k;
proxy_cache_lock            on;
proxy_cache_lock_timeout    200ms;
proxy_connect_timeout       3s;
proxy_read_timeout          5s;
proxy_send_timeout          5s;

开启 proxy buffer,缓存内容将存放 tmpfs (内存文件系统)以提升性能,设置超时时间。

  • location 配置

 location ~* ^/dao/(.*)$ {
	# 设置一致性哈希负载均衡 key
	set_by_lua_file $consistent_key "/dao/lua/lua_balancing_dao.properties";
	# 失败重试配置
	proxy_next_upstream error timeout http_500 http_502 http_504;
	proxy_next_upstream_timeout 2s;
	proxy_next_upstream_tries 2;
	# 请求上游服务器使用服务器使用 GET 方法(不管是什么方式请求)
 	proxy_method GET;
 	# 不给上游服务器传递请求体
 	proxy_pass_request_body off;
 	# 设置上游服务器的哪些响应头不发送给客户端
 	proxy_hide_header Vary;
 	# 支持 keep-alive
 	proxy_http_version 1.1;
 	proxy_set_header Connection "";
 	# 给上游服务器传递 X-Forwarded-For、Referer、Cookie 和 Host (按需传递)
 	proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Host $server_name;
    proxy_set_header Referer $http_referer;
    proxy_set_header Cookie $http_cookie;
    proxy_set_header Host $host;
 }

我们开启了 proxy_pass_request_body 和proxy_pass_request_headers,禁止向上游服务器传递请求头和内容体,从而使得上游服务器不受请求头攻击,也不需要解析;如果需要传递,则使用 proxy_set_header 按需传递即可。
我们还可以通过如下配置来开启 gzip 支持,减少网络传输的数据包大小。


gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
#gzip_http_version 1.0;
gzip_comp_level 2;
gzip_proxied any;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";

  • gzip:开启 Gzip
  • gzip_min_length:不压缩临界值,大于 1K 的才压缩,一般不用改
  • gzip_buffers:buffer 就是缓存,不用改
  • gzip_http_version:用了反向代理的话,末端通信是 HTTP/1.0,有需求的应该也不用看我这科普文了;有这句的话注释了就行了,默认是 HTTP/1.1
  • gzip_comp_level:压缩级别,1-10,数字越大压缩的越好,时间也越长,看心情随便改吧
  • gzip_proxied:Nginx作为反向代理的时候启用,根据某些请求和应答来决定是否在对代理请求的应答启用gzip压缩,是否压缩取决于请求头中的“Via”字段,指令中可以同时指定多个不同的参数,意义如下:
  • expired - 启用压缩,如果header头中包含 “Expires” 头信息
  • no-cache - 启用压缩,如果header头中包含 “Cache-Control:no-cache” 头信息
  • no-store - 启用压缩,如果header头中包含 “Cache-Control:no-store” 头信息
  • private - 启用压缩,如果header头中包含 “Cache-Control:private” 头信息
  • no_last_modified - 启用压缩,如果header头中不包含 “Last-Modified” 头信息
  • no_etag - 启用压缩 ,如果header头中不包含 “ETag” 头信息
  • auth - 启用压缩 , 如果header头中包含 “Authorization” 头信息
  • any - 无条件启用压缩
  • gzip_types text/plain:进行压缩的文件类型,缺啥补啥就行了,JavaScript 有两种写法,最好都写上吧,总有人抱怨 js 文件没有压缩,其实多写一种格式 application/javascript 就行了
  • gzip_vary:跟 Squid 等缓存服务有关,on 的话会在 Header 里增加 “Vary: Accept-Encoding”,我不需要这玩意,自己对照情况看着办吧
  • gzip_disable:IE6 对 Gzip 不怎么友好,不给它 Gzip 了

http 动态负载均衡

如上的负载均衡实现中,每次 upstream 列表有变更,都需要到服务器进行修改,首先是管理容易出现问题,而且对于 upstream 服务上线无法自动注册到 nginx upstream 列表。因此,我们需要一种服务注册,可以将 upstream 动态注册到 nginx 上,从而实现 upstream 服务的自动发现。
Consul 是一款开元的分布式服务注册与发现系统,通过 http api 可以使得服务注册,发现,实现起来非常简单,它支持如下特性:

  • 服务注册:服务实现者可以通过 HTTP API 或 DNS 方式,将服务注册到 Consul。
  • 服务发现:服务消费者可以通过 HTTP API 或 DNS 方式,从 Consul 获取服务的 IP 和 PORT.
  • 故障检测:支持如 TCP 。 HTTP 等方式的健康检查机制,从而当服务有故障时自动摘除。
  • K/V 存储:使用 K/V 存储实现动态配置中心,其使用 HTTP 长轮询实现变更出发和配置更改。
  • 多数据中心:支持多数据中心,可以按照数据中心注册和发现服务,即支持只消费本地机房服务,使用多数据中心集群还可以避免单数据 中心的单点故障。
  • Raft 算法:Consul 使用 Raft 算法实现集群数据一致性。
    通过 Consul 可以管理服务注册与发现,接下来需要有一个与 nginx 部署在同一台机器的 Agent 来实现 nginx 配置更改和 nginx 重启功能。我们有 Confd 或者 Consul-template 两个选择,而 Consul-template 是 Consul 官方提供的,我们一般选择它。其使用 http 长轮询实现变更触发和配置更改(使用 Consul 的 watch 命令实现)。即我们使用的 Consul-template 实现配置模板,然后拉取 Consul 配置渲染模板来生成 nginx 实际配置。
    除了 Consul 之外,还有一个选择是 etcd3,其使用了 gRPC 和 protobuf 可以说是一个亮点。但是 etcd3 目前没有提供多数据中心、故障检测、web 界面。

Consul + Consul-template

让我们看看如何实现 nginx 的动态配置。首先,下图是我们要实现的架构图。

nginx 动态配置架构图
首先,upstream 服务启动,我们通过管理后台向 Consul 注册服务。
我们需要在 nginx 机器上部署并启动 Consul-template Agent,其通过长轮询监听服务变更。
Consul-template 监听变更后,动态修改 upstream 列表。
Consul-template 修改完 upstream 列表后,调用重启 nginx 脚本重启 nginx。
整个实现过程还是比较简单的,不过实际生产环境要复杂得多。一般使用 Consul 0.7.0 和 Consul-template 0.16.0 来实现。

  • Consul-Server
    首先我们要启动 Consul-Server
./consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul-bind 0.0.0.0 -client 0.0.0.0

此处需要使用 data-dir 指定 agent 状态存储位置,bind 指定集群通信的地址,client 指定客户端通信的地址(如 Consul-template 与 Consul 通信)。在启动时还可以使用 -ui-dir 指定 Consul Web UI 目录,实现通过 Web UI 管理 Consul,然后访问如 http://127.0.0.1:8500 即可看到控制界面。
控制界面
使用如下 HTTP API 注册服务


curl -X PUT http://127.0.0.1:8500/v1/catalog/register -d '{"Datacenter": "dc1","Node": "tomcat", "Address": "192.168.1.1", "Service": {"Id" : "192.168.1.1 :8080","Service": "item_ jd_ tomcat", "tags": ["dev"], "Port": 8080}}'

curl -X PUT http://127.0.0.1:8500/v1/catalog/register -d '{"Datacenter": "dc1","Node": "tomcat", "Address": "192.168.1.2", "Service": {"Id" : "192.168.1.1 :8090","Service": "item_ jd_ tomcat", "tags": ["dev"], "Port": 8090}}'

Datacenter 指定数据中心,Address 指定服务器 IP ,Service.Id 指定服务唯一标识,Service.Service 指定服务分组,Server.tags 指定服务标签(如测试环境,预发环境等),Service.Port 指定服务器端口。
通过如下 HTTP API 摘除服务。


curl -x PUT http://127.0.0.1:8500/v1/catalog/deregister -d '{"Datacenter": "dc1","Node": "tomcat", "serviceID":"192.168.1.1:8080"}'

通过如下 HTTP API 发现服务。


curl http://127.0.0.1:8500/v1/catalog/service/item_jd_tomcat

可以看到,通过这几个 HTTP API 可以实现服务注册与发现。更多 API 请参考 consul 文档中心

  • Consul-template
    接下来我们需要在 Consul-template 机器上添加一份配置模板 item.jd.tomcat.ctmpl.

	upstream item_jd_tomcat{
		server 127.0.0.1:111;
		# 占位 server,必须有一个 server,否则无法启动
		{{range service "dev.item_jd_tomcat@dc1"}}
			server {{.Assress}}:{{.Port}} weight=1;
		{{end}}
	}

service指定格式为:标签。服务@数据中心,然后通过循环输出 Address 和 Port,从而产生 nginx upstream 配置。
启动 Consul-template 命令如下:


#!/bin/bash
ps -ef|grep nginx |grep -v grep
if [ $? -ne 0 ]
then
	sudo /usr/server/nginx/sbin/nginx
	echo "nginx start"
else
	sudo /usr/servers/nginx/sbin/nginx -s reload
	echo "nginx reload"
fi

也就是说 nginx 没有启动,则启动,否则重启

  • Java 服务
    建议配合 Spring Boot + Consul Java Client 实现,我们使用的 Consul Java Client 如下。

<dependency>
	<groupId>com.orbitz.consul</groupId>
	<artifactId>consul-client</artifactId>
	<version>0.12.8</version>
</dependency>

如下代码是进行服务注册与摘除。


public static void main(String[] args){
	// 启动嵌入容器
	SpringApplication.run(Bootstrap.class,args);
	// 服务注册
	Consul consul = Consu.builder().withHostAndPort(HostAndPort.fromString("192.168.61,129:8500")).build();
	final AgentClient agentClient = consul.gentClient();
	String service = "item_jd_tomcat";
	String address = "192.168.61.1";
	String tag = "dev";
	int port = 9080;
	final String serviceId = address + ":" + port;
	ImmutableRegistration.Builder builder = ImmutableRegistration.builder();
	builder.id(serviceId).name(service).address(address).port(port).addTags(tag);
	agentClient.register(builder.build());
	// JVM 停止时摘除服务
	Runtime.getRuntime().addShutdownHook(new Thread(){
		@Override
		public void run(){
			agentClient.deregister(serviceId);
		}
	});
}

在 Spring Boot 启动后进行服务注册,然后在 JVM 停止时进行服务摘除。

到此我们就实现了动态 upstream 负载均衡,upstream 服务启动后自动注册到 nginx,upstream 服务停止时,自动从 nginx 上摘除。
通过 Consul+Consul-template 方式,每次发现配置变更都需要 reload nginx,而 reload 是有一定损耗的。而且,如果你需要长连接支持的话,那么当 reload nginx 时长连接所在的 worker 进程会进行优雅退出,并当该 worker 进程上的所有连接都释放时,进程才真正退出(表现为 worker 进程处于 worker process is shutting down)。因此,如果能做到不 reload 能动态更改 upstream,那就完美了。对于社区版的 nginx 目前有三种选择:

  • Tengine 的 Dyups 模块
  • 微博的 Upsync
  • 使用 OpenResty 的 balancer_by_lua

微博使用的是 Upsync + Consul 实现动态负载均衡的,而又拍云使用开源的 slardar(Consul + balancer_by_lua) 实现动态负载均衡的。

Consul + OpenResty

使用 Consul 注册服务,使用 Openresty balancer_by_lua 实现无 reload 动态负载均衡,架构如下所示。
Openresty balancer_by_lua 实现无 reload 动态负载均衡 架构图

  • 通过 upstream server 启动/停止时注册服务,或者通过 Consul 管理后台注册服务

  • Nginx 启动时会调用 init_by_lua,启动时拉取配置,并更新到共享字典来存储 upstream 列表;通过 init_worker_by_lua 启动定时器,定期去 Consul 拉取配置并实时更新到共享字典

  • balancer_by_lua 使用共享字典存储的 upstream 列表进行动态负载均衡

    dyna_ upstreams . lua 模块

      local http = require("socket.http")
      local ltn12 = require("ltn12")
      local cjson = require "cjson"
      local function update_ upstreams()
      	local resp = {}
      	http. request{
	      url="http://192.168.61.129:8500/v1/catalog/service/item_ jd_ tomcat",
	      sink = ltn12.sink.table(resp)
		}
      	resp = table.concat(resp)
      	resp = cjson.decode(resp)

      	local upstreams = {{ip="127.0.0.1", port=1111}} 
      	for i, v in ipairs (resp) do
	      upstreams[i+1] = {ip=v.Address, port=v.ServicePort}
	    end 
		ngx.shared.upstream_ list:set("item_ jd_ tomcat", cjson.encode(upstreams))
		end 

	local function get_upstreams ()
      local upstreams_str = ngx.shared.upstream_list:get ("item_ jd_ tomcat")end
	local M={
      update_upstreams = update upstreams,
      get_upstreams = get_upstreams
	}


通过 luasockets 查询 Consul 来发现服务,update_upstreams 用于更新 upstream 列表,get_upstreams 用于返回 upstream 列表,此处可以考虑 worker 进程级别的缓存,减少因为 json 的反序列化造成的性能开销。

还要注意我们使用的 luasocket 是阻塞 API, 因为截至本文书写,OpenResty 在 init by_lua 和init worker_by_lua 不支持 Cosocket (未来会添加支持),所以我们只能使用 luasocket, 但是,注意这可能会阻塞我们的服务,使用时要慎重。

init_*by lua 配置


#存储upstream列表的共享字典

lua_shared_dict upstream_list 10m;

#Nginx Master 进程加载配置文件时执行,用于第一次初始化配置
init_by_lua_block {
      local dyna_upstreams = require "dyna_ upstreams";
      dyna_upstreams.update_upstreams();
}

#Nginx Worker 进程调度,使用 ngx.timer.at 定时拉取配置
init_worker_by_lua_block {
      local dyna_upstreams = require "dyna_upstreams";
      local handle = nil;
      handle = function ()
	      --TODO:控制每次只有一个 worker 执行
	      dyna_upstreams.update_upstreams ();
	      ngx.timer.at(5,handle) ;
      end

      ngx.timer.at(5, handle);
}

init_worker_by_lua 是每个 nginx Worker 进程都会执行的代码,所以实际实现时可考虑使用锁机制,保证一次只有一个人处理配置拉取。另外 ngx.timer.at 是定时轮询,不是走的长轮询,有一定的时延。有个解决方案,是在Nginx 上暴露 HTTP API, 通过主动推送的方式解决。

在这里插入图片描述
Agent 可以长轮询拉取,然后调用 HTTP API 推送到 Nginx 上,Agent 可以部署在 Nginx 本机或者远程。

对于拉取的配置,除了放在内存里,请考虑在本地文件系统中存储一份,在网络出问题时作为托底。

upstream配置


upstream item_jd_tomcat {
	server 0.0.0.1; #占位server
	balancer_by_lua_block{
		local balancer = require "ngx . balancer"
		local dyna_upstreams = require "dyna_ upstreams";
		local upstreams = dyna_upstreams.get_upstreams() ;
		local ip_port = upstreams[math.random(1,table.getn (upstreams))]
		ngx.log(ngx.ERR,  "current : =============",math.random(1, table.getn(upstreams)))
		balancer.set_current_peer(ip_port.ip, ip_port.port)
		}
}

获取 upstream 列表,实现自己的负载均衡算法,通过 ngx.balancer API 进行动态设置本次 upstream server。通过 balancer_by_lua 除可以实现动态负载均衡外,还可以实现个性负载均衡算法。

最后,记得使用 lua-resty-upstream-healthcheck 模块进行健康检查。

nginx 四层负载均衡

Nginx 1.9.0 版本起支持四层负载均衡,从而使得 Nginx 变得更加强大。目前,四层软件负载均衡器用得比较多的是 HaProxy;而 Nginx 也支持四层负载均衡,一-般场景我们使用 Nginx 一站式解决方案就够了。本部分将以 TCP 四层负载均衡进行示例讲解。

静态负载均衡

在默认情况下,ngx_stream_core_module 是没有启用的,需要在安装 nginx 时,添加 --with-stream 配置参数启用。


./configure --prefix=/usr/servers --with-stream

  • stream 指令
    我们配置 HTTP 负载均衡时,都是配置在 http 指令下,而四层负载均衡是配置在 stream 指令下。
	
	stream{
		upstream mysql_dao{
			···
		}
		server{
			···
		}
	}
	
  • upstream 配置

类似于 http upstream 配置,配置如下


	upstream mysql_dao{
		server 192.168.0.10:3306 max_fail_tiimeout=10s weight=1;
		server 192.168.0.11:3306 max_fail_tiimeout=10s weight=1;
		least_conn;
	}
	

进行失败重试。惰性健康检查、负载均衡算法相关配置。与 HTTP 负载均衡配置类似,不再重复解释。此处我们配置实现了两个数据库服务器的 TCP 负载均衡。

  • server 配置

      server {
	      #监听端口
	      listen 3308;
	      #失败重试
	      proxy_next_upstream on;
	      proxy_next_upstream_timeout 0;
	      proxy_next_upstream_tries 0 ;
	      #超时配置
	      proxy_connect_timeout 1s;
	      proxy_timeout 1m;
	      #限速配置
	      proxy_upload_rate 0;
	      proxy_download_rate 0;
	      #上游服务器
	      proxy_pass mysq1_dao;
      }

listen 指令指定监听的端口,默认 TCP 协议,如果需要 UDP,则可以配置“listen 3308 udp”。

proxy_next_upstream* 与之前讲过的 HTTP 负载均衡类似,不再重复解释。proxy_connect_timeout 配置与上游服务器连接超时时间,默认60s。proxy_timeout 配置与客户端或上游服务器连接的两次成功读/写操作的超时时间,如果超时,将自动断开连接,即连接存活时间,通过它可以释放那些不活跃的连接,默认10分钟。proxy_upload_rate 和 proxy_download_rate 分别配置从客户端读数据和从上游服务器读数据的速率,单位为每秒字节数,默认为0,不限速。
Nginx 的 3308 端口,访问我们的数据库服务器了。
目前的配置都是静态配置,像数据库连接–般都是使用长连接,如果重启 Nginx 服务器,则会看到如下 Worker 进程一直不退出。


      Nobody 10268 ..... nginx: worker process is shutting down

这是因为 Worker 维持的长连接一直在使用,所以无法退出,解决办法只能是杀掉该进程。

当然,一般情况下是因为需要动态添加/删除上游服务器,才需要重启 Nginx, 像 HTTP 动态负载均衡那样。如果能做到动态负载均衡,则一大部分问题就解决了。一 个选择是购买 Nginx 商业版,另一个选择是使用 nginx-stream-upsync-module,目前,OpenResty 提供的 stream-lua-nginx-module 尚未实现 balancer_by_lua 特性,因此暂时无法使用。当前开源选择可以使用 nginx-stream-upsync-module。

动态负载均衡

nginx-stream-upsync-module 有一个兄弟 nginx-upsync-module,其提供了 HTTP 七层动态负载均衡,动态更新上游服务器不需要 reload nginx。 当前最新版本是基于 Nginx1.9.10 开发的,因此兼容 1.9.10+ 版本。 其提供了基于 consul 和 etcd 进行动态更新上游服务器实现。本部分基于 Nginx 1.9.10 版本和 consul 配置中心进行演示。

首先,需要下载并添加 nginx-stream-upsync-module 模块最新版本。


./configure --prefix=/usr/servers --with-stream --add-module=./nginx-stream-upsync-module

  • upstream 配置

upstream mysq1_ dao {
	server 127.0.0.1:1111;
	#占位 server
	upsync 127.0.0.1: 8500/v1/kv/upstreams/mysql dao upsync_timeout = 6m
				upsync_interval=500ms upsync_type=consul strong_dependency=off;
	upsync_dump_path /usr/ servers/ nginx/conf/mysq1_dao.conf;
}

upsync 指令指定从 consul 哪个路径拉取上游服务器配置; upsync_timeout 配置从 consul 拉取上游服务器配置的超时时间;upsync_interval 配置从 consul 拉取上游服务器配置的间隔时间;upsync_type 指定使用 consul 配置服务器;strong_dependency 配置 nginx 在启动时是否强制依赖配置服务器,如果配置为 on,则拉取配置失败时 nginx 启动同样失败。

upsync_dump_path 指定从 consul 拉取的上游服务器后持久化到的位置,这样即使 consul 服务器出问题了,本地还有一个备份。

  • 从 Consul 添加上游服务器

curl -X PUT -d "{\"weight\":1, \"max_ fails\":2, \"fail_ timeout\":10}" http://127.0.0.1:8500/v1/kv/ upstreams/mysql_dao/10.0.0.24:3306
curl -X PUT -d "{\"weight\":1, \"max_ fails\":2, \"fail_ timeout\":10}" http://127.0.0.1:8500/v1/ kv/upstreams/mysql_dao/ 192.168.0.11:3306

  • 从 Consul 删除上游服务器

curl -X  DELETE http://127.0.0.1 :8500/v1/kv/upstreams/mysql_ dao/192.168.0.11 :3306

  • upstream_show

	server {
		listen 1234;
		upstream_show;
	}

配置 upstream_show 指令后,可以通过 curl http://127.0.0.1:1234/upstream_show 来查看当前动态负载均衡上游服务器列表。

到此动态负载均衡就配置完成了,我们已讲解完动态添加/删除上游服务器。在实际使用时,请进行压测来评测其稳定性。在实际应用中,更多的是用 HaProxy 进行四层负载均衡,因此,还是要根据自己的场景来选择方案。

需要云服务器的不要错过优惠

阿里云低价购买云服务,值得一看

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值