开场白
人在机场,曼谷刚落地,等待签证的闲暇时光,想起最近有人问我数据库该怎么做负载均衡?有感,特此宣泄。
三年前这么写或许还有人信、甚至有些钦佩,如今的疫情加之B乎名气,看到这种描述大家肯定会想:呸 又一个不要脸的装X逗比。事实告诉我们科技在发展、段子手也需要与时俱进,老段子必然会石沉大海~
闲言少叙,我们进入正题,今天我们来聊聊数据库读写分离架构下如何做负载均衡?同时也是数据库中间件不可缺少的功能。
1. 为什么读写分离?
这是个老生常谈的问题,相信大家都知道原因,业务访问并发量增加、数据的膨胀、用户体量增大、数据可靠性等因素导致单机无法支撑业务;在TP领域大多数场景还是写少读多,而且一写多读的架构避开了分布式事务的处理。好事多磨,相信大家都察觉到了这点,因此读写分离的主从架构也是大多数云原生数据库采用的模式。
主节点能处理读写请求,主要负责写请求,从节点只能处理读请求,当然主从数据之间的同步由数据库自身保证,传统数据库、云原生数据库等都有自己的玩法。
读写分离
2. 负载均衡
负载均衡一个不能再老的术语,好比即时通讯中的 “886”;我们常常通过节点扩容来增加服务的吞吐量、抗并发能力,通过负载均衡来协调各节点的压力、并通过不同策略保证处理请求的正确性及负载的均衡性。熟知的负载均衡软件如Nginx/Haproxy等都支持多种负载均衡算法,如轮询、Hash、source源等,这些能力在应用开发领域足够用了,那在数据库领域我们该如何做负载均衡呢?
负载均衡
2.1 非事务内查询
抛开事务数据库的负载均衡和传统应用区别不大,为了尽量使请求均匀的发往各个读节点,一般选用轮询或权重的方式,和应用开发中的流量治理类似,这种策略计算量小,基本能在请求进来后根据权重设置,实时计算得到本次请求应该落在哪个节点上,权重策略的实现可以看看kingshard的代码 kingshard-balancer,有意思的是kingshard先将节点的权重配置转化为一组数据,如A、B两个节点,A的权重是2,B的权重是1,按权重生成一个切片并打散节点排列顺序,类似[A,B,A],然后轮询这个切片获取执行的节点,从而减少计算耗时,是个不错的实现方式。当然也有其他的实现方式,对数据库而言性能是个硬指标,因此不易选择计算复杂的算法。
权重分配策略
权重轮询这种方式适合处理各查询请求计算量相差不大的场景,假设我们的数据库表有些表数据量较大,有些较小,使得查询请求处理耗时、资源消耗不均,再使用轮询的方式负载还合适吗?
显然不太合适,比如赶巧大表的查询都落到A、小表查询都落到了B,那么A节点资源消耗远大于B,好比工作中忙的忙死、闲的闲死,负载均衡也失去了意义,针对这种场景该如何处理呢?
负载失衡
显然不能再无脑的进行轮询分发,自然想到需要结合机器负载指标进行判断,比如通过node_exporter、db的exporter获取机器的cpu、mem、磁盘io、网络io等的空闲率、数据库节点的连接数等指标,并采用合适的计算公式为每个节点打分,通过分值进行负载均衡,获取指标 -> 计算 -> 节点选取 这种方式执行链路变长,如果实时进行处理势必造成数据库性能下降,因此这种方式只能采用异步准实时的计算,但异步延迟的时间需要根据场景进行合理设置,如果在并发较大的场景下,延迟较大,则会造成均衡策略计算不准确,导致负载不均。
基于指标的负载策略
有没有什么更好的方案呢?提升指标获取速度?精减指标项/简化计算公式?结合AI模型辅助决策?本人粗浅的理解,没有完美的解决方案,只有更适合某场景的方案,是个权衡的过程,负载做的越精细就需要越多的计算量,我们能做的或许是提供更多的负载策略供不同用户选择。
2.2 事务内查询
事务是TP数据库中永远避不开的话题,事务使事情变得更复杂;事务中查询请求有什么不同?该如何进行负载均衡呢?
事务内操作由于原子性、隔离级别等限制,从节点不一定能查到事务内一致性数据,即节点间数据变的不对等,因此不仅要考虑节点的负载情况还要考虑数据的可见一致性(这里指的是事务内外数据的不一致,先不考虑因主从复制导致数据的延迟,这种数据一致性属于另外一个话题,我们暂且认为主从数据是强一致的)。我们可以将事务内的查询请求简单的分为两种类型:先查后改、先改后查,如下图示:
事务内查询
对于第一种情况处理起来相对容易,虽然是在事务内操作,但因为查询时数据没有更改,各从节点数据一致,因此可以延迟事务开始的时机,如将事务开始的位置设置在insert/update语句前的位置,这样前面三条select语句也可以做负载均衡,减少主节点的处理压力。这种场景覆盖面广、比较常见,大多数应用开发也是采用先查后改逻辑,并且一些开发框架会把所有的执行语句都放在事务里,即使是简单的查询,这种方案能够很好的解决这类问题。(说这么多其实就想表明这种场景已经能解决大部分主节点因查询操作压力大的问题,并且第二种场景确实不好解决 )
先查后改
对于第二种情况就显得有些棘手,较难处理,传统数据库如MySQL采用逻辑日志复制,未提交的事务日志不会做同步,因此从节点拿不到更新后的数据,所以也不能做负载均衡,但我们可以处理一些其他场景,比如数据更新后而查询操作的是其他表数据,这时我们就可以做负载均衡。
在一些存算分离的云原生数据库中,这种场景处理有了新的方案,参考PolarDB的解法,云原生数据库采用共享存储的架构,所有节点共享一份数据,在RC级别下,增改操作时记录事务的id,随后将读请求负载到只读节点,并在链接通道内设置相同的事务id,因此能看到事务内修改的最新数据。
先改后查
3. 小结
本文我们介绍了数据库的读写分离,以及在读写分离之后如何做负载均衡,分别从非事务、事务内两种维度进行分析;至于文中提到的读写分离后如何保证查询数据的一致性,将在后续文章中单独探讨。
由于自身能力有限,文中错误不当之处望批评指正,感激不尽!




