暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

OceanBase系统架构弱一致性读

2024-02-06
106

OceanBase 数据库提供了两种一致性级别(Consistency Level):STRONG 和 WEAK。STRONG 指强一致性,读取最新数据,请求路由给主副本;WEAK 指弱一致性,不要求读取最新数据,请求优先路由给从副本。OceanBase 数据库的写操作始终是强一致性的,即始终由主副本提供服务;读操作默认是强一致性的,由主副本提供服务,用户也可以指定弱一致性读,由从副本优先提供服务。

一致性级别的指定方式

有两种方式指定一致性级别:

  • 通过 ob_read_consistency 系统变量指定

    • 设置 Session 变量,影响当前 Session

      obclient> SET ob_read_consistency = WEAK;
      obclient> SELECT * FROM t1;  -- 弱一致性读
      
      复制
    • 设置 Global 变量,影响之后新建的所有 Session

      obclient> SET GLOBAL ob_read_consistency = STRONG;
      
      复制
  • 指定 Hint 方式

    • 指定 WEAK Consistency,优先级高于 ob_read_consistency

      obclient> SELECT /*+READ_CONSISTENCY(WEAK) */ * FROM t1;
      
      复制
    • 指定 STRONG Consistency

      obclient> SELECT /*+READ_CONSISTENCY(STRONG) */ * FROM t1;
      
      复制

SQL 语句的一致性级别

  • 写语句 DML (INSERT/DELETE/UPDATE):强制使用 STRONG Consistency,要求基于最新数据进行修改。

  • SELECT FOR UPDATE(SFU):与写语句类似,强制使用 STRONG Consistency。

  • 只读语句 SELECT:用户可以配置不同的 Consistency Level,满足不同的读取需求。

事务的一致性级别

弱一致性读的最佳实践是为不在事务中的 SELECT 语句指定 WEAK 一致性级别,它的语义是确定的。对于显式开启事务的场景,语法上,OceanBase 数据库允许不同的语句配置不同的一致性级别,这样会让用户很困惑,而且如果使用不当,SQL 会报错。

原则:

  • 一致性级别是事务级的,事务内所有语句采用相同的一致性级别。

  • 由事务的第一条语句决定事务的一致性级别,后续的 SELECT 语句如果指定了不同的一致性级别,则强制改写为事务的一致性级别

  • 写语句和 SFU 语句只能采用 STRONG,如果事务级一致性级别为 WEAK,则报错 OB_NOT_SUPPORTED

下面举例说明。

BEGIN;
-- 修改语句,consistency_level=STRONG,整个事务应该是 STRONG
insert into t1 values (1);                  

-- SQL自身的 consistency_level=WEAK,但由于第一条语句为 STRONG,
-- 因此这条语句的 consistency_level 强制设置为 STRONG
select /*+READ_CONSISTENCY(WEAK) */ from t1;
COMMIT;

BEGIN;
-- SFU属于修改语句,consistency_level=STRONG,整个事务应该也是STRONG
select * from t1 for update;

-- SQL自身的consistency_level=WEAK,但由于第一条语句为STRONG,
-- 因此这条语句的consistency_level强制设置为STRONG
select /*+READ_CONSISTENCY(WEAK) */ from t1;
COMMIT;

BEGIN;
-- 第一条语句为WEAK
select /*+READ_CONSISTENCY(WEAK) */ from t1;

-- 虽然本条语句为STRONG,但是会继承第一条语句的consistency level,会强制设置为WEAK
select * from t1;
COMMIT;

BEGIN;
-- 第一条语句为WEAK
select /*+READ_CONSISTENCY(WEAK) */ from t1;

-- 修改语句,必须为STRONG,由于第一条语句为WEAK,这里会报错:NOT SUPPORTED
insert into t1 values (1); 

-- SFU属于修改语句,必须为STRONG,这里同样会报错:NOT SUPPORTED
select * from t1 for update;
COMMIT;
复制

因此,对于单条 SQL 而言,一致性级别的确定规则优先级从大到小可以概括为:

  1. 根据语句类型确定的一致性级别,例如 DML 和 SFU 必须采用 STRONG。

  2. 事务的一致性级别,如果语句在事务中,而且不是第一条语句,则采用事务的一致性级别。

  3. 通过 Hint 指定的一致性级别。

  4. 系统变量指定的一致性级别。

  5. 缺省采用 STRONG。

与隔离级别关系

  • STRONG 支持所有的隔离级别。

  • WEAK 仅支持读已提交 READ COMMITTED 隔离级别,其他隔离级别下会报错 OB_NOT_SUPPORTED

弱一致性读配置项

V2.2.x 之前的版本

名称范围语义
enbale_causal_order_read集群级是否开启单调读,默认为 false
max_stale_time_for_weak_consistency集群级弱一致性读最大落后时间,默认值是 5 秒

V2.2.x 之前的版本仅支持 Proxy 级别的单调读,即只要客户端始终访问同一个 Proxy,就可以保证单调读。具体实现方式是在 Proxy 上维护单调递增的弱一致性读版本号。如果客户端跨 Proxy 访问,则不保证单调读。如果用户需要集群级别单调读,则需要使用 V2.2.x 及之后的版本。

V2.2.x ~ V3.x 版本

名称范围语义
enable_monotonic_weak_read租户级是否开启单调读,默认为 false
max_stale_time_for_weak_consistency租户级弱一致性读最大落后时间,默认值是 5 秒
weak_read_version_refresh_interval集群级弱一致性读版本号刷新周期,默认值 50 毫秒

各个配置项具体含义如下:

  • enable_monotonic_weak_read :租户级单调读开关

    弱一致性读会路由到不同副本上,不同副本上读到的数据新旧没有保证;单调读开关打开后,OceanBase 数据库保证读到的数据版本不回退,保证单调性。一个典型的应用场景是保证因果序:两个事务 T1 和 T2,T1 提交之后,T2 才提交,如果客户端读到了 T2 事务的修改,那么之后一定可以读到 T1 事务的修改。

  • max_stale_time_for_weak_consistency:弱一致性读最大落后时间

    OceanBase 数据库弱一致性读提供有界旧保证,即保证读到的数据最多落后 max_stale_time_for_weak_consistency 时间,默认配置值是 5 秒,支持租户级配置。

    正常情况下,各个分区的备副本落后时间在 100 毫秒到 200 毫秒,弱一致性读的时效性在百毫秒级别;当出现网络抖动、无主等情况,弱一致性读的时效性会降低,一旦一个副本落后时间超过 max_stale_time_for_weak_consistency,该副本将不可读,内部重试机制会重试其他有效副本;如果所有副本都不可读,则持续重试,直到语句超时。

    当开启单调读开关后,OceanBase 数据库内部会为每个租户维护一个 Cluster 级别的弱一致性读版本号,该版本号也满足 max_stale_time_for_weak_consistency 约束。它的生成方式是统计租户下所有分区副本回放进度的最小值,如果某些分区副本落后时间超过 max_stale_time_for_weak_consistency,则不统计该副本。目前机制下,一个落后的副本会影响整体单调读的版本号,例如有两个分区的两个副本,一个副本落后 100ms,一个副本落后 1 秒,那么整体的单调读版本号是 1 秒。我们认为副本长时间落后不会是常态,正常情况下,单调读版本号都应该在百毫秒级别。

  • weak_read_version_refresh_interval:弱一致性读版本号刷新周期

    弱一致性读版本号刷新周期影响读取数据的新旧程度,它配置的值不能大于 max_stale_time_for_weak_consistency。当它配置为 0 时,弱一致性单调读功能关闭,即不再维护 Cluster 级别弱一致性读版本号。另外,它是集群级别配置项,不支持租户级别配置。

2.2.x 之前版本

名称范围语义
enbale_causal_order_read集群级是否开启单调读,默认为 false
max_stale_time_for_weak_consistency集群级弱一致性读最大落后时间,默认为 5 秒

2.2 之前版本仅支持 Proxy 级别单调读,即只要客户端始终访问同一个 Proxy,就可以保证单调读。具体实现方式是在 Proxy 上维护单调递增的弱一致性读版本号。如果客户端跨 Proxy 访问,则不保证单调读。如果用户需要集群级别单调读,需要使用 2.2 及之后的版本。

V4.x 版本

对于 4.x 版本,max_stale_time_for_weak_consistency 和 weak_read_version_refresh_interval 两个配置项的功能保持与之前的版本一致。解除了对 enable_monotonic_weak_read 的依赖,弱读默认就是租户级别的单调读,不再支持关闭。

弱一致性读时间戳

弱一致性读,通常是指从副本、备库上的查询语句。OceanBase 数据库的弱一致性读依旧返回事务一致性点, 不会出现返回未提交事务和一半事务的情况。

弱一致性读时间戳,包括两个方面:

  • 弱读执行之前,需要确定一个读快照数据。

  • 分区自身实时维护一个最大安全可读的位点,凡是读快照大于该位点的读请求,均不能读该副本。

时间戳生成方式

OceanBase 数据库弱读一致性分为两大类:单调读和非单调读。不同的能力,时间戳生成的方式不同。

单调读

单调读是指根据读请求发起的绝对时间,后发起的请求使用的读快照一定不比前者小,确保读到的数据不会出现回退。这里的单调是针对语句读快照而言的。单调读的保证依赖集群级别单调弱读版本号,全局版本号需要做到不回退。

全局版本号的生成来源于每个 OBServer 维护的最小最大安全版本号,该版本号的生成依赖本机日志同步的进度。对于 OceanBase 数据库而言,事务的日志数据,由三个模块来维护,apply servicereplay_service 和 Transaction

  1. apply service 负责事务日志的本地落盘、发送备机、接收 Leader 同步的日志等操作。对备机而言,它为每个日志流维护了一个滑动窗口,将收到的日志,按照 logid 从小到大管理起来。日志从滑动窗口中顺序滑出,提交给 replay_service 去回放。

  2. replay_service 负责日志的回放,它将同一个事务的多条日志 Hash 到同一个工作线程,保证同一个事务的日志顺序回放,但是同一个分区的多个事务是并发回放的。对一个分区的日志而言,日志按照提交时间戳有序串到链表中,由多个线程并发回放。

  3. Transaction 负责事务状态的管理,Transaction 执行日志的回放操作,即将 Redo 数据回放到 MemStore,并记录事务的状态。

由上描述可知,一条日志从 apply service 接收到提交 replay_service 回放任务,再通过 Transaction 回放事务日志,为保证单个分区备机读不会读到一半的事务,需要找到该分区备机读的安全版本号,保证该版本号之前的所有事务都已经回放完成。计算公式如下:

slave_read_ts = min(replay_service_ts_ts, apply_service_ts, trans_service_ts) - 1

其中:

  • apply_service_ts 表示 apply service 滑动窗口中下一条将要滑出或将要生成的日志 Timestamp。

  • apply_service_ts 表示该日志流尚未回放日志的 Timestamp 最小值。

  • trans_service_ts 表示当前所有正在回放事务的 prepare_log_ts 的最小值。

举例说明:假设 apply service 的滑动窗口中存在两条日志,logid 和日志的提交时间戳分别为(10,100),(11,200);replay_service 中,该日志流尚未回放的日志分别为(7,70),(8,80)Transaction 中,只有一个正在回放的事务,刚刚回放完 redo 和 prepare log,并且 prepare log 的 logid 为 9prepare_log_ts=90;此时该分区的 安全版本号=min(100, 70, 90) - 1 = 69

非单调读

相对于单调读,非单调读保证的能力较弱。非单调读则不保证前后发起的请求快照递增,由于同一份数据不同副本同步进度的差异,如果前后两次读取不同的副本,数据可能出现回退。

原子性

不论单调读还是非单调读,对单个分区同一个事务修改的数据,均能保证原子性。不论何种场景,只要读快照小于单个分区维护的最大安全可读版本号,读到的数据一定具备原子性,这是原子性保证的原则。

因此设计上,需要引入分区是否可读的校验,对于不可读的分区,让 SQL 执行引擎重试其他副本。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
1人已赞赏
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论