1. PolarDB从节点不可用问题背景解析
上周五凌晨2点37分,我正喝着第三杯咖啡调试一个紧急工单时,监控系统突然弹出一连串告警——某金融客户的生产环境PolarDB集群出现从节点完全无流量的异常情况。主节点CPU瞬间飙到90%,而三个只读节点的连接数始终显示为0。这种典型的"从节点罢工"场景,相信每个DBA都经历过那种头皮发麻的瞬间。
PolarDB作为阿里云自研的云原生数据库,其读写分离架构本应自动将SELECT查询负载分散到多个只读节点。但在实际生产环境中,我们会遇到各种导致从节点不可用的情况:
-
事务一致性陷阱:当应用使用JDBC等连接池时,默认会将所有SQL封装在事务中(即使只是简单查询)。由于PolarDB默认要保证事务内读写一致性,Proxy会将整个事务路由到主节点。这就是为什么你会在监控中看到大量本应走从节点的查询压垮了主库。
-
复制延迟雪崩:当主节点写入压力激增时,WAL日志同步到只读节点的延迟可能超过阈值(默认1秒)。此时Proxy会主动规避这些"落后"的从节点,导致剩余可用节点压力骤增,形成恶性循环。
-
配置误区:特别是从传统MySQL迁移过来的用户,经常忽略PolarDB特有的"事务拆分"、"一致性级别"等参数配置。我曾见过一个客户因为没开启事务拆分功能,导致价值百万的只读节点资源整整闲置了三个月。
重要提示:PolarDB的只读节点不可用问题,90%的情况都不是节点本身故障,而是路由策略和配置问题。盲目重启节点往往适得其反。
2. 事务拆分机制深度剖析
2.1 事务拆分的底层逻辑
PolarDB的事务拆分功能(Transaction Splitting)本质上是一种智能SQL路由机制。其核心算法可以概括为:
-
会话跟踪:Proxy会为每个连接维护事务状态机,关键状态包括:
IDLE:非事务状态ACTIVE:事务中但尚未发生写操作DIRTY:事务中已执行INSERT/UPDATE/DELETE等写操作
-
路由决策树:
mermaid复制graph TD A[收到SQL] --> B{是否在事务中?} B -->|否| C[根据读写类型路由] B -->|是| D{事务状态?} D -->|IDLE| E[升级为ACTIVE] D -->|ACTIVE| F{是否写操作?} F -->|否| G[路由到从节点] F -->|是| H[升级为DIRTY并路由到主节点] D -->|DIRTY| I[强制路由到主节点] -
临界点处理:当检测到第一个写操作时,Proxy会:
- 立即将当前连接的所有后续请求路由到主节点
- 在客户端执行COMMIT/ROLLBACK后重置状态为IDLE
2.2 不同隔离级别的影响
事务拆分对事务隔离级别的支持存在重要限制:
| 隔离级别 | 是否支持拆分 | 风险说明 |
|---|---|---|
| Read Uncommitted | × | 可能读到从节点未提交的数据 |
| Read Committed | √ | 标准推荐配置 |
| Repeatable Read | 部分支持 | 需配合一致性级别使用 |
| Serializable | × | 串行化要求严格一致性 |
特别要注意的是,使用JDBC时默认的connection.setAutoCommit(false)会将隔离级别隐式设置为REPEATABLE READ。这就是为什么很多Java应用即使开启了事务拆分,从节点依然没有负载。
2.3 一致性级别参数调优
PolarDB提供三种数据一致性级别,直接影响事务拆分的生效时机:
-
最终一致性(EVENTUAL):
- 特点:读请求立即路由到从节点,不检查复制延迟
- 适用场景:秒级延迟可容忍的报表查询
- 事务拆分效果:最佳
-
会话一致性(SESSION):
- 特点:确保会话内读到自己已提交的写入
- 实现机制:写操作后300ms内读请求自动路由到主节点
- 事务拆分效果:中等
-
全局一致性(GLOBAL):
- 特点:确保读到最新已提交数据
- 实现机制:通过TSO时间戳同步
- 事务拆分效果:最差
配置建议(以金融业务为例):
sql复制-- 核心交易库使用会话一致性
ALTER DATABASE account_db SET polar_consistency_level = 'SESSION';
-- 历史查询库使用最终一致性
ALTER DATABASE report_db SET polar_consistency_level = 'EVENTUAL';
3. 从节点不可用故障排查手册
3.1 诊断流程图
mermaid复制graph TD
A[从节点无流量] --> B{检查连接数}
B -->|>0| C[检查负载均衡]
B -->|=0| D{事务拆分是否开启?}
D -->|否| E[立即开启]
D -->|是| F{检查隔离级别}
F -->|非RC| G[调整隔离级别]
F -->|RC| H{检查一致性级别}
H -->|GLOBAL| I[评估降级可能]
H -->|其他| J[检查复制延迟]
J -->|>1s| K[优化主库写入]
J -->|<1s| L[检查Proxy日志]
3.2 关键监控指标解读
-
polar_stat_activity监控:
sql复制SELECT datname, usename, state, COUNT(*) as connections, SUM(CASE WHEN query LIKE '%SELECT%' THEN 1 ELSE 0 END) as selects FROM polar_stat_activity GROUP BY 1,2,3;健康状态应呈现:
- 主节点:大量"active"状态的UPDATE/INSERT
- 从节点:大量"idle in transaction"状态的SELECT
-
复制延迟检测:
sql复制SELECT client_addr, pg_xlog_location_diff(sent_location, replay_location) as delay_bytes, now() - reply_time as delay_interval FROM pg_stat_replication;危险阈值:
- 字节延迟 > 16MB
- 时间延迟 > 1s
-
Proxy路由统计:
bash复制# 需要SSH登录Proxy容器 cat /var/log/polardb_proxy.log | grep "route decision" | awk '{print $NF}' | sort | uniq -c正常输出示例:
code复制1423 master 8768 replica
3.3 典型故障案例
案例1:Spring Boot默认配置陷阱
现象:从节点始终无查询流量
根因:Spring默认设置spring.datasource.tomcat.default-auto-commit=false
解决方案:
yaml复制spring:
datasource:
hikari:
auto-commit: true # 强制启用自动提交
tomcat:
default-auto-commit: true
案例2:长事务阻塞复制
现象:从节点延迟持续增长
根因:报表系统执行了30分钟的SELECT COUNT(*)
解决方案:
sql复制-- 在从节点上设置
SET max_standby_streaming_delay = '5s'; -- 超过5秒的查询自动取消
案例3:错误的连接串配置
错误配置:
code复制jdbc:postgresql://cluster-endpoint.rds.aliyuncs.com:3433/db?prepareThreshold=0
问题点:禁用预处理会导致Proxy无法解析SQL类型
正确配置:
code复制jdbc:postgresql://cluster-endpoint.rds.aliyuncs.com:3433/db?prepareThreshold=3&preparedStatementCacheQueries=256
4. 高级调优与最佳实践
4.1 读写分离权重配置
通过调整读负载分配策略,可以精细控制各节点的压力:
sql复制-- 查看当前权重
SELECT node_name, node_type, weight FROM polar_cluster_nodes;
-- 调整从节点负载权重(默认100)
ALTER CLUSTER NODE 'ro-1' SET weight = 150; -- 该节点将承担更多读请求
ALTER CLUSTER NODE 'ro-2' SET weight = 80; -- 减轻该节点压力
经验值:主节点CPU利用率与最忙从节点差值应保持在15%-20%。差距过大说明需要调整权重或扩容。
4.2 热点查询自动路由
对于特定模式的查询,可以强制路由到主节点:
sql复制-- 创建路由规则
CREATE ROUTE RULE hotspot_queries
WHEN (query LIKE '%SELECT * FROM account_balance%')
THEN ROUTE TO master;
-- 特殊报表查询走主库
CREATE ROUTE RULE critical_reports
WHEN (query LIKE '%/*+MASTER*/%')
THEN ROUTE TO master;
4.3 连接池优化建议
不同连接池的推荐配置:
| 连接池类型 | 关键参数 | 推荐值 | 说明 |
|---|---|---|---|
| HikariCP | autoCommit | true | 避免隐式事务 |
| isolationLevel | READ_COMMITTED | 必须设置 | |
| Druid | defaultAutoCommit | true | |
| defaultTransactionIsolation | 2 | 对应READ_COMMITTED | |
| C3P0 | autoCommitOnClose | false | 防止意外提交 |
| forceIgnoreUnresolvedTransactions | true | 避免连接泄漏 |
4.4 压力测试方法论
使用sysbench验证事务拆分效果:
bash复制# 1. 准备测试数据
sysbench oltp_read_write \
--db-driver=pgsql \
--pgsql-host=$ENDPOINT \
--pgsql-port=3433 \
--pgsql-user=test \
--pgsql-password=Test1234 \
--pgsql-db=testdb \
--tables=10 \
--table-size=100000 \
prepare
# 2. 运行混合负载(读写比8:2)
sysbench oltp_read_write \
--rand-type=uniform \
--db-driver=pgsql \
--pgsql-host=$ENDPOINT \
--pgsql-port=3433 \
--pgsql-user=test \
--pgsql-password=Test1234 \
--pgsql-db=testdb \
--threads=64 \
--time=300 \
--report-interval=10 \
--percentile=95 \
run
# 3. 监控节点负载
watch -n 1 "psql -h $ENDPOINT -p 3433 -U test -c 'SELECT node_name, cpu_usage, connections FROM polar_cluster_status;'"
关键指标对比:
- 未开启事务拆分:主节点CPU≈80%,从节点CPU<10%
- 开启后:主节点CPU≈40%,从节点CPU≈60%
5. 生产环境应急方案
当出现从节点完全不可用时,建议按照以下优先级处理:
-
立即措施:
sql复制-- 临时将所有读请求路由到主节点 ALTER CLUSTER SET polar_read_only = off; -- 快速扩容主节点规格(通常5分钟内生效) CALL polar_scale_up('4C16G'); -
中期修复:
sql复制-- 检查并修复复制冲突 SELECT * FROM pg_stat_replication_conflicts; -- 重建问题从节点(需10-30分钟) CALL polar_rebuild_replica('ro-1'); -
长期优化:
sql复制-- 启用自动负载均衡 ALTER CLUSTER SET polar_load_balance_mode = 'auto'; -- 设置读写分离白名单 CREATE ROUTE RULE read_only_whitelist WHEN (user IN ('report_user', 'bi_user')) THEN ROUTE TO replica;
特别提醒:在双11等大促期间,建议提前设置"主库保护模式":
sql复制-- 当主节点CPU>70%时自动将部分读请求路由回主节点
ALTER CLUSTER SET polar_master_protection_mode = 'aggressive';
