内容简介:nginScript系列:通过TCP负载均衡和Galera集群来扩展MySQL
这是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负载均衡
- 高可用和健康检查
- 日志和诊断
- 并发写入
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节点看成一个主数据库,执行读取和写入操作。大部分应用程序来的读写比率比较高,集群多主数据库为我们带来了很大的灵活性,所以同一个表被多个客户端更新的风险是完全可接受的。不过,如果发生并发写入的风险很高,我们有两个解决方案。
- 创建两个单独的上游组,一个用于读,一个用于写,每个组监听不同的端口。使用一个或多个节点进行写操作,其他节点则放在读分组里。更新客户端,选择合适的端口进行读写操作。我们的博客上有一篇文章“ Advanced MySQL Load Balancing with NGINX Plus ”详细讨论了这种方法,通过使用多个MySQL服务器节点构建高度伸缩的环境。
- 只使用一个上游组,改写客户端代码,让它们检测写操作错误。在检测到写操作错误时,客户端暂停一段时间,等待并发结束后再进行尝试。我们的博客上另一个篇文章“ MySQL High Availability with NGINX Plus and Galera Cluster ”详细讨论了这个方法,它使用很小的一个集群,让几个专门的节点负责处理写操作,可以保证很高的可用性。
总结
在这篇文章里,我们探讨了对TCP应用(比如MySQL)进行负载均衡需要考虑到的几个问题。NGINX Plus提供了全功能的TCP/UDP负载均衡器,用于交付高性能、可靠、安全和可伸缩的应用。
查看英文原文: Scaling MySQL with TCP Load Balancing and Galera Cluster
以上所述就是小编给大家介绍的《nginScript系列:通过TCP负载均衡和Galera集群来扩展MySQL》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- dubbo集群和负载均衡
- 认识认识LVS负载均衡集群
- Nginx+Tomcat 配置负载均衡集群
- 高可用负载均衡集群之 HAProxy 部署
- Keepalived+Nginx搭建高可用负载均衡集群
- 在 Kubernetes 集群中 gRPC 的负载均衡
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。