Cobar SQL审计的设计与实现
背景介绍
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编写,于是我们框定了范围:TCP、UDP、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的系统之一。
回头来看对性能的极致追求可能或许过于"偏执",创造的收益在旁人眼里看来并没有那么大,加一台机器就能搞定的事情非要搞这么复杂。但这份“偏执”却是我们对技术最初的追求,生活不止眼前的苟且,还有诗和远方。
关于作者:专注后端的中间件开发,公众号"捉虫大师"作者,关注我,给你最纯粹的技术干货

Cobar SQL审计的设计与实现的更多相关文章
- 转载:SQL Server高效 -- 设计(ITPUT 讨论汇总
http://blog.csdn.net/zjcxc/article/details/8979756 认为在设计SQL Server对象时,主要会考虑哪些因素来避免出现性能问题? 讨论汇总——总体设计 ...
- SQL笔记 --- 数据库设计步骤(转)
SQL笔记 --- 数据库设计步骤 目录 总体设计过程需求分析概念结构设计逻辑结构设计数据库物理设计数据库实施数据库运行和维护 总体设计过程 0 » 下一篇:vim 命令集 posted @ 2012 ...
- 【学习记录】第一章 数据库设计-《SQL Server数据库设计和开发基础篇视频课程》
一.课程笔记 1.1 软件开发周期 (1)需求分析阶段 分析客户的业务和数据处理需求. (2)概要设计阶段 设计数据库的E-R模型图,确认需求信息的正确和完整. /* E-R图:实体-关系图(Ent ...
- DRDS SQL 审计与分析——全面洞察 SQL 之利器
背景 数据库存储着系统的核心数据,其安全方面的问题在传统环境中已经成为泄漏和被篡改的重要根源.而在云端,数据库所面临的威胁被进一步的放大.因此,对云数据库的操作行为尤其是全量 SQL 执行记录的审计日 ...
- SQL审计 Yearning 介绍(SQL审核平台)
yearning SQL 是一个成熟的SQL审计平台
- (2.15)Mysql之SQL基础——开发设计最佳规范
(2.15)Mysql之SQL基础——开发设计最佳规范 关键字:mysql三大范式,mysql sql开发规范 分析: show profile.mysqllsla.mysqldrmpslow.exp ...
- 《SQL Server 2000设计与T-SQL编程》
<SQL Server 2000设计与T-SQL编程> <SQL Server 2000设计与T-SQL编程>笔记1 http://dukedingding.blog.sohu ...
- SQL Server 数据库设计、命名、编码规范
https://blog.csdn.net/songguozhi/article/details/5858159 SQL Server 数据库设计.命名.编码规范
- SQL Server索引设计 <第五篇>
SQL Server索引的设计主要考虑因素如下: 检查WHERE条件和连接条件列: 使用窄索引: 检查列的选择性: 检查列的数据类型: 考虑列顺序: 考虑索引类型(聚集索引OR非聚集索引): 一.检查 ...
随机推荐
- 关于当前PHP脚本运行时系统信息相关函数
我们的 PHP 在执行的时候,其实可以获取到非常多的当前系统相关的信息.就像很多开源的 CMS 一般会在安装的时候来检测一些环境信息一样,这些信息都是可以方便地动态获取的. 脚本文件运行时的系统用户相 ...
- SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xF0\x9F\x90\xA3\xF0\x9F...' for column
在做微信公众号保存用户数据时出现这种错误,一直不知道是哪里的原因,后来发现那个用户昵称带着一只兔子表情,由于数据库编码限制不能保存数据,所有需要先编码, 用PHP的函数就是base64_encode, ...
- js中针对dom的crud
1.怎样添加.移除.移动.复制.创建和查找节点? 1)创建新节点 createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 cr ...
- P7099-[yLOI2020]灼【数学期望,结论】
正题 题目链接:https://www.luogu.com.cn/problem/P7099 题目大意 给出\(n\)个坐标轴上的点,\(q\)次询问从某点出发每次等概率向左或者向右一格求到达某个给出 ...
- Python3入门系列之-----函数
什么是函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.你已经知道Python提供了许多内建函数,比如print().但你也可以自己 ...
- 踩坑系列《四》a标签的href属性拼接问题
如上所示,无法直接在 html里面的 a 标签的href属性传递参数时,只需要在 JS 中获取对应 a 标签的id,再通过 attr 方法抓到 href,进行字符串拼接即可
- UE4蓝图AI角色制作(三)
接上一节 6. 寻路网格体代理 通过允许配置多个"代理",虚幻引擎使得用户能够轻松为大小各异的AI创建寻路网格体.首先,选中世界大纲视图中的"RecastNavMesh& ...
- 10.2 PHP
WEB资源类型 静态资源:原始形式与响应内容一致,在客户端浏览器执行 动态资源:原始形式通常为程序文件,需要在服务器端执行之后,将执行结果返回给客户端 WEB相关语言 客户端技术:html JavaS ...
- System.Drawing Linux Centos7 The type initializer for 'Gdip' threw an exception
System.Drawing 在linux使用时提示异常 The type initializer for 'Gdip' threw an exception 解决方案: yum install au ...
- Markdown语法熟悉
==(1)标题== # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题 ==(2)字体== **加粗** *斜体* ***斜体加粗*** ...