OceanBase 高可用技术介绍

2022-02-22 00:00:00 执行 线程 请求 租户 副本
作者简介:沈炼,蚂蚁集团技术风险部数据库专家
毕业于东南大学,2014年以来从事 OceanBase 在蚂蚁的架构工作,目前职责包括蚂蚁 OceanBase 高可用体系建设 和 OceanBaseKV 在蚂蚁的架构及研发工作,对标业界的“自治数据库”和“多模型数据库”,致力于让 OceanBase 走得更稳、更远、更快。在蚂蚁OceanBase 体系中,沈炼先后负责蚂蚁核心链路上OceanBase 、 OceanBase 高可用体系建设、 OceanBaseKV 架构及研发。沈炼对互联网金融场景、数据库内核(关系型及 NoSQL )、数据库 PAAS 、高可用体系、安全体系 等方面有深入的理解,将会推出系列文章介绍 OceanBase 的存储引擎、高可用内核、高可用体系及理念、 OceanBase 多模型数据库等。

引言

本文将为大家介绍 OceanBase 设计中的高可用技术,回答关于 OceanBase 数据库的以下相关提问:

  • 分布式系统不怕通断性故障(例如宕机),但怕非通断性故障(例如网卡丢包、IO hang ),OceanBase 如何解决 ?
  • 分布式系统往往都有一个“胖客户端”解决路由问题, OceanBase 客户端如何解决各种场景下的路由和容灾问题 ?
  • 分布式系统各节点之间存在大量的 RPC 交互,OceanBase 如何维护 blacklist ?
  • 多租户系统存在业务高峰期资源互相抢占的痛点,OceanBase 如何资源隔离以避免租户之间互相影响 ?
  • 分布式系统元数据管理有 Master 结构和 P2P 结构, OceanBase 如何管理元数据及保证高可用 ?
  • 数据库故障中大部分都是由 SQL 引起,OceanBase 如何支持 SQL调优 ?
  • OceanBase 如何防范程序问题或者硬件问题导致的数据错误?

背景

物理学解决两个难题,一是世界由什么物质构成,二是这些物质如何耦合在一起,也就是四大作用力。

OceanBase 是一款分布式数据库系统,那么 OceanBase 要解决什么难题呢?一是扩展性问题,二是高可用问题。

分布式存储系统的设计有一套共性:通过数据分区以支持水平扩展,但会引入跨分区协调问题,通过 2PC 解决;通过多副本复制机制以支持高可用,但会引入数据一致性问题,通过分布式共识协议 Paxos 解决;集群拓扑架构、数据分区和多副本又引入海量元数据管理和客户端路由的问题;分布式架构又引入了成员变更、动态负载均衡、资源隔离、爆炸半径 等问题。本文将介绍 OceanBase 在分布式存储系统共性问题上的实现,及其高可用设计。

多副本架构

传统数据库为了达到高可用,有主备架构和共享存储架构两种模式。主备架构有个强一致和高可用的权衡,例如 Oracle 的 DataGuard 可以选择大保护模式,当备库不可用的时候,主库也不可用,可用性降低;如果选择大可用模式,主库异常切备库的时候可能丢数据,也不能接受。这是一个典型的 CAP 权衡。

共享存储架构例如 Oracle 的 RAC ,业务将事务提交给某个实例,该实例将数据及事务日志写到共享存储中,当某个实例出现故障的时候,由于该实例没有状态,可以很快的由其他实例接管业务流量。该方案对共享存储存在依赖而成本高昂,及无法跨机房部署。也需要在强一致和高可用之间做出权衡。

OceanBase 基于高性价比的普通服务器采用 Paxos 协议,同一份数据写到多台(>=3)服务器的半数以上,因此当少数派数据发生故障时不会有任何数据丢失,保证 RPO=0 ;主库发生故障后,剩余的服务器能够在短时间内自动选出新主继续提供服务,无需外部工具及人工介入,保证 RTO<30 秒,从而达到强一致和高可用的平衡。可以采用灵活的部署模式,例如 同城三副本、两地三中心、三地五中心等,从而达成各级无损容灾能力,例如同城容灾和异地容灾。

分布式系统怕的是非通断性故障,例如硬件故障导致网卡丢包,发生无主选举后 Leader 切到正常机器,后续由于 Leader 优先级配置 RootService 又将 Leader 切回异常机器,导致选举的“乒乓效应”,从而业务受损。

OceanBase 引入了选举黑名单来解决该问题,在 Leader 发生无主选举后,一段时间内该副本不会再被选为 Leader (该时间由全局参数控制,可配置)。

对于多地域部署的集群, Leader 会优先切到同地域的其他副本,避免 Leader 切到异地。如果同地域的所有副本都在选举黑名单中,会忽略此优先级。对于进入选举黑名单的机器,需要外部系统或者 DBA 尽快物理隔离及机器替换,避免黑名单到期后影响业务。

如果某台服务器不稳定,可能时不时提供服务,可能存在响应慢等各种不正常情况。如果客户端连接到这台服务器上,将导致客户端感受到不稳定。OceanBase 提供了 stop server 命令用于隔离该服务器,隔离后该机器不会对外提供服务,客户端也不会把请求路由到该机器上,可以安全的进行诊断/替换/维修等动作,是运维和应急的利器。

stop server 会检查所有剩余副本是否满足多数派、是否日志同步,条件不满足的时候 stop server 会返回失败,条件满足的情况下等待故障机器上的 Leader 全部切走后返回用户成功。OceanBase 还提供了 isolate server 命令用于隔离服务器,相比于 stop server , isolate server 更加轻量,仅将故障机器上的 Leader 全部切走后返回用户,避免某些极端场景下 stop server 无法成功的问题。

stop server 成功后保证能够做任何事情,包括 kill OceanBase 进程,而 isolate server 则无此保证。

对于硬件问题引入的数据落盘过程中“静默错误”带来的数据坏块问题, OceanBase 引入了校验和校验机制。 OceanBase 的数据落盘分为 RedoLog 落盘和 SSTable 落盘。在每条 RedoLog 的头部会记录这条 RedoLog 的校验和,在网络传输和日志回放时,强制对每条日志的校验和进行校验,保证三副本同步到的 RedoLog 及回放到内存的数据一定不会出错。 SSTable 有很多宏块组成,宏块定长,大小是 2M ;宏块又由很多微快组成,微快变长,默认大小大约是16KB。宏块是小的写 IO 单位,微快是小的读 IO 单位。

在每个微快头部会记录整个微快的校验和,在每次数据读取时会对微快进行校验和校验。在每个宏块头部会记录整个宏块的校验和,在数据复制迁移时会对每个宏块进行校验和校验,保证静默错误不会扩散。在蚂蚁环境已经多次预防过磁盘固件 bug 引入的数据坏块。

对于合并过程中由于程序 bug 导致的数据错误问题, OceanBase 同样引入了校验和校验机制。行检验和表示 SSTable 行数据累计的校验和,列校验和表示 SSTable 中列数据的校验和。合并过程中会校验索引列的列校验和与主表中相应列的列校验和是否一致,保证主表与索引表的数据一致性;会校验每个分区的多个副本的行校验和 和 列校验和是否一致,保证多副本的数据一致性。合并过程中发现校验和错误会立即暂停合并,并置位合并错误位,从而人力介入,由于上个版本的 SSTable 依然存在,不会对业务产生影响。

数据链路



我们看下 OceanBase 的数据链路, OceanBase 采用 APP <-> OBProxy <-> OBServer 的数据链路。 APP 通过任意 MySQL 驱动连接 OBProxy 发送请求,由于 OceanBase 是分布式数据库,用户数据以分区多副本的方式放在多个 OBServer 上, OBProxy 将用户请求转发到合适的 OBServer 去执行,并将执行结果返回用户。

每个 OBServer 也有路由转发的功能,如果发现请求不能在当前机器执行,则会转发请求到正确的 OBServer ,该场景需要进行一次远程执行,对请求的RT会有一定影响。

OBProxy 是 OceanBase 的反向代理服务器,提供从用户端到数据库端的路由选择和容灾功能,屏蔽用户对分布式数据库的感知。OBServer 包含完整的 SQL 引擎、存储引擎和分布式事务引擎,负责解析 SQL 生成物理执行计划并执行,及事务提交时通过 Paxos 协议达成多数派提交,提供高性能、高可用、高扩展的数据库服务。

OBProxy 和 OceanBase 采用松耦合的方式,允许 OBProxy 上的路由信息更新不及时导致错误的路由选择,OceanBase 内部会再一次转发,返回结果时通知 OBProxy 更新路由信息。

路由选择

路由选择是 OBProxy 的核心功能。 OBProxy 自己实现了一个简单的 SQL Parser 模块,解析出 SQL 中的库名、表名 及 hint ,从而根据用户输入的 SQL 、路由规则、及 OBServer 的状态,选择合适的一个 OBServer 转发请求。

强一致读及 DML 期望发到副本的 Leader 所在的 OBServer 上;弱一致读发送到 Leader 和 Follower 均可;如果 OceanBase 集群是多地部署,OBProxy 还提供了 LDC 路由,优先发给同机房的 OBServer ,其次是同城的OBServer 、后才是其他城市的 OBServer ;如果 OceanBase 集群是读写分离部署, OBProxy 还提供读 zone 优先、只限读zone 、非合并优先等规则供业务按照自身特点配置,还支持弱读防时序回退功能。

对于路由选择重要的是获取路由信息。 OBProxy 缓存路由信息用于根据用户 SQL 获取该 SQL 涉及的副本位置。请求时同步获取路由信息,后续采用被动触发更新机制,被动刷新机制通过业务 SQL 触发,异步刷新,不增加业务 SQL 耗时。以下几类场景触发更新:

  • 期望与实际不符合:路由选出一个副本,期望是主,但是实际上该副本是备;或者期望是备,但是实际上该副本是主
  • 集群机器发生变化:路由信息机器 与 集群机器不是子集关系;机器上下线
  • 只读副本变化和副本迁移:通过内部表感知租户副本变迁(例如上线只读副本、Follower 副本迁移),从而失效该租户下所有路由信息
  • OBserver 返回特定报错: 例如 “ tenant not in this server ”等

每个 OBserver 中都会缓存表的路由信息,由 location cache 模块负责管理。 location cache 模块是 OBServer 上的基础模块,为其余模块(存储、事务、 SQL 、 OBProxy )提供获取及缓存某个 Partition 位置信息及副本级元信息的能力,本质上是维护 serverlist 和 blacklist 。同一个 OBServer 各模块共享同一个 location cache 。

location cache 依赖各模块调用被动刷新及缓存 cache 。 location cache 刷新有 RPC 刷新和 SQL 刷新两种方式,RPC 刷新消耗很小,能快速感知副本的 Leader 、 Paxos 成员列表、直接级联在本 Region Paxos 成员下只读副本的位置信息,但无法感知到二级及以上级联的只读副本的变更,也无法感知级联在其他 Region 下的只读副本变更; SQL 刷新能保证刷到所有副本的位置信息,但是 SQL 刷新消耗大,并依赖 Meta 表(见下文介绍)、 SQL 引擎、底层汇报等逻辑,在网络异常的情况下,会导致 location cache 一直无法刷新,所以优先采用 RPC 刷新,RPC 刷新成功后会周期性触发强制 SQL 刷新,解决只读副本位置不准的问题。 location cache 还支持批量刷新,优化RTO场景 location cache 刷新速度,对日常 SQL 执行也有优化效果。

OBProxy 可以向任意 OBServer 请求 location cache ,优先发送到同 IDC 的所有 OBServer ,从而打散流量,避免APP 重启后业务流量突发进来后同步刷新 location cache 打爆 OBServer。

容灾

OBProxy 需要能够自适应处理 OBServer 错峰合并、升级、 leader 切换、宕机、启动/停止等过程中 OBServer 访问控制,避免业务流量被硬件异常和内部流量影响, OBProxy 对每个集群维护着黑名单,黑名单分为宕机黑名单和活着不可用黑名单。

宕机黑名单采取周期刷新维护,由 OBServer 反馈哪些节点死不可用,黑名单中的 OBServer 将被过滤不访问,直到某次刷新后被洗白才恢复访问。

活着不可用黑名单采取主动触发维护,当 OBProxy 转发请求给 OBServer ,如果发现 OBServer 返回特定的系统错误,或者 OBServer在 N 秒内有 M 次连续不可用,则将该 OBServer 加入黑名单;如果 zone 正在做合并/升级, 则将该 zone 的所有 OBServer 都加入黑名单,直到 zone 合并完成, zone 状态更新后,将该 zone 的OBServer 的状态洗白,可以重新被访问。活着不可用黑名单中的 OBServer 每隔一段时间会重试一次,检查是否需要洗白,以避免长时间将 OBServer 拉黑。

黑名单判断优先级:宕机黑名单 > zone 合并/升级 > server 合并/升级 > 活着不可用黑名单。当所有可选择的 OBServer 都处于黑名单中时,则强制重试黑名单中的 OBServer 。

RootService

RootService(简称RS) 即总控服务, RS 不是独立的进程,是启动于 __all_core_table 的 Leader 所在的 OBServer 上的一组服务,当此 Leader 变为 Follower 时,会停止上面的 RS 模块,保证全局只有一个 RS 在工作。 __all_core_table 是整个集群启动时生成的张表,存储了 RS 启动需要的一些信息,通过 __all_core_table 可以逐级索引到所有其他分区。 RS 以系统租户作为载体,系统租户包含全部系统表及各种后台服务。 RS 承担元数据管理、集群资源管理、合并管理、运维命令入口等功能。

RS 管理 OBServer 元数据,通过心跳机制监控集群中各个 OBServer 的存活状态,并同步更新系统表,以及进行异常处理。同时也通过心跳向各个 OBServer 传输配置变更、 schema 变更等多种信息。

RS管理分区元数据,所有数据放于系统表中,这套系统表我们统称为 Meta 表。 __all_root_table 存放所有系统表的分区信息, __all_tenant_meta_table 存放所有用户表的分区信息。

RS 通过 RPC 主动获取及定时任务维护元数据的准确性,通过 location cache 模块对内部其他模块及外部 OBProxy 提供位置信息及副本级元信息的查询服务, location cache 模块缓存 Meta 表的内容,并采用被动刷新机制,后续也将支持主动刷新机制,副本信息发生变化时主动广播到各 cache 。

为了避免队列线程池模型造成多 server 间取 location 循环依赖问题,为每一级系统表使用单独队列,例如 __all_core_table 、 __all_root_table 、系统表、用户表 分别有各自独立队列及工作线程。




RS 管理集群资源,包括 Leader 管理、分区负载均衡、unit 负载均衡等。 Leader 管理包括将分区组中所有分区的Leader 切到一起、将 Leader 切到 parimary zone 、轮转合并及隔离切主等场景。分区负载均衡是指在租户的多个 unit 内调整分区组的分布使得 unit 的负载比较均衡。 unit 负载均衡是指在多个 OBServer 间调度 unit 使得OBServer 的负载比较均衡。

RS 管理合并功能,不同于小版本冻结(转储)由各个 OBServer 自行处理,大版本冻结(合并)由 RS 协调发起,是一个由 RS 和所有分区 Leader 组成的两阶段分布式事务。某个分区无主会导致大版本冻结失败。合并可以由业务写入(转储达到一定的次数,由全局参数控制)触发,也可以定时触发(例如每日合并,一般设置于业务低峰期)及手动触发。RS 还可以控制轮转合并,从而减少合并对业务的影响。

RS是管理命令执行的入口,包括 bootstrap 、 DDL 、 alter system 等命令。 bootstrap 是系统的自举过程,主要用于创建系统表,初始化系统配置等。DDL 是指创建表、创建索引、删除表等动作,DDL 不会被优化器处理,而是作为命令直接发送到 RS,DDL 产生的 schema 变更保存于系统表并更新到内存,然后产生新的版本号通知所有在线的 OBServer ,OBServer 再刷新 schema 。

alter system 是指常见的运维命令,包括创建租户、调整租户资源、调整部署、切主、 stop/isolate server 、unit 迁移、绑定执行计划、限流 等运维动作和应急动作。

虽然有多副本机制保证 RS 可以容忍少数派节点故障,但 RS 依然是 OceanBase 的单点,由于 bug 导致 RS 异常后将会影响 OceanBase 的服务可用性,会导致客户端无法建立连接、合并异常、无法应急等严重影响,RS 的稳定性对 OceanBase 集群的稳定性至关重要( RS 本质上是 OceanBase 的 MetaServer , MetaServer 是所有分布式存储的阿喀琉斯之踵,往往以一己之力瘫痪整个集群,需要降低读写路径依赖及减少爆炸半径),为此我们采取多项措施来保证 RS 的健壮性。

OceanBase 支持了一套检测机制,通过该机制发现 RS 的异常,并通过运维命令强制切换 RS 来恢复。RS 异常包括 RS 无主、RS 上任失败、RS 线程卡住、快照点回收异常、配置项异常、工作线程满、DDL 线程满等,主要分为两类,一类是 RS 可以正常服务请求,一类是 RS 不可服务。前者可以通过 stop server 的方式隔离异常 RS,将 RS 切到其他机器。后者包括 RS 上任失败、队列满等异常,可以通过外部 admin 工具来强制切换 RS 服务,新 RS 上任后,再隔离原来的异常机器。

RS 承担了命令入口的功能,从而可以采集系统状态,并执行运维命令和应急命令,但 RS 异常后会导致该通道失效。为此 OceanBase 支持了诊断租户的功能,诊断租户是一个独立的租户,预留专用资源,包括 login 线程、工作线程和内存,保障异常情况下可以查询监控、及简单运维 SQL 的执行,从而规避系统租户和用户租户线程异常及内存异常。

资源隔离

多租户是 OceanBase 的一个重要特性,租户是数据库对象管理和资源管理的重要单位,在同一集群内支持多个租户同时访问,每个租户看上去都像是在“独占集群”。多租户带来了很多便利:减少运维成本,将多个数据库实例管理和运维的成本和复杂性降低到一个数据库集群的级别;提高资源利用率,通过将波峰波谷不同的业务系统部署到一个集群,以实现对系统资源大限度的使用,使得同样的资源可以服务更多的业务。

但也带来了诸多挑战,大的挑战在于资源隔离能力,需要保证单个租户异常对其他租户无影响,从而减少爆炸半径。

租户由一个或者多个 unit 组成,同一个租户的不同 unit 分布在不同节点上,一个节点上分布多个租户的 unit 。unit 是资源分配的小单位,有独立的资源规格,主要是 CPU 和内存。

OceanBase 内核实现了多租户的资源隔离,分为三个维度:同一个租户不同 unit 在节点间动态调整资源配额,使得存放了更多热点数据的unit获得更多的计算资源;不同租户的 unit 在同一个节点上动态调整资源配额,使得租户使用的计算资源与其配额匹配,同时在负载较轻的情况下让出多余的配额;unit 内部的资源隔离,确定大请求和小请求的资源分配及资源隔离,避免大请求阻塞队列及耗尽工作线程使得小请求无法响应的问题。

内存有 SQL 工作区内存、 MemStore 、 KVCache 、系统内存等内存区域。 SQL 工作区内存是 SQL 执行过程中Operator 所占用的内存; MemStore 是用户写入数据所占用的内存,不可挤占;KVCache 是缓存 SSTable 所占用的内存,可挤占。系统内存包括预留给各种系统线程运行所占用的内存。 SQL 工作区内存、 MemStore 、KVCache 是每个租户独占的,互相隔离。系统内存是所有租户共享的。

OceanBase 是一个单进程多线程架构,线程主要分为租户线程和系统线程。租户线程主要包括处理 SQL 和事务请求的工作线程,每个租户单独配置并独占。系统线程包括处理网络 io 的 EasyRPC 线程、处理磁盘 IO 的线程、处理合并的线程、写 clog 的线程、选举线程、后台定时器线程等,所有租户共享使用系统线程。租户的工作线程个数由租户的 unit 规格决定,系统线程由对应配置项指定。

EasyRPC 线程接受客户端的请求,解析租户信息,从而将请求发送到相应租户的任务队列。租户工作线程负责消费队列中请求,开始执行 SQL 和事务的核心流程( SQL 解析 -> 生成执行计划 -> start_trans -> start_stmt -> execute -> end_stmt -> end_trans),end_trans 过程中会执行两阶段提交流程,租户线程和系统线程一起推动事务 2PC 状态机的完成。事务提交过程中提交本地日志时先写入内存 IOBuf 中,等待 ClogWriter 线程完成落盘。事务提交过程中的 RPC 消息通过 EasyRPC 线程发出,包括 SQL 消息、事务消息、Paxos log 消息。

排队系统常见的问题是大请求阻塞小请求,例如后台批处理的大请求和交易处理的小请求共存于一个租户内,可能出现后台批处理请求占用工作线程导致交易处理请求被延迟甚至饿死的情况,OceanBase 引入了优先级队列来解决该问题。

租户任务队列分为大请求队列和普通队列,普通队列又分为多个优先级,每个队列都有对应的工作线程,可以在用户请求中指定优先级。OceanBase 把执行时间超过一定阈值的 SQL 当作是大请求(受全局配置项控制,可以适当调大,避免由于抖动把普通查询误标记为大请求),一旦某个 SQL 被判定为大请求,其对应的执行计划即被标记为大请求计划,后续命中该计划的 SQL 会被判定为大请求而直接进入大请求队列。

OceanBase 会把固定比例的CPU 资源用于处理大请求(默认为30%),如果执行过程中某个请求被判定为大请求,会检查当前正在处理大请求的工作线程是否超过设定值,如超过则把该线程挂起,并额外创建一个工作线程继续处理普通请求,从而保证普通请求不受影响。

对于分布式排队系统,又引入了线程死锁的问题,例如处理一个 rpc 请求时又发出了 rpc 请求,高并发时导致线程耗尽而死锁,OceanBase 引入了嵌套层级队列来解决该问题。将租户请求根据 rpc 来源分层,如果是用户请求那么作为第0层,如果是 OBServer 内部请求,可以作为第1层,由内部请求发出的 rpc 请求作为第3层,以此类推。然后为不同的请求优先级层创建不同数目的工作线程来处理对应的请求。

某一个 OBServer 发出的 rpc 请求总是比当前请求大1层,从而依赖下一层的线程处理,打破循环依赖,超过一定层级的请求可以报错处理。

对于在队列等待过久或者执行耗时过久的请求,就算终执行完成,可能由于触发各种超时时间而失败,已经没有了继续的意义,需要尽快失败以避免占用服务端资源。同时在服务端等待的同时,会卡住客户端线程,可能导致应用线程耗尽造成更加严重的影响,也需要“ fast fail ”机制响应客户端。OceanBase 引入了特征限流来解决该问题,对占用资源过多的请求进行限流,可以指定多个维度( cpu 、IO 、网络、逻辑读),也可以指定已经经历的服务端时间,多维度时达到任一条件即会限流。

特征限流是对大小请求隔离机制的补充,大小请求隔离只考虑了执行时间,没有考虑资源占用;大请求就算被识别出来,依然占用服务端和客户端的工作线程。

SQL执行

一条 SQL 从客户端发送请求,到服务端返回结果,会经历 obproxy 路由、队列等待、 plan cache 查询、计划优化(如果 plan cache 没有命中)、计划执行、返回结果等过程。

其中任一步骤异常都会造成 SQL 耗时增加,进而造成 APP 工作线程 hang 住,例如 obproxy 将请求发送至异地 OBServer 、队列积压导致排队时间增加、 plan cache 内存过小导致 SQL 硬解析、执行计划不是优导致全表扫描等,本章节将重点介绍针对执行计划的高可用设计。

执行计划指优化器为某条 SQL 生成的执行过程,一般用操作符树来表示。执行计划描述了 SQL 的访问路径(主表 or 二级索引)、连接顺序、连接算法( NLJ join 、 merge join 、 hash join )、查询改写、排序聚合集合算子等逻辑。按照执行计划涉及的分区数及分区所在的节点,分为 本地计划、远程计划、分布式计划。在优化过程中,优化器会综合考虑 SQL 语义、数据统计特征、数据物理分布等多方面因素,支持基于规则和基于代价两套模型,终选择一个对应该 SQL 的佳执行计划。

执行计划的生成相对 SQL 执行是一个耗时的过程,为了避免重复生成,生成的执行计划会加入到计划缓存中,以便再次执行该 SQL 时使用。用户可以通过 Explain 命令查看优化器针对给定 SQL 实时生成的执行计划,可以通过内部表查询指定 SQL 在计划缓存中的实际执行计划。

OceanBase 支持人为控制优化器的行为,上线前可以在 SQL 中添加 hint 以控制优化器按以指定的行为生成执行计划;对于正在运行的 SQL,支持在线计划绑定,通过 DDL 操作将一组 hint 加入到 SQL 中,从而使得优化器根据 hint 生成更优的执行计划。我们将该组 hint 称为 Outline ,通过对某条 SQL 创建 Outline 从而达到计划绑定的目的。

通过 Outline 也可以对某条 SQL 进行限流,区别于上文介绍的“特征限流”,我们称 Outline 限流为限流,Outline 限流更加,避免误杀,但需要首先识别出 SQL 才能限流,存在耗时较长的问题。

OceanBase 提供了立体化的 SQL 性能诊断工具:系统表 sql_audit 记录了每一次 SQL 请求的来源、执行状态及统计信息;sql trace 交互式的提供上一次执行的 SQL 请求执行过程信息及各阶段的耗时;计划缓存相关视图记录了执行计划的缓存状态、执行统计的相关信息及计划信息;慢查询日志记录了耗时超过一定阈值的 SQL 的本次执行的各阶段耗时信息;租户线程信息记录了每个租户的队列请求积压情况、线程状态信息及统计信息。

当租户 RT 出现抖动时,可以通过 sql_audit 抓出执行耗时异常的 SQL ,需要排除由于队列等待而导致总耗时增加的 SQL 的干扰。然后分析该请求的 sql_audit 记录,如果 retry 次数很多,可能是锁冲突或切主等情况导致;如果获取执行计划时间过长,可能是执行计划缓存没有命中;如果 cpu 执行耗时过长,需要查看是否有异常等待事件并针对性分析;如果逻辑读过高,需要通过计划缓存视图分析执行计划是否优,并判断是否需要创建 Outline 。

由于优化器的模型缺陷,或者统计信息不准确,可能导致优化器生成的执行计划不是优。OceanBase 引入了 SPM 和ACS 机制。SPM(SQL Plan Management) 是一种控制计划演进的机制,当优化器生成新的计划时,判断性能是否回退,如果出现回退,就拒绝使用该计划,从而保证计划性能朝好的方向不断更新。

ACS(Adaptive Cursor Sharing)则是一种让优化器为每一个参数化 SQL 存储多个计划,并根据 SQL 语句中谓词的选择率选择合适的计划的机制。ACS 很好的解决了一类“大小账号”问题,大账号走主表,小账号走索引表,避免了大账号走索引表带来回表代价过大的问题。

总结

高可用是一个精细活,涉及到链路上的每一个细节,任一点的异常都可能整体服务异常。需要慢功夫积累, OceanBase1.x 系列在蚂蚁上线始于2016年,开源始于2021年,但 OceanBase 研发始于2010年,其高可用技术是从2010年就开始不断积累的,不处当时之场景,无法理解当时之设计,向 OceanBase 的前辈们致敬。

分布式数据库尤其复杂,一篇文章的篇幅也无法介绍 OceanBase 内核中所有的高可用设计,本文试图管中窥豹,介绍在生产环境中踩坑多的系统模块及其高可用设计。

高可用更是一个体系活,OceanBase 提供了诸多机制去防御故障,但仅仅有内核的高可用能力是不够的,依然需要人肉或者配套外部工具体系去恢复故障。后续将介绍介绍蚂蚁的数据库高可用体系建设。

相关文章