云数据库躲在内网出不来?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。记住,穿透不是银弹,调优才是。