OceanBase作为关系型数据库,本身完全兼容MySQL协议。但与传统单机数据库不同,OceanBase是分布式数据库,每个表甚至每个表的不同分区都可能存放在不同的机器上,而想要对表进行读写,必须先要定位到数据所属的表或是分区的位置。尽管任何一台OBServer均有路由转发和本地执行的能力,但实际中这样做将严重影响性能和整体吞吐量。ObProxy作为OceanBase的高性能反向代理服务器,其核心功能就是路由转发。ObProxy路由的目标是将具体SQL转发到最恰当的Server上执行。本文详细描述了ObProxy的路由逻辑。

    路由逻辑 - 图1

    1. 解析SQL

    proxy需要从DML类型SQL中解析出Hint/数据库名/表名。DML类型SQL包括select/insert/delete/update/replace。 只解析DML类型SQL是因为其对路由是否准确最关心,也是用户最常用的SQL,其他类型的SQL路由规则是相同的。解析出Hint可以用来确定SQL的一致性读属性(强一致性读/弱一致性读),这将决定第2步中路由规则。 解析出”数据库名+表名”可以用来在确定该SQL的目标server路由表,这将在第3步中发挥作用。

    2.确定路由规则

    proxy需要根据请求类型/一致性读属性/OceanBase集群部署属性/用户指定路由策略等因素决定单条SQL的具体路由规则:

    • 请求类型:DML请求,其他请求;
    • 一致性读属性:强一致性读,弱一致性读;
    • OceanBase集群部署属性:普通部署,读写分离部署;
    • 用户指定路由策略:读zone优先策略,只限读zone策略;

    不同路由规则将导致目标server的优先级排序不一样。 排序考虑的主要维度有:

    • proxy和server是否同城? 同城 > 异地。
    • proxy和server是否同机房? 同机房 > 不同机房。
    • server是否在合并? 不在合并 > 在合并。
    • server是否含有SQL涉及的partition? 含有partition > 不含有partition。
    • server是否为partition leader? leader > follower。

    当OceanBase使用默认的普通部署时,proxy当前的路由规则主要有:

    • LDC优先

    在路由表的所有server中LDC优先选择,即路由排序维度的考虑优先级为:是否同城 > 是否含有SQL涉及的partition > 是否在合并 > 是否同机房

    使用此规则的场景有:

    • 非DML类型的请求
    • 使用弱一致性读的DML

    • Leader优先

    优先选择路由表中的leader server,其次在路由表剩余server中根据LDC优先选择。 即路由排序维度的考虑优先级为:是否为partition leader > 是否同城 > 是否含有SQL涉及的partition > 是否在合并 > 是否同机房

    使用此规则的场景有:

    • 使用强一致性读操作的DML请求(select)
    • 使用写操作的DML请求(insert/delete/update/replace)

    当OceanBase使用读写分离部署时,proxy路由规则还包括:读zone优先,只限读zone,只限写zone,非合并优先等。 这里进行不详细描述。

    3.获取路由表

    路由表包含两类,一类是含有SQL涉及partition的目标server集合(本文简称partition server),另外一类是不含有SQL涉及partition的目标server集合(本文简称non-partition server)。 两者公共构成用户可以访问目标server集合,简称tenant server。 完整的tenant server根据用户的登录信息即可获得,而partition server则需要根据具体SQL信息确定。

    路由逻辑 - 图2

    路由表获取流程如下:

    • 在登录认证阶段,仅需要tenant server路由表。 proxy根据用户的登录信息(租户名+集群名),从本地缓存中读取tenant server,并保存在当前连接上。

    如果本地缓存中没有,或者本地缓存已经失效,则发起后台异步任务去向后端的observer查询。 查询得到的结果会在本地缓存一份。

    • 在非登录认证阶段

    • proxy根据第一步获取到SQL信息(数据库名+表名),去本地路由表缓存中找到其对应的table entry。

    如果本地缓存没有,或者缓存已经失效,则发起后台异步任务去向后端的observer查询。 查询得到的结果会在本地缓存一份。

    • 如果发现该SQL涉及的table entry是非分区表,则提取table entry中server作为目标partition server。

    • 如果发现该SQL涉及的table entry是分区表,则通过进一步的parse和resolve,计算出涉及的partition_id,根据”数据库名+表名+partition_id”,去本地路由表缓存中找到其对应的partition server。

    如果本地缓存没有,或者缓存已经失效,则发起后台异步任务去向后端的observer查询。 查询得到的结果会在本地缓存一份。

    为什么要区分partition server和non-partition server? 这是因为不包含partition的server生成的执行计划并不是最优的。

    4. 选择目标 Server

    proxy需要根据第3步获取的路由表,按照第2步中确定的路由规则,对partition server和non-partition server组成的tenant server进行优先级排序,从优先级最高的server开始,第一次输出优先级最高的server,第二次输出优先级次高的server,以此类推,当遍历完输出完所有server后,从优先级最高的server重新开始输出。

    因为选择的目标sever并不能直接使用,还需要经过第5步的黑名单检查。 当第4步选择的server没有通过第5步的黑名单检查时,根据proxy路由逻辑的重试机制,将重新进行第4步,输出下一个server。 因此选择目标server将可能是一个多次的操作。

    5. 黑名单检查

    对第4步选择的目标server,需要经过黑名单的检查才能真正的确定server是否可用,才能将请求转发给目标server。 对于处于/宕机/下线/升级中的server,将不能通过黑名单检查。 对于处于初始化中/退出中/访问超时的server,如果在N秒内有有M次失败,那么也将不通过黑名单检查。

    如果第4步中的所有server都不能通过黑名单检查,那么根据proxy路由逻辑的重试机制,将重新进行第4步,输出下一个server,但是此时不在进行黑名单检查。

    6. 转发Request

    对于用户的SQL请求,proxy进行透明流式转发,并不会改写用户的请求,也不会拦截用户请求自己对用户的请求直接回应。