编程 MaxScale readwritesplit 默认值排障:max_slave_connections=255 与 max_replication_lag=0s

2026-11-04 00:04:19

MaxScale readwritesplit 默认值排障:max_slave_connections=255 与 max_replication_lag=0s

一条客户端连接在 MaxScale 的 readwritesplit 上并不等于一条后端连接。

默认参数下,路由器为每个客户端会话最多建立 255 条从库连接(max_slave_connections,默认 255,上限也是 255);新会话按 255 这个数铺初始连接(slave_connections,同样默认 255)。max_replication_lag 默认值是 0s——不是“零容忍”,而是这个过滤功能没开。

所以「读写分离配上去,从库为什么被连接打满」「延迟很久的从库为什么照样接读」,不用先看 SQL 解析逻辑,先看这两个默认值。

文档:mariadb.com/docs/maxscale/reference/maxscale-routers/maxscale-readwritesplit,教程:MariaDB MaxScale 24.02 Read-Write Splitting,仓库:mariadb-corporation/MaxScale。

后端连接数怎么算

readwritesplit 是 query-level 路由:解析每条语句,写发主库,autocommit 的普通 SELECT 发从库。要这么做,会话必须同时在主库和从库上持有连接。

  • max_slave_connections:一个会话同一时刻最多用多少条从库连接。默认 255,最小值 0。它按会话计,不是跨会话复用的连接池。
  • slave_connections:新会话初始铺几条从库连接。默认 255。

两者是硬上限与软目标的关系:slave_connections 只是软限制,如果负载均衡算法发现某台还没连上的从库比已连的更好,实际连接数可以超过它。

单条客户端连接的从库连接数上界是 min(max_slave_connections, 从库实际台数)——不会为不存在的从库开连接。1 主 3 从、max_slave_connections=2 时,一个会话是 1 条主库 + 2 条从库,读只在这 2 台之间平摊。

这个参数兑换的是动态性与连接开销:调低,每会话连接少、可选从库集合小,负载均衡变钝;调高,连接多,几乎总能给单条查询挑到最快的那台。

短连接场景的写法:

# 客户端连接生命周期短(PHP-FPM、每次请求新建连接)
slave_connections=1

max_slave_connections=0:全走主库,但保留重放与重连

设成 0 时,readwritesplit 把所有读也路由到当前主库,但事务重放、断线重连仍然生效。

用途是灰度或回退:从库延迟不可控时先把读切回主库止血,不用改应用连接串、也不用换 router 模块。

这个模式下 master_failure_mode=fail_on_write 有一处变化:主库挂了又没有新主库可用时来一个读,会话会被直接关闭,而不是临时开一条从库连接——那条连接在新主库出现后不会被关掉,会漏。

max_replication_lag 默认 0s 意味着关着

  • 类型 duration,默认 0s,即功能关闭。
  • 23.02 之前叫 max_slave_replication_lag,旧名仍作为 alias 可用。
  • 语义是“从库落后秒数必须严格小于该值才有资格接读”。max_replication_lag=1s 的实际含义接近只接受基本追平的从库。
  • 粒度是秒,写毫秒会被直接拒绝,即使换算后大于 1 秒。
  • readwritesplit 自己不测延迟,靠 monitor(mariadbmon)刷新状态;monitor_interval 决定这个判据的时效。
  • Galera 集群没有从库延迟这个概念,这个参数不适用。
  • 从 23.08.7 / 24.02.3 / 25.01.1 起,某台落后超过 MAX(300, 2 × max_replication_lag) 秒时,到该服务器的连接会被主动丢弃。

它不等于因果读:即便所有从库都满足延迟阈值,也不能保证“刚写的那条”能被同一个连接读到。

选从库的判据

slave_selection_criteria 默认 least_current_operations:

  • least_current_operations(默认):按当前活跃操作数挑最闲的,优化吞吐。
  • adaptive_routing:按服务器平均响应时间与估算负载挑,选“下一个查询最先跑完”的那台。异构硬件、跨机房、机器上还跑着备份或其他客户端时用。它能间接把外部负载算进去。
  • least_behind_master:按复制延迟挑最新的从库,基于 SHOW REPLICA STATUS 的 Seconds_Behind_Master,只适用于传统主从,Galera 不行。它提供的是“更可能读到新数据”,不是保证。
  • least_router_connections / least_global_connections:不要用。文档写明是兼容性遗产,用连接数这种粗粒度指标均衡单条 SQL,结果是负载倾斜。

23.08.1 起全大写写法已弃用,运行时改成小写并持久化为小写;旧大写值目前还能认。

主库故障:三个值

master_failure_mode 默认 fail_on_write(23.08 默认为 fail_instantly):

  • fail_instantly:检测到主库故障立刻关客户端连接。
  • fail_on_write:收到写查询且无主库可用时关连接;纯读照常。
  • error_on_write:收到写查询且无主库可用时返回只读错误,连接保留。

fail_on_write 与 error_on_write 下,只要从库还在,新会话仍会被接受;空闲会话即使后端连接全断也不会被切掉,留时间等重连。代价是客户端不主动关就会一直挂着,得靠 wait_timeout 兜。

error_on_write 的坑:连主库丢了之后,即使新主库已经选出来,客户端没重连 MaxScale 写还是报只读。除非 master_reconnection 开着,让会话在主库被提升时恢复。

master_reconnection 默认值按版本分叉:24.02 起 true,23.08 及以前 false。它依赖会话命令历史(sescmd history):24.02 起配了 disable_sescmd_history,master_reconnection 会被自动禁用。主库切换还要求 prune_sescmd_history 开着、或 max_sescmd_history 未超限,并且会话里没有开放事务。

推荐组合:

master_reconnection=true
master_failure_mode=fail_on_write

strict_tmp_tables=true 会把重连悄悄禁掉

  • 24.02 起 strict_tmp_tables 默认 true(23.08 及以前 false)。
  • 为 true 时,只要会话里还有临时表就阻止主库重连;主库在这期间丢失,会话直接关。
  • 于是建了临时表但从不 DROP 的会话,等于在整个会话期间把重连能力关掉。
  • 为 false 时,重连后临时表会莫名消失,这是升级前的老行为。

配合 strict_multi_stmt / strict_sp_calls(都默认 false):多语句查询和存储过程调用本身会被当写发主库,开启 strict 后它们之后的所有查询也钉主库,用一致性换主库压力。

维护模式不会踢掉空闲连接和长事务

maxctrl set server db2 maintenance 之后,正在执行的请求允许跑完再关连接;如果这台是主库,开放事务也允许提交完。空闲会话和长事务都不会被 readwritesplit 主动切。要真断加 --force:

maxctrl set server db2 maintenance --force

Draining 状态不同:已有连接照常用,只在需要新建连接时避开 Draining/Drained 的服务器。滚动升级用 Draining 比 maintenance 平滑。

一份起点配置

[Read-Write-Service]
type=service
router=readwritesplit
servers=db1,db2,db3
user=maxscale
password=***

slave_connections=1
max_slave_connections=2
max_replication_lag=1s

slave_selection_criteria=least_current_operations
master_reconnection=true
master_failure_mode=fail_on_write
retry_failed_reads=true

retry_failed_reads 默认 true:autocommit 的只读查询在从库上失败可以在另一台重试;结果已部分下发则不重试,除非开 transaction_replay。delayed_retry 默认 false,delayed_retry_timeout 默认 10s;21.06.18 / 22.08.15 / 23.02.12 / 23.08.8 / 24.02.4 / 25.01.1 起,已经发出去的写查询不再重发,避免重复执行。

改完参数只用 maxctrl alter service,但只对新会话生效:

maxctrl alter service Read-Write-Service max_slave_connections=2
maxctrl show service Read-Write-Service

观测点:maxctrl list servers 看状态与连接数;show service 确认会话数;日志里延迟从库被剔除/重新纳入时会有成对的 warning(只在有查询被路由、且状态翻转时才打)。

一条判断顺序

  1. 从库连接数是不是被 max_slave_connections × 客户端连接数 顶满?
  2. max_replication_lag 是否还是默认 0s,延迟从库压根没被排除?
  3. 读全落主库?先看 master_accept_reads(默认 false,无从库时才走主),再看是否 max_slave_connections=0、或会话全在写事务里。
  4. 主库切换后应用写不进去?看 master_failure_mode 是否为 error_on_write、master_reconnection 是否被 disable_sescmd_history 连带关掉。
  5. 升级到 24.02 之后重连行为变了?看 strict_tmp_tables 从 false 变 true 带来的临时表效应。

推荐文章

程序员茄子在线接单