Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Cobar SQL审计的设计与实现

Cobar SQL审计的设计与实现

原创
作者头像
龟仙老人
修改于 2021-03-22 06:22:37
修改于 2021-03-22 06:22:37
4750
举报
文章被收录于专栏:捉虫大师捉虫大师

背景介绍

Cobar简介

Cobar 是阿里开源的一款数据库中间件产品。

在业务高速增长的情况下,数据库往往成为整个业务系统的瓶颈,数据库中间件的出现就是为了解决数据库瓶颈而产生的一种中间层产品。

软件工程中,没有什么问题是加一层中间层解决不了的,如果有,再加一层。

一款proxy类型(本文不讨论client SDK类型的数据库中间件)的数据库中间件具备以下能力:

  • 支持数据库的透明代理,做到用户无感知
  • 能够水平、垂直拆分数据库和表,横向扩展数据库的容量和性能
  • 读和写的分离,降低主库压力
  • 复用数据库连接,降低数据库的连接消耗
  • 能够检测数据库集群的各种故障,做到快速failover
  • 足够稳定可靠,性能足够好

而本文的主角Cobar除了读写分离外其他特性都支持的很好,而且基于Cobar开发读写分离的特性并不是一件很难的事。

SQL审计

笔者有幸也曾在公司内的Cobar上做过定制开发,开发的功能是SQL审计。

从数据库产品的运营角度看,统计分析执行过的SQL是一个必要的功能;从安全角度看,信息泄露、异常SQL也需要被审计。

SQl审计需要审计哪些信息?通过调研,大致确定要采集执行的SQL、执行时间、来源host、返回行数等几个维度。

SQL审计的需求很简单,但就算是一个很简单的需求放在数据库中间件的高并发、低延迟,单机QPS可达几万到十几万的场景下都需要谨慎考虑,严格测试。

举个例子,获取操作系统时间,在Java中直接调用 System.currentTimeMillis(); 就可以,但在Cobar中如果这么获取时间,就会导致性能损耗非常严重(怎么解决?去Cobar的github仓库上看看代码吧)。

技术方案

大方向

经调研,SQL审计实现的方向大致有两种

  • 一种是比较容易想到的直接修改Cobar代码,在需要收集信息的地方埋点
  • 另一种是阿里云数据库提供的方案,通过抓取数据库的通信流量进行分析。

考虑到技术的复杂度,我们选择了较为简单的第一种实现方式。

SQL审计在Cobar中属于“锦上添花”的需求,不能因为这个功能导致Cobar性能下降,更不能导致Cobar不可用,所以必须遵循以下两点:

  • 性能尽可能接近无SQL审计版本
  • 无论如何不能造成Cobar不可用

对于性能的损耗,没有度量就没法优化,于是使用sysbench(一种数据库基准测试工具)来对现在版本的Cobar进行压测。

Cobar部署在4C8G的机器上,mysql部署在性能足够好的物理机上,压出了5.5w/s的基准,后续的版本都和这个数值进行比对。

由于采取了侵入Cobar代码的方式,想对Cobar造成影响最小,就需要保持代码最小的修改,于是采取了agent的方案。

这样可以保持代码的最小修改,只需要打点采集并传输给agent,向远端传输审计信息的逻辑就只需要在agent中处理即可,向远端传输信息几乎在一开始就确定了用kafka,这样也能保持Cobar不引入新的第三方依赖,保持代码的干净(要知道Cobar的第三方依赖只有log4j),让kafka和Cobar保持在两个JVM中,更是一种隔离。于是有了下图的架构初稿

通过上图梳理出了两个关键技术点:线程通信和进程通信。

进程通信容易理解,为什么这里还涉及线程通信?

首先Cobar的execute线程是执行SQL的主线程,如果在这个线程中去进行进程通信,那性能肯定被消耗的体无完肤。于是只能丢给审计线程去做,这样对Cobar的性能影响最小。

进程间通信

先说进程间的通信,这块稍微简单点,我们只需要罗列出可用的进程间通信方式,然后对比优缺点,选择一个合适的使用即可

首先Cobar是Java编写,于是我们框定了范围:TCPUDP、UnixDomainSocket、文件。

经过调研,UnixDomainSocket与平台相关性太强,且没有官方的实现,只有第三方的实现(如junixsocket),测试下来,不同linux的版本支持都不一致,所以这里直接排除。

写文件会导致高IO,甚至有写满磁盘的风险,毕竟在如此高的并发之下,遂排除。

最终在TCP和UDP中选择,考虑性能UDP比TCP好,且TCP还得自己解决粘包问题,于是我们选择了UDP。其实想想,SQL审计需求类似日志收集、metric上报,许多日志收集、metric上报都是采取UDP的方式。

线程间通信

如果说进程间通信拍拍脑袋就能决定,是因为他并不直接影响Cobar,他是审计线程与agent进程间的通信。然而线程间的通信则直接决定了对Cobar的性能影响,必须谨慎。

线程间通信必须通过一个中间的缓冲buffer来中转,我们对这个buffer有如下要求

  • 有界,无界就可能会导致内存溢出
  • 投递不能阻塞,阻塞会导致夯住主线程,极大影响Cobar性能
  • 可以无序,为了保证Cobar可用性,甚至可以在极端情况下丢失一些数据
  • 线程安全,高并发下如果线程不安全,数据就会错乱
  • 高性能
Java内置队列

Java中内置的队列可以充当这个buffer

有界的只有ArrayBlockingQueue和LinkedBlockingQueue,然而他们都是加锁的,直觉告诉我,他的性能不会太好。

想到Java中CurrentHashMap和LongAdder都是通过分段来解决锁冲突的,于是打算使用多个ArrayBlockingQueue来构造这个buffer

实测下来,只达到了4.7w/s,性能损失约10%

Disruptor

Java内置的队列属于有锁队列,那么有没有不加锁且有界的队列呢?搜索后发现了一款开源的无锁队列实现Disruptor,大量的产品如Log4j2等都使用了Disruptor。它是一种环形的数据结构,使用了Java中的CAS代替了锁,且有许多细节上的性能优化,导致他的性能非常强悍。

但很可惜的是,在测试时发现当Disruptor的buffer写满之后,再写就会阻塞,这和我们的需求不符合,如果主线程发生阻塞将是灾难性的,于是放弃。

SkyWalking的RingBuffer

刚好当时组内同学在研究SkyWalking,SkyWalking是一款开源的应用性能监控系统,包括指标监控,分布式追踪,分布式系统性能诊断。

他的原理是利用Java的字节码修改技术在调用处插入埋点,采集信息上报。和Cobar的采集上报过程类似。

那么他的RingBuffer是如何实现的呢?其实非常简单,缓冲区就是一个数组,每次投递时获取一个没有写入数据的数组下标即可,在多线程下只要保证获取的下标不会被两个线程同时获取即可。数据的写入速度快慢就看这个下标获取是否高效即可,如下图:

获取数组下标和Disruptor类似也是使用了CAS,但他实现非常简单,甚至有点粗糙,但他可以在写满时选择是阻塞、覆盖或是忽略,我们选择覆盖这个策略,在极端情况下丢掉老数据来换取Cobar的可用性。我们测试了一下使用多个SkyWalking的RingBuffer的场景,结果只有3w/s,损失45%性能。

于是我们对这个Ringbuffer进行了一些优化

这个优化主要是将CAS换成incrementAndGet,这样就能利用到JDK8对incrementAndGet的优化,在JDK8之前,incrementAndGet底层也是CAS,但在JDK8之后,incrementAndGet使用了fetch-and-add(CPU指令),性能要强劲很多。这块具体的介绍和代码可以参考《一种极致性能的缓冲队列》

除了这个主要的优化外,还参考Disruptor进行对SkyWalking进行了缓存行填充优化,最后达到了5.4w/s,性能损失仅仅1.8%,非常给力,于是使用了这个版本的Ringbuffer作为Cobar SQL审计的缓存区。

优化后的Ringbuffer也回馈给了SkyWalking社区,SkyWalking作者赞赏这是一个“intersting contribution”。

总结

Cobar的SQL审计在上线后稳定支撑了公司所有Cobar集群,是承载最高QPS的系统之一。

回头来看对性能的极致追求可能或许过于"偏执",创造的收益在旁人眼里看来并没有那么大,加一台机器就能搞定的事情非要搞这么复杂。但这份“偏执”却是我们对技术最初的追求,生活不止眼前的苟且,还有诗和远方。


欢迎关注我的公众号:“捉虫大师”

原文链接:https://mp.weixin.qq.com/s/OEuZIfbKba8sq_rect809w

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
别看唐探了,Q(ueue)的真相在这里
注:虚线部分为对 cobar 中间件的改造,业务调用是无感知的如图示,主要步骤如上图所示
kunge
2021/07/16
5210
一种极致性能的缓冲队列
举个日志采集的例子,日志在不同的线程上生产,在日志生产速度远超消费者速度时,可以丢弃部分数据,要求打日志的性能损耗最小,这种情况下可采用本文提供的极致性能的缓冲队列。
龟仙老人
2020/12/15
8050
深入浅出生产者-消费者模式
生产者-消费者模式是一个经典的多线程设计模式,它为多线程间的协作提供了良好的解决方案。也经常有面试官会让手写一个生产者消费者,从代码细节可以看出你对多线程编程的熟练程度,今天我们来详细看一下如何写出一个生产者消费者模式,并且逐步对其优化争取做到高性能。
beifengtz
2019/06/03
3.5K0
深入浅出生产者-消费者模式
并发编程之Disruptor
一、Disruptor是什么 Disruptor是一个高性能的异步处理框架,或者可以认为是最快的消息框架(轻量的JMS),也可以认为是一个观察者模式实现,或者事件-监听模式的实现,直接称disruptor模式。 Disruptor最大特点是高性能,它被设计用于在生产者—消费者问题(producer-consumer problem,简称PCP)上获得尽量高的吞吐量(TPS,Transaction Per Second))和尽量低的延迟。Disruptor是LMAX在线交易平台的关键组成部分,LMAX平台使
lyb-geek
2018/03/27
2.4K1
并发编程之Disruptor
springboot 项目使用 Disruptor 做内部消息队列
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/03/21
9930
还在用BlockingQueue?读这篇文章,了解下Disruptor吧
听到队列相信大家对其并不陌生,在我们现实生活中队列随处可见,去超市结账,你会看见大家都会一排排的站得好好的,等待结账,为什么要站得一排排的,你想象一下大家都没有素质,一窝蜂的上去结账,不仅让这个超市崩溃,还会容易造成各种踩踏事件,当然这些事其实在我们现实中也是会经常发生。
用户5397975
2019/10/14
1.6K0
还在用BlockingQueue?读这篇文章,了解下Disruptor吧
异步编程 - 13 高性能线程间消息传递库 Disruptor
Disruptor是一个高性能的线程间消息传递库,它源于LMAX对并发性、性能和非阻塞算法的研究,如今构成了其Exchange基础架构的核心部分。
小小工匠
2023/09/09
9620
异步编程 - 13 高性能线程间消息传递库 Disruptor
高性能线程间消息传递库Disruptor概述
Disruptor是一个高性能的线程间消息传递库。它源于LMAX对并发性 、性能和非阻塞算法的研究,如今构成了其Exchange基础架构的核心部分。
加多
2019/08/29
7940
高性能线程间消息传递库Disruptor概述
并发框架disruptor_ringbuffer的常规用法
指向了位置4,然后返回3。这样,线程D就获得了位置3的操作权限。 * 接着,另一个线程E做类似以上的操作 * 提交写入 * 以上,线程D和线程E都可以同时线程安全的往各自负责的区块(或位置,slots)写入数据。但是,我们可以讨论一下线程E先完成任务的场景…
全栈程序员站长
2022/09/30
5040
并发框架disruptor_ringbuffer的常规用法
异步批处理教程
书接上回 大数据量、高并发业务怎么优化?(一)[1] 文章中介绍了异步批处理的三种方式,本文继续深入针对前两种进行讲解,并给出代码示例:
wayn
2023/02/01
3810
架构师必备词汇和知识点
01 高可用 负载均衡(负载均衡算法) 反向代理 服务隔离 服务限流 服务降级(自动优雅降级) 失效转移 超时重试(代理超时、容器超时、前端超时、中间件超时、数据库超时、NoSql超时) 回滚机制(上
Java高级架构
2018/04/19
1.8K0
架构师必备词汇和知识点
线程框架模型总结
1. Disruptor:Apache Storm底层应用了Disruptor来实现worker内部的线程通信;
章鱼carl
2022/03/31
8190
线程框架模型总结
Cobar源码分析之AST
Cobar是阿里开源的数据库中间件,关于它的介绍这里不再赘述,可以参考之前的文章《Cobar SQL审计的设计与实现》
龟仙老人
2021/07/07
7270
Cobar源码分析之AST
秒级达百万高并发框架-Disruptor
Disruptor是一个高性能的并发框架,主要应用于创建具有高吞吐量、低延迟、无锁(lock-free)的数据结构和事件处理系统。它最初由LMAX公司开发的,已经成为了业界广泛使用的高性能并发框架。
逍遥壮士
2023/09/01
1.6K0
秒级达百万高并发框架-Disruptor
Java异步批处理教程
书接上回 大数据量、高并发业务怎么优化?(一) 文章中介绍了异步批处理的三种方式,本文继续深入针对前两种进行讲解,并给出代码示例:
wayn
2022/12/11
1K0
Java异步批处理教程
2018-08-02 你应该知道的高性能无锁队列Disruptor你应该知道的高性能无锁队列Disruptor1.何为队列2.jdk中的队列3.Disruptor4.Log4j中的Disruptor最
https://juejin.im/post/5b5f10d65188251ad06b78e3
Albert陈凯
2018/08/03
8930
disruptor笔记之一:快速入门
disruptor是LMAX公司开发的一个高性能队列,其作用和阻塞队列(BlockingQueue)类似,都是在相同进程内、不同线程间传递数据(例如消息、事件),另外disruptor也有自己的一些特色:
程序员欣宸
2021/11/05
5430
disruptor笔记之一:快速入门
disruptor框架原理_disruptor使用
https://github.com/LMAX-Exchange/disruptor/blob/master/README.md https://github.com/LMAX-Exchange/disruptor/wiki/Introduction https://github.com/LMAX-Exchange/disruptor/wiki/Getting-Started
全栈程序员站长
2022/09/30
4270
disruptor框架原理_disruptor使用
disruptor源码分析一之核心组件介绍
disruptor是LAMX用于交易场景的一个环形队列。按照disruptor的官方wiki中说的,学习disruptor的最好的方式就是与java中的BlockingQueue进行比较。在disruptor中同一个进程中的线程间数据的移动是依托于 messages或者events。和queue相同的一些关键特性中,disruptor提供了更好的实现,比如:
山行AI
2019/07/01
1.2K0
disruptor源码分析一之核心组件介绍
深入理解Disruptor
Disruptor通过缓存行填充,利用CPU高速缓存,只是Disruptor“快”的一个因素,快的另一因素是“无锁”,尽可能发挥CPU本身的高速处理性能。
JavaEdge
2023/01/14
6230
深入理解Disruptor
相关推荐
别看唐探了,Q(ueue)的真相在这里
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档