许金柱,携程资深DBA,专注于分布式数据库研究及运维。
台枫,携程高级DBA,主要负责MySQL和OceanBase的运维。
读写分离,是一种将数据库的查询操作和写入操作分离的方案,目的是为了降低读写操作的相互影响并提升资源利用率。在携程,读写分离的应用场景非常普及,只读的业务场景主要包括线上业务的读请求、缓存的拉取,大数据ETL取数等。
OceanBase 数据库天然支持读写分离的功能,即通过 OBProxy 代理服务和OBServer的配置修改即可实现业务的读写分离策略;然而OceanBase 读写分离方案存在与业务耦合度过高、匹配不够灵活等问题。因此,我们在 OceanBase已有的读写分离方案下继续探索并优化更符合当前业务场景的解决方案。
OceanBase数据库在读取数据时,提供了两种一致性级别:强一致性和弱一致性。
强一致性指的是读取最新数据,请求路由给主副本;
弱一致性不要求读取最新数据,请求优先路由给备副本。
基于此,通过应用侧为执行的sql添加sql hint来显性开启弱一致性读就可以实现基于注释的读写分离,同时也衍生出了如下三种常用的读写分离策略:
通过修改obproxy的路由策略为follower_first,并将业务读流量指定到该obproxy从而保证读请求优先访问follower副本。
优点: 配置相对简单,只修改obproxy的配置,无需修改observer的配置;读流量均摊到全部follower副本上。
缺点: 读请求可能存在跨zone访问;在不调整副本leader的情况下,同zone或同server下不同副本的leader和follower可能共存,不能完全实现zone级别或者server级别的读写隔离。
高可用: 当两个follower副本中的一个宕机时,配置读写分离的obproxy将优先路由到另一个follower副本。
新增一个只读副本,为其添加idc标签,并将obproxy的参数proxy_idc_name指向该idc。指定读流量访问该obproxy,通过LDC配置仅访问只读副本。
优点: 读写完全隔离,读扩展性好。
缺点: 只读型副本只同步数据,不参与分布式选举,需要额外增加一个副本。对读请求不大的场景存在资源浪费。
高可用: 当只读副本宕机时,配置读写分离的obproxy将自动把流量切到其他follower副本上。
适用场景: 业务的读请求远大于写请求,且大部分读请求对实时性要求不高或者有大量的AP分析场景。
将一个全功能型副本添加idc标签,驱逐该副本上的leader,并将obproxy的参数proxy_idc_name指向该idc。指定读流量访问该obproxy,通过LDC配置仅访问该副本,完全分离读写流量
优点: 读写完全隔离,且相对于策略二不需要额外资源
缺点: leader分布集中在两个zone中,有可能会降低写入性能
高可用性: 当指定idc侧的follower副本宕机时,配置读写分离的obproxy将自动路由到其他follower副本上。
适用场景: 读写比较均衡,存在少量AP场景
在满足读写分离需求的同时,我们也关心在使用这些策略时的高可用性:
当只读节点宕机时,配置读写分离的OBproxy将自动修改路由,优先将读流量路由到其他follower副本上。在使用策略二或者策略三时,每一个partition只有单个的readonly或者follower副本承担读压力,因此切换到新的follower副本上的压力是可以负担的。
当配置读写分离的OBproxy出现问题时,由于obproxy是一组无状态的服务,因此只需要依赖数据库访问中间件的判活来修改应用访问obproxy的入口即可实现高可用。
上面三种策略,在不同场景下各有优劣,也都能达成读写分离的效果,但相较于前两种策略,我们更倾向于使用策略三。携程的业务场景中,大部分数据库的读写相对均衡,同时包含少量的etl取数、BI查询等场景,与策略三的适用场景更贴合,且成本相对较低。
同时,我们也关注到oceanbase现有的读写分离策略中存在一个共性问题:应用侧必须为sql添加弱一致性读的sql hint,否则即使在obproxy做了相应配置,仍然不能达到读写分离的效果,所有读写压力将集中在leader副本上。这涉及到用户的代码改造且使读写分离策略与应用的耦合度高。因此,我们需要在已有方案的基础上,根据当前业务场景探索并实践出一个灵活的、与应用耦合度低的读写分离策略。
携程目前已接入OceanBase近百套集群,租户约140个,同时越来越多基于MySQL数据库的业务计划迁移至OceanBase 数据库。对于读写分离场景,迁移OceanBase成本较大。
针对需要保证OceanBase数据库适配当前读写分离场景的需求,我们首先梳理了携程业务目前读写分离的主要适用场景:
- ETL取数:Zeus定时作业,需要将线上订单相关数据同步至Hive或ES
- BI查询 Offline报表,为业务部门提供分析数据
- MySQL业务中已存在的读写分离场景(如缓存拉取): 读请求远大于写请求的产品库场景,要求在延迟阈值内,将读请求路由到只读节点
我们看到携程现有的读写分离场景包含OLTP和OLAP,并且db涉及的业务线可能比较复杂很难简单分辨。
同时,读写分离改造涉及到增加sql hint /*+READ_CONSISTENCY(WEAK) */ 的问题 。sql hint在传统的关系型数据库中是一种通过注释的方式改写sql执行计划的设计,而在OceanBase中它同样有类似功能,并且提供了sql选择副本访问策略的能力。为了使弱一致性读生效,需要对有读写分离需求应用中的sql做如下改造:
- Mysql原版sql
SELECT * FROM test
- Oceanbase改造读写分离的sql
SELECT /*+READ_CONSISTENCY(WEAK) */ * FROM test
综上,在携程的业务上线或业务迁移过程中,容易遇到以下问题:
- 现有业务迁移过程不透明,涉及业务层面的代码改造。
- 代码和读写分离策略耦合,造成技术债。
- DBA需要大量重新审核查询语句和业务行为,给出改造评估建议。
针对这些痛点,我们希望可以不使用sql hint,直接指定这些sql进行弱一致性读来达到读写分离的目的。我们开始调研能否在OceanBase代码层寻求解决方案。通过对源码研究,我们尝试在OBProxy组件上进行改造,最终实现不涉及用户代码改造前提下的自动开启弱一致性读功能。代码的改造包含以下两个方面:
- 功能1、自动为访问本OBProxy的所有连接开启弱一致性读策略
- 功能2、自动为访问本OBProxy的指定帐号(只读账号)开启弱一致性读策略
我们通过新增两个参数enable_weak_read和weak_read_user_list在建连时进行判断来分别实现上述两个功能。enable_weak_read参数开启时,将为访问该OBProxy的所有连接开启弱一致性读;weak_read_user_list参数则针对账号维度来指定部分账号进行弱一致性读。
以上弱一致性读策略均基于会话级别开启,通过配置LDC和read(slave)域名,进行读、写隔离,分担主库压力,自动实现读写分离策略,不需要人工干预。
对于obproxy进行改造后,为了验证其功能性,我们将其与LDC(Logical Data Center)全功能型副本读写分离策略相结合,进行了如下实验。
场景一:leader副本集中在zone1,obproxy指定账号开启弱一致性读并且绑定idc2(zone2+zone3)
实验结果:zone1的流量消失,流量被zone2和zone3分摊
Leader副本集中在zone1和zone3,obproxy开启对所有连接开启弱一致性读,并绑定idc2(zone2)
实验结果:zone1和zone3的流量消失,流量全部由zone2承担
在携程现有的业务读写分离场景中,我们往往通过区分业务的账号来识别业务的行为,因此我们将obproxy上设置针对部分账号开启弱一致性读并将其中一个zone和proxy绑定来将这部分请求路由到指定zone上实现读写分离。
优点:
1、同一个集群的obproxy配置统一(不开启弱一致性读时LDC路由不生效),便于obproxy的横向扩展和快速切换应用流量的指向。
2、按照账号维度配置灵活度高,在部分极端场景方便人工介入。
我们根据现有oceanbase的读写分离方案通过优化obproxy源码,新增enable_weak_read和weak_read_user_list两个功能,在obproxy上直接实现读写流量的分离,解决了mysql迁移oceanbase过程不透明、用户代码与原读写分离方案耦合度高等痛点。为推动分布式数据库oceanbase适配携程当前业务场景,更好地在携程落地,走出扎实的一步。