您的当前位置:首页正文

国产数据库中读写分离实现机制

2024-11-08 来源:个人技术集锦

在数据库高可用架构下会存在1主多备的部署,备节点可以根据业务场景分发一部分流量以充分利用资源,并减轻主库的压力,因此在数据库的功能上需要读写分离来实现。

  • 充分利用备节点的资源,提升业务的吞吐量;
  • 防止运维等非业务查询访问主节点导致CPU飙升的场景,进而影响业务正常运行;
  • 在实现上考虑到主备时延过大的情况下,业务的容忍度以及规避措施;
  • 事务的处理上,更新操作在主节点而读在备节点,如何保证事务的一致性?

1、OceanBase数据库

OceanBase数据库的读写分离是由ODP(OceanBase Database Proxy)数据库代理实现的,读写分离就是数据在主节点修改后,数据同步到备节点,备节点提供数据的读取功能。OceanBase数据库支持读写分离到表的Partition级别,这也是原生的分布式数据库的优势。
在OceanBase数据库中使用读写分离功能,需要经过两部分设置:

1.1 弱读配置

1)在SQL语句中通过Hint

/*+READ_CONSISTENCY(WEAK)*/配置
select /*+READ_CONSISTENCY(WEAK)*/ * from t1;

2)通过OBProxy配置项obproxy_read_consistency设置

alter proxyconfig set obproxy_read_consistency = 1;

配置项默认值0表示强读、1表示弱读。SQL hint是语句级别的,配置项是实例级别的。通常可以配置多个OBProxy实例,选择其中部分OBProxy开启读配置作为运维查询使用。

1.2 修改路由策略

ODP通过配置项proxy_route_policy修改路由策略,通过设置为follower_first和follower_only可以让弱读请求优先发给备副本:

  • follower_first:优先选择备副本,如果备副本都不可用,选择主副本;
  • follower_only:选择备副本,如果备副本都不可用,断开和客户端的连接。

上述两个策略需要根据业务场景选择

2、PolarDB数据库

PolarDB MySQL版本的集群自带读写分离功能,在PolarDB控制台的数据库连接中配置读写模式,包括只读模式和可读可写模式(自动读写分离)。当配置可读可写模式后,写请求会自动转发到主节点,读请求会自动根据各节点的负载(当前未完成的请求数)转发到主节点或只读节点。

同时读写分离模块自动对集群内的所有节点进行健康检查,当发现某个节点宕机或者延迟超过阈值(全局一致性读超时时间,取值范围:0~60000,默认为20ms)后,PolarDB将不再分配读请求给该节点,读写请求在剩余的健康节点间进行分配,以此确保单个只读节点发生故障时,不会影响应用的正常访问。当节点被修复后,PolarDB会自动将该节点纳回请求分配体系内。

2.1 请求转发逻辑

在配置了可读可写模式下,SQL语句的转发遵循以下规则:

  • 只发往主节点:DML和DDL操作、未开启事务拆分时所有事务请求、自定义函数、存储过程等;
  • 发往只读节点或主节点:非事务中的读请求、COM_STMT_EXECUTE命令等
  • 发往所有节点:所有系统变量的修改、COM_STMT_EXECUTE命令等

2.2 基于权重的动态负载均衡

PolarDB在可读可写(自动读写分离)模式下仅支持基于活跃请求数负载均衡的策略,优先选择最小活跃(并发)请求数的节点去路由请求,来保证多个只读节点间的负载均衡。该策略基本可以保证流量根据后端节点的负载均衡的路由到不同的后端节点上,即使后端节点的规格不一致,也能较好的进行负载均衡。

为了满足线上业务负载的多样性,PolarDB后续版本引入了基于权重的动态负载均衡策略,为每个节点配置不同的权重后,在后续的路由过程中,权重和并发请求数会同时作为参考标准去动态的调整最终的路由决策。在控制台“数据库代理服务配置”中,可以为不同的节点配置不同的权重,动态权重越大,节点的优先级越高,路由分发的流量也越大。

3、GaussDB数据库(for MySQL)

在GaussDB数据库控制台的数据库代理页面,新增数据库代理,包括代理模式、一致性级别、路由模式和添加的数据库节点。

  • 代理模式:支持读写模式和只读模式,只读模式下仅支持读请求业务,并且不支持DDL、DML操作和临时表操作;
  • 一致性级别:分为最终一致性和会话一致性,由于主备之间存在复制延迟,可能会导致每次读结果存在差异,默认情况只能保证最终一致性。当设置会话一致性后,保证在同一个会话内,保证每次读请求都是上一次更新操作后的结果。
  • 路由模式:包括权重负载和负载均衡,权重负载是根据设置的读权重比较分发请求,负载均衡是根据数据库节点的活跃连接数情况进行读请求分发。

另外,在SQL中通过hint方式指定发往主节点或只读节点。需要注意的是Hint注释仅作为路由建议,非只读SQL、事务中的场景不能强制路由到只读节点。

  • /FORCE_MASTER/强制路由到主节点;
  • /FORCE_SLAVE/强制路由到只读节点;
4、GoldenDB数据库

GoldenDB数据库的读写分离策略由连接实例级别的策略+语句级别的策略共同决定:

  • 在连接实例的服务端口进行配置,包括不开启读写分离、本地同城策略以及异地策略,其中本地同城策略需要配置权重;
  • SQL语句级别的读写分离策略,通过在SQL语句中增加hint实现,hint包括READMASTER、READBALANCE和READSLAVE

不同策略的组合如表所示:

另外,为避免只读数据节点读取的数据长时间和主数据节点不一致,当一个只读数据节点的延迟时间超过设置的延迟阈值,则不论该只读数据节点的读权重是多少,读请求都不会转发至该只读数据节点。在新版本的读写分离配置中,增加了“是否可读主”的配置,选择否后,当主备时延超过阈值时,依旧不会读主节点。

需要注意的是在多分片的实例中,当CN节点配置参数force_read_split后,开启读写分离时,显示的开启事务后,为了保证事务操作的一致性,事务内是不允许更新操作的,只允许只读事务,否则会提示“read-only transaction”的报错。

5、TDSQL数据库(for MySQL)

TDSQL数据库默认支持读写分离功能,包括3种读写分离的配置操作,其中非只读账号由参数gateway.mode.single_backend.rw_split控制:

  • 创建只读账号:创建账号时标记为只读,系统将根据只读策略将读请求发往备机,只读策略的配置分为以下场景:
    • 1:优先选择备机进行读操作,如果备机的延迟都大于设置的延迟,则从主机读取。
    • 2:只选择备机进行读操作,如果所有的备机都大于设置的延迟,则直接报错。
    • 3:只选择备机进行读取,忽略延迟参数,一般用于拉取binlog同步。
  • slave注释:参数rw_split=1,在SQL中增加slave模式的hint标签,将指定的SQL发往备机。比如在SQL中添加/slave/标记,该SQL会发送给备机。注意在mysql客户端使用时添加-c参数来解析注释,/slave/必须为小写,并且前后无空格。
//主机读//
select * from emp order by sal,deptno desc;
//从机读//
/*slave*/ select * from emp order by sal,deptno desc;
  • 全局自动读写分离:参数rw_split=2,配置该参数后会将读请求自动发送所有的备机。如果备机延迟较大,读到的数据可能会有延迟。如果是在事务中的读请求或者autocommit=0, begin/commit/rollback …等情况下,select也会发到主节点。

官方建议使用第1种和第2种方式配置读写分离,第三种配置方式会有一定程度的性能损耗。

6、OpenGauss数据库
  • Master:尝试连接到URL连接串中的主节点,如果找不到就抛出异常;
  • Slave:尝试连接到URL连接串中的备节点,如果找不到就抛出异常;
  • preferSlave:尝试连接到URL连接串中的备数据节点(如果有可用的话),否则连接到主数据节点;
  • any:尝试连接URL连接串中的任何一个数据节点,默认为“any”
##优先连接到备节点
jdbc:opengauss://node1,node2,node3/database?autoBalance=roundrobin&targetServerType=preferSlave
##只连接备机进行只读操作
jdbc:opengauss://node1,node2,node3/database?autoBalance=roundrobin&targetServerType=slave
##只连接主机进行读写操作
jdbc:opengauss://node1,node2,node3/database?autoBalance=false&targetServerType=master

OpenGauss数据库在一主多备的架构下,当主节点宕机的时候通过自主寻主的机制,找到主备切换后的新主。通过JDBC连接中的配置参数hostRecheckSecond,当主机状态发生更改时再次检查主机状态,快速地找到新的主机并恢复连接,默认值为10秒。JDBC连接流程如下:

  1. 设置参数开启targetServerType=xx。
  2. 判断输入的主机列表是否已经加入了候选主机列表中,如果不存在hostStatusMap中并且更新时间间隔超过10s(hostRecheckSecond)同时HostStatus状态也和现在一致,这时直接加入候选主机列表。
  3. 对列表进行遍历查找对应的节点信息,如果找到了,通过knownStatus查看状态是否一致,一致就执行sql查询语句,之后更新全局变量hostStatusMap及局部变量konwnStatus中存储。
  4. 当前操作的节点状态如果与用户选择的节点状态一致则返回主机连接,之后用户可以根据连接进行读或者写操作。
7、TiDB数据库

在TiDB数据库中读写分离通过follower read机制实现的,follower read是将TiKV读负载从Region的leader副本上offload到follower副本的负载均衡机制,以提升TiDB集群的吞吐能力并降低leader负载。TiDB的Follower Read功能开启通过变量tidb_replica_read配置:

set [session | global] tidb_replica_read = '<目标值>';
  • leader:默认值,将所有的读操作都发送给leader副本处理。
  • follower :选择Region的follower副本完成所有的数据读操作。
  • leader-and-follower:可以选择任意副本来执行读操作,此时读请求会在leader和follower之间负载均衡。
  • prefer-leader:优先选择leader副本执行读操作。当leader副本的处理速度明显变慢时,例如由于磁盘或网络性能抖动,TiDB将选择其他可用的follower副本来执行读操作。
  • closest-replicas:优先选择分布在同一可用区的副本执行读操作,对应的副本可以是leader或follower。如果同一可用区内没有副本分布,则会从leader执行读操作。
  • closest-adaptive:当一个读请求的预估返回结果大于或等于变量 tidb_adaptive_closest_read_threshold 的值时,TiDB会优先选择分布在同一可用区的副本执行读操作。
  • learner:选择learner副本执行读取操作。在读取时,如果当前Region没有learner副本,TiDB会报错。

Tidb数据库的Follower Read在实现机制上是follower节点使用Raft ReadIndex协议确保当前读请求可以读到当前leader上已经commit的最新数据。因此在TiDB层面,Follower Read只需根据负载均衡策略将某个Region的读取请求发送到follower节点。

8、总结

以上是几种国产数据库的读写分离实现,各个数据库厂商根据不同的业务场景在不同程度上实现了读写分离的功能,对比如下所示:

总体上而言,PolarDB、GaussDB和GoldenDB在读写分离的功能上更为完善,考虑到实例级别、SQL级别以及对事务的影响,还有在主备时延超过阈值时的处理。


  1. https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000819334
  2. https://help.aliyun.com/zh/polardb/polardb-for-mysql/user-guide/read-or-write-splitting-2
  3. https://support.huaweicloud.com/usermanual-gaussdbformysql/gaussdbformysql_11_0017.html
  4. https://www.goldendb.com/#/docsIndex/docs/instruction_TenantManagement
  5. https://cloud.tencent.com/privatecloud/document/123248530626207744/12325495623387545
  6. https://zhuanlan.zhihu.com/p/445545196
  7. https://docs.pingcap.com/zh/tidb/stable/follower-read#follower-read
Top