diff --git a/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction.md b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction.md new file mode 100644 index 000000000..62c32ac28 --- /dev/null +++ b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction.md @@ -0,0 +1,516 @@ +--- +title: ODP 背景知识 +weight: 1 +--- + +> 如果大家对 OceanBase 的 ODP(OceanBase Database Proxy)组件尚不了解,在阅读社区版 ODP 问题排查手册之前,建议先快速浏览这篇文档,以了解 ODP 相关的背景知识。 +> +> **如果时间紧迫,可以只阅读本文最开始的 “ODP 的特性” 和 “ODP 路由的实现逻辑” 这两个部分。** + +ODP 是代理服务器,代理服务器会让访问数据库的链路多一跳,那为什么需要 ODP 呢?我们以下图为例进行说明。 + +![代理](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/001.png) + +图中 APP 是我们的业务程序,APP 下面有三台 ODP(ODP 的进程名叫做 obproxy),在实际部署中,ODP 和 APP 之间一般会有一个负载均衡(如 F5、LVS 或 Nginx 等)将请求分散到多台 ODP 上面,ODP 下面是 OBServer 节点,图中有 6 个 OBServer 节点。 + +需要使用 ODP 的原因如下: + +- 数据路由 + + ODP 可以获取到 OBServer 节点中的数据分布信息,可以将用户 SQL 高效转发到数据所在机器,执行效率更高。例如,对于 `insert into t1 where c1 in P1` 语句 ODP 可以将 SQL 转发到 Zone 2 中含有 P1 主副本的机器上。对于 `update t1 where c1 in P2` 语句 ODP 可以将 SQL 转发到 Zone 1 中含有 P2 主副本的机器上。 + +- 连接管理 + + 如果一个 OceanBase 集群的规模比较大,那么运维机器上、下线以及机器出现问题的概率也会相应增大。如果直连 OBServer 节点,遇到上面的情况,客户端就会发生断连。ODP 屏蔽了 OBServer 节点本身分布式的复杂性,客户连接 ODP,ODP 可以保证连接的稳定性,自身对 OBServer 节点的复杂状态进行处理。 + +也就是说,ODP 可以实现像使用单机数据库一样使用分布式数据库。 + +## ODP 的特性 + +作为 OceanBase 数据库的关键组件,ODP 具有如下特性: + +- 高性能转发 + + ODP 完整兼容 MySQL 协议,并支持 OceanBase 自研协议,采用多线程异步框架和透明流式转发的设计,保证了数据的高性能转发,同时确保了自身对机器资源的最小消耗。 + +- 最佳路由 + + ODP 会充分考虑用户请求涉及的副本位置、用户配置的读写分离路由策略、OceanBase 数据库多地部署的最优链路,以及 OceanBase 数据库各机器的状态及负载情况,将用户的请求路由到最佳的 OBServer 节点上,最大程度的保证了 OceanBase 数据库整体的高性能运转。 + +- 连接管理 + + 针对一个客户端的物理连接,ODP 维持自身到后端多个 OBServer 节点的连接,采用基于版本号的增量同步方案维持了每个 OBServer 节点连接的会话状态,保证了客户端高效访问各个 OBServer 节点。 + +- 安全可信 + + ODP 支持使用 SSL 访问数据,并和 MySQL 协议做了兼容,满足客户安全需求。 + +- 易运维 + + ODP 本身无状态支持无限水平扩展,支持同时访问多个 OceanBase 集群。可以通过丰富的内部命令实现对自身状态的实时监控,提供极大的运维便利性。 + +ODP 社区版完全开源,使用 MulanPubL - 2.0 许可证,用户可以免费复制和使用源代码,修改或分发源代码时,请遵守木兰协议。 + +## ODP 路由的性能因素 + +高性能是 OceanBase 数据库的重要特性,路由对性能的影响主要在网络通信开销方面。ODP 通过感知数据分布和机器地理位置降低网络通信开销,提高整体性能。第七章是介绍 SQL 性能诊断和调优的章节,所以我们这一小节主要会从性能方面对路由策略进行介绍。 + +我们首先会介绍三个背景知识: + +- ODP 路由的实现逻辑是什么? + +- SQL 主要的计划类型有哪些? + +- 如何查看资源分布信息? + +### ODP 路由的实现逻辑 + +路由是 OceanBase 分布式数据库中的一个重要功能,是分布式架构下,实现快速访问数据的利器。 + +Partition 是 OceanBase 数据存储的基本单元。当我们创建一张 Table 时,就会存在表和 Partition 的映射。非分区表中,一张 Table 对应一个 Partition;分区表中一个 Table 会对应多个 Partition。 + +路由实现了根据 OBServer 节点的数据分布精准访问到数据所在的机器。同时还可以根据一定的策略将一致性要求不高的读请求发送给副本机器,充分利用机器的资源。路由选择输入的是用户的 SQL、用户配置规则、和 OBServer 节点状态,路由选择输出的是一个可用 OBServer 地址。 + +其路由实现逻辑如下图所示: + +![路由逻辑](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/002.png) + +1. 解析 SQL 并提取信息 + + 解析 SQL 模块使用的是 ODP 自己定制的 Parser 模块,只需要解析出 DML 语句中的数据库名、表名和 Hint,不需要通过其他复杂的表达式推演。 + +2. 通过 location cache(路由表)获取位置信息 + + ODP 根据用户的请求 SQL 获取该 SQL 涉及的副本位置。ODP 每次首先会尝试从本地缓存中获取路由表,其次是全局缓存,如果都没有获取到,最后会发起异步任务去向 OBServer 节点查询路由表。对于路由表的更新,ODP 采用触发更新机制。ODP 每次会把 SQL 根据路由表转发给相应的 OBServer 节点,当 OBServer 节点发现该 SQL 不能在本地执行时,会在回包时反馈给 ODP。ODP 根据反馈决定下次是否强制更新本地缓存路由表。通常是在 OBServer 节点合并或者负载均衡导致切主时,路由表才会发生变化。 + +3. 确定路由规则 + + ODP 需要根据不同情况确定最佳的路由规则。比如:强一致性读的 DML 请求期望发到分区主副本所在的 OBServer 节点上,弱一致性读的 DML 请求和其他请求则不要求,主副本和从副本均衡负载即可。如果 OceanBase 集群是多地部署,ODP 还提供了 LDC 路由,优先发给同机房的 OBServer 节点,其次是同城的 OBServer 节点,最后才是其他城市的 OBServer 节点。如果 OceanBase 集群是读写分离部署,ODP 还提供了读 Zone 优先、只限读 Zone、非合并优先等规则供业务按照自身特点配置。上述的几种情况在路由选择中是组合关系,输出是一个确定的路由规则。 + +4. 选择目标 OBServer 节点 + + 根据确定的路由规则从获取的路由表中选择最佳的 OBServer 节点,经过黑名单、灰名单检查后,对目标 OBServer 节点进行请求转发。 + +### SQL 的计划类型 + +分区(Partition)是数据存储的基本单元,当我们创建表时,就会存在表和分区的映射。如果是非分区表,一张表仅对应一个分区,如果是分区表,一张表可能会对应多个分区。每个分区根据副本角色又分为一个主副本和多个备副本,默认读写的都是分区的主副本。 + +在 OceanBase 数据库中,SQL 在执行前会生成一个执行计划,执行器根据这个执行计划来调度不同的算子完成计算。计划主要分为三种不同的类型,分别是本地(Local)计划、远程(Remote)计划和分布式(Distributed)计划。 + +下面以 OceanBase 数据库默认的强一致性读为例,介绍这三种不同的计划类型,即查询过程中需要访问分区的主(Leader)副本。 + +#### 本地计划 + +本地计划表示语句所涉及的所有分区的主副本都在当前 Session 所在的 OBServer 节点上,整条 SQL 在执行过程中,不需要和其他 OBServer 节点再进行额外地交互。 + +![本地计划](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/003.png) + +一般来说,本地执行通过 explain 命令看到的计划会长这样: + +```shell +======================================== +|ID|OPERATOR |NAME|EST. ROWS|COST | +---------------------------------------- +|0 |HASH JOIN | |98010000 |66774608| +|1 | TABLE SCAN|T1 |100000 |68478 | +|2 | TABLE SCAN|T2 |100000 |68478 | +======================================== +``` + +#### 远程计划 + +远程(Remote)计划表示当前语句所涉及的所有分区主副本都与当前 Session 所在的 OBServer 节点不同,且都集中在另外一台 OBServer 节点上,需要 OBServer 节点再对 SQL 或者子计划进行一次转发。 + +![远程计划](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/004.png) + +一般来说,远程执行通过 explain 命令看到的计划会长这样(存在 `EXCHANGE REMOTE` 算子): + +```shell +================================================== +|ID|OPERATOR |NAME|EST. ROWS|COST | +-------------------------------------------------- +|0 |EXCHANGE IN REMOTE | |98010000 |154912123| +|1 | EXCHANGE OUT REMOTE| |98010000 |66774608 | +|2 | HASH JOIN | |98010000 |66774608 | +|3 | TABLE SCAN |T1 |100000 |68478 | +|4 | TABLE SCAN |T2 |100000 |68478 | +================================================== +``` + +上面的计划中,0 号 `EXCHANGE IN REMOTE` 算子和 1 号 `EXCHANGE OUT REMOTE` 算子用于切分在不同 OBServer 节点上做的工作。 + +0 号算在所在的 OBServer 节点接收到 ODP 路由到的 SQL,负责生成这个远程计划,并把完整 SQL 或者 1 - 4 号算子构成的子计划转发到分区主副本所在的另一个 OBServer 节点。 + +1 - 4 号算子所在的 OBServer 节点负责对 `HASH JOIN` 进行计算,并通过 1 号算子 `EXCHANGE OUT REMOTE` 把计算结果返回给 0 号算在所在的 OBServer 节点。 + +最后 0 号算在所在的 OBServer 节点通过 `EXCHANGE IN REMOTE` 算子接收这个结果,并把计算结果继续向上层返回。 + +#### 分布式计划 + +分布式计划不能确定当前语句涉及到的分区主副本和当前 Session 的关系,往往都是 SQL 需要访问多个分区且多个分区的主副本分布在多个不同的 OBServer 节点上。 + +分布式计划会使用并行执行的方式进行调度,调度过程中会将其切分成多个操作步骤,每个操作步骤称之为一个 DFO(Data Flow Operation)。 + +![分布式计划](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/005.png) + +分布式执行通过 explain 命令看到的计划(存在 `EXCHANGE DISTR` 算子): + +```shell +================================================================ +|ID|OPERATOR |NAME |EST. ROWS|COST | +---------------------------------------------------------------- +|0 |PX COORDINATOR | |980100000|1546175452| +|1 | EXCHANGE OUT DISTR |:EX10002|980100000|664800304 | +|2 | HASH JOIN | |980100000|664800304 | +|3 | EXCHANGE IN DISTR | |200000 |213647 | +|4 | EXCHANGE OUT DISTR (HASH)|:EX10000|200000 |123720 | +|5 | PX BLOCK ITERATOR | |200000 |123720 | +|6 | TABLE SCAN |T1 |200000 |123720 | +|7 | EXCHANGE IN DISTR | |500000 |534080 | +|8 | EXCHANGE OUT DISTR (HASH)|:EX10001|500000 |309262 | +|9 | PX BLOCK ITERATOR | |500000 |309262 | +|10| TABLE SCAN |T2 |500000 |309262 | +================================================================ +``` + +总的来说,Local 计划和 Remote 计划涉及到的分区主副本都分布在单节点上,ODP 的作用就是尽量消除性能较差的 Remote 计划,将路由尽可能的变为性能更优的 Local 计划。 + +如果通过 OBServer 节点生成 Remote 计划后再进行转发,在转发前需要对 SQL 进行完整的 parser 和 resolver 流程,还需要在优化器模块对 SQL 进行改写和选择最佳的执行计划等等,而且转发的可能还是一个网络传输代价较大的子计划。 + +而 ODP 转发 SQL 只需要进行 SQL 解析和简单的分区信息获取即可(参见上面的 ODP 路由的实现逻辑),整体流程相比通过 OBServer 节点生成 Remote 计划后再进行转发要轻量的多。 + +如果表路由类型为 Remote 计划的 SQL 过多,说明该 SQL 的路由可能存在问题(可通过查看 oceanbase.GV$OB_SQL_AUDIT 视图中 `plan_type` 字段来确认)。查看 SQL 计划类型的相关 SQL 如下: + +```sql +MySQL [oceanbase]> select plan_type, count(1) from gv$ob_sql_audit where +request_time > time_to_usec('2021-08-24 18:00:00') group by plan_type; +``` + +输出如下: + +```shell ++-----------+----------+ +| plan_type | count(1) | ++-----------+----------+ +| 1 | 17119 | +| 0 | 9614 | +| 3 | 4400 | +| 2 | 23429 | ++-----------+----------+ +4 rows in set +``` + +其中,plan_type = 1、2、3 分别表示 Local、Remote、Distribute 执行计划。一般来讲,`0` 代表无 plan 的 SQL 语句,比如:`set autocommit=0/1`,`commit` 等。 + +### 资源分布信息 + +ODP 的主要功能是提供 SQL 路由。所以要先了解数据的位置,再了解 SQL 路由策略。 + +#### 查看租户资源单元位置 + +ODP 为了把 SQL 准确地路由到最佳的节点上,首先需要知道的就是租户资源所在的节点位置信息(LOCATION CACHE)。有如下 2 种方法可以确认租户的位置: + +- 系统(sys)租户直接查询 + + ```sql + select + t1.name resource_pool_name, + t2.`name` unit_config_name, + t2.max_cpu, + t2.min_cpu, + round(t2.memory_size / 1024 / 1024 / 1024) max_mem_gb, + round(t2.memory_size / 1024 / 1024 / 1024) min_mem_gb, + t3.unit_id, + t3.zone, + concat(t3.svr_ip, ':', t3.`svr_port`) observer, + t4.tenant_id, + t4.tenant_name + from + __all_resource_pool t1 + join __all_unit_config t2 on (t1.unit_config_id = t2.unit_config_id) + join __all_unit t3 on (t1.`resource_pool_id` = t3.`resource_pool_id`) + left join __all_tenant t4 on (t1.tenant_id = t4.tenant_id) + order by + t1.`resource_pool_id`, + t2.`unit_config_id`, + t3.unit_id; + ``` + + 输出如下: + + ```shell + +------------------------------+-----------------------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+---------------+ + | resource_pool_name | unit_config_name | max_cpu | min_cpu | max_mem_gb | min_mem_gb | unit_id | zone | observer | tenant_id | tenant_name | + +------------------------------+-----------------------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+---------------+ + | sys_pool | config_sys_zone1_xiaofeng_sys_lpj | 3 | 3 | 6 | 6 | 1 | zone1 | xx.xxx.xx.20:22602 | 1 | sys | + | pool_for_tenant_mysql | 2c2g | 2 | 2 | 2 | 2 | 1001 | zone1 | xx.xxx.xx.20:22602 | 1002 | mysql | + | pool_mysql_standby_zone1_xcl | config_mysql_standby_zone1_S1_xic | 1.5 | 1.5 | 6 | 6 | 1002 | zone1 | xx.xxx.xx.20:22602 | 1004 | mysql_standby | + +------------------------------+-----------------------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+---------------+ + 3 rows in set + ``` + +- 普通用户租户直接查询 + + ```sql + select * from GV$OB_UNITS where tenant_id=1002; + ``` + + 在业务租户里,条件 `tenant_id=1002` 也可以不加,因为每个业务租户只能查看自己的租户资源单元信息,命令输出如下: + + ```shell + +--------------+----------+---------+-----------+-------+-----------+------------+---------+---------+-------------+---------------------+---------------------+-------------+---------------+-----------------+------------------+--------+----------------------------+ + | SVR_IP | SVR_PORT | UNIT_ID | TENANT_ID | ZONE | ZONE_TYPE | REGION | MAX_CPU | MIN_CPU | MEMORY_SIZE | MAX_IOPS | MIN_IOPS | IOPS_WEIGHT | LOG_DISK_SIZE | LOG_DISK_IN_USE | DATA_DISK_IN_USE | STATUS | CREATE_TIME | + +--------------+----------+---------+-----------+-------+-----------+------------+---------+---------+-------------+---------------------+---------------------+-------------+---------------+-----------------+------------------+--------+----------------------------+ + | 1.2.3.4 | 22602 | 1001 | 1002 | zone1 | ReadWrite | sys_region | 2 | 2 | 1073741824 | 9223372036854775807 | 9223372036854775807 | 2 | 5798205850 | 4607930545 | 20971520 | NORMAL | 2023-11-20 11:09:55.668007 | + +--------------+----------+---------+-----------+-------+-----------+------------+---------+---------+-------------+---------------------+---------------------+-------------+---------------+-----------------+------------------+--------+----------------------------+ + 1 row in set + ``` + +#### 查看分区副本位置 + +根据上面介绍的 ODP 路由的实现逻辑,ODP 在解析 SQL 获得涉及到的表和分区信息后,需要根据表和分区的信息获得对应分区的主副本的位置信息。 + +OceanBase 数据库 4.x 版本的副本管理策略是租户级的,即同一个租户下所有表的 Primary Zone 的规则是统一的。在系统租户下可以通过查询 `oceanbase.DBA_OB_TENANTS` 表确定租户信息。 + +```sql +select * from oceanbase.DBA_OB_TENANTS; +``` + +输出如下: + +```shell ++-----------+-------------+-------------+----------------------------+----------------------------+--------------+---------------+-------------------+--------------------+--------+---------------+--------+-------------+-------------------+------------------+---------------------+---------------------+---------------------+---------------------+--------------+----------------------------+----------+------------+-----------+ +| TENANT_ID | TENANT_NAME | TENANT_TYPE | CREATE_TIME | MODIFY_TIME | PRIMARY_ZONE | LOCALITY | PREVIOUS_LOCALITY | COMPATIBILITY_MODE | STATUS | IN_RECYCLEBIN | LOCKED | TENANT_ROLE | SWITCHOVER_STATUS | SWITCHOVER_EPOCH | SYNC_SCN | REPLAYABLE_SCN | READABLE_SCN | RECOVERY_UNTIL_SCN | LOG_MODE | ARBITRATION_SERVICE_STATUS | UNIT_NUM | COMPATIBLE | MAX_LS_ID | ++-----------+-------------+-------------+----------------------------+----------------------------+--------------+---------------+-------------------+--------------------+--------+---------------+--------+-------------+-------------------+------------------+---------------------+---------------------+---------------------+---------------------+--------------+----------------------------+----------+------------+-----------+ +| 1 | sys | SYS | 2024-04-10 10:48:59.526612 | 2024-04-10 10:48:59.526612 | RANDOM | FULL{1}@zone1 | NULL | MYSQL | NORMAL | NO | NO | PRIMARY | NORMAL | 0 | NULL | NULL | NULL | NULL | NOARCHIVELOG | DISABLED | 1 | 4.2.3.0 | 1 | +| 1001 | META$1002 | META | 2024-04-10 10:49:30.029481 | 2024-04-10 10:50:27.254959 | zone1 | FULL{1}@zone1 | NULL | MYSQL | NORMAL | NO | NO | PRIMARY | NORMAL | 0 | NULL | NULL | NULL | NULL | NOARCHIVELOG | DISABLED | 1 | 4.2.3.0 | 1 | +| 1002 | mysql | USER | 2024-04-10 10:49:30.048284 | 2024-04-10 10:50:27.458529 | zone1 | FULL{1}@zone1 | NULL | MYSQL | NORMAL | NO | NO | PRIMARY | NORMAL | 0 | 1717384184174664001 | 1717384184174664001 | 1717384184174664001 | 4611686018427387903 | NOARCHIVELOG | DISABLED | 1 | 4.2.3.0 | 1001 | ++-----------+-------------+-------------+----------------------------+----------------------------+--------------+---------------+-------------------+--------------------+--------+---------------+--------+-------------+-------------------+------------------+---------------------+---------------------+---------------------+---------------------+--------------+----------------------------+----------+------------+-----------+ +3 rows in set +``` + +在系统租户下,可以通过查询 `oceanbase.cdb_ob_table_locations` 获得各个租户中所有表的各分区位置信息。 + +```sql +select * from oceanbase.cdb_ob_table_locations where table_name = 't1'; +``` + +输出如下: + +```shell ++-----------+---------------+------------+----------+------------+----------------+-------------------+------------+---------------+-----------+-------+-------+--------------+----------+--------+--------------+-----------------+-----------+-----------------+---------------+----------+ +| TENANT_ID | DATABASE_NAME | TABLE_NAME | TABLE_ID | TABLE_TYPE | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | DATA_TABLE_ID | TABLET_ID | LS_ID | ZONE | SVR_IP | SVR_PORT | ROLE | REPLICA_TYPE | DUPLICATE_SCOPE | OBJECT_ID | TABLEGROUP_NAME | TABLEGROUP_ID | SHARDING | ++-----------+---------------+------------+----------+------------+----------------+-------------------+------------+---------------+-----------+-------+-------+--------------+----------+--------+--------------+-----------------+-----------+-----------------+---------------+----------+ +| 1 | oceanbase | t1 | 500010 | USER TABLE | NULL | NULL | NULL | NULL | 200003 | 1 | zone1 | xx.xxx.xx.20 | 22602 | LEADER | FULL | NONE | 500010 | NULL | NULL | NULL | +| 1002 | test | t1 | 500087 | USER TABLE | NULL | NULL | NULL | NULL | 200049 | 1001 | zone1 | xx.xxx.xx.20 | 22602 | LEADER | FULL | NONE | 500087 | NULL | NULL | NULL | +| 1004 | test | t1 | 500003 | USER TABLE | NULL | NULL | NULL | NULL | 200001 | 1001 | zone1 | xx.xxx.xx.20 | 22602 | LEADER | FULL | NONE | 500003 | NULL | NULL | NULL | ++-----------+---------------+------------+----------+------------+----------------+-------------------+------------+---------------+-----------+-------+-------+--------------+----------+--------+--------------+-----------------+-----------+-----------------+---------------+----------+ +3 rows in set +``` + +在普通租户下,可以通过查询 `oceanbase.dba_ob_table_locations` 获得各个租户中所有表的各分区位置信息。 + +```sql +select database_name, table_name, table_id, table_type, zone, svr_ip, role from oceanbase.dba_ob_table_locations where table_name = 't1'; +``` + +输出如下: + +```shell ++---------------+------------+----------+------------+-------+--------------+--------+ +| database_name | table_name | table_id | table_type | zone | svr_ip | role | ++---------------+------------+----------+------------+-------+--------------+--------+ +| test | t1 | 500087 | USER TABLE | zone1 | xx.xxx.xx.20 | LEADER | ++---------------+------------+----------+------------+-------+--------------+--------+ +1 row in set +``` + +#### 查看和调整 LDC 设置 + +逻辑数据中心(Logical Data Center,LDC)路由可用于解决分布式关系型数据库中多地多中心部署时产生的异地路由延迟问题。 + +OceanBase 数据库作为典型的高可用分布式关系型数据库,使用 Paxos 协议进行日志同步,天然支持多地多中心的部署方式以提供高可靠的容灾保证。但当真正多地多中心部署时,任何数据库都会面临异地路由延迟问题。逻辑数据中心(Logical Data Center,LDC)路由正是为了解决这一问题而设计的。在为 OceanBase 集群的每个 Zone 设置地区(Region)属性和机房(IDC)属性,并为 ODP 指定机房(IDC)名称配置项的情况下,当数据请求发到 ODP 时,ODP 将按 “同机房 > 同地区 > 异地” 的优先级顺序进行 OBServer 节点的选取。具体的设置方法详见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/路由策略路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517780)。 + +![LDC](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/006.png) + +同时,OceanBase 数据库还支持通过调整系统变量的方式改变默认的路由策略,详见官网《OceanBase 数据库》文档 [参考指南/系统原理/数据链路/数据库代理/SQL 路由](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001050895)。 + +## ODP 路由的高可用因素 + +高可用因素是指 OceanBase 数据库对机器故障有容忍能力,让故障对应用透明无感知,ODP 发现 OBServer 节点故障后,路由时会排除故障节点,选择健康节点,对于正在执行的SQL也有一定的重试能力。高可用涉及故障探测、黑名单机制、重试逻辑等内容。如图所示,ODP 发现 OBServer1 故障后,将该节点加入黑名单。路由时从健康节点选择。 + +![高可用](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/007.png) + +了解了数据路由的影响因素和路由原则后,我们就可以更高效地进行路由策略设计了。不过,现实情况会复杂很多,原则上我们要实时感知 OBServer 节点状态、数据分布等,但在工程实践中很难做到,便引发出许多问题。因此,我们在考虑路由时需要兼顾功能、性能和高可用,让 OceanBase 数据库“更好用”。 + +## ODP 路由的功能和策略 + +ODP 实现了集群路由、租户路由和租户内路由,通过 ODP 可以访问不同集群的不同租户的不同机器。接下来我们将围绕这三部分介绍 ODP 的路由功能。 + +### 集群路由 + +集群路由是指 ODP 路由功能支持访问不同的集群,它的关键点在于获取集群名和 rslist(rootservice_list) 的映射关系: + +- 对于启动参数指定 rslist 的启动方式,集群名和 rslist 的映射关系通过启动参数指定。 + +- 对于指定 `config_server_url` 的启动方式,集群名和 rslist 的映射关系通过访问 URL 获取。 + +需要注意的是,这里的 rslist 不需要包含所有的集群机器列表,ODP 会通过访问视图获取集群所有机器,一般 rslist 为 RootServer(OceanBase 数据库的总控服务)所在的机器。 + +![集群路由步骤](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/008.png) + +从上图中可以看到,OCP 是集群路由时非常重要的一个模块。当生产环境中出现集群路由问题时,需着重排查是否是 OCP 模块出现了问题。 + +ODP 是在用户登录首次访问集群时获取 rslist,并保存到内存中,后续再访问该集群,从 ODP 的内存中获取就可以了。 + +需要注意的是,因为 ODP 路由功能支持访问不同的集群,所以在命令行中通过 ODP(假设 ODP 的 ip 和 port 分别为 `127.0.0.1` 和 `2883`)进行连接时,除了需要指定用户名 user_name 和租户名 tenant_name 外,还需要额外指定要连接的集群名 cluster_name。如果一些周边工具例如 ODC 等,说明必须通过 ODP 进行连接,在 ODC 等工具中配置连接串时,也需要在连接串中加上集群名 cluster_name,例如: + +```shell +mysql -h 127.0.0.1 -u user_name@tenant_name#cluster_name -P2883 -Ddatabase_name -pPASSWORD +``` + +如果在命令行中不通过 ODP 进行连接,而是对 OBServer 节点(假设 OBServer 节点的 ip 和 port 分别为 `1.2.3.4` 和 `12345`)进行直连,那么就只需要指定用户名和租户名,不能再额外指定对应的集群名了。例如: + +```shell +mysql -h 1.2.3.4 -u user_name@tenant_name -P12345 -Ddatabase_name -pPASSWORD +``` + +### 租户路由 + +OceanBase 数据库中,一个集群有多个租户,租户路由是指 ODP 路由功能支持访问不同的租户。在众多租户中,sys 租户比较特殊,类似于管理员租户,和集群管理相关。我们将分开讨论 sys 租户路由和普通租户路由。 + +#### sys 租户路由 + +ODP 完成上述的集群路由后,可获得集群的 rslist,此时 ODP 会通过 proxyro@sys 账号登录 rslist 中的一台机器,并通过视图 `DBA_OB_SERVERS` 获取集群的所有机器节点。 + +在 OceanBase 数据库的现有实现中,sys 在每个节点都有分布,因此,`DBA_OB_SERVERS` 返回的结果也就是 sys 租户的路由信息。 + +ODP 会每 15 秒访问一次 `DBA_OB_SERVERS`,维护最新的路由信息,这样可以感知到集群发生的节点变更。除了集群机器列表,ODP 还会通过 sys 租户获取 partition 分布信息、Zone 信息、租户信息等。 + +#### 普通租户路由 + +与 sys 租户的路由信息就是集群的机器列表不同,普通租户路由信息是租户资源所在的机器。 + +ODP 查询租户路由信息并不是通过 unit 相关的表,而是通过特殊表名 `__all_dummy` 表示查询租户信息。ODP 需要通过内部表 `__all_virtual_proxy_schema` 获取租户的机器列表,在访问 `__all_virtual_proxy_schema` 时,ODP 指定表名(`__all_dummy`)和指定租户名获取租户的节点信息。 + +![普通租户路由](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/009.png) + +ODP 会将获取到的租户信息保存在本地内存中,并根据一定策略进行缓存信息的更新。对于 sys 租户,通过每 15 秒一次的拉取任务获得最新的信息;对于普通租户,ODP 的刷新频率并不高,普通租户的路由缓存策略如下: + +- 创建:首次访问租户时,通过 `__all_virtual_proxy_schema` 获得普通租户路由信息并创建。 + +- 淘汰:当 OBServer 节点返回错误码 `OB_TENANT_NOT_IN_SERVER` 时设置缓存失效。 + +- 更新:当缓存失效后重新访问 `__all_virtual_proxy_schema` 获得普通租户路由信息。 + +总的来说,在多租户架构下,ODP 通过 sys 租户获得元数据信息(sys 租户本身路由信息就是集群的机器列表),然后通过元数据信息获得租户的路由信息。通过租户路由功能,ODP 支持了 OceanBase 数据库的多租户架构。 + +#### 租户内路由 + +##### 强一致性读路由策略 + +路由策略很多,这里针对部分主要的路由策略由简单到复杂进行列举。 + +- 强一致性读路由策略将 SQL 路由到访问表的分区的主副本所在节点。这一条理解起来比较简单,但实际 SQL 情形很复杂。 + +- 如果 SQL 访问了两个表,会依据第一个表及其条件判断出该分区主副本节点。如果无法判断就随机发。所以 SQL 里多表连接时,表的前后顺序对路由策略是有影响的,间接对性能有影响。 + +- 如果要判断的表是分区表,会判断条件是否是分区键等值条件。如果不是,则不能确定是哪个分区,就随机发到该表的所有分区所在的节点任意一个。 + +- 如果开启事务了,则事务里开启事务的 SQL 的路由节点会作为事务后面其他 SQL 路由的目标节点,直到事务结束(提交或者回滚)为止。 + +- 当 SQL 被 ODP 路由分配到一个节点上时: + + - 如果要访问的数据分区的主副本恰好在那个节点上,SQL 就在该节点执行,这个 SQL 的执行类型是本地 SQL(`plan_type` 为 `1`)。 + + - 如果要访问的数据分区的主副本不在这个节点上,SQL 会被 OBServer 节点再次转发。这个 SQL 的执行类型是远程 SQL(`plan_type` 为 `2`)。 + + - 如果 SQL 执行计划要访问的数据分区是跨越多个节点,则这个 SQL 的执行类型是分布式 SQL(`plan_type` 为 `3`)。 + +- 如果事务中有复制表的读 SQL,只要 SQL 被路由到的节点上有该复制表的备副本,则该 SQL 可以读取本地备副本,因为复制表的所有备副本跟主副本是强一致的。这个 SQL 的执行类型是本地 SQL。 + +实际 SQL 类型很复杂,ODP 的路由策略也变得很复杂,有时候会出现路由不准的情形。如果不符合设计预期就可能会产生 BUG,但很可能也是设计如此(BY DESIGN)。毕竟当前版本的 ODP 只能做简单的 SQL 解析,不像 OceanBase 数据库那样做完整的执行计划解析。 + +当业务 SQL 很多很复杂时,远程 SQL 和分布式 SQL 将会无法避免,这时主要观察远程 SQL 和分布式 SQL 在业务 SQL 中的占比。如果比例很高,整体上业务 SQL 性能都不会很好。此时应尽可能地减少远程 SQL 和分布式 SQL。比如,通过表分组、复制表和 PRIMARY_ZONE 设置等。 + +##### 弱一致性读路由策略 + +OceanBase 数据库默认是强一致性读,即写后读立即可见(READ AFTER WRITER)。使用强一致性读策略时,ODP 会优先路由到访问表的分区的主副本节点上。 + +和强一致性读对立的就是弱一致性读,弱一致性读不要求写后读立即可见。弱一致性读也可以路由到分区的主副本和备副本节点,通常有三副本所在节点可以选。 + +但是开启弱一致性读后,如果 OceanBase 数据库和 ODP 都开启了 LDC 特性,那么弱一致性读语句的路由策略会改变,弱一致性读语句将按下述顺序进行路由: + +- 同一个机房或者同一个 Region 状态不是合并中(merging)的节点。 + +- 同一 Region 中正在合并的节点。 + +- 其他 Region 的不在合并的或在合并的节点。 + +即 ODP 会尽力避开合并中的节点。不过若 OceanBase 集群关闭了轮转合并(参数 `enable_merge_by_turn` 设置为 `false`),合并(major freeze)则是所有节点都开始合并,那么 ODP 也就无法避开合并中的节点。 + +还有一些 SQL 不是访问数据,而是查看或者设置变量值等。如: + +```sql +set @@autocommit=off +show variables like 'autocommit'; +``` + +这类 SQL 的路由策略则是随机路由。在随机路由策略中,如果 OceanBase 数据库和 ODP 开启了 LDC 设置,也会按照上文的路由顺序进行路由。 + +弱一致性读通常用在读写分离场景中。不过当租户 PRIMARY_ZONE 为 `RANDOM` 时,租户的所有分区的主副本也是分散在所有 Zone 下,这时弱一致性读备副本的意义也不是很大。 + +但是,如果使用了只读副本,只读副本设置为独立的 IDC,然后单独的 ODP 设置为同一个 IDC,则这个 ODP 可以用于只读副本的路由。 + +##### 其他路由策略 + +ODP 的路由策略非常丰富,本小节只做大概的介绍。大家仅需了解上述的两个最基本的 ODP 路由策略(强度策略和弱读策略)即可。 + +ODP 租户内路由的完整策略如下。 + +- 指定 IP 路由 + + 通过 ODP 配置项(`target_db_server`)或语句注释指定 OBServer 节点,ODP 会将语句准确路由至指定的 OBServer 节点。此路由功能优先级最高,当指定 IP 时,ODP 会忽略其他的路由功能。详细介绍请参见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/指定 IP 路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517773)。 + +- 强读分区表路由 + + 在强读分区表的语句中提供分区键值、表达式或分区名称,ODP 会将语句准确路由到数据所在分区的主副本 OBServer 节点执行。详细介绍请参见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/强读分区表路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517777)。 + +- 强读全局索引表路由 + + 在强读主表的语句中提供全局索引表的列值、表达式或索引分区名称,ODP 会将语句准确路由到数据所在的索引分区的主副本 OBServer 节点执行。详细介绍请参见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/强读全局索引表路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517779)。 + +- 强读复制表路由 + + 在强读复制表时,ODP 将语句路由至与 ODP 位置关系最近的 OBServer 节点执行。详细介绍请参见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/强读复制表路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517774)。 + +- 强读 Primary Zone 路由 + + 通过 ODP 配置项配置 Primary Zone,ODP 将无法计算路由的强读语句路由至 Primary Zone 内的 OBServer 节点。详细介绍请参见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/强读 Primary Zone 路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517782)。 + +- 路由策略路由 + + ODP 按照用户配置的路由策略规则进行路由。详细介绍请参见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/路由策略路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517780)。 + +- 分布式事务路由 + + 通过 ODP 配置项(enable_transaction_internal_routing)开启,开启后事务内的语句不受事务开启节点限制,无需强制路由至事务开启的 OBServer 节点。详细介绍请参见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/分布式事务路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517775)。 + +- 二次路由 + + 通过 ODP 配置项开启后,当语句路由至某 OBServer 节点,但未命中分区或者分布式事务无法在该 OBServer 节点执行时,ODP 可以重新进行一次准确路由。详细介绍请参见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/二次路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517781)。 + +- 强制路由 + + 用户无法控制此行为,由 ODP 决定是否强制路由,主要有以下几种情况。详详细介绍请参见官网《OceanBase 数据库代理》文档 [数据路由/租户内路由/强制路由](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000000517776)。 + + - 非分布式事务路由,事务内语句强制路由至事务开启 OBServer 节点。 + + - 会话级临时表路由,对会话级临时表进行查询时,会强制路由至第一次查询临时表的 OBServer 节点。 + + - 复用会话路由,当计算路由失败且 enable_cached_server 配置项开启时,ODP 会强制路由到上一次会话所在的 OBServer 节点。 + + - CURSOR/PIECES 路由,客户端使用 CURSOR/PIECES 流式获取/上传数据时,所有请求会强制路由至同一 OBServer 节点。 + + ## 推荐阅读 + - OceanBase 社区博客专题:[《详解 OBProxy:高性能的数据访问中间件》](https://open.oceanbase.com/blog/topics/3983484160)。 \ No newline at end of file diff --git a/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/02_show_trace.md b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/02_show_trace.md new file mode 100644 index 000000000..aaa5edd7b --- /dev/null +++ b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/02_show_trace.md @@ -0,0 +1,201 @@ +--- +title: 排查 ODP 是否是性能瓶颈 +weight: 2 +--- + +> 当用户遇到 SQL 性能不符合预期问题时,一般首先需要通过全链路追踪(show trace)确认各阶段耗时占比,确认耗时长的阶段是什么。 +> +> **这个小节就为大家介绍在 SQL 性能差时,如何通过 show trace 来为 ODP 洗脱罪名。如果大家已经能够熟练应用 show trace 对 SQL 性能问题进行初步分析,可以直接跳过本小节。** + +## 全链路追踪(show trace) + +OceanBase 数据库的数据链路为 `APPServer <-> OBProxy <-> OBServer`。APPServer 通过数据库驱动连接 ODP 发送请求,由于 OceanBase 数据库的分布式架构,用户数据以多分区多副本的方式分布于多个 OBServer 节点上,ODP 将用户请求转发到最合适的 OBServer 节点执行,并将执行结果返回用户。每个 OBServer 节点也有路由转发的功能,如果发现请求不能在当前节点执行,则会转发请求到正确的 OBServer 节点。 + +当出现端到端的性能问题时(在数据库场景下,端到端表示在应用服务器上观察到 SQL 请求的 RT 很高),此时首先需要定位是数据库访问链路上哪个组件的问题,再排查组件内的具体问题。 + +![全链路追踪](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/02_show_trace/001.png) + +全链路追踪覆盖了两条主要的数据流路径: + +- 一条是请求从应用出发,通过客户端(比如 JDBC 或 OCI)传递至 ODP(代理服务器),然后由 ODP 转发至 OBServer 节点,最终结果返回给应用程序。 + +- 另一条则是请求直接从应用程序通过客户端发送至 OBServer 节点,然后结果直接返回。 + +### 示例 + +接下来用这两条路径,分别举个一简单的例子。 + +1. 通过 ODP 连接 OceanBase 数据库,创建表并插入数据。 + + ```sql + create table t1(c1 int); + + insert into t1 values(123); + ``` + +2. 在当前 session 上开启全链路诊断的 show trace 功能。 + + ```sql + SET ob_enable_show_trace = ON; + ``` + +3. 执行一条简单的查询语句。 + + ```sql + SELECT c1 FROM t1 LIMIT 2; + ``` + + 输出如下: + + ```shell + +------+ + | c1 | + +------+ + | 123 | + +------+ + 1 row in set + ``` + +4. 执行 show trace 语句。 + + ```sql + SHOW TRACE; + ``` + + 输出如下: + + ```shell + +-----------------------------------------------------------+----------------------------+------------+ + | Operation | StartTime | ElapseTime | + +-----------------------------------------------------------+----------------------------+------------+ + | ob_proxy | 2024-03-20 15:07:46.419433 | 191.999 ms | + | ├── ob_proxy_partition_location_lookup | 2024-03-20 15:07:46.419494 | 181.839 ms | + | ├── ob_proxy_server_process_req | 2024-03-20 15:07:46.601697 | 9.138 ms | + | └── com_query_process | 2024-03-20 15:07:46.601920 | 8.824 ms | + | └── mpquery_single_stmt | 2024-03-20 15:07:46.601940 | 8.765 ms | + | ├── sql_compile | 2024-03-20 15:07:46.601984 | 7.666 ms | + | │ ├── pc_get_plan | 2024-03-20 15:07:46.602051 | 0.029 ms | + | │ └── hard_parse | 2024-03-20 15:07:46.602195 | 7.423 ms | + | │ ├── parse | 2024-03-20 15:07:46.602201 | 0.137 ms | + | │ ├── resolve | 2024-03-20 15:07:46.602393 | 0.555 ms | + | │ ├── rewrite | 2024-03-20 15:07:46.603104 | 1.055 ms | + | │ ├── optimize | 2024-03-20 15:07:46.604194 | 4.298 ms | + | │ │ ├── inner_execute_read | 2024-03-20 15:07:46.605959 | 0.825 ms | + | │ │ │ ├── sql_compile | 2024-03-20 15:07:46.606078 | 0.321 ms | + | │ │ │ │ └── pc_get_plan | 2024-03-20 15:07:46.606124 | 0.147 ms | + | │ │ │ ├── open | 2024-03-20 15:07:46.606418 | 0.129 ms | + | │ │ │ └── do_local_das_task | 2024-03-20 15:07:46.606606 | 0.095 ms | + | │ │ └── close | 2024-03-20 15:07:46.606813 | 0.240 ms | + | │ │ ├── close_das_task | 2024-03-20 15:07:46.606879 | 0.022 ms | + | │ │ └── end_transaction | 2024-03-20 15:07:46.607009 | 0.023 ms | + | │ ├── code_generate | 2024-03-20 15:07:46.608527 | 0.374 ms | + | │ └── pc_add_plan | 2024-03-20 15:07:46.609375 | 0.207 ms | + | └── sql_execute | 2024-03-20 15:07:46.609677 | 0.832 ms | + | ├── open | 2024-03-20 15:07:46.609684 | 0.156 ms | + | ├── response_result | 2024-03-20 15:07:46.609875 | 0.327 ms | + | │ └── do_local_das_task | 2024-03-20 15:07:46.609905 | 0.136 ms | + | └── close | 2024-03-20 15:07:46.610221 | 0.225 ms | + | ├── close_das_task | 2024-03-20 15:07:46.610229 | 0.029 ms | + | └── end_transaction | 2024-03-20 15:07:46.610410 | 0.019 ms | + +-----------------------------------------------------------+----------------------------+------------+ + 29 rows in set + ``` + + 从上面这条 show trace 的结果里,大家可以分析出一些信息: + + - 这条 SQL 一共花了 191.999 ms。 + + - SQL 的整体的耗时主要在 `ob_proxy_partition_location_lookup` 上,占了 181.839 ms,顾名思义,这一部分耗时是 ODP(即 proxy) 在寻找表 `t1` 的主副本的位置信息。但是因为这张表刚刚创建,ODP 里暂时还没有对应的位置信息的缓存(也就是 location cache),所以第一次花的时间会比较久,不过之后 ODP 里就会缓存下这张表的 location cache 信息。 + + - SQL 转发到合适的 OBServer 节点之后,在 `com_query_process` 的耗时 8.824 ms。 + +> 这里再简单介绍 show trace 中其他几个重要点位: +> +> - **ob_proxy**: ODP 的起始点位,记录从 ODP 收到 SQL 请求到给客户端完整反馈响应的时间。 +> +> - **ob_proxy_server_process_req**:表示发完 SQL 请求,到第一次收到 OBServer 节点返回响应的时间,等于 OBServer 节点处理时间 + 返回的网络耗时。 +> +> - **com_query_process**:OceanBase 数据库的总点位,记录 OBServer 节点从收到 SQL 请求到转发响应完毕所需的时间。 + +5. 在同一个 session 中再重新执行一遍 `SELECT c1 FROM t1 LIMIT 2;` 这条 SQL,执行 show trace 语句查看有什么变化。 + + ```sql + SHOW TRACE; + ``` + + 输出如下: + + ```shell + +-----------------------------------------------+----------------------------+------------+ + | Operation | StartTime | ElapseTime | + +-----------------------------------------------+----------------------------+------------+ + | ob_proxy | 2024-03-20 15:34:14.879559 | 7.390 ms | + | ├── ob_proxy_partition_location_lookup | 2024-03-20 15:34:14.879652 | 4.691 ms | + | ├── ob_proxy_server_process_req | 2024-03-20 15:34:14.884785 | 1.514 ms | + | └── com_query_process | 2024-03-20 15:34:14.884943 | 1.237 ms | + | └── mpquery_single_stmt | 2024-03-20 15:34:14.884959 | 1.207 ms | + | ├── sql_compile | 2024-03-20 15:34:14.884997 | 0.279 ms | + | │ └── pc_get_plan | 2024-03-20 15:34:14.885042 | 0.071 ms | + | └── sql_execute | 2024-03-20 15:34:14.885300 | 0.809 ms | + | ├── open | 2024-03-20 15:34:14.885310 | 0.139 ms | + | ├── response_result | 2024-03-20 15:34:14.885513 | 0.314 ms | + | │ └── do_local_das_task | 2024-03-20 15:34:14.885548 | 0.114 ms | + | └── close | 2024-03-20 15:34:14.885847 | 0.190 ms | + | ├── close_das_task | 2024-03-20 15:34:14.885856 | 0.030 ms | + | └── end_transaction | 2024-03-20 15:34:14.885997 | 0.019 ms | + +-----------------------------------------------+----------------------------+------------+ + 14 rows in set + ``` + + 从上面这条 show trace 的结果里,大家可以分析出一些信息: + + - 第二次执行这条 SQL,时间变快了很多,从 191.999 ms 缩短到了 7.390 ms。 + + - ODP 有了 location cache 信息,所以寻找路由信息和转发都变快了,从之前的 181.839 ms 缩减到了 4.691 ms。 + + - SQL 转发到合适的 OBServer 节点之后,在 `com_query_process` 的耗时从 8.824 ms 缩减到了 1.237 ms。这里多啰嗦两句,大家仔细看一下就会发现,SQL 分为编译阶段和执行阶段,编译阶段是优化器生成执行计划,执行阶段是执行引擎根据执行计划计算结果。第二次执行时,编译阶段的流程变短了,速度也变快了,大概是因为编译阶段在最开始的时候,先会去计划缓存里查询下有没有之前已经生成好并且缓存下来的计划(即上面的 `pc_get_plan`),如果有的话,就不需要重新生成计划了,所以省了一个完整解析 SQL 并生成计划(即上面的 `hard_parse`)的开销。 + +6. 再通过直连 OBServer 节点登录 OceanBase 数据库,再重新执行一遍 `SELECT c1 FROM t1 LIMIT 2;` 这条 SQL,执行 show trace 语句查看有什么变化。 + + ```sql + SHOW TRACE; + ``` + + 输出如下: + + ```shell + +-------------------------------------------+----------------------------+------------+ + | Operation | StartTime | ElapseTime | + +-------------------------------------------+----------------------------+------------+ + | com_query_process | 2024-03-20 15:54:38.772699 | 1.746 ms | + | └── mpquery_single_stmt | 2024-03-20 15:54:38.772771 | 1.647 ms | + | ├── sql_compile | 2024-03-20 15:54:38.772835 | 0.356 ms | + | │ └── pc_get_plan | 2024-03-20 15:54:38.772900 | 0.143 ms | + | └── sql_execute | 2024-03-20 15:54:38.773209 | 1.052 ms | + | ├── open | 2024-03-20 15:54:38.773232 | 0.150 ms | + | ├── response_result | 2024-03-20 15:54:38.773413 | 0.421 ms | + | │ └── do_local_das_task | 2024-03-20 15:54:38.773479 | 0.192 ms | + | └── close | 2024-03-20 15:54:38.773857 | 0.379 ms | + | ├── close_das_task | 2024-03-20 15:54:38.773913 | 0.069 ms | + | └── end_transaction | 2024-03-20 15:54:38.774139 | 0.058 ms | + +-------------------------------------------+----------------------------+------------+ + 11 rows in set + ``` + + 从上面这条 show trace 的结果里,大家可以分析出一些信息: + + - 直连 OBServer 节点,没了 ODP,所以也就没了和 proxy 相关的开销。 + + - 这条 SQL 的计划,之前已经进了这台 OBServer 节点的计划缓存里,所以在多次执行时,编译阶段都不需要再走一遍完整的硬解析流程。 + +** 分析结束后,大家记得执行如下命令关闭 session 级别 show trace,避免对后续 SQL 的性能产生影响。 ** +``` +set ob_enable_show_trace='off'; +``` + +### 总结 +最后简单总结一下:对于性能不符合预期的一条 SQL,通过全链路追踪,可以看到耗时在什么阶段:是耗时在 ODP(proxy)转发阶段?还是耗时在计划生成阶段?亦或是耗时在执行阶段?在各个阶段中,我们还可以看到更加细节的耗时数据(上面那条 SQL 实在太简单,又利用到了计划缓存,所以看上去细节不多),然后我们就可以针对具体慢的地方做进一步的分析。 + +如果慢在 ODP 转发阶段,可以怀疑是不是 ODP 和 OBServer 节点之间的网络出现故障了,或者是不是 ODP 还没缓存位置信息等问题。 + +如果慢在 SQL 执行阶段,就可以怀疑是不是创建的索引不够优等问题,并进行相应的 SQL 调优,详见本教程的[《SQL 性能诊断和调优》小节](https://oceanbase.github.io/docs/user_manual/operation_and_maintenance/scenario_best_practices/chapter_03_htap/performance_tuning)。 \ No newline at end of file diff --git a/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/03_connection_diagnosis.md b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/03_connection_diagnosis.md new file mode 100644 index 000000000..b61cc5398 --- /dev/null +++ b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/03_connection_diagnosis.md @@ -0,0 +1,440 @@ +--- +title: 排查 ODP 连接问题 +weight: 3 +--- + +> 当前使用 ODP 时,请求执行的链路主要为:客户端发送请求到 ODP --> ODP 将请求路由到对应的 OBServer 节点 --> OBServer 节点处理请求发送回包给 ODP --> ODP 回包给客户端。 +> +> 目前整条链路上都可能发生断连接的场景,比如:请求处理时间较长导致客户端长时间没有收到回包而断开连接、用户登录信息填写错误的集群租户等导致无法登录、ODP 以及 OceanBase 数据库的内部错误导致断开连接等等。 + +## 排查 ODP 问题的基本方法 + +> **个人建议重点看一下 “排查 ODP 问题的基本方法” 这一部分的内容,能根据 trace id 捞出日志,对于大多数场景,基本也就够用了。** + +初步排查 ODP 问题的方法,和排查 observer 问题的方法类似。 + +例如连接报错,想看详细的报错日志。 +``` +[xiaofeng.lby@sqaobnoxdn011161204091.sa128 /home/xiaofeng.lby] +$obclient -h 127.0.0.1 -P2883 -uroot@sys#xiaofeng_91_435 -Dtest +ERROR 4669 (HY000): cluster not exist +``` +> 这个例子举的不太好,在初始报错 cluster not exist 的时候基本就能看出是啥问题了,大家重在领会精神。 + +一般不确定是否属于 ODP 的责任,会先在 ``/oceanbase/log`` 目录中 ``grep "ret=-4669" *``(如果 log 目录中的日志较多,可以根据实际情况,把 grep 后面的 * 改成 observer.log.202412171219* 等)。 +``` +[xiaofeng.lby@sqaobnoxdn011161204091.sa128 /home/xiaofeng.lby/oceanbase/log] +$sudo grep "ret=-4669" * +``` + +如果在 observer 目录中 grep 不到任何错误信息,大概率就是 ODP 的问题了。可以再到 ``/obproxy/log`` 下面去 grep 一把。 + +``` +[xiaofeng.lby@sqaobnoxdn011161204091.sa128 /home/xiaofeng.lby/obproxy/log] +$grep "ret=-4669" * + +# 这里的输出原本是一行,为了方便在网页中展示,手动拆成了多行。 +obproxy.log:[2024-12-17 14:34:14.024891] +WDIAG [PROXY.SM] setup_get_cluster_resource (ob_mysql_sm.cpp:1625) +[125907][Y0-00007F630AAA2A70] [lt=0] [dc=0] +cluster does not exist, this connection will disconnect +(sm_id=26403246, is_clustername_from_default=false, cluster_name=xiaofeng_91_435, ret=-4669) +``` + +一般到这步就能够根据日志看出一些端倪了,日志里明确说了这个 cluster_name 叫 xiaofeng_91_435 的集群不存在,所以 this connection will disconnect。 + + + +在上面的日志里,还可以获得一个 trace id:``[Y0-00007F630AAA2A70]``,如果希望进一步获得更多信息,grep 这个 trace id 就能够获得所有这次操作相关的所有日志信息。 + + +``` +[xiaofeng.lby@sqaobnoxdn011161204091.sa128 /home/xiaofeng.lby/obproxy/log] +$grep Y0-00007F630AAA2A70 * + +obproxy_diagnosis.log:[2024-12-17 14:34:14.024938] [125907][Y0-00007F630AAA2A70] [LOGIN](trace_type="LOGIN_TRACE", connection_diagnosis={cs_id:278640, ss_id:0, proxy_session_id:0, server_session_id:0, client_addr:"127.0.0.1:9988", server_addr:"*Not IP address [0]*:0", cluster_name:"xiaofeng_91_435", tenant_name:"sys", user_name:"root", error_code:-4669, error_msg:"cluster does not exist", request_cmd:"OB_MYSQL_COM_LOGIN", sql_cmd:"OB_MYSQL_COM_LOGIN", req_total_time(us):196}{internal_sql:"", login_result:"failed"}) + +obproxy_error.log:2024-12-17 14:34:14.024960,xiaofeng_cluster_430_proxy,,,,xiaofeng_91_435:sys:,OB_MYSQL,,,OB_MYSQL_COM_LOGIN,,failed,-4669,,194us,0us,0us,0us,Y0-00007F630AAA2A70,,127.0.0.1:9988,,0,,cluster not exist, + +obproxy.log:[2024-12-17 14:34:13.584801] INFO [PROXY.NET] accept (ob_mysql_session_accept.cpp:36) [125907][Y0-00007F630AAA2A70] [lt=0] [dc=0] [ObMysqlSessionAccept:main_event] accepted connection(netvc=0x7f630aa7d2e0, client_ip={127.0.0.1:9980}) +... +``` + +当发生断连接之后,ODP 会记录一段断连接日志到 `obproxy_diagnosis.log` 日志文件中,这里会详细记录断连接相关的信息,以租户名写错为例: + +```shell +# 这里的输出原本是一行,为了方便在网页中展示,手动拆成了多行。 +[2023-08-23 20:11:08.567425] +[109316][Y0-00007F285BADB4E0] [CONNECTION] +(trace_type="LOGIN_TRACE", +connection_diagnosis={ + cs_id:1031798792, ss_id:0, proxy_session_id:0, server_session_id:0, + client_addr:"10.10.10.1:58218", server_addr:"*Not IP address [0]*:0", + cluster_name:"undefined", tenant_name:"test", user_name:"root", + error_code:-4043, + error_msg:"dummy entry is empty, please check if the tenant exists", request_cmd:"COM_SLEEP", sql_cmd:"COM_LOGIN"}{internal_sql:""}) +``` + +看到最后 error_msg 中的 ``please check if the tenant exists``,基本也就能猜出断连接的原因了。 + +> **在这个文档中,后面的部分都是一些 “类字典” 的内容,只需要粗略过一遍(因为认真看完也记不住)。** +> +> **建议先收藏。如果真出了问题,捞出日志,拿着日志里的关键信息,在下面查一下对应的解决方法即可。** + +obproxy_diagnosis 日志通用内容如下: + +* LOG_TIME:日志打印时间,即示例中的 `2023-08-23 20:11:08.567425` + +* TID:线程 ID,即示例中的 `109316` + +* TRACE_ID:trace_id,即示例中的 `Y0-00007F285BADB4E0`,可以通过 trace_id 与其他日志进行关联 + +* CONNECTION:表示这条日志为连接诊断相关的日志 + +* trace_type:诊断类型,目前诊断日志有以下几种类型,不同的诊断类型也对应不同的断连接问题 + + * LOGIN_TRACE:登录问题相关的诊断日志 + + * SERVER_INTERNAL_TRACE:OceanBase 数据库内部错误的诊断信息 + + * PROXY_INTERNAL_TRACE:ODP 内部错误导致断连接的诊断信息 + + * CLIENT_VC_TRACE:客户端主动断连的诊断日志 + + * SERVER_VC_TRACE:OceanBase 数据库主动断连的诊断日志 + + * TIMEOUT_TRACE:ODP 执行超时的诊断日志 + +* CS_ID:ODP 内部标识客户端连接的 ID + +* SS_ID:ODP 内部标识 ODP 与 OceanBase 数据库之间连接的 ID + +* PROXY_SS_ID:由 ODP 生成的标识客户端连接的 ID,会传递给 OceanBase 数据库,可以用来筛选 OceanBase 数据库日志或者 sql_audit 表 + +* SERVER_SS_ID:由 OceanBase 数据库生成的标识 ODP 与 OceanBase 数据库之间连接的 ID + +* CLIENT_ADDR:客户端的 IP 地址 + +* SERVER_ADDR:出错或者断连接时对应的 OBServer 节点的地址 + +* CLUSTER_NAME:集群名 + +* TENANT_NAME:租户名 + +* USER_NAME:用户名 + +* ERROR_CODE:错误码 + +* ERROR_MSG:错误信息,诊断断连接的关键内容 + +* REQUEST_CMD:ODP 当前正在执行的 SQL 语句的类型,可能为内部请求的 SQL 语句类型 + +* SQL_CMD:用户 SQL 语句的类型 + +除上述通用的信息外,诊断日志还包含额外诊断信息,具体内容由 trace_type 决定。 + + + +## 常见断连接场景 + +接下来为大家介绍几种常见的断连接场景,以及对应断连接场景的排查和解决方法。 + +### 登录断连接 + +登录断连接对应的 trace_type 为 LOGIN_TRACE,租户名错误导致断连接的诊断日志示例如下: + +```shell +[2023-09-08 10:37:21.028960] [90663][Y0-00007F8EB76544E0] [CONNECTION](trace_type="LOGIN_TRACE", connection_diagnosis={cs_id:1031798785, ss_id:0, proxy_session_id:0, server_session_id:0, client_addr:"10.10.10.1:44018", server_addr:"*Not IP address [0]*:0", cluster_name:"undefined", tenant_name:"sys", user_name:"root", error_code:-10018, error_msg:"fail to check observer version, empty result", request_cmd:"COM_SLEEP", sql_cmd:"COM_LOGIN"}{internal_sql:"SELECT ob_version() AS cluster_version"}) +``` + +额外诊断信息为 `internal_sql`,表示 ODP 当前执行的内部请求。 + +登录断连接产生的原因比较复杂,本文从用户操作和 OceanBase 数据库两个方面介绍产生原因和解决方法。 + +从用户操作方面来看,有如下几种场景会导致产生断连接: + +| 场景 | 错误码 | 错误信息 | 解决方法 | +|---------|-------------|-------------|-----------| +| 集群名错误 | 4669 | cluster xxx does not exist | 确保对应集群存在且集群名正确,可直连 OBServer 节点后执行 `show parameters like 'cluster';` 命令进行确认,输出中 `value` 值即为待连接的集群名。 | +| 租户名错误 | 4043 | dummy entry is empty, please check if the tenant exists | 确保对应的租户存在,可使用系统租户(root@sys)直连 OBServer 节点后执行 `SELECT * FROM DBA_OB_TENANTS;` 命令,查看集群中的所有租户。 | +| ODP 白名单校验失败 | 8205 | user xxx@xxx can not pass white list | 通过管控台确认 ODP 白名单是否配置正确,详细操作可参见 OB Cloud 云数据库文档 [白名单分组](https://www.oceanbase.com/docs/common-oceanbase-cloud-1000000000020209)。 | +| OceanBase 数据库白名单校验失败 | 1227 | Access denied | 确认 OceanBase 数据库白名单是否配置正确,可通过查看 [ob_tcp_invited_nodes](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220734) 变量进行确认。 | +| 客户端连接数达上限 | 5059 | too many sessions | 可通过执行 `ALTER proxyconfig SET = ;` 命令调整 ODP 的配置项 `client_max_connections` 做暂时的规避。 | +| ODP 配置要求使用 SSL 协议,但是用户发起普通协议请求 | 8004 | obproxy is configured to use ssl connection | 修改 SSL 协议配置 `enable_client_ssl` 为不使用 SSL 协议连接(值为 false);或者使用 SSL 协议访问。 | +| 直接访问 proxyro@sys | 10021 | user proxyro is rejected while proxyro_check on | 不应直接使用 proxyro@sys 访问数据库。 | +| 云上用户在关闭 `enable_cloud_full_user_name` 的场景下使用三段式访问 | 10021 | connection with cluster name and tenant name is rejected while cloud_full_user_name_check off | 云上用户关闭 `enable_cloud_full_user_name` 时,ODP 会限制三段式的访问。您可开启 `enable_cloud_full_user_name` 配置,或者使用非三段式访问。 | +| proxyro 密码配置错误 | 10018 | fail to check observer version, proxyro@sys access denied, error resp ``{ code:1045, msg:Access denied for user xxx }`` | 默认情况下,proxyro 的密码不会存在问题的,如果手动更改过 OceanBase 数据库中 proxyro@sys 用户的密码,请确保 ODP 的配置项 `observer_sys_password` 值和 proxyro@sys 用户密码一致。 | +| 启动 ODP 时配置的 rootservice_list 不可用 | 10018 | fail to check observer version, empty result | 这里可以通过直连 OBServer 节点,通过 `SHOW PARAMETERS LIKE 'rootservice_list';` 命令查看 OceanBase 数据库的 Root Service,以确认 ODP 启动时配置的 server ip 是否可用。 | + +从 OceanBase 数据库方便来看,有如下几种场景会导致产生断连接: + +| 场景 | 错误码 | 错误信息 | 解决方法 | +|---------|-------------|-------------|-----------| +| 集群信息查询为空 | 4669 | cluster info is empty | 直连 OceanBase 数据库,执行 internal_sql 字段的 SQL 语句确认 OceanBase 数据库返回的集群信息是否为空。 | +| 集群信息查询失败 | 10018 | fail to check observer version

fail to check cluster info

fail to init server state | 直连 OceanBase 数据库,执行 internal_sql 字段的 SQL 语句确认 OceanBase 数据库返回的集群信息是否为空。 | +| Config Server 信息查询失败 | 10301 | fail to fetch root server list from config server

fail to fetch root server list from local | 可以手动拉取启动时配置的 Config Server 的 URL(`obproxy_config_server_url` 的值),确认 Config Server 返回的信息是否正常。 | + +### 超时断连接 + +超时断连接对应的 trace_type 为 TIMEOUT_TRACE,集群信息过期导致断连接的诊断日志示例如下: + +```shell +[2023-08-17 17:10:46.834897] [119826][Y0-00007FBF120324E0] [CONNECTION](trace_type="TIMEOUT_TRACE", connection_diagnosis={cs_id:1031798785, ss_id:7, proxy_session_id:7230691830869983235, server_session_id:3221504994, client_addr:"10.10.10.1:42468", server_addr:"10.10.10.1:21100", cluster_name:"undefined", tenant_name:"sys", user_name:"root", error_code:-10022, error_msg:"OBProxy inactivity timeout", request_cmd:"COM_SLEEP", sql_cmd:"COM_END"}{timeout:1, timeout_event:"CLIENT_DELETE_CLUSTER_RESOURCE", total_time(us):21736}) +``` + +额外诊断信息有如下两条。 + +* timeout_event:表示超时事件 + +* total_time:表示请求执行时间 + +下表根据不同的超时事件介绍如何解决断连接问题。 + +| 超时事件 | 场景 | 错误码 | 相关配置 | 解决方法 | +|-------------|--------|---------------|------------|----------| +| CLIENT_DELETE_CLUSTER_RESOURCE | 集群信息发生变化 | 10022 | ODP 配置项 [cluster_expire_time](../400.configuration-management/200.global-configuration-items/210.cluster-expire-time.md) | 可以通过执行 `ALTER proxyconfig SET = ;` 命令调整 ODP 中 `cluster_expire_time` 配置项暂时规避,`cluster_expire_time` 配置项默认过期时间为一天,新的请求会重置过期时间。 | +| CLIENT_INTERNAL_CMD_TIMEOUT | 内部请求执行超时 | 10022 | 固定时间 30s | 非预期超时,建议联系技术支持人员配合诊断。 | +| CLIENT_CONNECT_TIMEOUT | 客户端与 ODP 建连超时 | 10022 | 固定时间 10s | 非预期超时,建议联系技术支持人员配合诊断。 | +| CLIENT_NET_READ_TIMEOUT | ODP 等待用户请求数据超时 | 10022 | OceanBase 数据库系统变量 [net_read_timeout](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220757) | 修改系统变量 `net_read_timeout`,需注意修改 Global 级别的系统变量不会对已有连接生效。 | +| CLIENT_NET_WRITE_TIMEOUT | ODP 等待回包数据超时 | 10022 | OceanBase 数据库系统变量 [net_write_timeout](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220772) | 修改系统变量 `net_write_timeout`,需注意修改 Global 级别的系统变量不会对已有连接生效。 | +| CLIENT_WAIT_TIMEOUT | 用户请求过程中,客户端连接长时间没有发生交互导致超时 | 10022 | OceanBase 数据库系统变量 [wait_timeout](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220773) | 修改系统变量 `wait_timeout` 暂时规避。 | +| SERVER_QUERY_TIMEOUT | 用户请求查询超时 | 10022 | OceanBase 数据库系统变量 [ob_query_timeout](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220700)、hint 指定的 [query_timeout](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000222741) | 修改系统变量 `ob_query_timeout` 暂时规避。 | +| SERVER_TRX_TIMEOUT | 用户事务执行超时 | 10022 | OceanBase 数据库系统变量 [ob_trx_timeout](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220720) | 修改系统变量 `ob_trx_timeout` 暂时规避。 | +| SERVER_WAIT_TIMEOUT | 用户请求过程中,OceanBase 数据库连接长时间没有发生交互导致超时 | 10022 | OceanBase 数据库系统变量 [wait_timeout](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000220773) | 修改系统变量 `wait_timeout` 暂时规避。 | + +### OceanBase 数据库主动断开连接 + +OceanBase 数据库主动断开连接对应的 trace_type 为 SERVER_VC_TRACE,ODP 与 OceanBase 数据库建连失败的诊断日志示例如下: + +```shell +[2023-08-10 23:35:00.132805] [32339][Y0-00007F74C9A244E0] [CONNECTION](trace_type="SERVER_VC_TRACE", connection_diagnosis={cs_id:838860809, ss_id:0, proxy_session_id:7230691830869983240, server_session_id:0, client_addr:"10.10.10.1:45765", server_addr:"", cluster_name:"undefined", tenant_name:"sys", user_name:"root", error_code:-10013, error_msg:"Fail to build connection to observer", request_cmd:"COM_QUERY", sql_cmd:"COM_HANDSHAKE"}{vc_event:"unknown event", total_time(us):2952626, user_sql:"select 1 from dual"}) +``` + +额外诊断信息有如下三条。 + +* vc_event:表示断连接相关的时间,您无需太过关注 + +* total_time:表示请求执行时间 + +* user_sql:表示用户请求 + +OceanBase 数据库主动断连接有如下几种场景。 + +| 场景 | 错误码 | 错误信息 | 解决方法 | +|---------|-------------|-------------|-----------| +| ODP 与 OceanBase 数据库建连失败 | 10013 | Fail to build connection to observer | 需与 OceanBase 数据库配合诊断。 | +| ODP 传输请求给 OceanBase 数据库时连接断开 | 10016 | An EOS event eceived while proxy transferring request | 需与 OceanBase 数据库配合诊断。 | +| ODP 传输 OceanBase 数据库回包时连接断开 | 10014 | An EOS event received while proxy reading response | 需与 OceanBase 数据库配合诊断。 | + +
+

说明

+

OceanBase 数据库主动断连接的场景下,ODP 无法收集更为详细的信息,如果 ODP 配置的 OBServer 节点状态正常,则需要配合 OceanBase 数据库的日志进行诊断。

+
+ +### 客户端主动断连接 + +客户端主动断连接对应的 trace_type 为 CLIENT_VC_TRACE,ODP 读请求时客户端断开连接的诊断日志示例如下: + +```shell +[2023-08-10 23:28:24.699168] [32339][Y0-00007F74C9A244E0] [CONNECTION](trace_type="CLIENT_VC_TRACE", connection_diagnosis={cs_id:838860807, ss_id:26, proxy_session_id:7230691830869983239, server_session_id:3221698209, client_addr:"10.10.10.1:44701", server_addr:"10.10.10.1:21100", cluster_name:"undefined", tenant_name:"sys", user_name:"root", error_code:-10010, error_msg:"An EOS event received from client while obproxy reading request", request_cmd:"COM_SLEEP", sql_cmd:"COM_END"}{vc_event:"VC_EVENT_EOS", total_time(us):57637, user_sql:""}) +``` + +额外诊断信息有如下三条。 + +* vc_event:表示断连接相关的时间,您无需太过关注 + +* total_time:表示请求执行时间 + +* user_sql:表示用户请求 + +客户端主动断连接有如下几种场景。 + +| 场景 | 错误码 | 错误信息 | 解决方法 | +|---------|-------------|-------------|-----------| +| ODP 收发包时客户端发生断连接 | 10010 | An EOS event received from client while obproxy reading request | 需客户端配合诊断。 | +| ODP 处理请求时客户端断连接 | 10011 | An EOS event received from client while obproxy handling response | 需客户端配合诊断。 | +| ODP 回包时客户端发送断连接 | 10012 | An EOS event received from client while obproxy transferring response | 需客户端配合诊断。 | + +
+

说明

+

客户端断连接的场景下,ODP 无法收集更为详细的信息,只能指出客户端方面主动断开连接的操作。比较常见的断连接问题有驱动超时主动断开连接、Druid/Hikaricp/Nginx 等中间件主动断连接、网络抖动等问题,具体情况可与客户端配合诊断。

+
+ +### ODP 或 OceanBase 数据库内部错误 + +ODP 内部错误对应的 trace_type 为 PROXY_INTERNAL_TRACE,OceanBase 数据库内部错误对应的 trace_type 为 SERVER_INTERNAL_TRACE。ODP 内部错误的诊断日志示例如下: + +```shell +[2023-08-10 23:26:12.558201] [32339][Y0-00007F74C9A244E0] [CONNECTION](trace_type="PROXY_INTERNAL_TRACE", connection_diagnosis={cs_id:838860805, ss_id:0, proxy_session_id:7230691830869983237, server_session_id:0, client_addr:"10.10.10.1:44379", server_addr:"", cluster_name:"undefined", tenant_name:"sys", user_name:"root", error_code:-10019, error_msg:"OBProxy reached the maximum number of retrying request", request_cmd:"COM_QUERY", sql_cmd:"COM_QUERY"}{user_sql:"USE `ý<8f>ý<91>ý<92>`"}) +``` + +额外诊断信息为 `user_sql`,表示用户请求 SQL。 + +ODP 或 OceanBase 数据库内部错误有如下几种场景。 + +| 诊断类型 | 场景 | 错误码 | 错误信息 | 解决方法 | +|------------|---------|-------------|-------------|-----------| +| PROXY_INTERNAL_TRACE | 租户分区信息查询失败 | 4664 | dummy entry is empty, disconnect | 未预期错误场景,您可联系技术支持人员协助排查或在开源官网 [问答区](https://ask.oceanbase.com/) 提问。 | +| PROXY_INTERNAL_TRACE | ODP 部分内部请求执行失败 | 10018 | proxy execute internal request failed, received error resp, error_type: xxx | 未预期错误场景,您可联系技术支持人员协助排查或在开源官网 [问答区](https://ask.oceanbase.com/) 提问。 | +| PROXY_INTERNAL_TRACE | ODP 重试请求达上限 | 10019 | OBProxy reached the maximum number of retrying request | 未预期错误场景,您可联系技术支持人员协助排查或在开源官网 [问答区](https://ask.oceanbase.com/) 提问。 | +| PROXY_INTERNAL_TRACE | ODP 目标 Session 被关闭 | 10001 | target session is closed, disconnect | 未预期错误场景,您可联系技术支持人员协助排查或在开源官网 [问答区](https://ask.oceanbase.com/) 提问。 | +| PROXY_INTERNAL_TRACE | 其他未预期的错误场景 | 10001 | 诊断信息为空 | 未预期错误场景,您可联系技术支持人员协助排查或在开源官网 [问答区](https://ask.oceanbase.com/) 提问。 | +| SERVER_INTERNAL_TRACE | CheckSum 校验出错 | 10001 | ora fatal error | 未预期错误场景,您可联系技术支持人员协助排查或在开源官网 [问答区](https://ask.oceanbase.com/) 提问。 | +| SERVER_INTERNAL_TRACE | 主备库切换场景 | 10001 | primary cluster switchover to standby, disconnect | 主备库切换过程中可能存在的断连接问题,符合预期的场景。 | + +### 其他场景 + +除上述场景外,诊断日志中还会存在如下两种符合预期的场景,对应的 trace_type 为 PROXY_INTERNAL_TRACE。 + +| 场景 | 错误码 | 错误信息 | 备注 | +|---------|-------------|-------------|-----------| +| kil 当前会话 | 5065 | connection was killed by user self, cs_id: xxx | 符合预期的场景,诊断日志作记录。 | +| kill 其他会话 | 5065 | connection was killed by user session xxx | 符合预期的场景,诊断日志作记录。 | + +诊断日志示例如下,额外诊断信息为 `user_sql`,表示用户请求 SQL。 + +```shell +[2023-08-10 23:27:15.107427] [32339][Y0-00007F74CAAE84E0] [CONNECTION](trace_type="PROXY_INTERNAL_TRACE", connection_diagnosis={cs_id:838860806, ss_id:21, proxy_session_id:7230691830869983238, server_session_id:3221695443, client_addr:"10.10.10.1:44536", server_addr:"10.10.10.1:21100", cluster_name:"undefined", tenant_name:"sys", user_name:"", error_code:-5065, error_msg:"connection was killed by user self, cs_id: 838860806", request_cmd:"COM_QUERY", sql_cmd:"COM_QUERY"}{user_sql:"kill 838860806"}) +``` + +## 示例 + +客户端请求到 OceanBase 数据库的链路比较常见的有下图两种。 + +![链路图](/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/03_connection_diagnosis/001.png) + +客户端的请求到 OceanBase 数据库之间的链路需要经过多个节点,任一节点出现问题都有可能会导致客户端的连接断开。所以当发生断连接且客户端没有收到明确的错误包提示断连接原因时,排查断连接问题需先确定断连接方,之后再根据断连接方对应排查断连接原因。具体操作如下。 + +### 步骤一:确定断连接方 + +如果当前使用的 ODP 具备连接诊断能力,可通过诊断日志 `obproxy_diagnosis.log` 快速判断出现断连接问题的一方。您可根据用户名、租户名、集群名、从驱动中拿到的 thread_id(对应日志 cs_id)、断连接时间等信息从日志中快速筛选出对应的断连接日志,根据 trace_type 字段判断断连接方,trace_type 字段和断连接方的对应情况如下。 + +* CLIENT_VC_TRACE:客户端断连接 + +* SERVER_VC_TRACE:OceanBase 数据库主动断开连接 + +* SERVER_INTERNAL_TRACE:OceanBase 数据库内部错误 + +* PROXY_INTERNAL_TRACE:ODP 内部错误 + +* LOGIN_TRACE:登录失败 + +* TIMEOUT_TRACE:执行超时 + +### 步骤二:排查断连接原因 + +根据不同的断连接方,有如下几种不同的排查方法。 + +#### 客户端断连接 + +JDBC 默认的 socketTimeout 配置为 0,即不会产生 socketTimeout 超时,但是部分客户端比如 Druid/MyBatis 自身有控制 socketTimeout 的参数,如果发生请求执行时间过长导致的断连接,可以优先确认 socketTimeout 的配置,详细信息可参见 OceanBase 数据库文档 [数据库连接池配置](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000218815)。 + +1. 查看对应的 ODP 连接诊断日志,确定断连接的基本信息。 + + ```shell + [2023-09-07 15:59:52.308553] [122701][Y0-00007F7071D194E0] [CONNECTION](trace_type="CLIENT_VC_TRACE", connection_diagnosis={cs_id:524328, ss_id:0, proxy_session_id:7230691833961840700, server_session_id:0, client_addr:"10.10.10.1:38877", server_addr:"10.10.10.2:50110", cluster_name:"ob1.changluo.cc.10.10.10.2", tenant_name:"mysql", user_name:"root", error_code:-10011, error_msg:"An unexpected connection event received from client while obproxy handling request", request_cmd:"COM_QUERY", sql_cmd:"COM_QUERY"}{vc_event:"VC_EVENT_EOS", total_time(us):5016353, user_sql:"select sleep(20) from dual"}) + ``` + + 主要诊断信息如下。 + + * trace_type: 示例中诊断类型为 `CLIENT_VC_TRACE`,可判断出是客户端主动断开的连接。 + + * error_msg : 示例中错误信息为 `An unexpected connection event received from client while obproxy handling request`,可判断出客户端在 ODP 处理请求时断开连接。 + + * total_time: 示例中请求执行时间为 `5016353`,表示请求总的执行时间为 5s 左右,可以通过 total_time 去匹配客户端的超时参数。 + +2. 根据 ODP 连接诊断日志信息确定是客户端主动断开了连接,从客户端入手排查,查看 JDBC 堆栈。 + + ```shell + The last packet successfully received from the server was 5,016 milliseconds ago. The last packet sent successfully to the server was 5,011 milliseconds ago. + at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) + at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) + at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) + at java.lang.reflect.Constructor.newInstance(Constructor.java:423) + at com.mysql.jdbc.Util.handleNewInstance(Util.java:411) + at com.mysql.jdbc.SQLError.createCommunicationsException(SQLError.java:1129) + at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3720) + at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3609) + at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4160) + at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2617) + at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2778) + at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2819) + at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2768) + at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:949) + at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:795) + at odp.Main.main(Main.java:12) + Caused by: java.net.SocketTimeoutException: Read timed out + at java.net.SocketInputStream.socketRead0(Native Method) + at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) + at java.net.SocketInputStream.read(SocketInputStream.java:170) + at java.net.SocketInputStream.read(SocketInputStream.java:141) + at com.mysql.jdbc.util.ReadAheadInputStream.fill(ReadAheadInputStream.java:114) + at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:161) + at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:189) + at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:3163) + at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:3620) + 9 more + ``` + + 从堆栈以及收发包时间可大致判断出示例中为 socketTimeout 触发的问题。 + +#### ODP 断连接 + +ODP 会读取 OceanBase 数据库设置的 `net_write_timeout` 配置,控制收包和发包时传包的超时时间,该配置默认时间为 60s。当网络环境比较极端或者 OceanBase 数据库回包处理较慢时,可能会出现 ODP 等待回包数据超时断连接的问题。此处以 ODP 等待回包数据超时场景为例介绍如何排查断连接原因。 + +根据 ODP 断连接诊断日志判断断连接方。 + +```shell +[2023-09-08 01:22:17.229436] [81506][Y0-00007F455197E4E0] [CONNECTION](trace_type="TIMEOUT_TRACE", connection_diagnosis={cs_id:1031798827, ss_id:342, proxy_session_id:7230691830869983244, server_session_id:3221753829, client_addr:"10.10.10.1:34901", server_addr:"10.10.10.1:21102", cluster_name:"undefined", tenant_name:"mysql", user_name:"root", error_code:-10022, error_msg:"OBProxy inactivity timeout", request_cmd:"COM_QUERY", sql_cmd:"COM_QUERY"}{timeout(us):6000000, timeout_event:"CLIENT_NET_WRITE_TIMEOUT", total_time(us):31165295}) +``` + +主要诊断信息如下。 + +* trace_type: 示例中诊断类型为 `TIMEOUT_TRACE`, 可判断出是因为 ODP 执行超时导致断连接。 + +* timeout_event: 示例中超时事件为 `CLIENT_NET_WRITE_TIMEOUT`,可判断出 ODP 因为等待回包数据超时而发生断连接。 + +根据诊断信息可以确定触发了 net_write_timeout,客户端连接等待数据超过 6s(非默认值),导致连接断开,通过修改系统变量延长超时限制可暂时规避。 + +#### 登录失败 + +此处以两种场景介绍如何排查断连接原因。 + +* 以 rootservice_list 指定的 OBServer 节点不可用为例介绍如何排查断连接原因,连接诊断日志如下。 + + ```shell + [2023-09-08 10:37:21.028960] [90663][Y0-00007F8EB76544E0] [CONNECTION](trace_type="LOGIN_TRACE", connection_diagnosis={cs_id:1031798785, ss_id:0, proxy_session_id:0, server_session_id:0, client_addr:"10.10.10.1:44018", server_addr:"*Not IP address [0]*:0", cluster_name:"undefined", tenant_name:"sys", user_name:"root", error_code:-10018, error_msg:"fail to check observer version, empty result", request_cmd:"COM_SLEEP", sql_cmd:"COM_LOGIN"}{internal_sql:"SELECT ob_version() AS cluster_version"}) + ``` + + 主要诊断信息如下。 + + * trace_type:示例中诊断类型为 `LOGIN_TRACE`,可确定是登录失败的问题。 + + * internal_sql:示例中 ODP 当前执行的内部请求为 `SELECT ob_version() AS cluster_version`,可确定登录过程中 ODP 执行该内部请求失败。 + + * error_msg:示例中错误信息为 `fail to check observer version, empty result`,可确定内部请求失败的原因为结果集为空。 + + 诊断信息总结即为:ODP 执行内部请求 `SELECT ob_version() AS cluster_version` 失败,结果集为空。`SELECT ob_version() AS cluster_version` 这条 SQL 是 ODP 查询集群版本的请求,在您首次登录时 ODP 会执行这条请求校验集群信息,当 ODP 启动时配置的 rootservice_list 错误或者 OBServer 节点宕机时,ODP 查询失败,便会导致登录失败。 + +* 以客户端连接达到 ODP 上限为例介绍如何排查断连接原因。 + + 客户端连接达到 ODP 上线导致断连接有如下两种排查方法。 + + * 方法一:排查连接诊断日志。 + + ```shell + [2023-09-08 11:19:26.617385] [110562][Y0-00007FE1F06AC4E0] [CONNECTION](trace_type="LOGIN_TRACE", connection_diagnosis={cs_id:1031798805, ss_id:0, proxy_session_id:0, server_session_id:0, client_addr:"127.0.0.1:40004", server_addr:"*Not IP address [0]*:0", cluster_name:"undefined", tenant_name:"sys", user_name:"root", error_code:-5059, error_msg:"Too many sessions", request_cmd:"COM_SLEEP", sql_cmd:"COM_LOGIN"}{internal_sql:""}) + ``` + + 主要诊断信息如下。 + + * trace_type:示例中诊断类型为 `LOGIN_TRACE`,可确定是登录失败的问题。 + + * error_msg:示例中错误信息为 `Too many session`,可确定是因为连接数达到上限导致登录失败。 + + * 方法二:直接根据错误包判断,执行连接命令,输出 `Too many sessions`。 + + ```shell + $ obclient -h127.0.0.1 -P2899 -uroot@sys -Dtest -A -c + ERROR 1203 (42000): Too many sessions + ``` diff --git a/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/04_routing_diagnosis.md b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/04_routing_diagnosis.md new file mode 100644 index 000000000..51f46da7d --- /dev/null +++ b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/04_routing_diagnosis.md @@ -0,0 +1,490 @@ +--- +title: 排查 ODP 路由问题 +weight: 4 +--- + +> 本小节将介绍路由诊断功能被开发的原因、如何使用路由诊断功能,以及如何根据诊断信息查找和解决问题。 + +ODP 是 OceanBase 数据库的接入层和路由层,而路由是 ODP 的核心功能,目前使用过程中发现,路由过程出现问题的情况下排查问题较为困难,因此 ODP V4.2.1 迭代中开发了路由诊断功能,通过在 ODP 路由转发的关键过程中设置诊断点来记录关键状态信息,这些诊断点可以被输出到日志或以结果集方式返回。 + +通过分析输出的诊断点,可以清晰地了解一条 SQL 语句从进入 ODP 到被转发出去的过程中发生了什么。 + +## 路由诊断流程 + +使用路由诊断功能排查问题的流程如下。 + +1. 获取诊断信息,详细介绍可参见 “获取诊断信息” 部分。 + +2. 根据诊断信息排查问题,详细介绍可参见 “诊断点排查” 部分。 + + +## 获取诊断信息 + +本文介绍两种获取诊断信息的方法,可通过如下任一方法获取诊断信息。 + +### 命令行语句 + +可通过 `explain route ;` 命令获取 SQL 语句的路由状态信息,在配置项 `route_diagnosis_level` 不为 0 的情况下,该命令会展示详细的诊断信息。`` 语句将会在 ODP 内部进行处理,执行正常的 ODP 转发流程,但不会真正转发给 OBServer 节点。 + +配置项 `route_diagnosis_level` 是全局配置项,可用来控制输出路由状态信息的详细程度。该配置项取值为一个整数,默认值为 2,表示输出信息可以覆盖二级诊断点,诊断点的详细介绍可参见 [诊断点排查](300.diagnosis-point-troubleshooting/100.overview-of-diagnosis-point-troubleshooting.md) 章节。 + +`route_diagnosis_level` 配置项的可选值有 [0-4],配置的值越大,展示的状态信息越详细。配置值为 0 时表示关闭该模块。关闭该模块时,不会占用 ODP 内存,也不会影响 ODP 性能。 + +explain route 命令不支持诊断如下命令字。 + +* COM_STMT_PREPARE + +* COM_STMT_PREPARE_EXECUTE + +* COM_STMT_CLOSE + +* COM_STMT_RESET + +* 文本 Prepare(语法:`PREPARE statement_name FROM preparable_SQL_statement;` ) + +* 文本 Prepare drop(语法:`{DEALLOCATE | DROP} PREPARE stmt_name;`) + +* ODP 内部命令 + +#### 示例 + +查询一个不存在的表,SQL 语句为 `SELECT * FROM test.list_sub_parts_my_01 WHERE c3='1999-09-09' AND c1=mod(1999,1000) AND c2='tiger0'`。 + +```sql +obclient> EXPLAIN ROUTE SELECT * FROM test.list_sub_parts_my_01 WHERE c3='1999-09-09' AND c1=mod(1999,1000) AND c2='tiger0'\G +``` + +输出如下。 + +```sql +*************************** 1. row *************************** +Route Plan: +Trans First Query:"SELECT * FROM test.list_sub_parts_my_01" +Trans Current Query:"EXPLAIN ROUTE SELECT * FROM test.list_sub_parts_my_01 WHERE c3='1999-09-09' AND c1=mod(1999,1000) AND c2='tiger0'" +Route Prompts +----------------- +> ROUTE_INFO + [INFO] Will do table partition location lookup to decide which OBServer to route to +> TABLE_ENTRY_LOOKUP_DONE + [INFO] No available entry because table entry lookup failed +> ROUTE_INFO + [INFO] Will route to cached connected server(10.10.10.1:4001) + +Route Plan +----------------- +> SQL_PARSE:{cmd:"COM_QUERY", table:"list_sub_parts_my_01"} +> ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP", in_transaction:true} +> LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} + > TABLE_ENTRY_LOOKUP_START:{} + > FETCH_TABLE_RELATED_DATA:{table_entry:"partition information does not exist"} + > TABLE_ENTRY_LOOKUP_DONE:{is_lookup_succ:true, entry_from_remote:false} +> ROUTE_INFO:{route_info_type:"USE_CACHED_SESSION", svr_addr:"10.10.10.1:4001", in_transaction:true} +> CONGESTION_CONTROL:{svr_addr:"10.10.10.1:4001"} +``` + +结果集说明如下。 + +* Trans First Query:事务首条语句内容 + +* Trans Current Query:事务当前语句内容 + +* Route Prompts:路由提示,对某些路由过程中的步骤进行解释,分为 `[INFO]` 和 `[WARN]` 提示 + + * `[INFO]`:表示路由过程正常,会输出帮助您理解路由过程的一些信息。示例中 `ROUTE_INFO` 输出了 `[INFO]` 信息,通过阅读信息可以知道,本次请求将通过分区位置查询来决定路由至哪个 OBServer 节点 + + * `[WARN]`:表示路由过程中对应步骤出现异常,重点检查该步骤的信息输出 + +* Route Plan:路由计划,展示 ODP 转发过程。其中 `SQL_PARSE`/`ROUTE_INFO`/`LOCATION_CACHE_LOOKUP` 为诊断点,诊断点之间存在同级或者父子关系,呈树状。详细信息可参见 [诊断点排查](300.diagnosis-point-troubleshooting/100.overview-of-diagnosis-point-troubleshooting.md) 章节。 + +通过 Route Plan 中的 `FETCH_TABLE_RELATED_DATA:{table_entry:"partition information does not exist"}` 可以得知 ODP 查询该表的分区信息,但其信息不存在。 + +大多数时候通过 Route Prompts 输出信息就可以确认问题,如果不能则结合 Route Prompts 与 Route Plan 一起排查问题。 + +### 诊断日志 + +当 SQL 语句满足以下条件时,ODP 会将实际路由过程输出到 `obproxy_diagnosis.log` 日志中。 + +* 非 `EXPLAIN ROUTE executable_sql;` 语句 + +* 分区未命中(`is_partition_hit = false`)或者 route_diagnosis_level = 4 + +日志输出时,会根据当前语句所处的场景决定输出日志级别为 WARN 或 TRACE,具体场景如下。 + +* 输出 WARN 级别日志 + + * 分布式事务内路由语句 + + * 事务首条语句 + +* 输出 TRACE 级别日志 + + * 普通事务内事务路由语句 + + * 语句表名为空 + + * 语句超长(长度大于 request_buffer_length 配置项) + + * 分区键计算失败 + +诊断日志支持的命令字/类型如下所示。 + +* COM_QUERY + +* COM_STMT_PREPARE_EXECUTE + +* COM_STMT_PREPARE + +* COM_STMT_SEND_PIECE_DATA + +* COM_STMT_GET_PIECE_DATA + +* COM_STMT_FETCH + +* COM_STMT_SEND_LONG_DATA + +* 非 ODP 内部命令 + +#### 示例 + +在 `obproxy_diagnosis.log` 中找到想诊断的日志行,过滤关键字得到该行,然后替换该日志行内的 '/n' 为 '\n' 得到树状的诊断过程。 + +脚本命令:grep "some_key_word" obproxy_diagnosis.log | sed "s/\/n/\n/g",示例如下。 + +```shell +$ grep "2023-08-17 16:56:46.521180" obproxy_diagnosis.log | sed "s/\/n/\n/g" +``` + +输出如下。 + +```shell +[2023-08-17 16:56:46.521180] [31792][Y0-00007F38DAAF34E0] [ROUTE]((*route_diagnosis= +Trans Current Query:"select * from test.range_sub_parts_my_01 where c1=22222 and c2=111111 and c3=abcd" +... +> PARTITION_ID_CALC_DONE + [WARN] Fail to calculate first part idx may use route policy or cached server session + +Route Plan +> SQL_PARSE:{cmd:"COM_QUERY", table:"range_sub_parts_my_01"} +> ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP"} +> LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} + > TABLE_ENTRY_LOOKUP_DONE:{table:"range_sub_parts_my_01", table_id:1099511677778, partition_num:16, table_type:"USER TABLE", entry_from_remote:false} + > PARTITION_ID_CALC_START:{} + > EXPR_PARSE:{col_val:[[0]["c1", "22222"], [1]["c2", "111111"], [2]["c3", ""]]} + > RESOLVE_EXPR:{error:-4002, sub_part_range:"(111111,MIN ; 111111,MAX)"} + > RESOLVE_TOKEN:{resolve:{"BIGINT":22222}, token_type:"TOKEN_INT_VAL", token:"22222"} + > RESOLVE_TOKEN:{resolve:{"BIGINT":111111}, token_type:"TOKEN_INT_VAL", token:"111111"} + > RESOLVE_TOKEN:{error:-4002, , token_type:"TOKEN_COLUMN", token:"abcd"} + > PARTITION_ID_CALC_DONE:{error:-4002, partition_id:-1, level:2, partitions:"(p-1sp-1)"} +> ROUTE_POLICY:{replica:"10.10.10.1:50110", idc_type:"SAME_IDC", zone_type:"ReadWrite", role:"FOLLOWER", type:"FULL", chosen_route_type:"ROUTE_TYPE_NONPARTITION_UNMERGE_LOCAL", route_policy:"MERGE_IDC_ORDER_OPTIMIZED", trans_consistency:"STRONG", session_consistency:"STRONG"} +> CONGESTION_CONTROL:{svr_addr:"10.10.10.1:50110"} +> HANDLE_RESPONSE:{is_parititon_hit:"false", state:"CONNECTION_ALIVE"} +) +``` + +诊断过程如下。 + +1. 通过 `PARTITION_ID_CALC_DONE` 下的 `[WARN] Fail to calculate first part idx may use route policy or cached server session` 可以知道计算一级分区 ID 失败,将会通过路由策略路由或者复用会话。 + +2. 检查诊断点 `PARTITION_ID_CALC_DONE` 附近数据,发现 `RESOLVE_TOKEN:{error:-4002, , token_type:"TOKEN_COLUMN", token:"abcd"}`,由此得知 ODP 解析 c3=abcd 时出现解析错误。 + + +## 诊断点排查概述 + +> 诊断点描述的是 ODP 进行路由转发过程中的关键过程,通过观察诊断点,可以洞悉路由转发过程。本章节介绍如何根据诊断点进行分析排查。 + +### 排查流程 + +获取诊断信息后,可根据路由请求是否处于事务中选择下述不同的操作进行排查。 + +#### 请求处于事务中 + +处于事务中的请求路由不准有以下两种处理情况。 + +* 分布式事务路由 + + * 当前请求 ODP 路由不准,直接参照诊断流程进行诊断点的分析排查。 + + * 当前请求跟随协调者路由,可将【Trans First Query】中的内容取出,使用 `explain route` 命令重新执行一遍后再参照诊断流程进行诊断点的分析排查。 + +* 非分布式事务路由 + + 当前请求跟随事务第一条语句路由,将【Trans First Query】中的内容取出,使用 `explain route` 命令重新执行一遍后再参照诊断流程进行诊断点的分析排查。 + +#### 请求不处于事务中 + +当路由不准的请求不处于事务中时,您可直接参照诊断流程进行诊断点的分析排查。 + +### 诊断流程 + +当您通过 `explain route` 命令或诊断日志获取到诊断信息后,您可通过各诊断点信息进行路由诊断,各诊断点需诊断的变量及变量含义可参考本章具体的诊断点介绍文档。 + +下表为诊断点的概览,诊断点从上到下为其执行顺序,诊断级别越高,获取的诊断信息越详细。 + +
+

说明

+
    +
  • +

    诊断点数据不会全部输出到日志或者结果集中,诊断点只会输出对应当前诊断有用的数据。

    +
  • +
  • +

    TABLE_ENTRY_LOOKUP_STARTPARTITION_ID_CALC_START 为无数据诊断点,仅用于维持诊断点之间的树状结构

    +
  • +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
诊断阶段诊断点诊断点级别
文法解析SQL_PARSE1
获取路由信息ROUTE_INFO1
获取表副本位置LOCATION_CACHE_LOOKUP1
ROUTINE_ENTRY_LOOKUP_DONE2
TABLE_ENTRY_LOOKUP_START2
FETCH_TABLE_RELATED_DATA3
TABLE_ENTRY_LOOKUP_DONE2
PARTITION_ID_CALC_START2
EXPR_PARSE3
CALC_ROWID3
RESOLVE_EXPR3
RESOLVE_TOKEN4
CALC_PARTITION_ID3
PARTITION_ID_CALC_DONE2
PARTITION_ENTRY_LOOKUP_DONE2
根据路由策略选取ROUTE_POLICY1
副本访问控制CONGESTION_CONTROL1
处理 OBServer 回包HANDLE_RESPONSE1
选取副本并尝试重试RETRY1
+ +#### 诊断技巧 + +* 可重点关注诊断提示(Route Prompts)中的 WARN 提示信息,以及其对应诊断点附近的诊断数据。 + +* 可重点关注诊断数据(Route Plan)中的 error 数据。 + +* 可通过设置 route_diagnosis_level = 4 以及 monitor_log_level='TRACE',在诊断日志中查看所有可用日志诊断的用户请求的诊断结果。 + + route_diagnosis_level 的详细介绍请参见 [route_diagnosis_level](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000001601210);monitor_log_level 的详细介绍请参见 [monitor_log_level](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000001601148)。 + +**这里不再详细地介绍所有所有诊断点了。原因是个人觉得这些诊断点,涉及到太多 ODP 工作流程和具体实现相关的内容,如果用户获取 EXPLAIN ROUTE 的信息之后,如果能够凭借背景知识(主要还是凭感觉)直接读懂并进行分析,自是最好;如果不能直接读懂,也没关系,拿着诊断的信息去联系 OceanBase 社区论坛的值班同学协助排查即可。** + +## 使用示例 + +本文结合 MySQL 租户模式下不同示例介绍如何使用路由诊断功能。 + +> 在下面的示例中,均已将 route_diagnosis_level 设置为 4。 + +### 示例一:PS/PL 语句调用 + +当 PS 调用路由不准时,execute 实际执行的语句不方便寻找,您可查看对应诊断日志,通过诊断日志查看执行的相关语句。 + +```shell +[2023-09-19 18:48:49.079458] [106700][Y0-00007FD892AB64E0] [ROUTE]((*route_diagnosis= +Trans Current Query:"execute stmt" +Route Prompts +-------------- +> ROUTE_INFO + [INFO] Will do table partition location lookup to decide which OBServer to route to +> ROUTE_POLICY + [INFO] Will route to table's partition leader replica(10.10.10.1:4001) using non route policy because query for STRONG read +Route Plan +-------------- +> SQL_PARSE:{cmd:"COM_QUERY", table:"t0"} +> ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP"} +> LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} + > TABLE_ENTRY_LOOKUP_DONE:{table:"t0", table_id:"500078", table_type:"USER TABLE", partition_num:64, entry_from_remote:false} + > PARTITION_ID_CALC_START:{} + > EXPR_PARSE:{col_val:"=88888888,=1111111"} + > RESOLVE_EXPR:{part_range:"[88888888 ; 88888888]", sub_part_range:"[1111111 ; 1111111]"} + > RESOLVE_TOKEN:{token_type:"TOKEN_INT_VAL", resolve:"BIGINT:88888888", token:"88888888"} + > RESOLVE_TOKEN:{token_type:"TOKEN_INT_VAL", resolve:"BIGINT:1111111", token:"1111111"} + > CALC_PARTITION_ID:{part_description:"partition by hash(INT) partitions 8 subpartition by hash(INT) partitions 8"} + > PARTITION_ID_CALC_DONE:{partition_id:200073, level:2, partitions:"(p0sp7)", parse_sql:"prepare stmt from 'insert into t0 values(88888888,1111111,9999999)'"} + > PARTITION_ENTRY_LOOKUP_DONE:{leader:"10.10.10.1:4001", entry_from_remote:false} +> ROUTE_POLICY:{chosen_route_type:"ROUTE_TYPE_LEADER"} +> CONGESTION_CONTROL:{svr_addr:"10.10.10.1:4001"} +``` + +从诊断点 PARTITION_ID_CALC_DONE 的诊断信息 parse_sql 中可以查到对应语句的相关输出。 + +### 示例二:分区表查询 + +#### 未提供分区键值导致分区表路由不准 + +以 t0 为二级分区表,执行语句 `select * from t0 where c1=1` 路由不准为例,执行如下命令进行路由诊断。 + +```sql +obclient> EXPLAIN ROUTE select * from t0 where c1=1\G +``` + +输出如下所示。 + +```shell +Trans Current Query:"select * from t0 where c1=1" +Route Prompts +-------------- +> ROUTE_INFO + [INFO] Will do table partition location lookup to decide which OBServer to route to +> PARTITION_ID_CALC_DONE + [WARN] Fail to use partition key value to calculate sub part idx +Route Plan +-------------- +> SQL_PARSE:{cmd:"COM_QUERY", table:"t0"} +> ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP"} +> LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} + > TABLE_ENTRY_LOOKUP_DONE:{table:"t0", table_id:"500078", table_type:"USER TABLE", partition_num:64, entry_from_remote:false} + > PARTITION_ID_CALC_START:{} + > EXPR_PARSE:{col_val:"c1=1"} + > RESOLVE_EXPR:{part_range:"[1 ; 1]", sub_part_range:"(MIN ; MAX)always true"} + > RESOLVE_TOKEN:{token_type:"TOKEN_INT_VAL", resolve:"BIGINT:1", token:"1"} + > CALC_PARTITION_ID:{error:-4002, part_description:"partition by hash(INT) partitions 8 subpartition by hash(INT) partitions 8"} +> PARTITION_ID_CALC_DONE:{error:-4002, partition_id:-1, level:2, partitions:"(p1sp-1)"} +``` + +根据诊断结果进行分析。 + +1. 查看路由提示 `PARTITION_ID_CALC_DONE [WARN] Fail to use partition key value to calculate sub part idx` 表示计算二级分区位置失败。 + +2. 查看路由计划中 PARTITION_ID_CALC_DONE 附近数据是否有相关信息。 + +3. 路由计划中诊断点 `RESOLVE_EXPR:{part_range:"[1 ; 1]", sub_part_range:"(MIN ; MAX)always true"}` 表示二级分区的范围为 `MIN:MAX`,所以无法确定二级分区具体位置。 + +4. 反推语句 `Trans Current Query:"select * from t0 where c1=1"` 没有提供二级分区键值。 + +#### 使用不支持的表达式计算分区键值导致分区表路由不准 + +以 t0 为分区表,执行语句 `select * from t0 where c1=abs(-100.123);` 路由不准为例,查看对应诊断日志内容如下。 + +```shell +[2023-09-19 19:43:11.029616] [106683][Y0-00007FD890E544E0] [ROUTE]((*route_diagnosis= +Trans Current Query:"select * from t0 where c1=abs(-100.123)" +Route Prompts +-------------- +> ROUTE_INFO + [INFO] Will do table partition location lookup to decide which OBServer to route to +> RESOLVE_TOKEN + [WARN] Not support to resolve expr func(abs) +Route Plan +-------------- +> SQL_PARSE:{cmd:"COM_QUERY", table:"t0"} +> ROUTE_INFO:{route_info_type:"USE_PARTITION_LOCATION_LOOKUP"} +> LOCATION_CACHE_LOOKUP:{mode:"oceanbase"} + > TABLE_ENTRY_LOOKUP_DONE:{table:"t0", table_id:"500078", table_type:"USER TABLE", partition_num:64, entry_from_remote:false} + > PARTITION_ID_CALC_START:{} + > EXPR_PARSE:{col_val:"c1=abs"} + > RESOLVE_EXPR:{error:-5055, part_range:"(MIN ; MAX)always true", sub_part_range:"(MIN ; MAX)always true"} + > RESOLVE_TOKEN:{error:-5055, token_type:"TOKEN_FUNC", token:"abs"} + > PARTITION_ID_CALC_DONE:{error:-5055, partition_id:-1, level:2, partitions:"(p-1sp-1)"} +> ROUTE_INFO:{route_info_type:"USE_CACHED_SESSION", svr_addr:"10.10.10.1:4001"} +> CONGESTION_CONTROL:{svr_addr:"10.10.10.1:4001"} +> HANDLE_RESPONSE:{is_parititon_hit:"true", send_action:"SERVER_SEND_REQUEST", state:"CMD_COMPLETE"} +) +``` + +诊断结果中 `RESOLVE_TOKEN [WARN] Not support to resolve expr func(abs)` 表示不支持解析 abs 表达式。因此可知该语句无法解析表达式,从而无法计算出正确的分区键值,所以无法路由准确。 + +### 路由策略路由 + +以执行 `SELECT 100 - max(round(total / mem_limit * 100)) FROM oceanbase.gv$ob_memstore` 命令路由目标不符合预期为例,执行如下命令进行路由诊断。 + +```sql +obclient> EXPLAIN ROUTE SELECT 100 - max(round(total / mem_limit * 100)) FROM oceanbase.gv$ob_memstore\G +``` + +输出如下所示。 + +```shell +*************************** 1. row *************************** +Route Plan: +Trans Current Query:"EXPLAIN ROUTE SELECT 100 - max(round(total / mem_limit * 100)) FROM oceanbase.gv$ob_memstore" +Route Prompts +----------------- +> ROUTE_INFO + [INFO] Will do table partition location lookup to decide which OBServer to route to +> TABLE_ENTRY_LOOKUP_DONE + [INFO] Non-partition table will be routed by ROUTE_POLICY +> ROUTE_POLICY + [INFO] All OBServers treated as the SAME_IDC with OBProxy because 'proxy_idc_name' is not configured + [INFO] Will route to routing type(NONPARTITION_UNMERGE_LOCAL) matched replica(10.10.10.1:4001) using default route policy MERGE_IDC_ORDER because query for STRONG read +``` + +根据路由提示:`[INFO] All OBServers treated as the SAME_IDC with OBProxy because 'proxy_idc_name' is not configured` 可知没有配置 proxy_idc_name,ODP 将所有 OBServer 节点视为 SAME_IDC,LDC 路由将失效。 + + + +## 参考 + +- [OceanBase 官网文档的 “路由诊断” 部分](https://www.oceanbase.com/docs/common-odp-doc-cn-1000000001601271)。 + + > 因为在 OceanBase 官网中发现了十分完善的 ODP 路由诊断文档,所以这一小节就偷懒直接把官网的内容给照搬过来了。 + > + > 本文的内容只是基于官网文档,做了一些删改,并增加了一些批注(主要是删掉了对各诊断点的介绍,这东西主要还是给技术支持同学和 ODP 的研发同学拿去分析问题的,感兴趣的用户可以简单了解,但没必要花费时间学习)。 \ No newline at end of file diff --git a/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/_category_.yml b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/_category_.yml new file mode 100644 index 000000000..f7c0c9b17 --- /dev/null +++ b/docs/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/_category_.yml @@ -0,0 +1 @@ +label: 社区版 ODP 问题排查手册 diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/001.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/001.png new file mode 100644 index 000000000..f75b21a51 Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/001.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/002.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/002.png new file mode 100644 index 000000000..9609c0ac5 Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/002.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/003.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/003.png new file mode 100644 index 000000000..275575f27 Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/003.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/004.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/004.png new file mode 100644 index 000000000..895406ffa Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/004.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/005.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/005.png new file mode 100644 index 000000000..a3fdb4e97 Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/005.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/006.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/006.png new file mode 100644 index 000000000..ae349658b Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/006.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/007.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/007.png new file mode 100644 index 000000000..372bb8838 Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/007.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/008.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/008.png new file mode 100644 index 000000000..eca281259 Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/008.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/009.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/009.png new file mode 100644 index 000000000..6b8c2e7dd Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/01_introduction/009.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/02_show_trace/001.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/02_show_trace/001.png new file mode 100644 index 000000000..d797d30fe Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/02_show_trace/001.png differ diff --git a/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/03_connection_diagnosis/001.png b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/03_connection_diagnosis/001.png new file mode 100644 index 000000000..c58f40f1a Binary files /dev/null and b/static/img/user_manual/operation_and_maintenance/tool_emergency_handbook/odp_troubleshooting_guide/03_connection_diagnosis/001.png differ