@xuemingdeng
2017-05-28T11:04:48.000000Z
字数 6972
阅读 1634
运维
这是nginScript系列文章的第三篇,将介绍如何使用nginScript将客户端循序渐进地重定向到新的服务器。查看第一篇“nginScript简介”,第二篇“使用nginScript将客户端重定向到新服务器”。
NGINX Plus在R5版本里就引入了TCP的负载均衡,在随后的版本里不断地添加新特性,包括支持UDP的负载均衡。在这篇文章里,我们将探讨NGINX Plus是如何实现TCP负载均衡的。
为了了解NGINX Plus的特性,我们使用了一个简单的测试环境,这个环境包含了应用程序所必需的组件,包括一个可伸缩的数据库。
MySQL负载均衡测试环境
在这个环境里,NGINX Plus作为数据库服务器的反向代理,监听MySQL的3306端口。反向代理为客户端提供了一个简单的接口,后端的MySQL节点可以自由伸缩(甚至离线),不会对客户端有任何影响。我们使用MySQL命令行工具作为客户端,在测试环境里充当前端应用。
本文所描述的很多特性在开源的NGINX和NGINX Plus里都有提供。不过,为了简单起见,我们通篇使用NGINX Plus,有些在NGINX里没有的特性我们会明确指明。
我们将探讨如下几个应用场景。
TCP负载均衡
在为应用程序配置负载均衡之前,需要了解应用程序是如何连接到数据库的。我们使用MySQL命令行工具mysql
连接到Galera集群,运行查询,然后关闭连接。不过,在实际当中,很多应用框架使用连接池来减小延迟,有效利用数据库的资源。
TCP的负载均衡是在stream context里配置的,所以我们在nginx.conf文件里增加了一个stream配置块来配置我们的MySQL负载均衡。
stream { include stream.conf; }
我们的TCP负载均衡配置与主配置文件是分开的。我们在相同的目录创建stream.conf文件。要注意,在默认情况下,conf.d目录被保留用于http context配置,如果在这里添加stream配置是不会生效的。
upstream galera_cluster {
server 127.0.0.1:33061; # node1
server 127.0.0.1:33062; # node2
server 127.0.0.1:33063; # node3
zone tcp_mem 64k;
}
server {
listen 3306; # MySQL默认端口
proxy_pass galera_cluster;
}
首先,我们定义了一个upstream组,名字叫作galera_cluster,包含了Galera集群里的三个MySQL节点。在我们的测试环境里,可以分别通过本地的不同端口访问它们。
zone指令指定了一些内存,NGINX Plus的工作线程用它来维护负载均衡的状态。server{}配置块定义了NGINX Plus是如何处理客户端的。NGINX Plus监听MySQL的默认端口3306,并将流量转向到Galera集群。
为了验证配置的正确性,我们可以使用MySQL客户端来获取它所连接的MySQL节点的机器名。
$ echo "SHOW VARIABLES WHERE Variable_name = 'hostname'" | mysql --protocol=tcp --user=nginx --password=plus -N 2> /dev/null
hostname node1
我们可以重复同样的命令,来验证负载均衡是否正常。
$ !!;!!;!!
hostname node2
hostname node3
hostname node1
这足以说明轮询负载均衡算法是正常的。不过,如果我们的应用程序使用连接池来访问数据库,那么就有可能导致每个节点的连接数不均衡。另外,我们无法保证每一个连接的负载是均等的,因为处理查询的连接有可能很空闲也有可能很忙。另一种负载均衡算法叫作最少连接数(Least Connections),可以使用least_conn指令来配置。
upstream galera_cluster {
server 127.0.0.1:33061; # node1
server 127.0.0.1:33062; # node2
server 127.0.0.1:33063; # node3
zone tcp_mem 64k;
least_conn;
}
现在,如果有客户端连接到数据库,NGINX Plus会选择集群里具有最少连接数的节点。
高可用和健康检查
在集群里进行负载均衡的最大好处是它可以提供高可用性。基于上述的配置,如果一个新的TCP连接建立失败,NGINX Plus就把这台服务器标记为“down”,并停止向它发送TCP数据包。
除了能够探测到宕机的服务器,NGINX Plus还能自动进行自发的健康检查。因此,在客户端发送请求到那些不可用的服务器之前,NGINX Plus能够提前检测到它们(这个特性只在NGINX Plus里提供)。另外,我们可以通过应用程序级别的健康检测来测试服务器的可用性。我们向每一台服务器发送请求,如果服务器返回响应,说明它运行正常。我们在配置里添加了一些内容。
upstream galera_cluster {
server 127.0.0.1:33061; # node1
server 127.0.0.1:33062; # node2
server 127.0.0.1:33063; # node3
zone tcp_mem 64k;
least_conn;
}
match mysql_handshake {
send \x00;
expect ~* \x00\x00; # 用于过滤握手响应数据包中的空值
}
server {
listen 3306; # MySQL默认端口
proxy_pass galera_cluster;
proxy_timeout 2s;
health_check match=mysql_handshake interval=20 fails=1 passes=2;
}
在这个例子里,match配置块定义了初始化一个MySQL握手协议需要的请求和响应数据。server配置块里的health_check指令使用了由match配置块定义的模式,并确保NGINX Plus只会向可用的服务器发起MySQL连接。我们每20秒执行一次健康检查,如果连接服务器失败一次,就把这个服务器从TCP负载均衡池里移除,如果连续两次健康检查成功,那么就重新把服务器放回负载均衡池。
日志和诊断
NGINX Plus提供了灵活的日志,所有的TCP和UDP处理过程都可以被记录下来,用于调试和离线分析。对于使用了TCP协议的系统,比如MySQL,NGINX Plus会在每次连接关闭之后记录一条日志。log_format指令指定哪些值可以出现在日志里。我们可以选择出现在Stream模块里的任意可用变量值。我们在stream.conf文件最上面的stream context里定义日志格式。
log_format mysql '$remote_addr [$time_local] $protocol $status $bytes_received '
'$bytes_sent $upstream_addr $upstream_connect_time '
'$upstream_first_byte_time $upstream_session_time $session_time';
在server配置块里使用access_log指令来启用日志,并指定日志文件的路径和之前配置过的日志格式的名字。
server {
...
access_log /var/log/nginx/galera_access.log mysql;
}
这样的配置将生成如下格式的日志。
$ tail -3 /var/log/nginx/galera_access.log
192.168.91.1 [16/Nov/2016:17:42:18 +0100] TCP 200 369 1611 127.0.0.1:33063 0.000 0.003 12.614 12.614
192.168.91.1 [16/Nov/2016:17:42:18 +0100] TCP 200 369 8337 127.0.0.1:33061 0.001 0.001 11.181 11.181
192.168.91.1 [16/Nov/2016:17:42:19 +0100] TCP 200 369 1611 127.0.0.1:33062 0.001 0.001 10.460 10.460
通过nginScript使用高级日志
nginScript是NGINX的"原生"可编程配置语言。它是为NGINX和NGINX Plus专门实现的JavaScript,也是专门为服务器端的使用场景而设计的。
在Stream模块里,可以通过nginScript访问请求和响应消息里的数据包。也就是说,我们可以查看从客户端发出的SQL查询请求,并从中抽取有用的元素,比如SQL的SELECT或UPDATE方法。nginScript可以把这些值变成普通的NGINX变量。在这个例子里,我们的JavaScript代码被放在/etc/nginx/sql_method.js文件里。
var method = "-"; // 全局变量
var client_messages = 0;
function getSqlMethod(s) {
if ( !s.fromUpstream ) {
client_messages++;
if ( client_messages == 3 ) { // SQL语句出现在第3个数据包里
var query_text = s.buffer.substr(1,10).toUpperCase();
var methods = ["SELECT", "UPDATE", "INSERT", "SHOW", "CREATE", "DROP"];
var i = 0;
for (; i < methods.length; i++ ) {
if ( query_text.search(methods[i]) > 0 ) {
s.log("SQL method: " + methods[i]); //记录错误日志
method = methods[i];
return s.OK; // 停止查找
}
}
}
}
return s.OK;
}
function setSqlMethod() {
return method;
}
getSqlMethod()函数接收一个表示当前数据包的JavaScript对象。
这个对象的属性fromUpstream和buffer为我们提供了数据包和上下文的信息。
我们先检查TCP数据包是否来自客户端,因为我们不需要处理来自上游MySQL服务器的数据包。我们需要第三个数据包,因为第一个是握手信息,第二个是认证信息。第三个数据包包含了SQL查询字符串。我们将这个字符串的开头部分与数组里定义的SQL方法列表进行比较,如果找到一个匹配的字符串,就把它保存到全局变量$method里,并往错误日志里写入一条日志。因为nginScript日志是以“info”级别写到错误日志里的,所以默认情况下不会显示出来。
在计算同名的NGINX变量时,setSqlMethod()函数会被调用。在这个时候,从getSqlMethod()函数里获得的全局变量$method将被用于计算新变量。
要注意,这段nginScript代码可以用于处理MySQL命令行客户端发出的简单查询,但不能用于处理复杂的查询或多次查询,尽管可以通过修改代码来处理它们。
我们将$sql_method变量包含在log_format指令里,这样SQL的方法就能够被记录到日志里。
log_format mysql '$remote_addr [$time_local] $protocol $status $bytes_received '
'$bytes_sent $upstream_addr $upstream_connect_time '
'$upstream_first_byte_time $upstream_session_time $session_time '
我们还要告诉NGINX Plus如何以及何时执行nginScript代码。
js_include /etc/nginx/sql_method.js;
js_set $sql_method setSqlMethod;
server {
...
js_filter getSqlMethod;
error_log /var/log/nginx/galera_error.log info; #用于在nginScript代码里调用记录日志的方法
access_log /var/log/nginx/galera_access.log mysql;
}
首先,我们通过js_include指令指定了nginScript代码文件的位置,并使用js_set指令告诉NGINX Plus在计算$sql_method变量时调用setSqlMethod()函数。
然后,我们在server配置块里使用js_filter指令指定了每次处理完一个数据包之后要调用的函数。另外,我们还可以增加error_log指令来启用nginScript日志。
经过这些配置,我们的访问日志看起来是这样的。
$ tail -3 /var/log/nginx/galera_access.log
192.168.91.1 [16/Nov/2016:17:42:18 +0100] TCP 200 369 1611 127.0.0.1:33063 0.000 0.003 12.614 12.614 UPDATE
192.168.91.1 [16/Nov/2016:17:42:18 +0100] TCP 200 369 8337 127.0.0.1:33061 0.001 0.001 11.181 11.181 SELECT
192.168.91.1 [16/Nov/2016:17:42:19 +0100] TCP 200 369 1611 127.0.0.1:33062 0.001 0.001 10.460 10.460 UPDATE
NGINX Plus仪表盘
除了可以记录MySQL活动的详细信息,我们还可以在NGINX Plus的实时活动监控仪表盘上实时地观察上游MySQL服务器的度量指标和健康情况(开源版本的NGINX只提供了少数的几个度量指标,而且只能通过API获得)。NGINX Plus的仪表盘是在R7版本里引入的,并为JSON Status API提供了一个Web界面。我们在/etc/nginx/conf.d/dashboard.conf文件里添加server配置块来启用这个功能.
server {
listen 8080;
location /status { status; } # 启用JSON Status API
location = /status.html {
root /usr/share/nginx/html;
}
#deny all; # 在生产环境里保护远程地址
#allow 192.168.0.0/16; # 只允许私有网络访问
}
我们还要更新stream.conf里的server配置块,使用status_zone指令来启用对从MySQL服务收集来的数据进行监控。
server {
...
status_zone galera_cluster;
}
这样配置之后,NGINX Plus的仪表盘就可以使用了,端口为8080。从屏幕截图可以看到我们的三个MySQL服务器,每个服务器有很多连接,还有每个服务器的健康情况。我们可以看到,监听33062端口的节点之前发生了18.97秒的宕机(DT一列)。
NGINX Plus的实时活动监控仪表盘跟踪负载均衡池里MySQL服务器的健康情况
并发写入
Galera集群将每个MySQL节点看成一个主数据库,执行读取和写入操作。大部分应用程序来的读写比率比较高,集群多主数据库为我们带来了很大的灵活性,所以同一个表被多个客户端更新的风险是完全可接受的。不过,如果发生并发写入的风险很高,我们有两个解决方案。
总结
在这篇文章里,我们探讨了对TCP应用(比如MySQL)进行负载均衡需要考虑到的几个问题。NGINX Plus提供了全功能的TCP/UDP负载均衡器,用于交付高性能、可靠、安全和可伸缩的应用。
查看英文原文:Scaling MySQL with TCP Load Balancing and Galera Cluster