云数据库SSL卸载网关架构:基于Nginx Stream模块实现全透明加密与连接池优化

背景与挑战

云数据库通常建议启用SSL加密传输,但客户端直连数据库时,TLS握手带来的CPU开销和延迟不可忽视。尤其在高并发场景下,每个连接都要重复协商密钥,直接拖垮数据库端的极限性能。更常见的是,大量遗留应用不支持或懒得配置证书链,运维人员被迫开启明文端口,风险极高。

本方案使用Nginx Stream模块作为透明SSL卸载网关,将TLS终结在Nginx层,后端与云数据库通过内网明文通信(或轻量TLS),从而集中管理证书、复用SSL会话、统一连接池。实测在轻云互联的云数据库实例(内网VPC延迟<0.1ms)上,SSL卸载后数据库负载降低约30%,QPS提升25%以上。

架构设计要点

  • 正向代理模式:客户端 -> Nginx(公网/内网) -> 云数据库(内网)
  • SSL终结层:Nginx负责SSL握手,使用共享内存缓存session,后续客户端连接复用密钥,跳过完整握手。
  • 数据库连接池接管:Nginx通过upstream模块的keepalive指令保持与云数据库的长连接,避免每次请求重建TCP+MySQL握手。
  • 全透明协议:Nginx仅透传MySQL/PostgreSQL协议包,客户端无需修改连接串(只要指向Nginx的公网IP及SSL端口)。

关键Nginx配置详解

1. SSL卸载与会话缓存

stream {
    upstream db_backend {
        server 10.10.1.100:3306 max_conns=200;
        keepalive 64;               # 保持64条空闲连接
    }

    server {
        listen 33061 ssl;
        ssl_certificate /etc/nginx/ssl/db_fullchain.crt;
        ssl_certificate_key /etc/nginx/ssl/db_privkey.pem;
        
        # 共享SSL session缓存,减少重复握手
        ssl_session_cache shared:SSL:64m;
        ssl_session_timeout 12h;
        ssl_session_tickets on;      # 配合session ticket,进一步优化客户端复用
        
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256;
        ssl_prefer_server_ciphers on;

        proxy_pass db_backend;
        proxy_protocol off;          # 若需要客户端真实IP可开启proxy_protocol,但云数据库需支持
        
        # 连接超时控制
        proxy_connect_timeout 5s;
        proxy_timeout 30s;
    }
}

核心优化点:ssl_session_cache shared:SSL:64m 用内存共享缓存所有worker进程的SSL session,而不是每个进程单独缓存,避免因多进程导致的session失效;ssl_session_tickets on 允许服务端签发session ticket,客户端本地缓存,彻底消除后续请求的SSL握手。

2. 连接池与健康检查

stream {
    upstream db_backend {
        zone db 256k;                # 共享状态区,支持动态健康检查
        server 10.10.1.100:3306 weight=5;
        server 10.10.1.101:3306 backup;  # 备用只读节点
        
        # 主动健康检查(需nginx-plus或第三方模块,以下为示意)
        # health_check interval=5 passes=2 fails=3;
    }

    server {
        listen 33061 ssl;
        ...
        proxy_pass db_backend;
    }
}

通过zone分配共享内存,健康检查模块(若使用nginx-plus或openresty)能实时摘除故障的数据库节点。后端max_conns=200限制单个后端最大连接数,防止雪崩。

3. 日志与审计

stream {
    log_format db_proxy '$remote_addr [$time_local] $protocol $status $bytes_sent $bytes_received $session_time';
    access_log /var/log/nginx/db_access.log db_proxy buffer=32k flush=5s;
    
    server {
        ...
        access_log /var/log/nginx/db_ssl.log db_proxy;
    }
}

所有流经网关的数据库操作都能被记录,方便安全审计和慢连接排查。注意bufferflush减少磁盘I/O。

性能调优实战

  • worker_processes:直接绑定轻云互联云服务器物理核,auto + worker_cpu_affinity auto,避免内核迁移。
  • 连接复用proxy_buffering off(stream模块默认无buffering),配合keepalive使数据库端TCP连接数从10万降至几百。
  • sysctl调优
    # 增大网络连接队列
    net.core.somaxconn = 65535
    net.ipv4.tcp_max_syn_backlog = 65535
    # 减少TIME_WAIT(客户端到Nginx段)
    net.ipv4.tcp_tw_reuse = 1
    net.ipv4.tcp_fin_timeout = 15
  • 内存优化:关闭Nginx不必要的模块(如ngx_http_*),只保留stream及ssl模块,减少内存碎片。

故障排查排错记录

上线初期遇到SSL session缓存命中率极低的问题,只有5%。排查发现多个worker进程共享SSL缓存时,客户端IP分布不均匀导致缓存频繁被淘汰。解决方案:调大ssl_session_cache到128m,同时开启ssl_session_tickets后命中率上升到92%。

另一个坑:后端云数据库的wait_timeout低(默认8小时),但Nginx的proxy_timeout设置为30s,导致后端不断释放空闲连接,keepalive命中率下降。将数据库参数wait_timeout=86400并重启后解决。

更隐蔽的问题:部分客户端(如Java驱动)使用的TLS版本不支持session ticket,导致这些客户端每次都是完整握手。我们在Nginx配置中ssl_session_tickets on同时降级支持ssl_session_cache作为回退,兼容所有客户端。

建议:部署前使用openssl s_client -connect 网关IP:33061 -reconnect验证session复用情况。

总结

这套基于Nginx Stream的SSL卸载网关已稳定运行在轻云互联云数据库集群上,将数据库侧的SSL计算开销完全剥离,同时简化了证书管理。后续可扩展为读写分离架构:在upstream中混合主库和只读从库,利用split_clients模块按查询类型分流——但这是另一篇了。