作者简介:致新,OceanBase 数据链路高级研发工程师。
一、ODP 简介
1.1 OceanBase 生态
OceanBase 除了内核中大家相对熟悉的 SQL 模块、事务模块、存储模块等,其周边还有很多生态工具来帮助 OceanBase 服务好用户,比如本章将要介绍的 ODP(原名是OBProxy,现改名为 ODP)。ODP 是 OceanBase 的访问入口,具有代理和中间件的特性。
1.2 ODP 发展历史
ODP 目前的产品特性,除了方案设计外,和产品的发展历史也有关,介绍 ODP 发展历史可以帮助大家更好的理解 ODP 的产品特性。
ODP 的历史主要分成了三个阶段:
- 2014-2018:ODP是和OBServer内核一起开发的,伴随着 OceanBase 内核一起发版,最开始的目的就是帮助用户更好地去访问 OceanBase 。
- 2018-2021:在蚂蚁内部,ODP 转交给中间件团队,增加中间件和云原生能力。
- 2021-至今:在 OceanBase 去年成立公司后,ODP 团队又从中间件回到了 OceanBase 团队,目前随着 OceanBase 的商业化和开源一起服务更多的用户,目前在开发更多的能力支持,也在完善相应的产品化能力。
1.3 访问入口
ODP 是什么?简单来说,ODP 是数据库的访问入口,OceanBase 的定位是一个分布式数据库,ODP 让用户更简单地访问数据库。
举个例子,我们应用命令行登录 MySQL、Oracle 等产品时,需要指定 IP、port、用户名和密码,而对于分布式数据库 OceanBase 来说,这里存在的问题是如果同时存在三台 OBServer 时如何填写这些信息。ODP 让这一切都变得简单,用户使用时只需要填写 ODP 的 IP 和 port ,就可以正常访问数据库。
ODP 完全兼容 MySQL 协议,目前主要是 5x 系列版本协议,8.0 协议还在适配中。因为协议兼容,所以用户的代码可以使用现有的 MySQL 语言客户端,如 Java 程序可以直接使用 MySQL 的 jdbc 实现。对于访问数据库的代码,使用 ODP 时只需要修改 IP 和 port,其他的代码无需做任何改动就可以访问 OceanBase 数据库。
ODP 作为入口的性能是非常好的,支持多线程利用多核能力、实现异步编程提供高效转发,不会有阻塞操作。ODP 本身非常轻量,无状态不会存取数据,扩展性好。
上图说明了一条 SQL 的访问路径,下面的三个 zone 可以简单地理解成机房,一个机房里部署了两台 OBServer,数据分散在所有的 zone 里面。当执行一条 SQL 时,ODP 可以准确地把这个 SQL 请求发送到有该数据的 OBServer 上。
路由方面,ODP 还支持了 OceanBase 的弱读功能,可以实现读写分离,将读流量发往备副本,实现负载均衡。
1.4 代理模式
关于代理产品的优缺点,下面是我的一些思考:
- 性能损耗。客户端到代理之间的网络多一跳可能会影响RT和QPS。
- 代理本身负载均衡和高可用。
这里举两个场景,一个是现在比较流行的 Docker、Mesh 化的部署。ODP 本身支持了 sidecar 的部署,可以理解为应用部署在一个 Docker 中,ODP 部署在另一个 Docker 中,两个 Docker 在同一台机器上,这样它是一对一的关系,只需要用户的 Docker 访问旁边的 ODP 的 Docker。
除了这种模式之外,还有另一种可能是集中部署。ODP 前面都会部署一个 LB 组件,如 F5,云上也有专门的 LB 场景。
这两种场景都比较常用,sidecar 部署目前是和云原生一块发展的,蚂蚁内部广泛使用这一种部署方式。LB 部署也很多,阿里公有云上的 ODP 就使用了这种部署,在私有云场景中也经常被采用。
- 部署和运维。ODP 作为一个独立的进程,需要考虑这个进程的资源规划以及专门的配置推送,如重启、配置在 LB 上的相关信息,代理模式带来了运维的复杂性。
- 语言无关,和业务解耦合:Proxy 模式无需为每种语言开发一种驱动,减少研发成本;和业务解耦合,升级更加方便。
- SDK or Proxy。为用户提供选择。
1.5 架构比较
大家做架构比较时,对于单机数据库的代理一般使用直连方式,在生产环境下可能会采用代理组件,如 ProxySQL 会做一些 MySQL 主备之间的切换。关于 OceanBase 的架构,下面和另一款比较知名的开源分布式数据库 TiDB 去进行一些比较。
从 TiDB 的架构上大家可以看到,TiDB 本身是没有代理产品的,LB 后面就直接是 TiDB 的 server ,在 TiDB server 后面是 TiKV。为什么 OceanBase 就有 ODP 这个模块去做代理呢?这和 OBerver 本身的实现有关,虽然大家都是分布式架构,但也有不同。OceanBase 是单机分布式一体化的架构,目前没有做存算分离,它的计算节点和存储数据的节点都在一起。TiDB server是一个 SQL 处理引擎,本身是无状态的,它将请求转发给后面的 TiKV,是存算分离的。
TiDB server 里面实现了路由功能,还做了语法解析、计划生成等。ODP 就比较简单,只实现 SQL 转发,同时保证高性能的转发效率。ODP 都可以保证 SQL 发往的那台 OBServer 是有相关数据的。举个例子,向T1表插入数据,ODP 可以保证把请求发往有 T1 表数据的机器,这样整体性能最好。
另一方面,ODP 在访问核心链路上做了各种性能的优化,可以保证不会成为整个链路上的瓶颈。
最后,代理产品除了为 OceanBase 路由外,它还可以去做一些中间件的能力,比如白名单、分库分表等。整个产品作为独立的组件,安装部署非常方便。
二、ODP 特性介绍
2.1 高性能转发
ODP 的第一个特性一定是高性能的,在网络多一跳的情况下,我们一定要保证转发效率足够高,不会成为系统的瓶颈。ODP 在设计实现上是多线程加异步的编程模型。在性能测试中,我们在阿里云 ECS.g7.8xlarge 机型上测试了 sysbench的point_selec t场景,得到了下面这组数据。大家可以看到第三列,三机部署的情况下 ODP 做到100万的转化只需要30个核左右,所以它的单核转发效率是特别高的。
ODP 是怎么实现高性能的呢?上图简单地介绍了 ODP 的线程模型。ODP 的每个线程基本上可以认为是一样的,各自独立地去做转发任务,线程之间基本没有共享数据,可以充分地利用多核的能力。
代码实现上有两套逻辑,一个是 IO 逻辑,即 TCP 流的转发;另一个是业务逻辑,进行数据转发和处理的流程。这两个逻辑都是在同一个线程里完成的。
关于 ODP 的性能,一方面需要保证线程数,如果设置了 ODP 线程数是8,那我们最多只能利用8哥 CPU 核。所以要合理的设置工作线程数。不建议设置线程数超过机器本身 CPU 的核数,这样就会存在两个或多个线程抢占同一个 CPU,性能反而会下降。另一方面是我们可以保证把任务均匀地分发到每个线程里,这样做到每个核的负载均衡。
前面讲到了线程模型和我们的测试数据。我再从代码优化和整体性能去说一下。代码层面,针对主路径做了很多优化,提高程序执行效率。另一方面,为了保证性能不回退,我们实现了核心场景的性能回归。比如 TPCC 场景、蚂蚁内部的核心交易支付场景等。
从整体性能来看,ODP 不追求本身的性能最优,而是保证整个链路的性能最优。举例说明,来了一条 SQL 以后,一种转发策略是简单的把这条 SQL 转发给一台 OBServer ;另一种策略就是对这个 SQL 做解析,比如提取表名获取这个表的分布,再把 SQL 转发给有相关数据的 OBServer 。在 ODP 层面,第二种方式相比第一种方式多了 SQL 的解析以及获取 table、table 数据分布的逻辑。ODP 虽然需要牺牲 CPU 去计算这套逻辑,但根据实际测试场景的表现,第二种情况的整体性能要明显优于第一种。这就是所说的全链路最优。
简单介绍一下 ODP 性能问题的分析。ODP 前面连着 LB 或业务的 App,后面连着 OBServer ,所以一定要全链路去分析性能问题。ODP 和 OBServer 经过很多年的场景的锻炼,整体的性能还是非常稳定的。当确定是 ODP 的问题时,大家可以用 top 命令或 perf 命令去查看具体的性能。比如通过 top 命令,如果 ODP 和 OBServer 部署在一起,那么 ODP 的 CPU消耗是要小于 OBServer 的;使用 perf 命令查看 ODP 有没有热点函数,基本上 ODP 每个函数的消耗都没有超过5%的。性能优化是很复杂的问题,需要从全局把控。
2.2 连接管理
ODP 能够使得访问 OBServer 足够简单,首先介绍一下登录信息和登录流程方面的内容。
OceanBase 本身的架构非常复杂,从用户名就可以看出,如完整名字为 username@tenant_name#cluster_name:cluster_id,username 是连 MySQL 数据库时的用户名,tenantname 是访问的租户名,因为 OceanBase 本身是多租户的,cluster_name 指定访问的集群。ODP 不仅可以代理同一个集群,还可以代理多个集群,即通过一个 ODP 不仅可以访问一个集群下的不同租户,还可以访问多个集群下的不同租户。cluster_id 和 OceanBase 的主备库形态有关,比如0代表主库,1代表备库。
ODP 对 MySQL 协议完全兼容,目前是兼容的 5.7 版本,8.0 版本协议正在做相关的适配工作。基本上使用开源的 MySQL 的客户端都可以直接访问 ODP ,但是如果是 Oracle 模式,还是必须用 OceanBase 的专有客户端。
最后再解释一下 ODP 是怎么做到可以代理不同集群的不同租户的。一种方式是 rslist 的方式,OceanBase 中包含一个 rootserver 组件,把 rootserver 的地址配给 ODP 就可以。但是这种方式的问题是 ODP 只能访问一个集群了。另一种方式就是通过 OCP 组件的 URL ,OCP 保存不同集群的所有的 rslist 信息,这样就可以访问不同的集群了。
OceanBase 的整个登录流程还是非常复杂的,首先进行 MySQL 协议的握手,在建立连接后客户端会主动发一些 SQL,比如 set names UTF8 ,告诉数据库传输的数据都是 UTF8 编码的。OceanBase 比这个还要再复杂一些,因为 ODP 本身也会发一些内部的 SQL,比如查询集群机器列表,因为访问每个集群时,需要拿到这个信息再去把这个请求转发给对应的机器。
整个登录流程比较复杂,但一般是没有什么问题的。特殊场景下,比如说某条 SQL 突然执行慢了,很可能会导致登录失败,这是本身复杂导致的。
在了解 ODP 的产品行为时需要从分布式角度考虑。大家使用 MySQL 时直接使用客户端连接,只需要考虑一个连接方向。但 ODP 场景就比较复杂,上图中的 OBProxy 是我们的 ODP进程名,对于一个客户端的连接,目前的实现是会和 server 建立多个连接的,形成一对多的关系,此时就要考虑和客户的连接、和 server 连接的问题。
假设分布式系统中一个 server 发生了宕机、重启等各种情况,ODP 一般都会对这各种场景会做一些相关的处理,屏蔽 server 的故障,客户端和 ODP 的连接还是继续保持,这样就可以保证客户端和 ODP 之间连接的稳定。
一个 ODP 和 OBServer 之间连接,它只属于一个客户端和 ODP 的连接,不会做共享复用。比如说有两个客户端,他同时访问同一个 OBServer,他这时候会建两个连接,而不是说共用同一个连接。目前我们的实现是这样的,后续会实现连接池的功能。
最后一个再介绍一下这个状态同步的功能。假设用户主动发生了发送 set autocommit=0,ODP 需要对所有的 OBServer 实现状态同步。Set autocommit=0 这条 SQL 只会发往一台 OBServer ,执行完成后将结果返回给客户端。当下一次访问另一台 OBServer 时,需要由 ODP 保证 set autocommit=0,实现 ODP 和 OBServer 之间的状态同步。
后面就是举几个问题,让大家更深入地了解一下连接管理部分。
- 两段连接的复杂性。当需要查看当前有哪些连接时,向 ODP 发送 show processlist 命令。此时 ODP 不能直接转发给 OBServer ,不然只会展示一台 OBServer 的连接情况,但用户想看到的是所有的连接。所以这里由 ODP 拦截这个 SQL ,统计自身有哪些连接,处理完成后返回给客户端。 Show processlist 命令展示的是 ODP 和客户端的连接,而不是把这个命令转发给 OBServer 。另外比如说对于 kill session 的操作,由于有两段连接,大家需要确定是在 kill 哪段 session 的连接。
- 连接保持。要保持住连接很困难,比如说前端的业务应用可能会断连接,ODP 如果本身出 bug 了也会断,OBServer 本身出问题了也有可能断。所以整个断连接的处理是非常的复杂的。如果想保持住这个连接,需要做很多容错处理。比如后端 OBServer 出现异常时,ODP 会进行屏蔽处理。
- 异常、问题分析、问题排查。ODP 的问题分析比较复杂的原因就是它涉及链路比较多,排查问题的时候要一个链路、一个链路逐个去排查,整体难度相比直连会复杂一些。
举例来说,如果客户端和 ODP 的网络慢,ODP 和 OBServer 的网络是正常的,但是对于用户的体验都是一样的,整个访问都是慢的,这里只能去分段去排查。
在实际场景中主要还是要大家都积极地配合,OBServer 提供相关的数据,客户端本身的数据也非常的关键,比如执行时间以及执行的 SQL 。比如说大家用 jdbc ,jdbc 本身的超时参数可能会把连接断掉,如果应用可以提供给我们时间点和具体的 SQL,可以帮助我们去分析问题。
再讲解一下 PS 模式的实现,了解一下 ODP 对整个分布式状态管理的复杂性。PS 模式直连比较简单,第一个请求是发送 prepare 请求,select * from T where A=?,下一个请求就是 execute 的请求,把相关数据带回来。
ODP 需要实现最佳路由,将请求发往数据所在的机器。假设表T是分区表,数据分布在多台 OBServer 上。用户执行第一个 execute 时,发现数据在 OBServer1 这台机器上,就可以把 prepare 请求发给 OBServer1 机器,再发送 execute 请求。用户在发送第二个 execute 请求时发现数据在 OBServer2 ,ODP 不能简单地把这个请求发往了发往 OBServer2,一定要把之前的 prepare 请求发过去,不然就出现了 OBServer 没有 prepare 就 execute 的情况。
2.3 超时管理
一些超时参数会影响相关的行为。
- jdbc 超时。socketTimeout 表示网络的超时时间,在蚂蚁内部设置是5秒,如果出现错误就迅速把连接给关掉。所以会出现应用程序5秒前发送数据后再也没有发送数据的情况,此时连接断开。connectTimeout 是连接超时参数,jdbc的文档中有对这些超时参数的相关介绍。
- OBServer 本身的实现中也有一些超时参数,其中部分超时参数是 OceanBase 特有的,如 ob_query_timeout 是一条 SQL 执行的超时时间,ob_trx_timeout 是事务超时的时间。
ob_query_timeout 设置为10秒时,如果一条 SQL 在10秒后没有执行完成,OceanBase 会返回一个错误。如果10秒内 OBServer 没有给 ODP 返回这个错误,ODP 这时候就比较难处理。因为在10秒后,这条 SQL 要么执行成功,要么返回错误,但是现在没有获得任何请求。ODP 在这里进行了兼容处理,专门提供一个额外增加20秒的配置项,如果比如说10秒+20 秒后还没有任何返回请求就会断开连接,因为这种状态是异常的。
- TCP 本身也有一些相关的参数,ODP 主要处理了两个,keepalive 参数主要是探活,NO_DELAY 参数是指 TCP 采取专门的算法快速发送数据减少延迟。
2.4 数据路由
OceanBase 本身也是有数据路由能力的,假设T1表的数据在 OBServer1,如果这条 SQL 发往另一台 OBServer 的话,另一台 OBServer 会做转发。但是目前 ODP 的转化性能是最高的。
路由除了数据分布以外还需要考虑更多的因素。
- 是否弱读,如果是弱读,不要求数据是最新,可以把这个请求发往备副本,这样主副本的压力就可以降下来,可以做一下负载均衡。
- server 状态的管理和数据分布。数据分布指表的数据与 server 的关系。server 状态管理指的是检测 Server 是否发生宕机或者重启等故障。如果 ODP 把请求发往了一台重启中的OBServer,会导致卡住的现象,影响性能和高可用体验,整体用户的 QPS 下降,无法保证一个平稳的状态。
- 路由相关的位置信息。位置信息是分布式系统中一个非常重要的因素,它反应的本质是延迟。如果是发生了跨城、跨州,整个请求的影响是特别大的。OBServer 本身的部署非常灵活,支持单机、双机房三机房、两地三机房、三地五中心等各种架构,需要考虑位置因素保证用户体验的。
路由因素这里举了三个比较重要的类,路由本身是和业务相关的,其他点暂时就不列举了。
ODP 路由的主要流程可以概括成四个情况:
- 解析 SQL,获取库名、表名和 hint,比如 hint 表示是否弱读,表名指示数据分布。
- 确定路由规则。leader 优先主要针对强读情况,此时要求读到最新的数据,因此需要发往 leader 所在的节点。LDC 路由是蚂蚁本身的 LDC 单元化架构下的一种路由规则,本身反映的是位置信息。对于弱读情况,LDC 在已知位置信息的情况下提供最近的节点,保证体验。比如应用部署在杭州,有两个备副本分别部署在杭州和上海,此时 LDC 路由会将请求发往杭州。
- 获取路由表。路由表的信息缓存在 ODP 里,确定路由规则时需要获取路由表的信息。简单的非分区表路由一般采用1:1:1的部署,包括一个 leader 和两个 follower ,t1表会拥有三个副本:一个主副本,两个备副本。这样的路由比较简单,对于 leader 优先的情况可以直接发往主副本。
另一个是分区表的路由,分区表本身有很多的 leader 副本,需要根据用户的 SQL算出具体的数据存放在哪个 leader 副本后才能发往正确的数据,与非分区表路由表相比增加了分区计算的步骤。
- 容灾检查:除了以正常逻辑选出 OBServer 之外,还需要做黑名单和灰名单的容灾检查。比如在路由表信息没有保持最新的情况下,选择的OBServer 可能有出现宕机的情况,此时必须将请求发送到其他节点。
路由是我们 ODP 处理的最多的问题之一,因为其中的每一个流程都存在着非常复杂的情况。
- 支持的 SQL 数量和 SQL 长度有限。在做 SQL 解析时,目前 ODP 能够支持常见的规范 SQL 语句,但并没有支持所有的 SQL 语法,解析不了过于复杂的语句,比如说像 insert+select 同时 select 内部嵌套更多的语句,会导致无法提取表名,不知道数据分布。
另一个问题是 parser 目前最多解析4K的数据,如果用户写了特别长的一个数据,比如写了一个16K的 SQL 请求,那很可能是拿取不到的。我们目前正在完善对复杂 SQL 语句的支持,但也需要考虑与快速解析的性能的权衡。对于 SQL 长度的问题,为了提取表名很可能需要把整个 SQL 全部保存下来才能进行分析,此时如果同时并发很多大请求,内存消耗会特别大。这也是我们保留目前设计的原因。
- 路由缓存的更新和过期机制。缓存系统的问题包括何时更新、何时过期两个部分,ODP 目前的实现还是相对简单,不会主动地去更新,这有一定的历史原因。在采用云原生后,蚂蚁内部部署 ODP 的数量非常多的,大概是几十万个。此时如果主动更新,对 OBServer 造成的压力会特别大。当集中部署的 ODP 数量比较少的情况下是可以考虑主动更新的,目前我们也在完善这些策略。
路由缓存被动更新的另一个例子是弹性问题。比如说双11大促的时候,突然需要爬表做副本重新分布等操作,此时由于 ODP 缓存没有主动更新,所以需要大流量地去触发 ODP的更新机制。ODP 虽然没有主动更新,但是对于错误的路由提供了反馈机制,因此可以触发所有表的反馈机制去完成更新操作。
此外,高可用机制下,对机器进行变更后可能会路由到不符合预期的机器,有可能就是缓存信息没有更新。
- 路由策略。我们支持丰富的路由策略。但是随着路由策略的丰富,路由的复杂性也在增加。其中一些路由策略和配置相关,导致管理的复杂性也增加。
- 容灾。在遇到错误时如何快速地发现问题,并将问题机器剔除。
- 远程计划。OceanBase 的计划分为三种:1. local 计划,把请求发往有数据的节点;2. 远程计划,ODP 数据发送错误导致 OceanBase 本身还需要进行一次路由。3. 分布式计划。远程计划举例来说,像蚂蚁内部大部分场景并没有远程计划,因为本地计划执行效率最好。在实际场景中,如果产生较多远程计划,则需要考虑 ODP 本身的路由是否出现问题。另外,由于 ODP 最开始面向是 OLTP 场景,所以其对分布式计划支持的相对有限。
2.5 路由缓存
- 路由信息。这一部分的主要问题在于如何识别出过期数据并更新。缓存路径类型分为两种:1. sys 和租户的机器列表信息。该部分信息与 OceanBase 的分布式架构有关,其支持多集群,多租户,因此需要缓存 sys 和租户的机器列表信息。2. 表的 partition 信息。如用户需要从t1表中读取数据,需要知道数据分布的具体位置,也就是表的 partition 信息。前者是机器分布信息,后者则是副本分布信息。
- 更新机制。了解更新机制有助于用户了解 ODP 的一些行为。更新机制在首次访问时为主动拉取,此后在淘汰后才进行触发。淘汰指的是,在 OceanBase 发现远程计划后会将相应的缓存信息标记为过期,进行淘汰。因此,系统一般不进行主动更新,但是提供了触发更新的运维命令,可供用户进行主动刷新。
- 淘汰机制。OBServer 的反馈会包含特殊的错误码,比如 OB server 无法分配内存,路由的机器没有租户等错误的提示。
2.6 LDC 路由
ODP 可以配置 LDC 路由策略,配置后可将位置信息与 OBServer 的状态信息纳入考虑。
- 位置信息和OBServer本身特性。状态信息非常重要:1. 每日合并会导致合并期间执行效率明显降低,OceanBase 需要识别是否在合并的状态,以确保不是所有的机器在同一时间点做每日合并;2. LDC 对两种场景有明显作用。一是在弱读的场景下,二是在 parser 没有识别出表名,需要从租户的机器中进行随机选择,需要考虑到机器的位置信息。
- 实现方式。LDC 即逻辑物理单元,可以将其理解为一个机房,同机房网络延迟低,跨机房的网络延迟高。由此,ODP 设置本身的 IDC 信息指的是 ODP 先设置自己所在的机房信息。OBServer 设置每个 zone 的相关信息,因为 zone 中的机器不存在跨机房的可能,因此该信息表明了所有相关机器的位置信息。而例如机房所在的城市信息,OBServer 中没有设置权,需要再 OCP 中进行设置。举例来说,如果某个应用部署在上海,应用会访问和其在一个机器内的 ODP ,并由 ODP 将数据发往上海的 OceanBase 的 zone 中,而不会发往深圳或者杭州,从而降低 sql 的延迟,避免出现慢查询。
2.7 Primary Zone 路由
分布式数据库可以把数据做分区打散,充分利用每一台机器的资源,但是也存在明显的问题,如无论如何优化,分布式事务的执行效率不会高于本机的执行效率。OBServer 为了性能场景,提出了 Primary Zone 的概念。
- Primary Zone 指的是所有 leader 分布在 Primary Zone 中,由此事务可在一台机器进行执行,执行性能高。
- Primary Zone 路由。如果确认 OBServer 的租户设置了 Primary Zone ,所有请求可以直接发往 Primary Zone 。另一方面,如果 ODP 不知道表名,无法找到副本的位置,则可以直接发往 Primary Zone,因为 Primary Zone 一定包含 leader,这样处理效率最高。目前开源版本中,包括 Primary Zone 路由能力,但是仍待完善。比如该版本的 Primary Zone路由只能进行全局设置,无法做到租户级别。后续可以进行完善,做到自动感知、租户级别生效。
- Primary Zone路由的缺点。Primary Zone 路由中,所有 eader 发往一台机器,会存在负载不均衡的问题。这个问题可以通过租户之间打散的方式进行改善。另一方面可以进行读写分离,不再将写请求发往 Primary Zone。
2.8 高可用
前述两点反应的延迟在于性能方面,另一方面是高可用的关于黑名单的处理。高可用中有30秒恢复或者45秒内恢复的说法。ODP 一方面从 OBServer 处查询获取信息,另一方面通过真正执行获取反馈信息,建立一套复杂的重试或黑名单相关的策略来完成高可用。
2.9 专有协议
ODP 的另一个杀手锏是协议。一方面,一般应用和 ODP 兼容 MySQL ,利于生态的扩展。另一方面 ODP 和 OBServer 对协议做了扩充。比如在数据安全方面,可以校验数据的 CRC 保证数据传输的准确性,进行 SSL 传输加密。能够支持 Oracle 模式的数据类型和交互模型。从监控诊断方面来说,可以进行全链路监控,记录监控信息。在状态同步方面,解决了 O BServer 和 ODP 之间如何高效的进行传输同步的问题。
2.10 中间件特性
ODP 支持很多中间件特性。
- 登录相关。ODP 支持常见的白名单和连接数的管理。
- 连接相关。ODP 支持传输加密。
- SQL 执行。ODP 支持限流和熔断。
- 架构相关。ODP 支持 sharding,分库分表。
三、ODP 实践
3.1 安装部署
ODP 的二进制名为 OBproxy ,编译后约占 400M ,除去符号表约占 30M,核心链路的函数有200多个。用户可从源码进行编译,或者通过开源的相关工具或者生产环境,通过 OCP 进行安装部署。OBproxy 足够轻量,对 CPU 核数没有要求,启动内存在百兆级别,在普通的 Linux 机器均可启动。守护脚本 obproxyd.sh 通过 Linux 的 nc 命令尝试连接 ODP,如果端口连接不同,对 ODP 进行重新拉取,可以秒级拉取恢复高可用。
如上图的截图中,显示了安装好后,ODP 的目录。bin 目录存放二进制文件,etc 目录存放配置文件,log 可供用户查询日志相关。
ODP 的部署有两种方式。
- 进入客户端进行部署。和应用是1:1的关系,减少延迟,并且排查问题相对简单。
- 集中部署。该方式将应用的机器连接SLB,SLB在通过负载均衡将流量发送给 ODP,ODP 再将流量发送给 OBServer。比如在私有云的场景中,客户不允许将 ODP 部署到应用机器,则只能采用该架构。在公有云场景中,ODP 和 OBServer 之间不能够有 SLB ,因此只能将 ODP 放在 SLB 后,SLB 做四层负载均衡,ODP 做七层负载。ODP 本身的扩展或摘除非常简单,只需要在启动后修改配置即可。
3.2 监控
系统提供了 SQL 化的接口命令查看 ODP 的状态。例如,用户可以通过 MySQL 的命令行连接管理员账号,然后通过特定的命令查看例如 ODP 的内存使用等。此外,ODP 支持普罗米修斯系统,可以将数据传输给普罗米修斯系统。最后,监控比较关键的日志,如慢 SQL 、错误 SQL、SQL 统计日志,以及 SQL 审计日志,监控指标包括 QPS、TPS、连接数、响应时间、错误数等。
3.3 运维
我们提供一个管理员的账号—— root@proxysys。连接这个账号,请求并不会发往 OBServer ,并且整套命令和 MySQL 协议完全兼容,用户可以用 MySQL 命令行或者用 jdbc 程序来运维 ODP。
这里介绍两个内部账号。第一个是运维管理员。第二个是一个 sys 租户的域账号,这个账号是 ODP 访问 OBServer 的内部数据的账号。现在 ODP 访问 OBServer 采用了 SQL 的接口,目前通过这个账号来实现的。
3.4 ODP 问题分析
ODP 如果出现了问题,该如何分析?
- 系统相关
- 程序 core,不符合预期。因为 obproxyd.sh 会快速拉取,所以可能在程序 core 后,迅速被拉取了,导致用户对此没有感知。可以通过 dmesg 进行查看。
- 内存相关。ODP 会设置一个内存上限,如果 ODP 使用内存超过上限,ODP 会主动退出并打印关键日志。
- 目录和文件权限的问题。比如,程序启动不起来什么的,可能因为之前用 root 账号操作,后面用 admin 账号的操作,导致的目录和文件权限问题。
- 业务相关
- 登录失败。一般该问题出现在配置不对等原因。
- 连接关闭。比如某个业务运行了几个小时,突然间中断了,导致该情况的原因较多,在前文连接管理中有所阐述。
- 慢 SQL。ODP 会对自身的主要环节,如路由规则打印进行耗时统计,方便定位问题。所以,需要用户定义好慢 SQL 的阈值,由此,SQL 执行超过该阈值,ODP 就会打印详细信息,供后续分析。
- 错误 SQL。如果 SQL 执行失败,obproxy_error.log 会记录错误信息。
3.4 ODP 日志介绍
ODP 的日志相对来说非常规范,基本的种类如 digest 日志是审计,slow 日志是慢 SQL,error 日志是错误 SQL,stat 日志是 SQL 的统计信息。相关 SQL 的关键信息在这里简单列举:如时间、租户、SQL 、错误码等。
这里介绍一下相关问题的处理思路:
- 如断连接或 SQL 执行失败。一般,不推荐用户先去看主日志,因为这个日志比较复杂难懂。如果是 SQL 登录失败的情况,在 error 中就会有记录,里面会有关键信息,获得 tracert 后再进行搜索,这样会高效很多。
- 对于慢 SQL ,在 obproxy_slow.log 日志以及主日志中搜索关键字 Slow Query,包含了每一个阶段的执行时间,可以查看哪一段时间不符合预期,再进行分析。
- 如果在查看 OceanBase 的 SQL 审计时发现 SQL audit 有个问题没有落盘,则可以查看 digest 日志,可能与淘汰机制和内存大小有关。
- 如果用户想要统计全量的通过 ODP 的数据,可以将 digest 的配置项改成一微秒。
该问题答案为 BC。
A 选项正确。MySQL 模式下,用户采用 MySQL 的驱动程序即可。唯一需要注意的是,建议用户使用5.71相关的版本,不要使用太新或者太旧的版本。
B 选项错误。ODP 无法保证路由 100%正确,可能会有各种各样复杂的原因导致路有错误。
C 选项错误。如果 OBproxy 和 OBserver 之间的连接断了,ODP 会主动地去对这种错误去进行容错。但是如果客户端与 ODP 之间的连接断了,比如用户在 jdbc 中设置 socket timeout 等于 5 秒,但实际的执行时间是15秒,那么 jdbc 程序就会把 TCP 连接断掉,此时 OBproxy 和 OBserver 的连接也不再需要,这个连接也会断掉。
D 选项正确。
四、Q&A
- 开源版 ODP和 OBproxy 有什么区别?这里 ODP 支持 RDS/OceanBase/MySQL 分库分表吗?
这里区别在于产品名字叫 ODP,二进制程序的名字叫 OBproxy 。针对第二个问题,首先我们是 OceanBase 公司的产品,支持 OceanBase 的分库分表。针对 RDS 和 MySQL 的分库分表实现过,基本上是兼容的,但是后面用得比较少,建议大家使用 OceanBase 的分库分表。
- 场景:app->lvs->OBproxy->oceanbase。在这个场景下,app 段执行 SQL 超时了,MySQL 驱动会新建一个连接,执行 kill query thrdad_id(与 OBproxy 创建连接的时候,连接的id),将超时 SQL kill。lvs 后的 OBproxy 有多个,什么机器保证 kill query 是正确的?两种情况:lvs 没有开会话保持,lvs 将新建的连接发错到其他 OBproxy,会不会导致杀错session?
这个场景可以理解为,比如 App 的一个 SQL 执行了 10 分钟,它不想再执行了。但是没有触发 OBserver 的 query timeout,因此 SQL 还在执行,此时用户想把这个 query 给关掉。然后 MySQL 驱动再新建一个连接,此时它这个请求很可能就会发往别的 proxy,所以这种情况下我们建议先确定这个执行很长时间 SQL 发往哪个ODP了,然后去主动避免 lvs 连接该OBproxy,再去杀死该进程。整体的操作比较复杂,因为这个新建的连接 lvs 会把请求发送给另一个 ODP,可能会导致杀错 session 或者很可能是一个错误的 session ID,然后报错。
- 路由错了,ODP 去更新,那能不能主动刷新后再返回结果。不然就会有大量交易失败,交易损失太大了。
这里有两种情况,首先路由会先去缓存拿,如果这个缓存本身是错的,但是 ODP 不知道,还是会主动把请求根据缓存去发送,发送以后 OBserver 才会告诉 ODP 这个缓存失效了,我们会设计标记缓存失效的信息。第二种情况,如果用户第二次 SQL 来时,因为缓存信息失效了,此时我们支持先刷新缓存,然后再去路由,然后发往仲裁机器。目前如果是 31x 版本,有个配置项去控制,如果发现路由缓存错了,可以先进行刷新。其实大部分场景下,如果不做频繁的扩缩容,一般不会出现这种问题。如果是做机器的运维,也建议可以先通过 SQL 在业务低峰进行刷新,让缓存变成最新版本。
- OBServer 发生了切主,执行 SQL 会报错,但是这个 SQL 的交易就失败,这个影响范围太大了。还希望能发现是这个错就主动刷新 locationcache。能容忍偶尔的慢 SQL,但是交易损失大了,那就不能接受了。
如果说 OBserver 发生了切主,OBserver 会进行转发,而不会直接导致 SQL 的失败。但如果 SQL 执行时间超过了 jdbc 的超时时间,jdbc 会主动 kill,这时才会导致 SQL 失败。举一个 OBserver 切主的场景,比如每日合并,对于每日合并的机器,ODP 会去做容错,SQL 失败需要进行详细的分析。
- ODP 如何解决有前端 app 的全链路跟踪?比如适配 skywalking。
全链路跟踪,一方面需要业务方配合。比如蚂蚁内部,业务 SQL中 OceanBase 只是 hint ,用户可以写一些自己的 hint ,传入 tracert,获得相关的全链路信息。另一方面比如说像开源用户或者是商业用户,很可能得不到别的业务方的配合,所以目前我们主动去 jdbc 中进行设置,来做到全链路跟踪。目前我们还没有完全实现全链路跟踪,但是相关的产品正在研发中。
最后的最后,您有任何疑问都可以通过以下方式联系到我们~
联系我们
欢迎广大 OceanBase 爱好者、用户和客户随时与我们联系、反馈,方式如下:
社区版官网论坛
社区版项目网站提 Issue