数据库代理这类中间件被引入,通常不是因为架构师喜欢多一层,而是因为三类非常具体的麻烦:应用实例一扩容,连接池就把数据库撑爆;主库一换机器,几十个应用的连接配置要跟着改;报表和导出这类重查询想分流到从库,却不想让业务代码感知拓扑。MariaDB MaxScale 这类代理就是冲着这三点来的,它让应用只认识一个地址,把拓扑变化关在自己这一层。
但代理本身也是一台要租的机器,有 CPU、内存、网卡包转发率要算,也有它自己挂了之后怎么办的问题。很多团队先算了"省下来的数据库连接数",没有算"多出来的一个故障域",上线半年后发现链路可用性反而比没有代理的时候更低:数据库明明还活着,应用却因为代理单点整体不可用。本文就沿着"资源账"和"失效边界"两条线写,把代理转移走了哪些成本、新增了哪些成本说清楚。
另外一个贯穿全文的纪律是区分两类信息。一类是文档可查的确定行为,比如 MaxScale 是 MariaDB 官方的数据库代理、提供 readwritesplit 读写分离路由与 readconnroute 连接路由、通过监控模块判定主从状态并支持自动故障转移、路由与过滤模块可插拔。另一类是只能靠自己流量实测校准的阈值,比如连接数上限、切换耗时、每会话内存开销。前者可以直接采信,后者本文只给估算口径和测量方法,不给具体数值。
本文讨论的是这一类通用代理的通用能力与通用失效模式,后端写 MySQL / MariaDB 均可,不构成对某一商业版本的评测。文中所有涉及容量和延迟的地方,都按"区间估算 + 以自身实测为准"的口径给出。
先看能解决的。其一是连接数收敛:每个应用实例各自维持一个连接池,实例从几台扩到几十台时,后端连接数近似线性上涨,而数据库侧每个连接都要占一个线程、一份会话缓冲和一份调度开销,实例数一多,真正用来干活的并发反而被挤压。代理把"应用实例数 × 每实例池大小"收敛成"代理数 × 后端池大小",这是最直接的收益。其二是拓扑透明:主库换了节点,改的是代理的路由目标,应用的连接串不动。其三是读分流:只读流量按规则落到从库,主库只承担写和强一致读。
再看解决不了的。写扩展不在代理的能力范围内:代理不会把写流量分片,写压力在主库上仍然是主库上的压力,分库分表是另外一整套工程。慢查询也不会因为过了一层代理就变快,代理只是换了个执行位置,该全表扫描还是全表扫描,而且还要额外付一跳网络往返和一次语句解析的成本。锁竞争同理,行锁、元数据锁、死锁都发生在数据库内核里,代理在协议层看得到语句,但干预不了锁。
由此产生一个很常见的误判:把"数据库 CPU 高"归因于连接太多,上代理之后发现 CPU 没降。判断方法并不复杂,上线前先看数据库侧的已连接线程数与正在运行的线程数之间的关系,再看慢查询的时间分布。如果连接数很高而活跃并发很低,说明大量连接是空闲的,代理复用会有明显收益;如果活跃并发本身就高、且慢查询集中在少数几条 SQL,那么该做的是 SQL 与索引治理,而不是加一层代理。
这三段要分开记,混在一起算必然出错。从应用到代理这一段,连接数量由应用实例数和每个实例的池上限决定。从代理到后端各节点这一段,由代理自身的连接池参数决定,与前端连接数没有固定的倍数关系。数据库侧实际看到的总连接数,等于代理这一段之和再加上直连的管理连接、备份连接、监控连接。真正压在数据库上的只有最后这一段,但它同时被应用侧这一段的行为影响,因为代理什么时候复用后端连接、什么时候新建后端连接,取决于前端会话上的状态。
决定复用率的关键是会话状态有没有被污染。如果一条前端连接上没有显式事务、没有临时表、没有用户变量、没有预处理语句句柄、也没有 SET 语句改过会话参数,代理就可以把它和后端连接解耦,一条后端连接被多个前端连接分时复用。反过来,只要会话上带了状态,这条后端连接就被粘在这个前端会话上,复用度立刻下降。预处理语句是最典型的例子,很多 ORM 默认走 prepare,会话上会留下语句句柄,这一项往往直接决定了后端的实际连接需求。
另一个变量是事务边界。事务进行中后端连接不能被别人借用,事务越长,后端连接被独占的时间越长。所以"平均连接数"是个会骗人的指标,真正该看的是"平均独占时长 × 并发事务数"。一个两百毫秒内结束的短事务和一个跑八秒的报表型事务,对后端连接池的需求差了一个数量级,但它们在连接数监控图上长得一模一样。
直觉是把池配大就不会排队,实际常见的运行结果相反。数据库侧并发越高,锁等待、上下文切换、缓冲池与日志相关的争用就越严重,单条查询的响应时间被拉长;响应时间被拉长之后,同样的每秒请求数需要更多并发连接才能维持;更多连接又进一步加剧争用。这是一个会自我强化的循环,把池子调大只是把拐点往后推了一点,代价是崩的时候更突然、更难看。
代理层的排队有同样的形状,但症状不一样。代理排队时前端看到的是"连接被占用,等待分配后端连接",表现得更像代理卡死而不是数据库变慢,很容易被误判成代理性能不足,然后去给代理加机器。加完发现没用,因为瓶颈在后端。判断依据要看代理侧的两个计数器:等待分配后端连接的排队数,以及后端连接全部忙碌的时间占比。两者同时高,说明后端顶不住;后端连接很空闲而前端在排队,才说明是代理自身的线程或过滤器配置太重。
更合理的做法是从并发目标反推,而不是从池上限正推。先压测出数据库在可接受响应时间下的最优活跃并发数,这个数通常在几十这个量级,具体是多少必须由自己的硬件和业务 SQL 实测决定;再让代理到后端的连接总数围绕这个值设定,前端连接数允许显著大于后端连接数,超出的部分在代理侧排队而不是打进数据库。排队的代价是延迟,打进去的代价是雪崩,两者之间选排队。
其一,事务内读。应用显式开启事务后执行查询,如果代理把这条读发给从库,事务的一致性视图就建立在从库上,而复制是有延迟的;事务内后续又写主库,就会出现"读到旧值、基于旧值做判断、再写回主库"的丢失更新。规避方式是让路由器识别事务状态,事务内的所有语句一律发往主库。这也是 readwritesplit 一类路由器的常见行为,但具体是否默认开启、开启到什么粒度,需要在自己使用的版本上确认,不能想当然。
其二,写后立即读。这类语句不在显式事务里,但有明确的因果关系:提交订单后立刻查订单列表,从库还没追上,用户看到的是"刚才的操作不见了"。这是读写分离上线后最常见的客诉形态,因为它只在复制延迟被放大的时刻出现——大事务、批量导入、从库备份、网络抖动的时候集中爆发,平时测不出来,上线评审也发现不了。
其三,存储过程与临时表。存储过程内部可能既有读又有写,代理在协议层只能看到一条 CALL 语句,判断不了内部意图;临时表同理,建在从库上的临时表不会出现在主库上,后续语句一旦被路由到主库就直接报表不存在。这两类的共同点是"状态在某一台机器上生成,后续语句却可能被路由到另一台",属于代理在协议层天然难以推断的情形。
其四,显式锁和会话级读。SELECT ... FOR UPDATE、LOCK TABLES、GET_LOCK 这类语句在从库上执行没有意义,甚至有危险:在从库上加的行锁不会阻止主库上的写入,等于锁了个空;而基于锁做互斥的业务逻辑一旦落到从库,多个应用实例会同时认为自己拿到了锁,互斥直接失效。这一类比前三类更隐蔽,因为它不会报错,只会偶发出现数据错乱。
代价最低的是事务内路由到主:把有因果关系的读写放进同一个显式事务,路由器自然会把整段发往主库。收益是不需要额外配置、行为确定;代价是主库读压力上升,如果这类接口占比高,读写分离的意义会被稀释。适合先把正确性保住、再谈性能优化的阶段。
第二种是代理侧的因果读支持。MaxScale 的 readwritesplit 提供基于 GTID 的因果读能力:写操作返回时记下一个 GTID 位点,随后的读请求会等待目标从库追上该位点再执行,来不及就落到主库。这能覆盖"写后立即读"这一类,代价是读延迟里会包含等待复制的时间,从库延迟大的时候,这个等待可能比直接读主库更慢。是否开启、开启到哪个级别,要看自身从库延迟的分布来定,延迟常年很小的集群开这个几乎没有代价,延迟波动大的集群开了可能适得其反。
第三种是应用层打标记。对明确要求强一致的接口,在语句上带提示性注释,由代理的过滤器识别后强制路由到主库。这种方式最灵活,可以精确到单个接口,代价是需要改代码,而且依赖团队有纪律地维护这张"强一致接口清单"。清单一旦失修,新接口上线就会静默地走到错误的一致性级别。
还有一个容易被忽略的配套项是从库选择策略。代理在多个从库之间挑一个时,可以按当前复制延迟、连接数、节点权重等条件排序,也可以按延迟阈值直接把落后的从库摘掉。这里的关键不是选哪种策略,而是阈值怎么定:阈值定得太紧,从库一有波动就被摘掉,读压力全部压回主库;定得太松,用户就会读到明显过期的数据。合理做法是先测出自身复制延迟的分布,把摘除阈值设在分布的尾部,再观察一个业务周期内的摘除次数,摘除过于频繁就说明阈值不合适。
无论用哪一种,都要留一条兜底:打开"路由到从库失败时回退到主库"的行为,并把回退次数做成计数器。回退次数长期为零,说明分流真的在生效;回退次数突然升高,通常意味着从库挂了或者延迟超过了阈值,这时候读写分离实际上已经名存实亡,主库正在独自承担全部流量,而不是像监控图上看起来那样"分流良好"。
代理的监控模块按固定间隔对后端节点做探测,检查连通性、复制线程状态、节点角色,连续失败达到一定次数才判定节点故障并触发切换。这里有两个参数在互相拉扯:间隔和次数越小,故障发现越快、业务中断越短;越小也越容易被一次网络抖动误判,把健康的节点判成故障。
误判的代价不是"多切一次"这么轻描淡写。如果主库其实活着,只是代理到主库这一段网络短暂中断,代理就可能把某个从库提升为新主;而旧主还在接受来自其他路径的写入,比如另一个代理实例、管理员的直连会话、或者定时任务,于是出现两个节点都在接受写的情况,也就是常说的双主或者脑裂。到了这一步,两条写入历史大概率已经无法简单合并,需要人工介入判断保留哪一份,恢复时间从秒级掉到小时级。
降低这类风险有几个方向。判定次数设到足以过滤单次抖动的量级,具体几次取决于自身网络的抖动特征,需要实测而不是照抄别人的配置。切换动作里加上"隔离旧主"这一步,不只是改路由表,而是把旧主的写路径真正断掉。同一个集群只让一个代理实例拥有切换权限,避免两个代理各自观测、各自决策。
还要区分计划内切换与故障切换。计划内切换是主动把主角色交给另一个节点,通常在维护窗口里执行,可以在确认复制已经追平之后再做,风险可控。故障切换是在主库已经不可用的前提下做的决策,必然要面对"旧主到底写到哪了"这个不确定性。两者的判定阈值不要混用同一套,计划内切换可以宽松,故障切换的参数要围绕"宁可晚一点切,也不要切错"来调。
拓扑变了之后,真正麻烦的不是路由,是连接。代理到旧主的连接要被踢掉,否则应用还在往一个已经不是主的节点写;代理内部缓存的路由信息和节点状态要失效重建;应用侧持有的、指向代理的那条连接本身没断,但它背后的会话状态——未提交事务、临时表、预处理语句句柄——已经失效了。这些状态不会自己修复,只能靠一次重建。
于是应用会看到一批连接级错误。写事务中途被中断的,代理返回的是连接级或事务级错误,应用必须能区分"可以安全重试"和"不能重试"。可以安全重试的典型是事务还没提交就被断开;不能盲目重试的是已经提交但响应在回程丢失的写。区分的办法不是靠猜错误码,而是让应用用唯一业务键做幂等,把重试的正确性交给业务语义而不是传输层。
重连风暴是被严重低估的一环。切换瞬间大量应用连接同时重建、同时重新准备预处理语句、同时重新拉取元数据,这一波冲击可能比故障本身更容易把刚刚提升的新主压垮。做法是双管齐下:应用连接池里加重试抖动与退避,避免整齐划一地重连;代理侧限制同时新建后端连接的速率,给新主一个缓冲。
如果条件允许,可以评估代理提供的事务重放能力:代理在内存里保留事务的语句历史,故障发生后在新的主节点上重放尚未提交的事务,把连接级错误对应用透明化。这项能力有明确边界,只对可重放的语句有效,对包含非确定性函数、依赖自增值、或者带会话状态的事务不适用,而且会占用代理内存。是否启用要看自身的 SQL 形态,不要默认它能兜住一切。
内存主要由几块构成:前端连接与后端连接各自的会话缓冲、路由与过滤器持有的会话状态、查询日志类过滤器开启后的语句缓存、以及可选的结果缓存。其中会话状态往往是弹性最大的一块,尤其是预处理语句句柄和会话命令历史,它们随业务 SQL 的数量和长度增长,不是随连接数线性增长那么简单。同样是五千条连接,一个只跑简单主键查询的库和一个跑大量多表连接与长 IN 列表的库,代理的内存占用可能差出好几倍。
CPU 主要消耗在协议解析、语句解析与路由决策、过滤器链的执行、以及 TLS 加解密。哪一项先到瓶颈,取决于开启了什么。只做连接路由时,CPU 通常要在相当高的吞吐下才吃紧;一旦开启正则重写、全量语句日志、语句改写这类过滤器,CPU 消耗会显著上升,而且与平均语句长度、正则复杂度强相关。换句话说,代理的 CPU 账不是"多少 QPS"能算清的,要加上"每条语句多少字节、过几道过滤器"这两个维度。
网络侧容易被忽略的是每秒新建连接数与包转发率。短连接频繁建连的场景,这两个是硬指标,网卡和内核网络栈先撑不住;长连接高吞吐的场景,更该关注的是带宽能否承载结果集。一次返回几万行的大结果集经由代理转发,占用的内存与带宽都会瞬时抬升,这类请求建议在应用侧就限制返回行数或者改成分页,而不是指望代理扛。
还有一个会显著影响资源账的配置是后端连接的保活与回收。代理通常会保留一部分空闲后端连接供复用,保留多少、保留多久,直接决定了"突发流量来了要不要现建连接"以及"平时占着多少后端连接不释放"。保留得多,突发时响应快,但数据库侧常驻连接数高;保留得少,数据库侧轻,但每次突发都要付一轮建连与认证的开销。短连接占比高的业务适合多保留一些,长连接稳定的业务可以少保留,这个取舍同样需要按自身的连接建立频率实测。
估算口径可以这样搭:内存按"目标前端连接数 × 每会话基础开销 + 每会话语句历史上限 × 平均语句长度"粗估,CPU 按峰值每秒请求数 × 单请求解析与过滤成本反推核数。这两个式子里的系数必须用自己的流量压测标定,不同 SQL 形态差距极大,本文不给具体数值,也不建议参考任何未标注测试条件的公开数字。
表面的理由是省一台服务器。实际上两者争的是同一批资源:数据库希望把内存尽可能多地给缓冲池,代理的会话缓冲与语句历史会挤占这部分;数据库的大查询会吃满 CPU,代理的解析与路由就会排队;大结果集转发还要额外走一遍内核网络栈与内存拷贝。
更麻烦的是故障相关性。放在一起意味着"数据库所在机器挂了"等价于"代理也挂了",而代理本应承担的职责之一恰恰是在数据库切换期间维持应用侧的连接不断。把两个组件放进同一个故障域,等于把冗余做成了摆设:看起来有两层,实际只有一个故障域。
运维上也不友好。排查"到底是数据库慢还是代理慢"的时候,同机部署会让 CPU、内存、网络指标互相污染,两边的曲线交织在一起,很难分离出各自的贡献,最后只能靠重启来试探,代价很高。
唯一勉强可接受的是资源极度受限的边缘场景,比如极小的内部系统,或者在从库上跑一个只服务本机只读请求的代理。即便如此,也要给代理设置明确的资源上限,避免它把数据库挤掉。生产集群不要这么做,多出来的那台机器的成本,远小于一次说不清的故障排查成本。
这两个概念经常被混为一谈。数据库高可用指的是后端一主多从能够自动切换,主库挂了业务还能写;链路高可用指的是应用到代理、代理到数据库之间的通路不中断。很多团队做了前者没做后者,结果数据库切换成功了,应用却因为代理是单点而整体不可用,监控上显示的"数据库正常"反而掩盖了真实的故障。
单代理是最常见的起步形态:一个实例,配置简单、观测简单、路由决策只有一份不会打架。风险是它本身就是单点,机器故障、进程崩溃、一次手滑的配置推送都会导致全站不可用。能不能接受,取决于业务的可用性目标与恢复能力:如果能在几分钟内重建并恢复,且这个中断时长在可接受范围内,单代理是可以的阶段形态,不必一上来就做双活。
双代理有两种接法。一种是虚拟 IP 漂移:两个代理共享一个对外地址,靠 keepalived 这类机制在故障时把地址漂到存活的实例上。应用侧零改动,但漂移本身有检测与生效的时间窗口,而且必须处理"旧代理还活着但已经不该持有这个地址"的裂脑情形。另一种是应用层多地址:把两个代理地址都配进连接池,由驱动或池自己处理故障转移。要多改一点配置,换来的是没有漂移窗口,而且两个代理平时可以同时承担流量。
选哪种,主要看能不能改应用配置。能改,倾向于应用层多地址,因为它把故障切换的决策交给了离故障更近的一端,少了一层中间机制的判断延迟;不能改,比如遗留系统的连接串写死在某个配置文件里,或者第三方中间件不支持多地址,才用虚拟 IP。
双代理下有一件事必须提前约定:切换权限只能有一个。如果两个代理都启用自动故障转移,而它们的网络视角不同,一个连得上主库、一个连不上,就会出现两个代理做出相反决策的情况,各自把一个不同的节点提升为主。常见做法是在配置里明确指定谁负责切换,另一个只做路由,并在演练里专门验证这一条。
下面这张表按部署形态列出主要收益、主要风险、大致适用的集群规模和最该盯的观测项。规模与阈值都是估算口径,用于给出选型方向,具体数字需要以自身压测结果校准后再写进配置。
| 部署形态 | 主要收益 | 主要风险 | 适用集群规模(估算,需实测) | 关键观测项 |
|---|---|---|---|---|
| 单代理独立部署,一主一从 | 配置与观测最简单,拓扑变化对应用透明 | 代理本身是单点,从库异常时读压力全部回退主库 | 后端两到三个节点、峰值并发较低的内部系统 | 路由回退次数、代理进程存活与内存增长曲线 |
| 单代理独立部署,一主多从 | 读分流收益明显,从库选择策略可调整 | 写后立即读与事务内读的路由错误集中爆发 | 读远多于写、从库数量在三到八个之间 | 主从语句分布、复制延迟最大值、因果读等待时长 |
| 双代理 + 虚拟 IP 漂移 | 应用侧零改动,遗留系统可直接接入 | 漂移有生效窗口,需额外处理地址归属的裂脑情形 | 中等规模且应用连接串无法修改的存量系统 | 地址漂移次数与漂移耗时、漂移期间的连接错误数 |
| 双代理 + 应用层多地址 | 无漂移窗口,两个实例平时可分担流量 | 两个代理的路由与监控口径需保持一致,否则行为漂移 | 可改应用配置、对中断时长敏感的在线业务 | 两个实例的负载差值、各自到后端的连接数差值 |
| 代理与应用同机(每应用节点一个旁路实例) | 省一层网络跳转,随应用横向扩展 | 故障域与应用合并,版本与配置一致性难维护 | 应用节点数固定的小规模集群,或容器内的旁路形态 | 各实例配置版本一致性、单实例内存占用上限 |
| 多机房:每机房本地代理 + 跨机房数据库复制 | 读请求不跨机房绕行,应用访问延迟稳定 | 跨机房探测延迟被拉长,故障判定阈值需重新校准 | 两个及以上机房、有灾备或就近读需求的集群 | 跨机房探测往返时间与超时比例、误切换次数 |
分层观测是前提。要能在三段上分别拿到指标:应用侧看到的响应时间(含建连时间)、代理侧的处理时间与排队时间、数据库侧的执行时间。缺了任何一段,剩下的问题就只能靠猜。代理侧一般能输出三类统计,一是路由统计,包括发往主库和发往从库的语句数、回退数;二是连接统计,包括前端连接数、后端连接数、空闲后端连接数;三是各类错误与超时计数。
延迟分位比平均值有用得多。平均值会被大量快查询稀释,而故障切换、复制等待、锁等待这些现象恰好只出现在长尾里。建议至少看 P95 和 P99,并且把代理侧耗时与数据库侧耗时的分位分开画两条线,不要只画一条总的响应时间就下结论。
判断逻辑可以这样走。数据库侧执行时间的分位平稳、应用侧响应时间分位抬升,说明瓶颈在代理或网络;两者同步抬升,说明瓶颈在数据库。代理侧显示后端连接全部忙碌、排队等待占比高,那可能是后端连接池配小了,也可能是后端真的慢,需要再用数据库侧的活跃并发区分。后端连接很空闲但前端在排队,那是代理自身的线程配置或过滤器太重。
采样与告警的粒度也要单独设计。平均值口径的图适合看趋势,不适合定告警线,因为一次持续几秒的切换会被一分钟的平均值抹平。告警建议直接挂在分位与计数器上,比如"P99 连续若干个采样周期超过基线的一定倍数"或者"回退次数在一个周期内非零",基线由自身历史数据滚动计算,而不是拍一个固定值。这样业务量自然增长时基线会跟着走,不会因为规模变大而天天误报。
还有几个低频但致命的计数必须盯住:路由回退次数、主从切换次数、复制延迟的最大值而不是平均值、语句历史超限导致的会话失效次数、以及连接被强制关闭的次数。这些平时都是零,一旦变成非零,就是在明明白白地告诉你架构里的某个假设已经不成立了,比如"从库延迟不会超过一秒"或者"预处理语句不会超过历史上限"。
小集群,一主一从或一主两从、请求量不大:一个独立代理实例就够了,配置上要保守——路由规则简单,不做复杂的语句改写,不开全量语句日志。这个阶段的重点不是性能,而是把切换流程与观测打通,并且把"代理挂了怎么办"写成一条可以照着执行的处置步骤。很多团队在这个阶段忽略了演练,等真正出事才发现没人知道该先重启还是先切。
中等规模、以读写分离为主:代理独立部署,CPU 与内存按前面的估算口径留出余量,重点调两个东西,一个是到后端的连接总数,一个是从库选择策略。这个阶段最容易出问题的不是资源,而是因果一致性,建议先把强一致接口清单梳理出来,再决定是走事务内路由到主,还是开启基于 GTID 的因果读。资源可以后加,一致性的坑一旦被业务方踩到,信任成本很高。
多机房场景:每个机房放自己的代理,应用连本机房的代理,让跨机房的流量只发生在数据库复制链路上,而不是应用请求链路上。这里要额外考虑的是代理到主库的跨机房探测,探测往返时间被拉长之后,超时与判定次数的组合需要重新校准,照搬同机房的阈值很容易在网络波动时误判。
分层还要考虑扩容路径。代理层的扩容有两种方向,纵向是给单台代理加内存与核心,横向是再增加一个代理实例。纵向扩容对会话状态没有影响,改配置重启一次即可,适合连接数缓慢增长的场景;横向扩容会引入多个实例之间的路由与监控口径一致性问题,但能真正消除单点。判断的分界点在于:如果业务增长主要表现为连接数涨而每秒请求数稳定,纵向扩容通常够用;如果每秒请求数也在涨,且单实例的 CPU 已经接近上限,就该尽早规划横向,因为横向带来的配置一致性工作需要一个周期来磨合,不宜等到撑不住了再临时上。
再往下是资源本身的选择。一万网络深耕 IDC 行业 19 年(成立于 2007 年),在华南、华东、华北及中国香港、海外多地整合自营节点与 BGP 多线资源,提供服务器租用、机柜托管与带宽接入的一体化交付;具体机型、带宽与报价以官网实时信息或咨询确认为准。代理层这类组件对内存带宽和网卡包转发率比较敏感,选型时不要只看核心数和内存容量,还要确认网卡规格与是否支持多队列,具体以机房实际提供的机型为准。
先列确定行为,这些可以查文档确认,不依赖具体环境:MaxScale 是 MariaDB 官方的数据库代理;提供 readwritesplit 读写分离路由与 readconnroute 连接路由;通过监控模块判定主从状态,支持自动故障转移、计划内切换以及故障节点重新加入;路由、过滤器、监控、协议与认证模块都是可插拔的。这些是能力事实,不需要自己测,也不应该凭印象改。
再列必须实测的:最大可用连接数、单代理能支撑的每秒请求数、从判定故障到切换生效的总耗时、因果读带来的额外读延迟、每个会话的内存开销、开启各类过滤器后的 CPU 增幅。这些数字同时受版本、机器规格、SQL 形态、语句长度、网络质量影响,任何从别处抄来的数值都不适用于你的集群,包括本文给出的所有区间估算。
自测方法可以按这个顺序做。先搭一个与生产同规格的最小集群,一主一从加一个代理,用生产真实 SQL 的采样做回放压测;连接数从低到高逐步加压,记录响应时间分位出现拐点时的并发值,拐点之前的值就是连接池的合理上限;切换测试要在持续有写入流量的情况下做,记录应用侧观察到的错误类型和恢复时长;复制延迟测试要人为制造延迟,比如跑一个大事务或者暂停复制,观察因果读的等待时间与回退行为。
最后一步最容易被省掉:把测出来的阈值写进配置文件的注释和运维手册,并且每隔一个业务周期重新测一次。业务的 SQL 形态变了、数据量涨了一个数量级、应用实例数翻了倍,之前测的值就失效了。阈值不是一次性的配置,是一份需要跟着业务演进维护的文档。
先要确认的一件事,是数据库压力到底来自连接数,还是来自 SQL 与锁。如果是前者,代理能明显缓解;如果是后者,代理只会多加一跳延迟,这时候该做的是 SQL 与索引治理,而不是加一层中间件。判断依据是数据库侧活跃并发与慢查询的时间分布,不是 CPU 使用率的绝对值,CPU 高只是一个结果,说明不了原因。
再要确认的一件事,是应用能否接受读写分离带来的语义变化。有没有写后立即读的接口,有没有存储过程和临时表,有没有用 SELECT FOR UPDATE 做业务互斥,有没有依赖会话变量的逻辑。这些必须在上线前梳理成一份清单,逐条决定路由策略,而不是上线之后靠用户投诉一条条发现。这类清单做一次的成本不高,漏一条的代价可能是一次数据错乱。
最后要确认的一件事,是代理挂掉时业务能容忍多久。这个答案直接决定是单代理还是双代理、是虚拟 IP 还是应用层多地址,也决定要不要为代理单独预留一台备用机。可用性目标要写成一个具体的"允许中断多久、允许丢多少数据",而不是"要高可用"这种无法验证的说法,否则拓扑讨论会一直悬着,谁也说服不了谁。
在代理层这件事上,我们能交付的和不能承诺的要分开说。能交付的是资源与链路:独立物理机或云主机的计算资源、内存与网卡规格、机房与带宽接入、以及代理层与数据库层之间的网络连通性与可观测性支撑。代理软件本身的部署、参数调优、切换演练属于应用架构范畴,通常需要客户侧与我们共同完成,而不是交付即结束。
验收口径建议写成可测的条目,而不是形容词。可以在上线前约定这几项:代理进程在目标连接数下的内存占用与 CPU 占用实测曲线;给定并发下的响应时间 P95 与 P99;主从切换演练时应用侧观察到的中断时长与错误类型清单;压测期间的路由回退次数统计。这些条目由双方共同跑一遍,结果落在文档里,后续扩容时可以直接复用。需要确认的跨地域资源与网络方案,需按实际机房与运营商条件核实后再定,我们不在条件未确认的情况下给出承诺数值。
最后说明价格与信息的口径。具体机型、带宽与报价以官网实时信息或咨询确认为准;涉及跨机房、跨地域的资源组合,需询价后以实时报价为准。本文给出的所有容量与延迟数值均为估算区间的表述,用于建立选型方向,实际配置请以自身压测结果为准。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品