OceanBase 源码解读(十):一号表及其服务寻址
此前,OceanBase 源码解读第九篇《存储层代码解读之「宏块存储格式」》,为大家介绍了宏块的存储格式。本期“源码解读”由 OceanBase 内核研发工程师镇楠为大家带来“系统租户的「一号表」”的前世今生,并对一号表相关的服务寻址过程进行了说明。
OceanBase 有一套独特的元数据管理方式,它的一个设计目标是:所有的信息都保存在表里(甚至包括配置项),系统表的表模式也不能 Hardcode,且一切是自包含的,没有外部依赖。这样一个明显的好处是在大规模集群管理时极大便利运维人员,但这也使得元数据有某些循环依赖需要解开。用户表的元数据存储在系统表,系统表的元数据存储在核心表,核心表的元数据存储在系统租户的一号表。一号表就是开天辟地的那一个,THE ONE。
RootService(简称 RS)即总控服务,负责整个 OceanBase 集群的资源调度、资源分配、数据分布信息管理以及 Schema 管理等功能。而 RS 不是独立的进程,是启动在 __all_core_table 的 Leader 上的一组服务。要想学习 RS 的相关代码,就需要先弄懂 __all_core_table。
__all_core_table 是什么?
从最基础的角度看,__all_core_table 是一张 key-value 结构的表,表号为 1,是整个集群启动时生成的第一张表,它内部存储了 RS 启动需要的一些信息。由于 RS 的启动、RS 提供的服务,都需要依赖 __all_core_table,使得 __all_core_table 成为了 RS 整个服务的基础、RS 一切的起点。
__all_core_table 的 schema
__all_core_table 是 key-value 结构的,它的 schema 硬编码在 int ObInnerTableSchema::all_core_table_schema(ObTableSchema &table_schema) 中,可以在 ob_inner_table_schema_def.py 中查看其 schema 具体内容。(其中 gm_columns 为隐藏列,自动生成时间,可忽略。)
__all_core_table 的 rowkey 包含三个:table_name、row_id、column_name ,每组 key 对应一个 column_value。可以理解为将正常的二维关系表拆分成一维进行存储。
__all_core_table 的内容
(*上图分别为 GROUP BY table_name 和 table_name = '__all_table_v2' 对应内容)
__all_core_table 保存了启动 RS 所需的必要信息,其中重要的核心表包括:
__all_root_table:location cache 模块的核心表,记录了系统表和 __all_tenant_meta_table(租户级,用于记录用户表位置的一张核心表)的位置。__all_root_table 的位置信息,记录在 __all_core_table 中;其 schema 信息记录在 __all_table_v2 和__all_column 中。
__all_table_v2(__all_table 已废弃)、__all_column、__all_ddl_operation:schema 模块的核心表,由该 3 张表按层级展开,记录所有表的 schema 信息。
(*这些表的 schema 被记录在 __all_column 和 __all_table_v2 中)
位置发现服务
在对一号表进行说明之后,接下来讲解一下“位置发现服务” 。
在单机数据库中,数据库中的表都存储在本地,但在 OceanBase 这样的分布式数据库中,一张实体表可能存在多个副本,分散在集群中的多个 server 上。那么当用户想去查询一张实体表时,数据库内部如何在众多 server 上定位这张表呢?
OceanBase 依靠内部的一套系统表来实现实体表的位置发现过程,这套系统表我们一般统称为 meta table。
Meta Table 与 location 信息
涉及 location 的 meta table 主要包括 __all_core_table、__all_root_table、__all_tenant_meta_table 三张内部表。OB 集群中所有实体表的 location(位置信息),以分区副本为粒度,记录在该层级关系中。
从下往上看:
用户表的 partition 的 replica 位置信息记录在该租户的内部表 __all_tenant_meta_table 中;
每个租户的 __all_tenant_meta_table 的位置信息、系统表(包括用户租户和系统租户的)位置信息记录在 __all_root_table 中;
__all_root_table 的位置信息记录在 __all_core_table 中;
__all_core_table 的位置信息存储在 RS 所在机器的内存中。
之所以设计成层级结构,一是易于维护,二是可以存储更多的实体表信息。当集群启动、实体表的分区副本变更时,都会将 location 信息主动汇报到 meta table 中。通过维护并查询这个层级结构,数据库内部就能够确定每一个实体表的位置所在。
Meta Table 更新与查询的实现
如上图所示,meta table 的更新与查询都是通过 ObPartitionTableOperator 实现。
ObPartitionTableOperator 会根据实体表的 table_id,调用不同的 ObPartitionTable 进行处理,然后再交给 ObPartitionTableProxy 执行更新/查询的具体操作。
其中 __all_core_table 作为 1 号表,是 OB 集群自举与服务发现的关键,相关处理较为复杂,下面单独讲述。
__all_core_table 位置如何确定?
一号表 __all_core_table 也是 meta table 层级关系的起点,那么集群启动时如何确定 __all_core_table 的位置信息呢?
__all_core_table leader 位置的确定
在 RS 的 bootstrap 过程中,需要输入 rs_list,该 rs_list 中会包括每个 zone 的一个 server 地址(IP:port)。例如:
preBootStrap 过程中,__all_core_table 会在 rs_list 中的 server 上创建 partition,并选择一个合适的 server 作为 __all_core_table 的 leader。指定 leader 后会等待选举完成(循环监测 replica 的 to_leader_time),并获取 leader,之后会发送 RPC 到该 server 上 execute_bootstrap,启动 RS。
其他 server 如何寻找 __all_core_table 的 leader
每个 observer 会有一个 RsMgr,在内存中维持 master rs 的位置信息。而 __all_core_table 的副本信息都存放在 rs 的内存中。其他 server 通过 rpc 的方式,到 master_rs_ 中拿取 partition_info。若拿取失败,则会触发多一套多层的寻址过程:
核心函数:int ObRpcPartitionTable::fetch_root_partition_v2(ObPartitionInfo &partition_info)
调用 RsMgr 的接口,通过 ObInnerConfigRootAddr 从本地配置项中获取 rootservice_list,并在其中寻找 master_rs。(rs_list 变化通过心跳刷新)
通过 partition service 获取 leader 和 memberlist(leader 无效则在 memberlist 中找一遍)(observer 上有 replica 就能拿到 memberlist)
在 config 中的 all_server_list 中查找
除以上方式外,RS 还会通过心跳机制,检查 observer 的状态,若 10s 以上无心跳,则认为该 server 的租期超时,向其主动广播自己的位置(broadcast_rs_list)。
Location Cache
如果多次处理同一张表,都要去 meta table 里面找一遍位置,未免效率太低,因此我们在每个 ObServer 中都会缓存实体表的位置信息,由 location cache 模块负责管理,在 ObPartitionLocationCache 中实现,其主要缓存内容如下图所示。
location cache 采用被动刷新机制,每个 observer 会将访问过的实体表的位置信息缓存在本地,之后重复访问直接使用 cache 即可。当其他内部模块发现 cache 失效时,会调用 ObPartitionLocationCache::nonblock_renew 等接口,刷新 location cache。至此,我们便可以清楚地知道集群中每个实体表的位置信息了。
下篇“源码解读”我们会为大家带来 ObTableScan 的设计和代码的相关知识,敬请关注!
评论