高性能数据访问中间件 OBProxy(八):揭秘高性能转发原理

本文涉及的产品
Serverless 应用引擎 SAE,800核*时 1600GiB*时
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 高性能是 OBProxy 的重要特性之一,为了实现 OBProxy 高性能特性,我们做了大量的工作。本篇文章我们将介绍 OBProxy 如何提升 OceanBase 数据库性能、OBproxy 单机性能优化工作以及 OBProxy 常见性能问题。对于 OBProxy 性能,我们分为两个部分:提升 OceanBase 数据库整体性能,如 OBProxy 的分区位置计算功能、LDC 路由功能、读写分离

高性能是 OBProxy 的重要特性之一,为了实现 OBProxy 高性能特性,我们做了大量的工作。本篇文章我们将介绍 OBProxy 如何提升 OceanBase 数据库性能、OBproxy 单机性能优化工作以及 OBProxy 常见性能问题。

对于 OBProxy 性能,我们分为两个部分:

  1. 提升 OceanBase 数据库整体性能,如 OBProxy 的分区位置计算功能、LDC 路由功能、读写分离功能等
  2. 提升 OBProxy 单机性能,如更快的 SQL 处理速度、更高的 QPS 等

我们优先实现 OceanBase 数据库性能提升,然后再考虑 OBProxy 单机性能提升。

OBProxy 提升 OceanBase 数据库性能

对于提升 OceanBase 数据库的性能,经过长期的性能问题分析和排查,我们总结了四个方面的影响因素,了解这四点大家就掌握了 OBProxy 对 OceanBase 数据库的性能影响,下面我们进行详细介绍。

链路对性能的影响

引入 OBProxy 后,虽然整体链路上多了一个模块,但对性能影响不大,参考下面例子:

我们比较连接 OBProxy 和连接 OBServer1 两种情况,假设数据分布在 OBServer2 上面,那么两种方式都需要两次网络交互。所以大部分情况下连接 OBProxy 和连接 OBServer 的性能差距不大,并且因为 OBProxy 转发效率更高,有时性能更好。

访问 OceanBase 数据库时,链路长度和模块的部署方式有很大关系。我们在第二篇文章介绍了 OBProxy 部署方式,提到 OBProxy 部署到应用端时性能最好。为了大家更方便的享受这种部署的好处,我们也研发了富客户端功能。所谓富客户端功能,就是指我们把 OBProxy 的功能打包成一个 so 动态库,JDBC/C驱动等加载该 so 就可以使用富客户端能力,而无需部署运维 OBProxy,我们以 Java 程序为例:

使用富客户端只需要修改下配置的 URL 即可,不需要修业务的代码,非常方便上手。

除了富客户端,随着云原生技术的普及,中间件产品的 Mesh 化也是部署的一个重要发展方向。OBProxy 的 Mesh 产品 DBMesh 在蚂蚁内部也大量部署使用。因为 Mesh 化对底层架构如 k8s、pilot 配置中心等有要求,因此有一定的使用门槛。

总结一下,大家根据真实的机房环境,按照我们第二篇文章给出的几种部署方式选择合适的就行,如果对性能有极致要求可以尝试使用 OBProxy 的富客户端形态。

扩展性

当排查发现分布式系统中某个模块达到单机性能瓶颈时,此时再去想提升单机性能是一件非常困难的事情,而通过增加机器进行扩容可以快速解决性能瓶颈问题,因此扩展性就是一个影响性能的重要因素。

OBProxy 本身是无状态的,并且启动速度特别快,可以实现秒级提供服务,因此 OBProxy 的扩展性特别好。根据真实测试数据,OBProxy 可以在 1~2 s内完成启动提供服务。

另一方面,在双十一等高并发场景,OBServer 本身也经常做云上的弹入和弹出操作,本质就是 OBServer 的机器变更(上下线、扩缩容等)。OBProxy 感知 OBServer 的副本位置变更操作,在路由时选择正确的 OBServer 为用户提供服务,帮助用户有丝滑的使用体验。

数据路由

在第五篇文章我们介绍了路由影响因素,其中性能因素至关重要。为了更好的性能,我们实现了丰富的路由算法如 LDC 路由、Primary Zone 路由、读写分离路由等,大家可以回顾前面的数据路由文章。

在 OBServer 的新版本,我们也将要上线事务路由功能,事务内的 SQL 都可以发往数据所在的节点,性能也会变得更好。我们假设事务有两条 SQL:

之前版本事务路由:

新版本事务路由:

通过比较我们也可以发现新的事务路由更加的简单高效,减少 TCP 通信次数,并且可以更高效发挥每一台 OBServer 能力,性能提升有 50% 左右。

OBProxy 单机性能提升

本节我们介绍下 OBProxy 单机性能提升的一些工作,方法论的东西比较多,和大家做一下交流,主要分为三个方面:

  1. 在 OBProxy 的功能设计和编码实现时考虑性能影响,面向高性能编程,降低新功能带来的性能损耗
  2. 关注新的硬件、linux 内核和编译器技术,使用新技术带来性能优化
  3. 建立性能回归体系,对每个版本做严格性能回归,保证性能优化的成果

下面我们将展开介绍。

OBProxy 高性能设计

OBProxy 在设计之初就考虑了高性能:

  • 采用 C++ 语言编写,可以充分使用硬件、内核的特性,也减少一些语言的 GC 等机制对性能影响
  • 设计优秀的多线程模型,可以充分发挥每一个 CPU 核心的能力,架构上做到尽量简单
  • 编码时采用异步调用、内存池、减少 Buffer 拷贝等手段优化性能

如果用一句话概括,OBProxy 通过多线程和异步框架提供优异的性能。为了满足异步接口的使用,我们也做了一些牺牲,一个完整的流程会被切割成多段代码,对代码可读性有一定影响。

对于 OBProxy 的线程模型,下图描述了公有云上 OBProxy 在 16c 机器上的任务分发模型。线程主要分为两类:accept 线程和 work 线程。accept 有两个,work 线程有 16 个(根据 CPU 核数确定)。work 线程完全一样,accept 线程完成 TCP 建连后,会将对应的套接字轮询的发给每个 work 线程。每个 work 线程运行 epoll 机制, 进行套接字的读写、异常等处理工作。

因此 OBProxy 可以做到 CPU 核心之间的负载均衡,充分发挥每一个核心的能力,不存在因为阻塞导致 CPU 利用率上不去的情况。

OBProxy 优化方向

虽然 OBProxy 有了优秀的线程模型和异步框架,但随着功能的不断丰富,对性能也会产生一定的损耗。此时为了提升性能,我们该怎么做呢?我们的做法是分为应用、编译器和内核、硬件三层去做性能优化。

应用层优化

对于应用层优化,我们不做具体工作介绍了,讲一下我们的方法步骤,常见做法分为四步:

  1. 确定优化场景:如数据库我们常选择 sysbench 中的场景或者 TPCC 场景
  2. 分析性能消耗:我们通过 perf 火焰图、打日志等一些手段获取性能消耗分布, 从大到小,模块、函数、代码语句层层拆分下去,明确问题点
  3. 提出解决方案:无论是从设计上还是 C++ 一些优化技巧(如减少拷贝、减少锁使用等)提出优化方案
  4. 验证优化效果:根据方案进行编码,并重新验证优化效果

对于大部分情况,我们发现应用层优化效果就会很显著。举个极端例子,某个客户性能不符合预期,我们调整了客户 Java 程序的日志级别,就发现性能有几倍的提升,都还未涉及到数据库层面优化。在 Github 的 OBProxy 项目下,文件 hotfuncs.txt 记录了影响 OBProxy 性能的重点函数。但对于 OBProxy 和 OBServer 这样经过长时间优化的项目,应用层优化的油水会越来越少。

编译器和内核新特性使用

内核和编译器技术也在不断发展,在性能方面也会提供越来越多的新特性,我们关注这些技术发展并应用到我们实践中。

我们以编译器为例子,PGO 和 LTO 是编译器的重要发展方向,编译器可以帮助应用程序更好的利用 CPU 的特性,如帮助 CPU 更好的做指令预取等。OBProxy 也在升级适配新版本的编译器,将这些技术引入到项目中,提升 OBProxy 性能。

软硬件结合

对于硬件影响,我们举个例子,同一份 OBProxy 代码,只升级 Intel CPU 型号,性能就会有巨大差异。因此我们也需要关注硬件技术。硬件方面,我们探索 RDMA 在 OceanBase 数据库的使用。

对于 RDMA,除了大家知道的对延迟的影响,另一个重要作用就是 Bypass Kernel,节约 CPU,优化性能。

在分析 OBProxy 性能时,我们发现内核态进行 TCP 报文收发就可能占用 30% ~ 50% 的 CPU 资源。使用 Bypass Kernel 设计(参考下图),可以进一步提升 OBProxy 性能。目前在蚂蚁内部我们也在验证 RDMA 技术。

OBProxy 优化成果保护

对于 OBproxy 项目,版本功能迭代是性能的一大杀手。新的功能特性意味着新代码引入,代码规模进一步变大。除了指令数增加,对 Cache 命中率等硬件特性影响也会导致下降。因此我们需要平衡好新功能开发对性能影响:

  1. 在性能设计时考虑性能影响,做评估好性能影响
  2. 建立完善的性能回归体系,发现每一次的性能变化,并做好记录

根据我们的工程实践,每个迭代性能损耗可以控制很好,在我们的预期范围内。

常见问题总结

问题分析方法论

下面我们分享总结我们一些性能优化方法论。

性能优化目的:

性能调优的目的旨在充分发挥软件和硬件的能力,在真实环境中实现系统的最佳性能,提高软硬件的性价比,降低客户的成本。

性能优化步骤:

  1. 确定优化目标:两个重要指标是吞吐量/延迟,如金融业务中延迟变大导致交易失败更加需要关注
  2. 压力测试及信息采集:因为性能压测往往伴随高并发等场景,所以需要尽量简化场景,更有利于问题分析
  3. 分析压力确定性能瓶颈:遵循从大到小原则,从整个系统拆分到某个模块,有模块进一步拆分,逐步攻克
  4. 实施优化:根据问题分析结果确定优化手段
  5. 确认优化效果:通过验证确定优化手段是否有效,如果无效需要重新分析

通用优化手段:

  1. 优化部署:我们做了很多介绍,大家参考我们的部署内容介绍
  2. 调整配置参数:了解常用参数参数对性能的影响,OBProxy 将大部分优化参数调整成默认值
  3. 分析及优化客户应用:性能优化方法论非常通用,也可以和客户交流,推动应用侧优化
  4. 负载均衡:OBProxy 有一些路由配置项,可以通过调整路由配置观察不同路由算法对性能影响

延迟问题排查

延迟问题分析的一个重要思路就是确定时间都去哪了。我们以一次云上延迟问题分析为例。

首先我们需要了解业务模型,以 sysbench 的 write_only 场景为例,客户端会发送如下 SQL:

BEGIN
UPDATE sbtest14 SET k=k+1 WHERE id=299
UPDATE sbtest25 SET c='44447767919-12274780880-99082970721-80233057876-56657929970-31232803895-99831366925-10847903674-03281233116-67159541952' WHERE id=253
DELETE FROM sbtest10 WHERE id=250
INSERT INTO sbtest10 (id, k, c, pad) VALUES (250, 252, '40759328906-38274427176-96674981047-99836330693-36612343492-26708648085-72288836314-84636340651-06191567091-70833666298', '76795660930-60533928600-47111820801-73921545159-04547938600')
COMMIT

第二步确定整体网络拓扑和 ping 延迟:

第三步我们根据 OBProxy 的审计日志、慢日志、统计日志分析 OBProxy 行为(参考第三篇文章)。根据 OBServer 的 sql_audit 分析 OBServer 行为。

最后根据分析出来的结论,整体梳理整个性能问题,进行总结归纳。

并发问题排查

并发问题表现就是 QPS 上不去,对于 OBProxy,主要关注三点:

  1. 打印日志是否频繁:观察 OBProxy 打印日志的速度,如果 2~3s 就生成一个日志文件,那么性能肯定不好,正常应该几个小时或者一天生成一个,此时需要观察打印日志内容分析 OBProxy 行为;另一方面打印日志会导致 SQL 变慢,SQL 变慢又会触发打印慢日志,导致恶性循环
  2. 是否有远程计划:如果 OBServer 的 sql_audit 有大量远程计划,那么性能一般不会太好,此时需要分析问题原因,不同的原因有不同的处理办法
  3. 使用 perf top:可以通过 perf top 看 OBProxy 的函数 CPU 使用,如果有函数的 CPU 使用超过 5% (显示红色),就可以找专家协助看一下

上面三点基本就可以解决大部分的 OBProxy 问题。

OBProxy 性能指标

对于 OBProxy,大家也可以记录一些常用性能数据。OBProxy 单核性能在 2~4w 之间,云上 16c 测试约有 40w QPS。

OBProxy 没有明显性能热点函数,使用 perf top 观察每个函数 CPU 消耗都在 5% 以下。

OBProxy 工作线程建议配置和 CPU 核数一样,更多的线程数不会再带来性能提升。

对于 OBProxy 使用的协议,性能数据如下:MySQL 协议 > 2.0 协议 > 压缩协议。将压缩协议改为 MySQL 协议后性能会带来明显提升。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
存储 缓存 中间件
|
2月前
|
消息中间件 存储 监控
|
18天前
|
运维 安全 Cloud Native
核心系统转型问题之分布式数据库和数据访问中间件协作如何解决
核心系统转型问题之分布式数据库和数据访问中间件协作如何解决
|
2月前
|
消息中间件 缓存 IDE
MetaQ/RocketMQ 原理问题之消息队列中间件的问题如何解决
MetaQ/RocketMQ 原理问题之消息队列中间件的问题如何解决
|
2月前
|
消息中间件 中间件 API
中间件数据转换与处理
【7月更文挑战第6天】
54 6
|
3月前
|
缓存 NoSQL 中间件
应对数据库不断膨胀的数据:缓存和队列中间件
【6月更文挑战第5天】该文探讨了优化数据库使用以提升应用系统性能的策略。文中建议利用Redis缓存和MQ消息队列作为辅助工具,以进一步优化性能和减少资源消耗。
66 2
应对数据库不断膨胀的数据:缓存和队列中间件
|
2月前
|
中间件 API 开发者
深入理解Python Web框架:中间件的工作原理与应用策略
【7月更文挑战第19天】Python Web中间件摘要:**中间件是扩展框架功能的关键组件,它拦截并处理请求与响应。在Flask中,通过`before_request`和`after_request`装饰器模拟中间件行为;Django则有官方中间件系统,需实现如`process_request`和`process_response`等方法。中间件用于日志、验证等场景,但应考虑性能、执行顺序、错误处理和代码可维护性。
57 0
|
3月前
|
消息中间件 缓存 监控
中间件中数据生成者
【6月更文挑战第12天】
36 3
|
4月前
|
存储 中间件 API
中间件应用程序发起读取数据的请求
【5月更文挑战第12天】中间件应用程序发起读取数据的请求
34 4
|
4月前
|
消息中间件 缓存 监控
中间件如果缓存中存在所需的数据(缓存命中)
【5月更文挑战第12天】中间件如果缓存中存在所需的数据(缓存命中)
41 3