乐趣区

关于mysql:通过-ProxySQL-在-TiDB-上实现-SQL-的规则化路由

回顾,以最佳实际为终点

作为一款 HTAP 数据库,TiDB 能同时解决来自用户端的 OLTP 在线业务与 OLAP 剖析业务。针对剖析类需要,优化器会主动将申请路由到列存的 TiFlash 节点;而对于在线申请,优化器会主动路由到行存 TiKV 申请。对于 HTAP 数据库,咱们最关怀的莫过于占用大量资源的剖析类查问是否会影响到在线的 OLTP 业务,针对这个问题,TiDB 在物理层上对 TiKV 与 TiFlash 进行了隔离,很好的防止了这种状况。

反思,最佳实际构造之痛

通过资源隔离的形式,咱们解决了业务之间的相互影响。然而要想实现更灵便、高效的利用,下面的架构中依然存在肯定的问题:

  • HAProxy 临时没有高可用性能
  • 对于 TiDB Server 来说,没有做到 TP 业务与 AP 业务的隔离

对于以上的两个问题,咱们能够采纳以下的两种计划躲避下面的危险。

HAProxy 的高可用计划

在生产环境中,个别咱们是不会独自应用 HaProxy 做 DNSRoundRobin 的。任何一个单点非高可用构造都会导致系统的不可用。HaProxy 自身是一个无状态的服务,对于无状态服务,咱们能够通过多个服务来来躲避单节点的可用性危险。另外,在 HaProxy 之上,咱们能够通过 Keepalived 的探活脚本将 VIP 飘到一个可用的节点上,以实现单入口的高可用构造。

TP 与 AP 的隔离计划

在 HTAP 场景中,咱们曾经通过将数据在物理层面上寄存在 TiKV 与 TiFlash 上来隔离 OLTP 和 OLAP 查问申请,真正实现了存储引擎级别的隔离。在计算引擎上,也能够通过 TiDB 实例级别设置 isolation-read 参数来实现 engine 的隔离。配置 isolation-read 变量来指定所有的查问均应用指定 engine 的正本,可选 engine 为“TiKV”、“TiDB”和“TiFlash”(其中“TiDB”示意 TiDB 外部的内存表区,次要用于存储一些 TiDB 零碎表,用户不能被动应用)。

无论前端是否做 HAProxy 的高可用,在 roundrobin endpoint 的时候,HAProxy 无奈判断 TiDB Server 的 isolation-read engine 隔离机制是什么样的。这样就可能造成一个难堪的场面,HAProxy 可能将 OLTP 的查问申请路由到了 isolation-read 设置为 TiFlash 的节点上,使得咱们无奈以最佳的姿势来解决申请。亦或是说,某些咱们强制应用了 hint 走 TiFlash 的剖析类查问,可能会被路由到 isolation-read 设置为 TiKV 的结点上,SQL 申请抛出异样。
从性能点登程,咱们须要从新定义一下 HTAP 数据库:

  • 我心愿存储层数据是拆散的,OLTP 和 OLAP 业务互不影响
  • 我心愿计算层的申请是拆散的,OLTP 和 OLAP 申请互不影响

变更,需要驱动架构转型

基于 HAProxy 的革新

为了解决计算层 TiDB Server 的路由,咱们能够应用两套 HAProxy 将 TiDB Server 集群进行物理上的辨别。一套 HAProxy 集群用来治理 isolation-read 为 TiKV 的 TiDB Server,另一套 HAProxy 集群用来治理 isolation-read 为 Tiflash 的 TiDB Server。出于高可用的思考,咱们依然须要在 HaProxy 集群上做高可用,这样一来,能够形象出如下的架构:

从整体架构上来看,这样的一套架构设计根本满足了咱们的需要,计算层 TiDB Server 被物理隔离开,前端的 Proxy 也做了高可用。但这样的构造还是存在缺点的:

  • 构造较为简单,以至为了保证系统的高可用性,破费的绝对物理结点较高
  • Proxy 的进口不对立,须要两套 Keepalived 保护两个 VIP,在业务逻辑中须要进行编码操作

如果采纳这样一套架构,从削减老本的角度思考,咱们能够进行结点混部。两套 keepalived 集群咱们能够思考部署在一套三节点的机器上,通过 virtual_router_id 进行物理隔离。或者间接部署一套 keepalived 集群,不应用 keepalived 中自带的 VIP,在一套 keepalived 别离部署两套 vrrp script,各自的探活脚本中保护独立的 VIP。HAProxy 咱们也能够应用 keepalived 的机器进行部署,做成一套 2 (3 Keepalived + 3 * Haproxy) 的构造。如此改良的集群架构,尽管能够将机器老本压缩到和保护一般集群雷同,但依然无奈从架构上削减复杂性,也无奈更改两个入口带来的不变。

应用 ProxySQL 实现 SQL 的路由

当初来看,咱们须要的是一款 TP/AP 拆散的 Proxy。从需要上来看是比拟匹配 MySQL 读写拆散的,或者明确的说,咱们的需要就是须要一款 SQL 路由的工具。
想必接触过 MySQL 的同学都会理解 ProxySQL 这款产品。ProxySQL 是一款基于 MySQL 的开源中间件产品,是一个灵便的 MySQL 代理工具。作为一款弱小的规定引擎中间件,ProxySQL 为咱们提供了很多个性:

  • 灵便弱小的 SQL 路由规定,能够智能的负载 SQL 申请。
  • 无状态服务,不便的高可用治理计划。
  • 主动感知结点的监控状态,疾速剔除异样结点。
  • 不便的 SQL 监控剖析统计。
  • 配置库基于 SQLite 存储,能够在线批改配置并且动静加载。
  • 相比于 MySQL query cache 更灵便的 cache 性能,能够在配置表中多维度的管制语句缓存。


在我看来,ProxySQL 是一款弱小到没有什么多余性能的产品,他的每一个个性都能切实的命中用户的痛点,满足用户的需要。如果硬要说有什么有余的话,我能想到的就是因为路由性能带来的性能消退,但这样的消退在其余的 Proxy 工具中仍然存在甚至更甚。
作为一款“大尺码”的 MySQL,TiDB 是否能够很好的适配 ProxySQL 呢?答案是必定的。咱们能够简略的复制 ProxySQL 在 MySQL 读写拆散的计划,进行 TP/AP SQL 申请的路由操作。甚至来说,以上介绍的种种弱小的性能,在 TiDB 中依然实用,在某种程度上,补救了 TiDB 生态的有余。

全链路的高可用

对于一套数据库系统,任何一个环节都可能成为故障点,所以任何服务都不能以单点的模式存在。TiDB Cluster 的任何组件都是有高可用并且可扩大的。ProxySQL 也能够配置高可用集群。对于 ProxySQL 的高可用,目前风行的次要有两种计划:

  • 多个互相独立的 ProxySQL
  • 应用 ProxySQL 的高可用集群

ProxySQL 自身是无状态的服务,所以前端多个互相独立的 ProxySQL 自身就是对可用性的一种保障。但因为多个 ProxySQL 是独立的,相干的配置文件无奈互联。对任何配置进行改变无奈主动同步,这对治理来说是存在危险的。如果应用集群版的 ProxySQL 高可用,为了保障集群状态的 watchdog 过程可能自身对于集群就是一种负载。

正如后面所处,对于一套集群,咱们冀望能有一个入口。而前端的 ProxySQL 自身是有多个入口的。咱们能够采纳 Keepalived + haproxy 的形式进行一个 endpoint 的负载平衡,或者说放心多级 proxy(HAProxy + ProxySQL)带来的性能大量消退,咱们能够本人保护 Keepalived 的探活脚本管制 VIP。对于网络监管比拟严格的公司,可能敞开了 VRRP 协定,那么能够抉择 Zookeeper 服务注册与发现来保护 ProxySQL 的状态,在 Zookeeper 中治理集群的 VIP。

针对于多 endpoint 的对立入口高可用计划,每个公司都有本人的解决架构。就我而言,相比于 Keepalived + HAProxy 或者在 Keepalived 的脚本中做负载平衡,我更偏向于应用 zookeeper 来治理集群的状态。咱们须要认真的去布局 Keepalived 的算分制度,为了缩小 HAProxy 对性能的衰减,可能又要在脚本中治理另一套 VIP 或者敞开失败结点上的 Keepalived 服务。当然,应用什么计划还要配合咱们本人的技术栈,只有适宜本人的才是最佳实际。

在下面的架构中,TP 与 AP 的申请通过 APP 程序接入到后盾的 TiDB Cluster。作为程序的惟一入口,Keepalived 的探活程序会抉择一台可用的 ProxySQL,在之上创立一个 VIP。这个 VIP 将作为应用程序与 TiDB Cluster 对接的惟一入口。在 ProxySQL 集群中,依据 Router Table(mysql_query_rules)中配置的 TP 和 AP 的 pattern 配置,将 TP 与 AP 的 查问申请主动的路由配置好的 TP_GROUP 与 AP_GROUP 中。
综上所述,这样的架构可能解决咱们之前的痛点问题:

  • 应用程序与数据库集群应用惟一的接口。
  • 简略的高可用构造,通过一套 keepalived 与一套 Proxy 集群实现高可用性。
  • TP/AP 的申请可能主动的路由到对应的计算节点 TiDB Server 中。

践行,从案例动手求后果

部署了一个 demo 零碎,简略的展现一下整套架构的运行流程与后果。

以下为结点上的组件列表:

装置 TiDB

略,可参考官网文档(TiDB 数据库疾速上手指南)。

装置 ProxySQL

能够抉择应用 rpm 的形式装置 ProxySQL。但为了对立装置的地位,个别我会习惯应用源码进行编译装置,而后应用 rpmbuild 打成安装包部署到其余结点上。编译装置能够参考 INSTALL.md 文档。
批改 proxy.cfg 文件,批改 datadir=”/opt/tidb-c1/proxysql-6033/data”。
应用以下命令能够启动 ProxySQL,或是配置 systemd 文件进行启动。
/opt/tidb-c1/proxysql-6033/proxysql -c /opt/tidb-c1/proxysql-6033/proxysql.cfg

配置 ProxySQL

因为在本例中,我应用了三台独立的 ProxySQL 做高可用负载,须要在这三台机器上做雷同的配置。如果抉择了 ProxySQL 自带的高可用,那么只须要在一台机器上进行配置。

[root@r31 proxysql-6033]# mysql -uadmin -padmin -h127.0.0.1 -P6032 --prompt 'admin>'## set server infoinsert into mysql_servers(hostgroup_id,hostname,port) values(10,'192.168.232.31',14000);insert into mysql_servers(hostgroup_id,hostname,port) values(10,'192.168.232.32',14000);insert into mysql_servers(hostgroup_id,hostname,port) values(10,'192.168.232.33',14000);insert into mysql_servers(hostgroup_id,hostname,port) values(20,'192.168.232.34',14000);insert into mysql_servers(hostgroup_id,hostname,port) values(20,'192.168.232.35',14000);load mysql servers to runtime;save mysql servers to disk;## set userinsert into mysql_users(username,password,default_hostgroup) values('root','mysql',10);load mysql users to runtime;save mysql users to disk;## set monitoring userset mysql-monitor_username='monitor';set mysql-monitor_password='monitor';load mysql variables to runtime;save mysql variables to disk;## set sql router rule## this is just a demoinsert into mysql_query_rules(rule_id,active,match_digest,destination_hostgroup,apply)values(1,1,'^select.*tikv.*',10,1),(2,1,'^select.*tiflash.*',20,1);load mysql query rules to runtime;save mysql query rules to disk;

配置 Keepalived

Keepalived 的装置参考 keeaplived-install,与 ProxySQL 的装置雷同,举荐编译装置后达成 rpm 包或者间接 copy keepalived 的 binary。
Keepalived 的配置文件脚本如下:

global_defs {notification_email {     acassen@firewall.loc     failover@firewall.loc     sysadmin@firewall.loc}   vrrp_skip_check_adv_addr   vrrp_strict   vrrp_garp_interval 0   vrrp_gna_interval 0}vrrp_script check_proxysql {script 'killall -0 proxysql || systemctl stop keepalived'    interval 2    weight 10}vrrp_script test_script {script 'echo `date` >> /tmp/aaa'    interval 1    weight 1}vrrp_instance proxysql_kp {state MASTER    interface ens33    virtual_router_id 51    priority 150    advert_int 1    authentication {        auth_type PASS        auth_pass 1888}    virtual_ipaddress {192.168.232.88}    track_script{check_proxysql        ##test_script}}

验证 ProxySQL

我关上了五台 TiDB Server 上的 general log 用来记录 SQL 语句。
在 TiDB Cluster 中创立了两张表:

  • test.t_tikv(idi int),数据从 1 – 1000
  • test.t_tiflash(idi int),数据从 1 – 1000

在前端应用简略的循环进行压测:

for i in `seq 1000`; do mysql -uroot -P6033 -h192.168.232.88 -pmysql -e "select * from test.t_tikv where idi = $i"; donefor i in `seq 1000`; do mysql -uroot -P6033 -h192.168.232.88 -pmysql -e "select * from test.t_tiflash where idi = $i"; done

TiDB Server log 过滤关键字“select * from test.t_tikv where idi =”的条数。能够看出针依照路由表中配置的 TiKV SQL,1000 条较为扩散的路由到了 TiDB-1,TiDB-2,TiDB-3 结点上。

彩蛋,你想要的审计性能

数据库审计是对数据库的拜访行为进行监管的零碎,他可能在产生数据库安全事件之后为事件的罪责定责提供根据。将审计日志抽取到实时数仓中进行风控解决,可能及时的发现危险,最大水平的挽回损失。审计日志在一些重要的金融、订单交易系统中至关重要。

如何捕捉 audit log

与当初很多用户一样,已经我也遇到过 audit 的需要。像 MongoDB 这样的开源数据库,很多都是不提供收费的审计性能的。审计性能对于很多金融类的场景是尤其的重要,为了实现审计性能,咱们通常有两种形式:

  • 在源码中解析语义
  • 数据的流量采集

所谓的源码语义解析,其实就是咱们在源码中手动的增加 audit 的性能。咱们能够批改源码,将一些心愿捕捉的变量信息落盘到本地文件中。然而这种形式可能会造成大量的期待,影响数据库的性能。通过将这种写操作异步执行,能够略微缓解性能的降落。
另一种数据流量采集的形式,相比于变量落盘这种形式,要略微好一些。流量监控的思路是搭建一套与数据库绝对独立的旁路零碎,通过抓包或者探针等工具截获流量,将针对于数据库的申请打印到本地的文件中。这种办法自身与数据库不挂钩,异步的获取审计日志。

在 TiDB 中捕捉 audit log

TiDB 上的审计目前来看次要有两种,一种是购买原厂提供的审计插件,另一种是开启 General log 性能,在 TiDB log 中能够查看到 SQL 语句。须要留神的是,因为前端咱们应用了 HAProxy,咱们须要配置 forwardfor 参数以捕捉客户端的 IP。General log 会将所有的包含 select 在内的申请都记录在 TiDB log 中,依据以往的测试来看,会有大略 10%-20% 的性能损失。记录的 SQL 语句包含工夫或 IP 等其余的信息可能不能满足咱们的需要,并且从 TiDB log 中整顿出 audit 也是一个较大的工程。

在 ProxySQL 中获取 audit log

Audit 的需要是十分常见的。如 MongoDB,开源数据库的社区版本不提供 audit 性能也是较为广泛的。从整条链路来看,能获取到残缺 audit 的结点有两个,一个是数据库端,一个是 Proxy 端。ProxySQL 能够为咱们提供了审计的性能。当咱们指定了 MySQL_enventslog_filename 参数,即设置开启审计性能。在审计文件中,咱们能够捕捉到 ProxySQL 入口的所有 SQL audit。
在我的环境中,能够捕捉到以下格局的 audit log,根本满足了用户的大部分需要:

通过探针截获 audit

能够通过 systemtap 做成 probe 挂在 proxySQL 上,依据一些 ProxySQL 关键字,比如说,run、execute、query、init、parse、MySQL、connection 等尝试追踪到这些函数的调用栈与参数。打印这些参数能够获取到解决申请时的 IP 与 Statement。如果这些函数没有方法追踪到 audit 信息,那么能够思考应用暴力破解的思路,追踪 ProxySQL 的所有函数(通过 function(“*”) 来匹配)。依据后果定位到指定的函数。但这种办法在开发时须要一台较为弱小的服务器。

目前通过空幻可能追踪到的审计日志如下:

>>>>>>>>>>>>>>>>>>>[function >> ZN10Query_Info25query_parser_command_typeEv]  [time >> 1622953221]  this={.QueryParserArgs={.buf="select ?", .digest=2164311325566300770, .digest_total=17115818073721422293, .digest_text="select ?", .first_comment=0x0, .query_prefix=0x0}, .sess=0x7f1961a3a300, .QueryPointer="select 1113 192.168.232.36", .start_time=2329486915, .end_time=2329486535, .mysql_stmt=0x0, .stmt_meta=0x0, .stmt_global_id=0, .stmt_info=0x0, .QueryLength=11, .MyComQueryCmd=54, .bool_is_select_NOT_for_update=0, .bool_is_select_NOT_for_update_computed=0, .have_affected_rows=0, .affected_rows=0, .rows_s   ######

其中能够抓到 query point,从中能够获取到 query 的文本,用户的 client IP,而 function name 与 time 是我通过 systemtap 的脚本间接本底写入的。

应用外挂探针这种形式,可能很好的加重 Proxy 或者 Database 的写日志期待,从而最小水平的缩小对数据库性能的影响,根本能够疏忽因为审计带来的性能损失。

退出移动版