对于DBA而言,确保数据库的高可用性、容灾等能力是其日常工作中需要持续思考和关注的重要事项。一方面,可以利用数据库自身所具备的功能来实现这些目标;若数据库本身不提供相应功能,DBA则需寻找其他工具来增强数据库的高可用性和容灾能力。OceanBase作为一款分布式数据库产品,天生就具备了高可用的特性;而在容灾方面,OceanBase也提供了主备租户的功能,通过物理日志的同步,确保两个租户之间的数据保持一致。在较早的版本,假如出现主备切换的情况,需要用户修改连接串去连接新的主租户,这会影响很多业务场景的体验。
因此,如何做到即使发生了主备切换,对业务也是透明无感知的,就成了一个迫切需求。而在最新的版本上,OceanBase 推出了 Service_name (服务名) 的功能,通过服务名连接 OceanBase 集群的业务租户,服务名会自动识别当前哪个租户是主,哪个是备,从而将请求自动路由到主租户上。具体原理如下。
原理介绍
OceanBase 数据库中主备租户路由的基本框架如下图。用户通过特定的连接串登录 OBProxy,OBProxy 识别主备租户登录,通过向 OCP 获取相关的租户信息,完成自动路由到主租户,并发往对应的 OBServer 节点。OBServer 节点将校验对应的租户信息,校验通过后 OBProxy 进行建连,完成整个登录流程。
注意事项:
主备租户路由功能需 OCP、OBProxy、OceanBase 数据库三者配合使用,缺一不可。使用主备租户路由功能时,要求 OCP 需为 V4.3.1 或之后版本,OBProxy 需为 V4.3.1 或之后版本,OceanBase 数据库需为 V4.2.4 或之后的系列版本。
创建主备租户
那么在介绍完原理之后,具体如何使用 service_name 来实现业务的透明无感知切换呢?下面就简单创建一个环境来做个演示,帮助大家有个更深层次的理解。
这里首先需要创建一个主备租户关系,可以在不同的集群,也可以在同一个集群,关于主备租户的搭建,具体可以参考官网文档:物理备库容灾。本次实验是分别创建两个单副本的 OceanBase 集群,然后分别再创建一个主租户和备租户,具体信息如下:
角色 | 集群名 | IP地址 | 租户名 |
集群A | obcluster | xx.xx9.12 | obtest |
集群B | obcluster_bak | xx.xx204.3 | obtest |
OBProxy | hongbo_zx | xx.xx9.12、xx.xx204.3 | -- |
首先打开OCP,在集群A上创建空的主租户,租户模式为MySQL模式,租户名为obtest,这里服务名可选,我们先空着,后续再添加服务名。
接着在集群B上创建备租户,创建备租户时,选择要同步的主集群和主租户,然后再填写租户名称为obtest,这里也可以选择不同的租户名。同样服务名这里选择后面再添加,因为主租户是空租户,因此不需要再通过备份恢复的方式来创建备租户。
创建完成之后,可以在OCP上检查下主备租户的情况,可以看到在集群B上,租户obtest为备租户。
主备租户同步测试
接着我们尝试测试下主备租户的同步情况,这里在主租户的test库下创建一张表,然后写入一条数据,看下在备租户上能否同步过去。
连接备租户,然后登录到test库下,可以看到创建的oceanbase表和数据都同步过来,说明主备租户同步是正常的。
创建OBProxy
接着,为集群A和集群B创建OBProxy,这里需要创建的OBProxy可以同时连接到集群A和集群B,因此启动方式需要为ConfigUrl模式,并选择可连接的OceanBase集群,将obcluster和obcluster_bak选中。然后填写其他OBProxy集群名、root@proxysys密码等信息。
添加服务名
OBProxy创建完成之后,接着就给租户添加服务名,在OCP上进到主租户中,在概览页面找到添加服务名进行添加。
这里添加服务名为 service_obtest,并且选择同步变更主备租户服务名,这样,在备租户上,也有了相同的服务名。
从OCP上可以看到,备租户上已经有了这个服务名。
通过服务名测试连接
登录每个集群的sys租户查询系统表,可以看到service_name和业务租户已经绑定
SELECT t.tenant_id, t.tenant_name
FROM oceanbase.CDB_OB_SERVICES AS s JOIN DBA_OB_TENANTS AS t
ON s.tenant_id = t.tenant_id WHERE s.service_name = "service_obtest";
接着,使用服务名进行数据库连接,这里的连接规则是 <用户名>@SERVICE:SERVICE_NAME, 因此根据实际情况,连接租户的用户信息部分就是 root@service:service_obtest。从下面图中可以看出,通过这个服务名连接业务租户之后,默认是连接到了集群A上的obtest租户,因为目前obtest租户的主在集群A上。
主备切换
主备切换有两种方式,一种是failover,一种是switchover。switchover一般是无损切换,即主备租户都正常情况下,切换主备角色;failover一般是当主租户出现故障,无法快速恢复数据库,需要切换到备租户上先恢复业务服务。这种情况下,是有损的,可能会丢失一部分业务数据。
日常切换
在这里两种场景都做一个演示,首先进行switchover。在OCP上,进入到主租户的概览页面,右上角"..."中选择日常切换
然后会提示具体切换信息,确认无误之后,点击开始切换即可
在任务中心会生成切换的任务,可以看到10秒左右就完成了切换,这里没有持续做压测,实际对业务的影响肯定是远小于这个时间的。
再次在OCP上检查租户状态,可以看到集群A的obtest租户,已经变成了备租户
再次通过命令行进行连接,可以看到,此时连接obtest租户,已经是在集群B上,说明切换是成功的,连接也没有问题。
故障切换
经过日常切换之后,obtest租户的主目前在集群B上,此时模拟集群故障,直接使用 kill -9 将集群B的进程 kill 掉,此时主租户将无法提供服务,需要将备租户切换为主,然后提供业务服务。
在集群A的 obtest 租户下,通过在右上角"..."选择容灾切换,会强制将备租户变更为主
此时会弹出提示框,需要输入新主租户服务名,因为要保证业务不修改连接串,所以这里服务名需保持一致,然后点容灾切换
切换会生成一个任务,查看任务可以看到,7秒左右就将备租户拉起成功
此时再用同样的服务名连接租户,可以看到,连接的集群已经自动切换到集群A,说明切换已经完成,业务可以正常访问
这里需要注意一点,原主租户因为故障无法访问,当集群和租户恢复之后,此时该租户的服务名和当前新主租户的服务名是一样的,因此最好是把这个服务名删除或者修改个别的服务名,虽然OCP会做一层保护,防止通过同样的服务名连接到这个租户,但是最好还是人为做一些保护,防止出现业务同时连两个租户,出现脑裂情况。等重新配置好主备关系之后,再将服务名修改回来即可。
OBProxy 路由
最后,再摘抄一部分官网上对于 Service Name 路由策略的介绍,让大家对整个功能有个更深入的认识:
OBProxy 支持配置 Service Name 登录后,支持主租户的自动路由能力。具体如下:
- 路由重试:当后端发生 Switchover/Failover 时,OBProxy 根据协商的错误码,主动重试可用租户列表(支持跨集群重试)。切换到主租户重试时将不会返回错误,从而实现业务无感知的路由切换。
需要注意的是,在以下类型的 SQL 请求中,OBProxy 不支持切换租户/重试请求,会返回 OceanBase 数据库的错误码:
-
- 事务中路由:OBProxy 不会切换租户,依然将请求发往事务开启的租户。
- 临时表路由:OBProxy 会发往上一个请求的节点。
- 依赖上一条 SQL 的执行结果:如
found_rows()
、row_count()
等函数,需要发往上一个请求的节点。 - LOAD FILE 导数场景:不支持切换租户。
- CURSOR/PIECES 场景:客户端使用 CURSOR/PIECES 流式获取/上传数据时,所有请求会强制路由到统一节点。
- 自动找主:发生路由重试时,表示缓存的主租户信息错误,OBProxy 将根据租户列表,主动获取对应 OBServer 节点的租户角色,找到主租户并更新缓存信息。
- 定时刷新:OBProxy 会间隔一段时间(由 config_server_refresh_interval 控制,默认 60s),更新 Service Name 相关路由信息,保证缓存信息不会落后太久。
- 高可用:当用户使用 Service Name 登录后,OBProxy 会定时将缓存信息写入磁盘,在 OCP 不可用时仍然提供自动路由能力。
官方关于Service Name的介绍可参考:主备租户自动路由