暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

「OceanBase 4.1 体验」全链路跟踪监控管理

原创 大数据模型 2023-04-24
637

项目背景

南拳王小亨与北腿王小利的矛盾斗争表面上来看原因是门派有别,功夫有分,实际上是工作面对立,导致矛盾冲突深厚,不可协调!小亨是应用开发部的老大,带领一个团队负责前端界面和后端业务应用开发,小利则是平台维护部的老大,带领一个团队负责公司所有系统包括数据库、中间件、操作系统及相关的容器、虚拟机正常运行及资源调度布署。小亨需要小利提供资源支持,小利需要小亨展现业务效果,按理他俩应该是兄弟。两个闹得不和源于历史上发生一次重大事件,测试环境系统尚好,但是正式上了生产环境 系统上线慢得不行,领导展开事故调查,追踪到直接致命的原因一个老旧驱动JAR包,而这个驱动JAR包说不清楚是由谁管理负责,因为当时要背责任事故,两人就开始拳打脚踢起来。

从那次事件开始,后面又发生数据库中间件事件,为了高性能,小利引进了mycat实现读写分离和流量导向,小亨一看对我的业务逻辑有大量的侵入,我需要重构,我之前的活不仅白干了,我还要干一大堆活 ,两人又是一场大战。

问题分析

深究两人的矛盾,其实就是客户端与服务端的矛盾,社会类似的事非常多,举个快递行业的发展。客户端是取件人,服务端是快递店,最初快递量不多的时候,服务端支持送货上门,电话联系后亲手递给取件人,计算机术语叫同步。

量大的时候, 我们通过异步的方式,服务端把快递放到保安处, 后来为了增强安全性性,服务端建了快递柜子上,并建设了相关的管理系统,给取件人一个密码去哪里拿快递。这可能对取件人体 验不舒服,所以体贴的服务端都会把柜子离取件人近一些。这样快递量大了,客户端的服务体验质量也没有下降,服务端也没有增加太多的人力成本。

如果遇上双11,快递量太多,柜子早已经塞满了,我们可以理解一级缓存已经满了。取件人必须要到驿店取件,除了驿站要离客户近一点,最重要的是把快速货物拿给客户。过去,双11的时候,服务端会加派人手,类比多线程加分布式,而且三更半夜把物品分类分级排列好,提高服务效率。为了提高效率,不再是服务端拿东西给客户端,而是客户端自己去拿东西。性能效率取决于客户端,首先客户端会收到信息提醒, 上面有快递的详细位置【货架位置、层数、尾数】,取件人拿到物品后,拿到出库机扫描一下,就可以进行出库。为了整个链条清晰可见,服务端需要加载摄像机、监控头、人脸识别,保证不会发生误拿、错拿的事。

前车可鉴,快递系统的升级改造必须服务端做改造,客户端做相应的配合。对客户端来说,让我改造习惯和方式,那是入侵,服务端必须重视客户端的情绪,同时提供一系列措施让安抚客户。产品技术布道也是同样的道理。

既要安全也有性能,必须要实现全链路监控跟踪,应用程序和数据库交互可以粗略分为以下流程。

  1. 用户发起请求, 数据库客户端发起请求到指定的数据库集群。
  2. 目标数据库响应 指定的数据库集群指定一个节点响应用户的请求。
  3. 两者建立会话, 数据库集群其中一个节点与客户端产生会话。
  4. 对象请求解析, 数据库将接收的请求进行 语法检查,对象解析、转换对应的关系代数结构、并进行计划任务优化。
  5. 调度并且执行, 寻找最合适的副本 ,根据优先级进行,是内存、缓存、数据快照、存储等等。
  6. 监测任务状态, 观测数据库的执行中状态。
  7. 返回数据结果, 数据库服务端返回结果给数据库客户端

从细节方面来看,客户端可以是JDBC、ODBC、C API的方式,而且下面还有细分,同样是JDBC,你使用mysql5.0.4.jar 与mysql8.0.4.jar两个完全不同的效果。目标数据库响应,有些数据库是中心化架构,而OB4.1则是去中化、点对点的架构,OB由obproxy模块担任对外服务的接入管理,正式建立会话连接后,用户请求会经过常规的检查,假如有历史有同样的请求,直接去缓存里找。如果没有,就要经历流程4和流程5。

流程4和流程5是数据库的核心能力,流程4好比是大脑,流程5好比是手脚,用户请求将会映射成什么样的数据对象,对象之间的路径是什么样的?对象执行花了多长时间?大脑会根据规则引擎和消耗成本转换成一个最佳的执行计划,可能使用MPP多机执行的方式,可能是索引导向到指定分区,可能是全盘扫描的方式 ,可能先广播后分发的方式。流程5提供数据计算能力和数据存储能力,为了计算贴近存储,可以采用MVCC,可以采用深度的列式存储,可以采用先行后列的存储方式,可以优先内存、缓存、数据快照的方式 。客户端到服务端的全链路监控管理,我们知道的细节越多越好。

应用实践

第一次安装OB4.1在Ubuntu 20.04.6,非常担心安装发现兼容问题,后面发现是多余的。下载介质oceanbase-all-in-one-4.1.0.0-100120230323143519.el7.x86_64.tar.gz,解压,第一步运行./bin/install.sh第二步运行obd demo就可以自动完成OB4.1单机的安装

环境配置

操作系统 Ubuntu 20.04.6 LTS
CPU Intel® Core™ i7-4710HQ CPU @ 2.50GHz 8核
内存 12G
硬盘 320G 5400转
软件版本 OceanBase4.1
集群规模 单个
目标 实践应用OceanBase的全链路跟踪

测试数据

CREATE TABLE EMP
(EMPNO INT(4) NOT NULL,
ENAME VARCHAR(10),
JOB VARCHAR(9),
MGR INT(4),
HIREDATE DATE,
SAL DECIMAL(7, 2),
COMM DECIMAL(7, 2),
DEPTNO INT(2)
);

测试用例

启用链路跟踪很简单,只需要把会话变量变成ON,如下

obclient [fish]> set ob_enable_show_trace=ON;

再运行SQL,SQL执行后会保存数据在/root/oceanbase-ce/log/trace.log ,跟踪日志的数据都是从trace.log里面出的。

最后运行== show trace ==,注意,show trace显示上一次会话SQL追踪执行日志记录。

下面准备了3个测试用例

  1. 命令行层面,激活跟踪,执行SQL,收集链路信息,依次输入错误的SQL,输入不存在的表,输入关联表。
  2. 代码层面链路跟踪,基于代码层,使用mysql5.1.46版本的JDBC驱动,执行SQL后,前端检测是否能收集跟踪信息,知道SQL在服务端运行的动态。
  3. 两个不同SQL,同是相同结果,查看他们链路执行过程,通过一个DEMO,查询计划和链路跟踪去排查两个SQL的执行过程。
CLI层面的链路跟踪
obclient [fish]> set ob_enable_show_trace='ON'; Query OK, 0 rows affected (0.001 sec) obclient [fish]> sellect * from t_order; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your OceanBase version for the right syntax to use near 'sellect * from t_order' at line 1 obclient [fish]> show trace; +-------------------------------+----------------------------+------------+ | Operation | StartTime | ElapseTime | +-------------------------------+----------------------------+------------+ | com_query_process | 2023-04-24 23:37:04.767402 | 0.262 ms | | └── mpquery_single_stmt | 2023-04-24 23:37:04.767407 | 0.247 ms | | └── sql_compile | 2023-04-24 23:37:04.767417 | 0.142 ms | | ├── pc_get_plan | 2023-04-24 23:37:04.767418 | 0.004 ms | | └── hard_parse | 2023-04-24 23:37:04.767460 | 0.084 ms | | └── parse | 2023-04-24 23:37:04.767461 | 0.066 ms | +-------------------------------+----------------------------+------------+ 6 rows in set (0.004 sec) obclient [fish]> select * from dkk; ERROR 1146 (42S02): Table 'fish.dkk' doesn't exist obclient [fish]> show trace; +-------------------------------+----------------------------+------------+ | Operation | StartTime | ElapseTime | +-------------------------------+----------------------------+------------+ | com_query_process | 2023-04-24 23:34:07.266692 | 0.308 ms | | └── mpquery_single_stmt | 2023-04-24 23:34:07.266695 | 0.295 ms | | └── sql_compile | 2023-04-24 23:34:07.266702 | 0.202 ms | | ├── pc_get_plan | 2023-04-24 23:34:07.266703 | 0.002 ms | | └── hard_parse | 2023-04-24 23:34:07.266731 | 0.161 ms | | ├── parse | 2023-04-24 23:34:07.266732 | 0.030 ms | | └── resolve | 2023-04-24 23:34:07.266777 | 0.099 ms | +-------------------------------+----------------------------+------------+ 7 rows in set (0.004 sec) obclient [fish]> SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number) limit 10; obclient [fish]>show trace; 如下

1.png

CLI层面小结

ob的sql_compile和对应前文的流程4对象请求解析, pc_get_plan是查看缓存,parse检测SQL语法有没有满足OB标准规范、resolve将会去检阅请求中的数据库对象,例如表是否存在。rewrite则是把SQL转换为内部的树结构,就绪后优化器就可以正工作、optimize优化器对当前的所有数据库对象进行排列组合,选择一个最佳的优化路径执行计划,code generate把计划编译成机器文件,准备执行。

sql_execute对应前文的流程5调度并执行,按照产品路径的发展,以后演变会有分布式执行还是本地执行,计算和存储用了哪几个节点。

code层面的链路跟踪

从IDEA里面提交代码,目标是jdbc:mysql://192.168.10.8:2883/fish,进行两表查询。

image.png

根据与OB工程师交流,必须要在代码层传递show trace,所以代码层面上必须要有String sql = set session ob_enable_show_trace=on; 代码如下

try (Connection connection = DriverManager.getConnection( JDBC_URL, JDBC_USER, JDBC_PWD ); PreparedStatement ps = connection.prepareStatement( "set session ob_enable_show_trace=on" ) ) { int ResultUpdate = ps.executeUpdate(); ps.executeQuery(sql); ResultSet rs = ps.executeQuery("show trace"); try{ while (rs.next()) { System.out.print(rs.getString(1)); System.out.print(rs.getString(2)); System.out.println(rs.getString(3));

成功输出以下界面
image.png

code层面小结

客户端通过JDBC递交的SQL请求,可以在代码层拿到trace log,这样客户端可以知道请求在服务端的运行情况。

不同的SQL链路跟踪

针对EMP表【ORACLE经典表】,SQL1是一个针对单表使用over窗口函数并进行分区的扫描,SQL2是基于单表EMP表派生两表关联查询。

按照正常思维理解,SQL1比SQL2快,结合explain查看相关逻辑执行计划进行SQL全盘查看。

obclient [fish]> explain  select count(*)  from (
 select ename, deptno, sal  from (select a.*, max(sal) over(partition by deptno) max_sal from emp a) where sal = max_sal );
+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan                                                                                                                                            |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| ============================================================                                                                                          |
| |ID|OPERATOR         |NAME           |EST.ROWS|EST.TIME(us)|                                                                                          |
| ------------------------------------------------------------                                                                                          |
| |0 |SCALAR GROUP BY  |               |1       |152943      |                                                                                          |
| |1 | SUBPLAN SCAN    |ANONYMOUS_VIEW1|24      |152943      |                                                                                          |
| |2 |  WINDOW FUNCTION|               |166188  |150108      |                                                                                          |
| |3 |   PARTITION SORT|               |166188  |31085       |                                                                                          |
| |4 |    TABLE SCAN   |a              |166188  |8309        |                                                                                          |
| ============================================================                                                                                          |
| Outputs & filters:                                                                                                                                    |
| -------------------------------------                                                                                                                 |
|   0 - output([T_FUN_COUNT(*)]), filter(nil), rowset=256                                                                                               |
|       group(nil), agg_func([T_FUN_COUNT(*)])                                                                                                          |
|   1 - output(nil), filter([.SAL = .max_sal]), rowset=256                                                                                              |
|       access([.SAL], [.max_sal])                                                                                                                      |
|   2 - output([a.SAL], [T_FUN_MAX(a.SAL)]), filter(nil), rowset=256                                                                                    |
|       win_expr(T_FUN_MAX(a.SAL)), partition_by([a.DEPTNO]), order_by(nil), window_type(RANGE), upper(UNBOUNDED PRECEDING), lower(UNBOUNDED FOLLOWING) |
|   3 - output([a.SAL], [a.DEPTNO]), filter(nil), rowset=256                                                                                            |
|       sort_keys([HASH(a.DEPTNO), ASC], [a.DEPTNO, ASC])                                                                                               |
|   4 - output([a.SAL], [a.DEPTNO]), filter(nil), rowset=256                                                                                            |
|       access([a.SAL], [a.DEPTNO]), partitions(p0)                                                                                                     |
|       is_index_back=false, is_global_index=false,                                                                                                     |
|       range_key([a.__pk_increment]), range(MIN ; MAX)always true                                                                                      |
+-------------------------------------------------------------------------------------------------------------------------------------------------------+
23 rows in set (0.003 sec)


obclient [fish]> select count(*)  from (
 select ename, deptno, sal  from (select a.*, max(sal) over(partition by deptno) max_sal from emp a) where sal = max_sal );
+----------+
| count(*) |
+----------+
|    30216 |
+----------+
1 row in set (4.865 sec)

obclient [fish]> show trace;
+-------------------------------------------+----------------------------+-------------+
| Operation                                 | StartTime                  | ElapseTime  |
+-------------------------------------------+----------------------------+-------------+
| com_query_process                         | 2023-04-24 11:10:43.391681 | 4864.419 ms |
| └── mpquery_single_stmt                   | 2023-04-24 11:10:43.391686 | 4864.401 ms |
|     ├── sql_compile                       | 2023-04-24 11:10:43.391696 | 2.333 ms    |
|     │   ├── pc_get_plan                   | 2023-04-24 11:10:43.391698 | 0.008 ms    |
|     │   └── hard_parse                    | 2023-04-24 11:10:43.391748 | 2.274 ms    |
|     │       ├── parse                     | 2023-04-24 11:10:43.391749 | 0.079 ms    |
|     │       ├── resolve                   | 2023-04-24 11:10:43.391848 | 0.291 ms    |
|     │       ├── rewrite                   | 2023-04-24 11:10:43.392279 | 1.054 ms    |
|     │       ├── optimize                  | 2023-04-24 11:10:43.393344 | 0.451 ms    |
|     │       ├── code_generate             | 2023-04-24 11:10:43.393806 | 0.098 ms    |
|     │       └── pc_add_plan               | 2023-04-24 11:10:43.393977 | 0.038 ms    |
|     └── sql_execute                       | 2023-04-24 11:10:43.394035 | 4861.638 ms |
|         ├── open                          | 2023-04-24 11:10:43.394036 | 17.532 ms   |
|         ├── response_result               | 2023-04-24 11:10:43.411583 | 4765.348 ms |
|         │   ├── get_das_id                | 2023-04-24 11:10:43.412222 | 0.000 ms    |
|         │   └── do_local_das_task         | 2023-04-24 11:10:43.412236 | 0.065 ms    |
|         └── close                         | 2023-04-24 11:10:48.176946 | 78.652 ms   |
|             ├── close_das_task            | 2023-04-24 11:10:48.176948 | 0.026 ms    |
|             └── end_transaction           | 2023-04-24 11:10:48.255575 | 0.003 ms    |
+-------------------------------------------+----------------------------+-------------+
19 rows in set (0.144 sec)






obclient [fish]> explain  select count(*)  from  (
select ename,deptno,sal  from emp a  where sal = (select max(sal) from emp b where a.deptno = b.deptno) );
+--------------------------------------------------------------------------------------------+
| Query Plan                                                                                 |
+--------------------------------------------------------------------------------------------+
| =================================================                                          |
| |ID|OPERATOR        |NAME |EST.ROWS|EST.TIME(us)|                                          |
| -------------------------------------------------                                          |
| |0 |SCALAR GROUP BY |     |1       |49577       |                                          |
| |1 | HASH JOIN      |     |2304    |49536       |                                          |
| |2 |  SUBPLAN SCAN  |VIEW1|100     |24811       |                                          |
| |3 |   HASH GROUP BY|     |100     |24811       |                                          |
| |4 |    TABLE SCAN  |b    |166188  |8309        |                                          |
| |5 |  TABLE SCAN    |a    |166188  |8309        |                                          |
| =================================================                                          |
| Outputs & filters:                                                                         |
| -------------------------------------                                                      |
|   0 - output([T_FUN_COUNT(*)]), filter(nil), rowset=256                                    |
|       group(nil), agg_func([T_FUN_COUNT(*)])                                               |
|   1 - output(nil), filter(nil), rowset=256                                                 |
|       equal_conds([a.SAL = VIEW1.max(sal)], [a.DEPTNO = VIEW1.b.DEPTNO]), other_conds(nil) |
|   2 - output([VIEW1.max(sal)], [VIEW1.b.DEPTNO]), filter(nil), rowset=256                  |
|       access([VIEW1.max(sal)], [VIEW1.b.DEPTNO])                                           |
|   3 - output([T_FUN_MAX(b.SAL)], [b.DEPTNO]), filter(nil), rowset=256                      |
|       group([b.DEPTNO]), agg_func([T_FUN_MAX(b.SAL)])                                      |
|   4 - output([b.DEPTNO], [b.SAL]), filter(nil), rowset=256                                 |
|       access([b.DEPTNO], [b.SAL]), partitions(p0)                                          |
|       is_index_back=false, is_global_index=false,                                          |
|       range_key([b.__pk_increment]), range(MIN ; MAX)always true                           |
|   5 - output([a.DEPTNO], [a.SAL]), filter(nil), rowset=256                                 |
|       access([a.DEPTNO], [a.SAL]), partitions(p0)                                          |
|       is_index_back=false, is_global_index=false,                                          |
|       range_key([a.__pk_increment]), range(MIN ; MAX)always true                           |
+--------------------------------------------------------------------------------------------+
28 rows in set (0.158 sec)


obclient [fish]> select count(*)  from  (
select ename,deptno,sal  from emp a  where sal = (select max(sal) from emp b where a.deptno = b.deptno) );                               

show trace;+----------+
| count(*) |
+----------+
|    30216 |
+----------+
1 row in set (0.023 sec)

obclient [fish]> 
obclient [fish]> show trace;
+-------------------------------------------+----------------------------+------------+
| Operation                                 | StartTime                  | ElapseTime |
+-------------------------------------------+----------------------------+------------+
| com_query_process                         | 2023-04-24 11:12:56.004332 | 20.213 ms  |
| └── mpquery_single_stmt                   | 2023-04-24 11:12:56.004335 | 20.203 ms  |
|     ├── sql_compile                       | 2023-04-24 11:12:56.004340 | 0.043 ms   |
|     │   └── pc_get_plan                   | 2023-04-24 11:12:56.004341 | 0.014 ms   |
|     └── sql_execute                       | 2023-04-24 11:12:56.004388 | 20.126 ms  |
|         ├── open                          | 2023-04-24 11:12:56.004389 | 0.031 ms   |
|         ├── response_result               | 2023-04-24 11:12:56.004426 | 20.025 ms  |
|         │   ├── get_das_id                | 2023-04-24 11:12:56.004482 | 0.001 ms   |
|         │   ├── do_local_das_task         | 2023-04-24 11:12:56.004490 | 0.029 ms   |
|         │   ├── get_das_id                | 2023-04-24 11:12:56.014238 | 0.000 ms   |
|         │   └── do_local_das_task         | 2023-04-24 11:12:56.014254 | 0.021 ms   |
|         └── close                         | 2023-04-24 11:12:56.024464 | 0.042 ms   |
|             ├── close_das_task            | 2023-04-24 11:12:56.024465 | 0.009 ms   |
|             ├── close_das_task            | 2023-04-24 11:12:56.024482 | 0.005 ms   |
|             └── end_transaction           | 2023-04-24 11:12:56.024499 | 0.001 ms   |
+-------------------------------------------+----------------------------+------------+
15 rows in set (0.003 sec)

不同的SQL层面小结

结果意外的是,SQL1耗时是4.865 sec,而SQL2耗时是0.023 sec,SQL1比SQL2慢,虽然SQL1的执行计划只有一个TABLE SCAN,但是PARTITION SORT和WINDOW FUNCTION消耗了大量的时间,最重要的运行SQL多次无法被 缓存,总是需要hard parse,时间成本主要在执行上。

而SQL2虽然它的执行计划用了两个TABLE SCAN【全盘扫描】,消耗时间最长的是HASH JOIN,从trace日志上, 多次运算后不需要hard parse,直接从缓存里拿出来就可以使用。

explain结合trace,我们注意emp这个表根本没有做索引,没有做分区,这个可能是SQL1为什么慢的原因。

用户收益

基于事实说话,基于客观的数据说话,基于全链路跟踪的数据大屏,以后测试用例,小亨与小利知道应用堵 在哪里了,两个再不用打架,从此两个人过上没羞没噪的幸福生活。笔者的体检,目前的全链路跟踪 还处于发展阶段,相信以后的功能会更丰富。

后记

全链路跟踪是一个很大的话题,从客户端到服务端,两者之间来回多次,ob的trace只是其中一个环节,它记录的是服务端接收到SQL请求,SQL在服务端怎么拆解,进行下一步的划分。客户端采用什么样的驱动,mysql jdbc?oceanbase jdbc? 这些信息还没有纳入进来管理,所以全链路跟踪的细节还有很大的提升优化空间。

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

文章被以下合辑收录

评论