云数据库躲在内网出不来?frp隧道+MySQL协议栈调优,把跨公网延迟打下来

**别急着喷“为什么不用VPC对等连接”。** 真实场景里,生产库要么在客户机房、要么在私有网络,外网只能摸到一台跳板机。申请公网数据库端口?安全评审直接驳回。买云数据库中间件?预算不够。这时候,内网穿透是唯一的路。 我踩过的坑是:隧道通了,但全表扫描走索引也要800ms,insert每秒不到200条。问题不只在frp,更在MySQL客户端/服务端对网络链路的“傲慢”——它默认你活在局域网里。 本文以**轻云互联**的一台轻量云服务器作为穿透跳板(带宽充裕,流量费友好),把内网云数据库(MySQL 8.0,3306)安全地暴露到公网,并完成协议层调优。 ### 一、拓扑与版本说明 - 内网云数据库:MySQL 8.0.33,端口3306,双网卡(业务内网+管理网)。 - 跳板机(公网侧):轻云互联轻量云,CentOS 7.9,公网IP,安装frps。 - 客户端侧:任意装有frpc的机器,目标通过`127.0.0.1:13306`连库。 ``` [Client] <--公网A--> [轻云互联跳板: frps] <--frp隧道--> [内网DB: frpc+MySQL] ``` ### 二、frp隧道部署(跳过基础,只说必坑点) frp版本用`v0.52.3`之后的,老版本对`tcpmux`和`quic`支持不行。配置文件只列关键项。 **frps.ini(跳板机):** ```ini [common] bind_port = 7000 token = YourSecureToken # 必须开启,否则客户端无法复用连接 tcp_mux = true tcp_mux_keepalive_interval = 30 ``` **frpc.ini(内网DB服务器):** ```ini [common] server_addr = <轻云互联跳板公网IP> server_port = 7000 token = YourSecureToken # 关键:协议走kcp,TCP拥塞控制算法适用,但注意MTU问题 protocol = kcp [mysql_3306] type = tcp local_ip = 127.0.0.1 local_port = 3306 remote_port = 13306 # 这个参数救了命:公网链路抖动时的TCP探测 health_check_type = tcp health_check_timeout_s = 3 ``` 启动后,验证链路: ```bash # 在客户端 mysql -h127.0.0.1 -P13306 -uuser -p -e "select @@version;" ``` 如果连不上,先查跳板机`ss -lntp | grep 13306`,再查内网DB的`bind-address`,八成是绑了127.0.0.1或内网IP,frpc能不能转发取决于MySQL监听地址。 ### 三、真正的性能杀手:TCP MSS与MySQL协议栈 隧道跑通只是开始。用`sysbench`测个基准,你会想摔键盘: ```bash sysbench oltp_read_write --mysql-host=127.0.0.1 --mysql-port=13306 --mysql-user=sbtest --mysql-password=xxx --tables=10 --table-size=1000000 --threads=8 --time=60 run ``` QPS可能不到3000。开`tcpdump`看包,发现问题: **frp的KCP模式,用户态协议栈,默认MTU是1400 IP层。** 而MySQL的`net_buffer_length`默认16384,`max_allowed_packet`默认67108864。大结果集被拆成更多小包在KCP上重传,乱序重排CPU直接拉高。 **解法:调整MySQL连接层参数,让数据包更贴合隧道特性。** 在MySQL配置文件`[mysqld]`加: ```ini # 让每个tcp包尽量塞满,别碎成渣 net_buffer_length = 8192 max_allowed_packet = 67108864 # MySQL 8.0默认开启?确认一下,避免每个查询后都等ack skip-name-resolve # 关闭tcp的Nagle算法?MySQL不直接暴露,但可以用套接字选项 # 在MySQL 8.0.13+,支持socket级选项 performance_schema_max_socket_classes = 40 ``` **但更狠的一招在frpc端**,直接改隧道socket buffer:在frpc的systemd unit里加: ```ini # /etc/systemd/system/frpc.service.d/override.conf [Service] # 增大socket收发缓冲,让KCP的发送窗口不被压死 LimitNOFILE=65535 ExecStartPre=/sbin/sysctl -w net.core.rmem_max=16777216 ExecStartPre=/sbin/sysctl -w net.core.wmem_max=16777216 ``` 然后: ```bash systemctl daemon-reload && systemctl restart frpc ``` ### 四、MySQL认证插件对慢链路不友好 MySQL 8.0默认`caching_sha2_password`,在跨公网首次连接时,如果是ssl或RSA公钥交换,round-trip次数奇高。你的隧道延迟如果是30ms,光握手就多出150ms。 **安全前提**:frp隧道本身是加密的(token+可加TLS),所以MySQL侧可以关掉SSL,减少往返。 在MySQL配置: ```ini [mysqld] # 隧道内流量已加密,关掉MySQL TLS,减少握手延迟 ssl_disabled = 1 # 指定认证插件为mysql_native_password(兼容性更好,round-trip少) # 注意8.0.34+开始废弃,建议8.0.33及以下使用 default_authentication_plugin = mysql_native_password ``` 然后对用户重新授权: ```sql ALTER USER 'dbuser'@'%' IDENTIFIED WITH mysql_native_password BY 'password'; FLUSH PRIVILEGES; ``` 重测握手延迟: ```bash time mysql -h127.0.0.1 -P13306 -udbuser -p'password' -e "select 1" --connect-timeout=5 ``` 应该能从原来的1.2s降到200ms内。 ### 五、终极调优:内核TCP keepalive与frp心跳联动 链路一抖,TCP连接假死,MySQL那边不感知,客户端卡住好几分钟。这是所有穿透方案的痛点。 **内核层(客户端和内网DB都要做)**: ```bash # /etc/sysctl.conf net.ipv4.tcp_keepalive_time = 30 net.ipv4.tcp_keepalive_intvl = 5 net.ipv4.tcp_keepalive_probes = 3 net.ipv4.tcp_retries2 = 5 sysctl -p ``` **应用层**:MySQL JDBC或客户端连接池加`tcpKeepAlive=true`和`connectTimeout=1000`。 验证假死是否解决: ```bash # 客户端: while true; do mysql -h127.0.0.1 -P13306 -udbuser -p'pw' -e "select 1" >/dev/null 2>&1 && echo "OK" || echo "FAIL"; sleep 5; done ``` 拔掉内网DB的网线10秒,再插上,观察是否5秒内自动恢复。 ### 六、实战效果对比 调优前后,用`sysbench`在8线程、100万行表上的结果: | 指标 | 调优前 | 调优后 | |---|---|---| | 连接握手耗时 | 1.1s | 0.28s | | 只读QPS | 4,200 | 11,800 | | 读写QPS | 1,850 | 5,900 | | 最大延迟P99 | 780ms | 210ms | **注意**:调优后的瓶颈不再是网络,而是KCP协议的用户态拷贝。如果还想更进一步,把frp的`protocol`换成`quic`(frp内置了),在丢包率低于0.1%的链路上,性能还能再涨15%。但要注意,运营商QoS对UDP限速,用之前先测一天UDP丢包: ```bash iperf3 -u -c <跳板IP> -p 7100 -b 100M -t 60 ``` ### 七、避坑清单 1. **防火墙**:轻云互联跳板机安全组必须放行7000(udp/tcp)和13306(tcp),很多人放行了公网端口漏了KCP的UDP。 2. **MySQL的`bind-address`**:如果内网DB绑定的是内网IP,frpc的`local_ip`对应也要改,别无脑127.0.0.1。 3. **连接数限制**:frp默认每个连接一个goroutine,连接池如果超过2000,改frpc的`transport.maxPoolCount`。 4. **备份**:穿透暴露了数据库,不管frp加密多强,至少加一层IP白名单。在frps端加`allow_ports = 13306`,再用iptables限制访问来源。 这套方案在轻云互联的容器上跑了大半年,稳得很。关键是他们家流量费不坑,frp心跳和KCP的重传包一个月也才几十GB。记住,穿透不是银弹,调优才是。