使用 TiDB 时的连接池和负载均衡器配置策略

【是否原创】是
【首发渠道】TiDB 社区
【正文】

一、背景

TiDB 计算层 —— tidb-server 的无状态特点、以及事务状态由存储组件(TiKV)分布式管理的特性不同于一般单机数据库或具备 GTM 的分布式数据库,可以使用常常被部署在应用前端的负载均衡器来实现 tidb-server 的负载均衡,同时也使 TiDB 计算层具备了高可用能力(高可用性详见 TiDB 集群可用性详解及 TiKV Label 规划 - Google 文档)。

理论上讲,TiDB 支持所有 4 层(TCP/IP)负载均衡器(LB - Load Balancer),并支持负载均衡器上的 7 层 MySQL 协议探活机制。

常见 4 层负载均衡器包括:

  • 硬件:F5、A10 等
  • 软件:HAProxy、Nginx、LVS(v1.9 及更高版本)、各种云上 ELB、NLB 等

连接池用于维护应用到数据库之间的长连接,避免了每次请求都要建立新的连接而造成的时间开销。常见的 Java 应用连接池:

  • Druid:阿里开源的数据库连接池,主要特点是功能全面, 性能和稳定性也都不错,被国内用户广泛使用。
  • C3P0:早期一款数据库连接池产品, Hibernate 将其作为内置的数据库连接池,简单易用稳定性好,但性能差,架构复杂。
  • DBCP:2014 年 DBCP 更新 2.0,性能尚可,但相比于竞品没有明显优势。
  • HikariCP:Springboot2.+ 官方默认的数据库连接池,性能强劲,稳定性好。
  • IBM WAS 内置连接池,传统金融用户广泛使用。
  • Oracle Weblogic 内置连接池,传统金融用户广泛使用。

二、负载算法

如上一章所讲,应通过长连接的方式来访问数据库以保障性能,因此建议使用最小连接数(least connection)的负载算法配置 LB,使连接数最少的服务器优先接收连接。

三、空闲连接超时配置策略

应用通过连接池维护应用到 LB 之间的长连接,同样的,LB 通过长连接和 TiDB 进行交互,而应用连接池和 LB 这两者都具备自动清理超时空闲连接的机制(idle timeout),为了避免出现空闲连接被“多次”清理的问题,需要考虑整体的配置策略。

根据过往的客户项目经验,我们建议将空闲连接超时交给应用连接池来处理,将 LB 的空闲连接超时配置为无限制或足够长的时间(比如一年),这样建议的原因是应用连接池更贴近应用程序和业务,方便管理连接和排查问题。

一定要避免将 LB 的空闲连接超时配置的比连接池更短,这将使 LB 提前杀死连接而导致连接池不知情。

四、探活

探活分为 4 层探活(ip:port)和 7 层探活(SQL 语句探活),主流的 LB 和连接池都支持这两种探活机制,4 层探活为固有能力,7 层探活一般需要额外配置。

就探活频率来说,LB 的探活频率较高些,而连接池一般支持在创建连接、关闭连接、连接空闲时进行探活。

以 Druid 连接池为例,7 层探活配置如下:

# validation-query 设置探活语句,当不设置时,下面三个参数将不生效
spring.datasource.druid.validation-query = select 1
# test-on-borrow 为 true 时,在创建连接时探活,影响性能,一般建议关闭
spring.datasource.druid.test-on-borrow = false
# test-on-return 为 true 时,在关闭连接时探活,影响性能,一般建议关闭
spring.datasource.druid.test-on-return = false
# test-while-idle 为 true 时,在连接空闲时探活,对性能基本无影响,建议开启
spring.datasource.druid.test-while-idle = true

除了以上配置项外,注意还需要在应用代码中加入:
System.setProperty("druid.mysql.usePingMethod","false");
这条代码的作用是关闭 ping 探活方式,让 validation-query 能够生效,1.x 版本以上可用。

以 HAProxy 负载均衡器为例,7 层探活配置如下:
option mysql-check user haproxy post-41 #对性能基本无影响,建议开启

下表展示了在不同故障下,配置了 7 层探活的 LB 和连接池的响应。

场景 LB(HAProxy) 连接池(Druid)
正常状态 UP L7OK -
kill -9 tidb-server DOWN L4CON Communications link failure.The last packet successfully received from the server was 1,300 milliseconds ago. The last packet sent successfully to the server was 32 milliseconds ago. Can not read response from server.
kill -19 tidb-server DOWN L7TOUT Communications link failure. The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. Read timed out

表 1:故障下 LB 与连接池的响应

五、负载均衡器 IP 透传

应用服务通过 LB 连接到 TiDB 时,其原本的服务器 IP 会被屏蔽掉,show processlist

中只能看到 LB 的 IP,不方便问题排查和定位。而通过配置负载均衡器 IP 透传策略,可以使 TiDB 的诊断日志中记录原始请求发起位置的 IP 地址,方便问题的排查。

HAProxy

  1. 在节点条目上增加 send-proxy 配置项
    server tidb1 10.0.1.4:4000 send-proxy
  2. 调整 TiDB 启动脚本,增加 proxy 协议配置项*
    –proxy-protocol-networks

*注意:开启 proxy 协议支持后,tidb-server 将无法直连,只能通过 HAProxy 连接。

LVS 或其他支持 TOA(TcpOptionAddress)的 LB

调整 TiDB 配置项 enable-tcp4-only(从 v5.0 版本开始引入)值为 true,这是因为 LVS 的 TOA 模块可以通过 TCP4 协议从 TCP 头部信息中解析出客户端的真实 IP。

六、连接池配置参考

springboot-druid:

# jdbc url
spring.datasource.druid.url=JDBC:mysql://{TiDBIP}:{TiDBPort}/{DBName}?characterEncoding=utf8&useSSL=false&useServerPrepStmts=true&prepStmtCacheSqlLimit=10000000000&useConfigs=maxPerformance&rewriteBatchedStatements=true&defaultfetchsize=-214783648
# 用户名
spring.datasource.druid.username=root
# 密码
spring.datasource.druid.password=root
# 驱动
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
# 初始化时建立物理连接的个数
spring.datasource.druid.initial-size=5
# 最大连接池数量
spring.datasource.druid.max-active=30
# 最小连接池数量
spring.datasource.druid.min-idle=5
# 获取连接时最大等待时间,单位毫秒
spring.datasource.druid.max-wait=60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 连接保持空闲而不被驱逐的最小时间
spring.datasource.druid.min-evictable-idle-time-millis=300000
# 用来检测连接是否有效的sql,要求是一个查询语句
spring.datasource.druid.validation-query=SELECT 1 
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.test-while-idle=true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-borrow=false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
spring.datasource.druid.test-on-return=false
# 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
spring.datasource.druid.pool-prepared-statements=true
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=50
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计
spring.datasource.druid.filters=stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
# 合并多个DruidDataSource的监控数据
spring.datasource.druid.use-global-data-source-stat=true

七、负载均衡器配置参考

HAProxy

global
        log 127.0.0.1   local2
        maxconn 4096
        user haproxy
        group haproxy
        chroot /var/lib/haproxy
        daemon
        pidfile /var/run/haproxy.pid
        stats socket /var/run/haproxy.sock    # Make sock file for haproxy
        nbthread 40    # 启动 n(以 40 为例) 个线程并发转发

defaults 
        log     global
        mode    http
        option  tcplog
        option  dontlognull
        retries 3
        option  redispatch
        maxconn 1024
        timeout connect 500000s
        timeout client 500000s
        timeout server 500000s

listen tidb_cluster 
        bind 0.0.0.0:8001    # 真正的 proxy 名以及接受服务的地址
        mode tcp
        balance leastconn    # 这个方法最适用于数据库
        option mysql-check user haproxy post-41    # 7层探活
        server tidb1 172.16.4.81:4000 check
        server tidb2 172.16.4.81:4001 check
        server tidb3 172.16.4.88:4000 check
        server tidb4 172.16.4.88:4001 check
        server tidb5 172.16.4.91:4000 check

        #IP 透传
        #server tidb1 172.16.4.81:4000 check send-proxy
2赞

druid配置实例很实用

1赞