博客 | NGINX

使用 TCP 负载平衡和 Galera Cluster 扩展 MySQL

NGINX-F5-horiz-black-type-RGB 的一部分
Liam Crilly 缩略图
利亚姆·克里利
2021 年 7 月 26 日发布

[编辑——该文章最初发表于 2016 年,现已更新,使用此后修订的 NGINX 功能。 有关详细信息,请参阅使用 NGINX JavaScript 模块NGINX Plus 仪表板进行高级日志记录。 ]

我们在NGINX Plus R5中引入了TCP负载均衡,并在后续版本中不断添加功能,以及对UDP负载均衡的支持。 在本文中,我们探讨 TCP 负载均衡的关键要求以及 NGINX Plus 如何解决这些要求。

为了探索 NGINX Plus 的功能,我们将使用一个简单的测试环境,该环境代表具有扩展数据库后端的应用的关键组件。 有关构建测试环境的完整说明,请参阅附录

负载均衡 MySQL 服务器的测试环境将 NGINX Plus 放置在 MySQL 客户端和 Galera 集群之间
MySQL 节点负载均衡测试环境

在此环境中,NGINX Plus 充当数据库服务器的反向代理,监听默认 MySQL 端口 3306。 这为客户端提供了一个简单的界面,同时后端 MySQL 节点可以扩展(甚至离线)而不会以任何方式影响客户端。 我们使用MySQL 命令行工具作为客户端,它代表测试环境中的前端应用。

本文描述的许多功能都适用于 NGINX Open Source 和 NGINX Plus。 为简洁起见,我们将始终引用 NGINX Plus,并明确指出 NGINX 开源中没有的功能。

我们将探讨以下用例:

TCP 负载平衡

在为任何应用配置负载均衡之前,了解应用如何连接到数据库非常重要。 对于大多数测试,我们使用 MySQL 命令行工具mysql(1)连接到 Galera 集群,运行查询,然后关闭连接。 然而,许多应用框架使用连接池来最大限度地减少延迟并有效利用数据库服务器资源。

TCP 负载均衡是在配置上下文中配置的,因此我们通过向主nginx.conf文件添加块来创建我们的基本 MySQL 负载均衡配置。

 

这将我们的 TCP 负载均衡配置与主配置文件分开。 然后我们在与nginx.conf相同的目录中创建stream.conf 。 请注意,默认情况下, conf.d目录保留用于http配置上下文,因此将配置文件添加到该目录不起作用。

 

首先,我们定义一个名为galera_cluster上游组,其中包含 Galera 集群中的三个 MySQL 节点。 在我们的测试环境中,它们每个都可以通过唯一的端口号在本地主机上访问。 zone指令定义了所有 NGINX Plus 工作进程共享的内存量,以维持负载平衡状态。 server{}块配置 NGINX Plus 如何处理客户端。 NGINX Plus 监听默认 MySQL 端口 3306,并将所有流量转发到上游块中定义的 Galera 集群。

为了测试这个基本配置是否正常工作,我们可以使用 MySQL 客户端返回我们连接到的 Galera 集群中节点的主机名。

$ echo “显示变量,其中变量名 = '主机名'” | mysql --protocol=tcp --user=nginx --password=plus -N 2> /dev/null hostname node1

为了检查负载均衡是否正常工作,我们可以重复相同的命令。

$ !!;!!;!!主机名 node2 主机名 node3 主机名 node1

这表明默认的循环负载平衡算法运行正常。 但是,如果我们的应用使用连接池来访问数据库(如上所述),那么以循环方式打开与集群的连接可能会导致每个节点上的连接数量不平衡。 此外,我们不能将连接与给定的工作负载等同起来,因为连接可能处于空闲状态(等待应用的查询)或正忙于处理查询。 对于长寿命 TCP 连接来说,更合适的负载平衡算法是最少连接算法,使用least_conn指令配置:

 

现在,当客户端打开与数据库的新连接时,NGINX Plus 会选择当前连接数最少的集群节点。

高可用性和健康检查

在集群中共享数据库工作负载的一大优势是它还提供高可用性。 通过上面讨论的配置,如果无法建立新的 TCP 连接,NGINX Plus 会将服务器标记为“关闭”并停止向其发送 TCP 数据包。

除了以这种方式处理宕机服务器之外,NGINX Plus 还可以配置为执行自动、主动的健康检查,以便在向服务器发送客户端请求之前检测到不可用的服务器(这是 NGINX Plus 独有的功能)。 此外,可以使用应用级健康检查来测试服务器的可用性,这意味着我们可以向每个服务器发送请求并检查是否收到表明健康状况良好的响应。 这将扩展我们的配置如下。

 

在此示例中,匹配块定义了启动MySQL 协议版本 10 握手所需的请求和响应数据。 server{}块中的health_check指令应用此模式并确保 NGINX Plus 仅将 MySQL 连接转发到实际能够接受新连接的服务器。 在这种情况下,我们每 20 秒执行一次健康检查,在一次故障后将服务器从 TCP 负载均衡池中排除,并在连续 2 次成功的健康检查后恢复负载均衡。

日志记录和诊断

NGINX Plus 提供灵活的日志记录,以便可以记录其所有 TCP/UDP 处理以进行调试或离线分析。 对于 MySQL 等 TCP 协议,NGINX Plus 会在连接关闭时写入日志条目。 log_format指令定义日志中出现的值。 我们可以从 Stream 模块可用的任何变量中进行选择。 我们在stream.conf文件顶部的上下文中定义日志格式。

 

通过在server{}块中添加access_log指令来启用日志记录,指定日志文件的路径和上一个代码片段中定义的日志格式的名称。

 

这将生成类似以下示例的日志条目。

$ tail -3 /var/log/nginx/galera_access.log 192.168.91.1 [23/Jul/2021: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 [23/Jul/2021: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 [23/Jul/2021:17:42:19 +0100] TCP 200 369 1611 127.0.0.1:33062 0.001 0.001 10.460 10.460

使用 NGINX JavaScript 模块进行高级日志记录

NGINX JavaScript 是“NGINX 原生”编程配置语言。 它是 NGINX 和 NGINX Plus 的独特 JavaScript 实现,专为服务器端用例和每个请求处理而设计。

[编辑器– 以下用例只是 NGINX JavaScript 模块的众多用例之一。 有关所有用例的列表,请参阅NGINX JavaScript 模块的用例

这篇博文已经更新,使用NGINX JavaScript 0.2.4中引入的 Stream 模块的重构会话对象和NGINX JavaScript 0.4.0中引入的js_import指令。]

在用于 TCP/UDP 负载均衡的 Stream 模块中,NGINX JavaScript 提供对请求和响应数据包内容的访问。 这意味着我们可以检查与 SQL 查询对应的客户端请求并提取有用的元素,例如 SQL 方法,例如SELECTUPDATE 。 然后,NGINX JavaScript 可以将这些值作为常规 NGINX 变量使用。 在此示例中,我们将 JavaScript 代码放在/etc/nginx/sql_method.js中。

 

getSqlMethod()函数传递一个代表当前数据包的 JavaScript 对象。 该对象的属性(例如fromUpstreambuffer)为我们提供了有关数据包及其上下文所需的信息。

我们首先检查 TCP 数据包是否来自客户端,因为我们不需要检查来自上游 MySQL 服务器的数据包。 这里,我们对第三个客户端数据包感兴趣,因为前两个数据包包含握手和身份验证信息。 第三个客户端数据包包含 SQL 查询。 然后将该字符串的开头与方法数组中定义的 SQL 方法之一进行比较。 当我们找到匹配项时,我们将结果存储在全局变量$method中,并将条目写入错误日志。 NGINX JavaScript 日志以严重性信息写入错误日志,因此默认不会出现。

当评估同名的 NGINX 变量时,将调用setSqlMethod()函数。 当发生这种情况时,该变量由通过调用getSqlMethod()函数获得的 NGINX JavaScript 全局变量$method填充。

请注意,此 NGINX JavaScript 代码是为 MySQL 命令行客户端设计的,其中执行单个查询。 尽管代码可以针对这些用例进行调整,但它无法准确捕获复杂查询或长连接上的多个查询。 有关安装和启用 NGINX JavaScript 模块的说明,请参阅NGINX Plus 管理指南

为了将 SQL 方法包含在我们的日志中,我们在log_format指令中包含了$sql_method变量。

 

我们还需要扩展我们的配置来告诉 NGINX Plus 如何以及何时执行 NGINX JavaScript 代码。

 

首先,我们使用js_import指令指定 NGINX JavaScript 代码的位置,并使用js_set指令告诉 NGINX Plus 在需要评估$sql_method变量时调用setSqlMethod()函数。 然后,在server{}块内我们使用js_filter指令来指定每次处理数据包时将调用的函数。 我们可以选择添加带有info选项的error_log指令来启用 NGINX JavaScript 日志记录。

有了这个额外的配置,我们的访问日志现在看起来像这样。

$ tail -3 /var/log/nginx/galera_access.log 192.168.91.1 [23/Jul/2021: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 [23/Jul/2021: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 [23/Jul/2021:17:42:19 +0100] TCP 200 369 1611 127.0.0.1:33062 0.001 0.001 10.460 10.460 更新

NGINX Plus 仪表板

[编辑器– 本节已更新,以引用NGINX Plus API ,该 API 取代并弃用了最初在此处讨论的单独扩展 Status 模块。 ]

除了详细记录 MySQL 活动之外,我们还可以在 NGINX Plus 实时活动监控仪表板上观察实时指标和上游 MySQL 服务器的运行状况(NGINX 开源提供了一组较小的指标,并且仅通过Stub Status API提供)。

NGINX Plus 仪表板是在NGINX Plus R7中引入的,它为NGINX Plus API提供了一个 Web 界面。我们通过在单独的/etc/nginx/conf.d/dashboard.conf文件中的http上下文中添加新的server{}块来启用此功能:

 

我们还必须使用status_zone指令更新stream.conf中的server{}块,以便为我们的 MySQL 服务收集监控数据。

 

通过此配置,NGINX Plus 仪表板可在端口 8080 上使用。 在下面的屏幕截图中,我们可以看到我们的三个 MySQL 服务器,每个服务器都显示了多个正在进行的连接的详细信息和当前的健康状态。 我们可以看到,监听端口 33062 的节点之前发生过一次短暂中断,持续时间为 18.97 秒(在DT列中报告)。

NGINX Plus 实时活动监控仪表板可让您在平衡 MySQL 节点时跟踪服务器运行状况
NGINX Plus 实时活动监控仪表板让您能够跟踪 MySQL 服务器的运行状况

并发写入的注意事项

Galera Cluster 将每个 MySQL 服务器节点作为执行读写操作的主数据库。 对于许多应用来说,读写比率非常大,与多主数据库集群带来的灵活性相比,同一张表的行被多个客户端同时更新的风险是完全可以接受的。 在发生并发写入风险较高的情况下,我们有两种选择。

  1. 创建两个独立的上游组,一个用于读取,一个用于写入,每个组监听不同的端口。 将集群中的一个或多个节点专用于写入,所有节点都包含在读取组中。 必须更新客户端代码以选择适当的端口进行读写操作。 我们博客上的《使用 NGINX Plus 的高级 MySQL 负载平衡》中讨论了这种方法,并且支持具有许多 MySQL 服务器节点的高度扩展环境。
  2. 保持单个上游组并修改客户端代码以检测写入错误。 当检测到写入错误时,并发结束后,代码会以指数方式退出,然后再次尝试。 我们博客上的《使用 NGINX Plus 和 Galera Cluster 实现 MySQL 高可用性》中讨论了这种方法,该方法有利于小型集群,将集群节点专用于写入会影响高可用性。

概括

在本文中,我们探讨了 TCP(或 UDP)应用(例如 MySQL)负载均衡的几个基本方面。 NGINX Plus 提供功能齐全的 TCP/UDP 负载均衡器,帮助您交付具有性能、可靠性、安全性和规模的应用——无论流量类型如何。

要试用 NGINX Plus,请立即开始30 天免费试用联系我们讨论您的用例。


附录: 创建测试环境

测试环境安装在虚拟机上,因此具有隔离性和可重复性。 但是,没有理由不能将其安装在物理的“裸机”服务器上。

安装 NGINX Plus

请参阅NGINX Plus 管理指南

安装 MySQL 的 Galera Cluster

在此示例中,我们在单个主机上安装 Galera Cluster,并为每个节点使用 Docker 容器。 以下说明改编自《使用 Docker 开始使用 Galera》 ,并假设Docker EngineMySQL 命令行工具均已安装。

  1. 创建一个基本的 MySQL 配置文件 ( my.cnf ),通过 Docker 镜像复制到每个 Galera 容器。

     

  2. 拉取 Galera 基本 Docker 镜像。

    $ sudo docker pull erkules/galera:basic
    
  3. 创建第一个 Galera 节点( node1 ),将默认 MySQL 端口公开为 33061。

    $ sudo docker run -p 33061:3306 --detach=true --name node1 -h node1 erkules/galera:basic --wsrep-cluster-name=local-test --wsrep-cluster-address=gcomm://
    
  4. 创建第二个 Galera 节点 ( node2 )。 MySQL 端口公开为 33062,并链接到node1以进行集群间通信。

    $ sudo docker run -p 33062:3306 --detach=true --name node2 -h node2 --link node1:node1 erkules/galera:basic --wsrep-cluster-name=local-test --wsrep-cluster-address=gcomm://node1
    
  5. 以与node2相同的方式创建第三个也是最后一个 Galera 节点 ( node3 )。 MySQL 端口公开为 33063。

    $ sudo docker run -p 33063:3306 --detach=true --name node3 -h node3 --link node1:node1 erkules/galera:basic --wsrep-cluster-name=local-test --wsrep-cluster-address=gcomm://node1
    
  6. 创建一个名为nginx的用户帐户,可用于从主机远程访问集群。 这是通过在 Docker 容器内部运行mysql(1)命令来执行的。

    $ sudo docker exec -ti node1 mysql -e“授予'nginx'@'172.17.0.1' 的所有权限,IDENTIFIED BY 'plus'”
    
  7. 验证您可以使用 TCP 协议从主机连接到 Galera 集群。

    $ mysql --protocol=tcp -P 33061 --user=nginx --password=plus -e "SHOW DATABASES" mysql:[警告] 在命令行界面上使用密码可能不安全。 +--------------------+ | 数据库 | +--------------------+ | information_schema | | mysql | | performance_schema | +--------------------+
    
  8. 最后,对另一个集群节点运行相同的命令,以显示nginx用户帐户已被复制并且集群正常运行。

    $ mysql --protocol=tcp -P 33062 --user=nginx --password=plus -e "SHOW DATABASES" mysql:[警告] 在命令行界面上使用密码可能不安全。 +--------------------+ | 数据库 | +--------------------+ | information_schema | | mysql | | performance_schema | +--------------------+
    

“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”