作者:苏涵FullSQL(访问数据库的所有SQL)可以有效帮助安全的数据库审计,帮助业务快速排查性能问题。一般可以通过打开genlog日志或者启动mysql审计插件来获取,但是美团选择了非侵入式绕过抓包的方案,使用Go语言实现。无论采用哪种方案,都需要关注其对数据库的性能损失。本文介绍美团基础研发平台抓包方案数据库审计实践中遇到的性能问题及优化实践,希望对大家有所帮助或启发。1背景数据库安全一直是美团点评信息安全团队和数据库团队非常关注的领域。但由于历史原因,对数据库的访问仅具备采样和审计能力,无法快速发现、评估和优化一些攻击事件。安全团队根据历史经验发现,访问数据库的攻击基本都有一定的特点,往往会使用一些特定的SQL。我们希望通过对MySQL访问流量的全面分析,识别惯用SQL,实现有针对性的数据库安全。.2现状与挑战下图是采样MySQL审计系统的架构图。数据采集??端基于pcap抓包实现,数据处理端采用美团大数据中心的日志接入方案。所有MySQL实例都部署了rds-agent用于收集MySQL相关数据,log-agent用于收集日志。rds-agent抓取MySQL访问数据,通过log-agent上报给日志接收端。为了减少延迟,上报端和接收端在同一机房进行了优化调度。日志接收端将数据写入约定的Kafka,安全团队通过Storm实时消费Kafka分析攻击事件,并定时拉取数据到Hive持久化。我们发现通常是一些核心的MySQL集群被攻击。统计显示,这些集群单机最大QPS的9995线在5万左右。rds-agent是MySQL机器上的一个寄生进程。为了主机的稳定,资源控制也是极其重要的。为了评估rds-agent在高QPS下的性能,我们使用Sysbench对MySQL进行压测,观察rds-agent在不同QPS下抓取的数据丢失率和CPU消耗,对比如下压测结果dataOops:高QPS下如何保证低丢包率和CPU消耗?已成为现行体制亟待解决的问题和挑战。3分析与优化下面主要围绕丢包率和CPU消耗问题,对数据采集端的进程、调度、垃圾回收、协议进行分析与改进。3.1数据采集端介绍首先简单介绍一下数据采集端rds-agent,它是MySQL实例上的一个进程,用Go语言编写,基于开源的MysqlProbeAgent改造而成。通过监控网卡MySQL端口的流量,分析出客户端的访问时间、源IP、用户名、SQL、目标数据库、目标IP等审计信息。下面是它的架构图,主要分为5大功能模块:1.probeprobe是探测的意思,使用gopacket作为抓包方案。是Google开源的Go抓包库,封装了pcap。探测器将捕获到的原始数据链路层帧封装成TCP层数据包。通过变体Fowler-Noll-Vo算法对源IP端口和目的IP端口字段进行哈希处理,可以快速将数据库连接分配给不同的worker。该算法确保同一连接的传入数据包和返回数据包的哈希值相同。2.watcher登录用户名对于审计来说极其重要。客户端经常通过长连接访问MySQL,登录信息只出现在MySQL通信协议的鉴权握手阶段,仅通过抓包很容易漏掉。watcher通过定时执行showprocesslist获取当前数据库的所有连接数据,通过Host字段与当前包的clientip端口进行比较来弥补丢失的用户名信息。3、不同的worker负责管理不同数据库连接的生命周期,一个worker管理多个连接。通过定时比较worker当前的连接列表和watcher中的连接列表,可以及时发现过期连接,关闭和释放相关资源,防止内存泄漏。4、connStream整个数据采集端的核心逻辑,负责根据MySQL协议解析TCP数据包,识别具体的SQL。一个连接对应一个connStreamGoroutine。因为SQL可能包含敏感数据,所以connStream还负责对SQL进行脱敏处理。具体具体的SQL识别策略,出于安全考虑,这里不再展开。5、sender负责数据上报逻辑,将connStream解析出的审计数据通过thrift协议上报给log-agent。3.2基础性能测试抓包库gopacket的性能直接决定了系统性能的上限。为了找出是不是gopacket的问题,我们写了一个简单的tcp-client和tcp-server,分别针对数据流图中涉及的gopacket前三步(如下图)进行了测试为了表现。从下面的测试结果数据来看,性能瓶颈不在gopacket。3.3CPUProfile分析丢包率和CPU消耗密不可分。为了探究CPU消耗如此高的原因,我们使用Go自带的pprof工具来分析进程的CPU消耗。从下面火焰图的调用函数,我们可以总结出几个大头:SQL脱敏、拆包、GC和Goroutine调度。下面主要介绍围绕它们的优化工作。3.4脱敏分析及改进由于SQL可能包含敏感信息,出于安全考虑,rds-agent会对每条SQL进行脱敏处理。脱敏操作使用pingcap的SQL解析器对SQL进行模板化:即将SQL中的所有值替换为“?”达到目标。该操作需要解析SQL的抽象语法树,开销较大。目前只需要对具体的SQL进行采样抓取,不需要在解析阶段对每条SQL进行脱敏处理。这里优化了流程,将脱敏下放到reporting模块,只对最终发出的样本进行脱敏。本次优化效果如下:3.5调度分析与改进从下面的数据流图中可以看出,整个链路比较长,容易出现性能瓶颈。同时,还有很多高频运行的Goroutines(红色部分)。由于数量众多,Go需要频繁地调度和切换这些Goroutine。切换对于我们CPU密集型程序来说无疑是一种负担。针对这个问题,我们做了如下优化:缩短链接:shunt、worker、解析SQL等模块合并成一个Goroutine解析器。降低切换频率:解析器每5ms从网络协议包队列中取出一次,相当于手动触发切换。(5ms也是多次测试后的折中数据,太小会消耗更多CPU,太大会造成数据丢失。)本次优化效果如下:3.6垃圾回收压力分析与改进30秒,分配指针的火焰图。可以看到已经分配了超过4000万个对象,GC压力可想而知。关于GC,我们了解到以下两种优化方案:Pooling:Go的标准库提供了sync.Pool对象池,可以通过重用对象来减少对象分配,从而减轻GC压力。手动管理内存:通过系统调用mmap直接向OS申请内存,绕过GC,实现手动内存管理。但是,场景2容易出现内存泄漏。从稳定性的角度考虑,我们最终选择了方案一,对频繁调用的函数中创建的指针对象进行管理。本次优化结果如下:3.7拆包分析与改进MySQL是基于TCP协议的。在功能调试过程中,我们发现了很多空包。从下面的MySQL客户端-服务端数据交互图可以看出,当客户端发送一条SQL命令,服务端返回结果时,由于TCP的报文确认机制,客户端会发送一个空的ack包进行确认消息,而且整个过程中空包的比例比较大,会渗透到解析环节。在高QPS下,对于Goroutine的调度和GC无疑是一种负担。下图是MySQL数据包的唯一格式。通过分析,我们观察到如下特点:一个完整??的MySQL数据包长度>=4Byte,客户端发送的新命令的sequenceid为0或1,pcap支持设置过滤规则。让我们排除内核层的空包。以下是对应上述特征的两条过滤规则:特征一:ip[2:2]-((ip[0]&0x0f)<<2)-((tcp[12:1]&0xf0)>>2)>=4Feature2:(dsthost{localIP}anddstport3306and(tcp[(((tcp[12:1]&0xf0)>>2)+3)]<=0x01))这样的效果优化如下:基于以上经验,我们对数据采集端的功能代码进行了重构,同时也进行了一些其他的优化。4最终结果下面是优化前后的数据对比。丢包率从最高60%下降到0%,CPU消耗从最高6核下降到1核。为了探究抓包功能对MySQL性能的损耗,我们使用Sysbench做了性能对比测试。从下面的结果数据可以看出,该函数在MySQL的TPS、QPS和响应时间99线指标上最大损失约6%。5未来规划虽然我们对抓包方案进行了多方面的优化,但是对于一些时延敏感的业务来说,性能损失还是太大了,而且该方案不支持一些特殊的场景:比如TCP协议层的丢包,重在传输和乱序时,MySQL协议层使用压缩的方式来传输大的SQL。但业界普遍采用直接修改MySQL内核的方式来输出全量的SQL,同时也支持输出更多的指标数据。目前,数据库内核团队也已经完成了程序的开发,正在将抓包程序替换为灰度在线。此外,我们还将继续弥补线上全SQL端到端丢包率指标的不足。本文作者苏涵来自美团基础研发平台/基础技术部/数据库技术中心。
