写点什么

如何提高大规模正则匹配的效能

  • 2021-03-10
  • 本文字数:3283 字

    阅读完需:约 11 分钟

如何提高大规模正则匹配的效能

背景

日常工作中会大量应用正则表达式,用正则表达式去定义规则,然后去匹配数据。这里先看两个安全场景下的正则应用需求:


场景 1,FTP 账号被成功暴力破解后数据遭窃取

•  数据源:FTP 服务器日志

•  关联逻辑:针对特定账号暴力破解,然后利用该特定账号登录成功,之后利用该特定账号下载大量文件

•  告警内容:FTP 账号 ${user_name}被成功暴力破解后窃取数据

•  告警级别:高危


场景 1 中,正则表达式用于在日志中匹配多次账户登录的行为上。


场景 2,Deep packet inspection (DPI) ,例如过滤网络威胁和违反安全策略的流量等

•         数据源:网络数据包

•         检测规则条件:数据命中规则集


场景 2 中,正则表达式用于时间序列上的多个数据包之间的安全检测。


其实,场景 1 中只列举了 FTP 被攻击的一种方式,FTP 攻击还有很多其他手段,所以检测 FTP 被攻击的正则匹配场景的另一个特征就是整个规则集可能很大;场景 2 中,利用已知的入侵行为构建模式集合,通过检测网络数据包,发现是否存在不符合安全策略的行为或被攻击的迹象,这需要对数据包的载荷部分进行检测,要求匹配的速度非常快,否则将会影响用户体验。


另一方面,这里用到的正则与传统用法又不太一样,对正则的传统用法是,给定一个文本,用一个或少数几个正则规则,去匹配文本,找出文本中匹配的数据。而现在面对的问题,首先是规则的数量大,上千上万或者超过十万的规则集,如果仍然采用之前的做法,用|分割,或者外层用循环去匹配,那么处理的时间将很长,对资源的消耗也很大,基本不可接受;其次在匹配的时候,待匹配的数据不是一个完整的整体,比如说网络数据包,是一个一个接收的,这是一个流式的形式,传统的正则处理引擎不能很好的处理流式数据,需要缓存一批数据去匹配,这样匹配就不够及时,而且目前正则处理有个很大的问题,如果正则表达式写的不好,那么匹配会很慢。所以,需要一个解决方案来应对以下这些挑战:


•  规则数量多

•  匹配速度要快

•  支持流式数据

•  资源消耗不能太大

Hyperscan 算子介绍

针对上述正则匹配中遇到的挑战,经过调研和对比测试市面上的主流正则匹配引擎,我们最终选择了 Hyperscan。


Hyperscan 是 Intel 开源的高性能正则表达式匹配库,提供了 C 语言 API,目前已经在很多商业项目和开源项目中得到应用。


Hyperscan 具备这些特性:

•  支持大部分 PCRE 正则语法(如果使用 Chimera 库,那将支持所有语法)

•  支持流式匹配

•  支持多模匹配

•  采用特定指令集加速匹配

•  易于扩展

•  内部多种引擎结合


Hyperscan 在设计之初就是为了更好的处理流式匹配和多模匹配,对流模式的支持极大的方便了正则用户,不再需要用户去维护接收到的数据,无需缓存数据;多模匹配允许把多个正则表达式传入并在同一时间进行匹配。


因为需要特定的指令集,所以 Hyperscan 对 CPU 有要求,如下图:



CPU 最低要支持 SSSE3 指令集,最下面一行的指令集能加速匹配


和大多数正则引擎类似,Hyperscan 也包括编译和匹配阶段,编译是把正则表达式解析然后构建成内部需要的 database,后续可以多次使用这个 database 去匹配;如果是多模匹配,编译时每个正则表达式需要有一个唯一的标识 id,id 在匹配的时候会用到。编译过程如下图所示:



匹配的时候 Hyperscan 默认会返回所有命中的结果,这点不像有些正则引擎,指定贪婪的时候返回贪婪的匹配结果,指定懒惰的时候返回懒惰的结果。匹配时如果有命中,那么会以回调函数的形式通知用户在哪个位置命中了哪个正则表达式 id。匹配过程如下图所示:



Hyperscan 的缺点是只能是单机执行,没有分布式能力,其可以解决延迟的问题,但无法解决吞吐的问题,解决吞吐问题,可以依靠主流实时计算框架 Flink。Flink 是一个在无界和有界数据流上进行状态计算的框架和分布式处理引擎。无界就是有开始但没有结束的数据,无界的数据流计算即流式计算,有界就是有开始有结束的数据,有界的数据流计算即批处理。



Flink 可以用于很多的计算场景中,这里列举了 3 个,Flink 可以处理事件驱动的程序,除了简单事件,Flink 还提供了 CEP 库可以处理复杂事件;Flink 还可以作为数据管道,做一些数据清洗筛选、转换等操作,把数据从一个存储系统转移到另一个系统;Flink 可以做流或批式数据的分析、指标计算,用于大屏展示等。Flink 已经成为业界公认的流式处理的第一选择。


把正则匹配引擎整合到 Flink 中,借助 Flink 强大的分布式能力,强强联合,那么将会发挥更大威力。所以提供了这样的解决方案,如下图所示:



该解决方案实现了一个自定义的 UDF 算子,算子支持指定只匹配输入数据中的某几个字段,算子的输出是待匹配的字段文本,匹配最终状态,包括命中,不命中,错误,超时四种状态,如果是命中的状态,那么还会返回匹配中的正则表达式的 id,输出还包括输入原始数据,如果有后续处理,这样不受影响;为了进一步方便用户使用,扩展了一个新的 datastream,称之为 Hyperscanstream,它把算子封装进了进去,用户在使用时只需要把 datastream 转换为 Hyperscanstream,然后通过调用一个方法就可以使用正则的算子了。整个解决方案以一个独立的 jar 包提供给用户,这样可以保持原来编写 Flink 作业的习惯,并且与 Flink 的核心框架解耦。


数据流转的过程是这样,数据源读取到一条记录后交给下游的 Hyperscan 算子,Hyperscan 算子把数据交给 Hyperscan 子进程,子进程匹配完成后把结果返回给 Hyperscan 算子,然后 Hyperscan 算子把原始记录和匹配的结果传递给后续算子。



算子使用说明

私有化部署


针对私有化部署场景,用法如下,用户首先需要去编辑正则表达式文件,然后用工具把正则表达式编译为 database 并且序列化为本地文件,如果部署的环境中有 HDFS,那么可以把序列化后的文件上传至 HDFS,如果没有那就不用上传,然后开发 Flink 作业,引用序列化的文件去匹配数据。


为什么要有工具编译并序列化这一步呢,编辑完正则表达式,直接在 Flink 作业中使用不行吗?前面说了,Hyperscan 执行包括编译和匹配阶段,如果作业中只引用正则表达式,假设作业设置了并行度为 5,那么每个 task 都需要编译一次,一共需要编译 5 次,浪费资源;而且编译在 hyperscan 中是个相对缓慢的动作,所以把编译过程单独出来也为了加速 flink 作业在尽快执行。编译提前进行也有利于提前知道正则表达式是否有语法错误,或者不支持的情况,而不是作业启动后才知道。


私有化部署时,hyperscan 相关依赖程序会提供给用户,依赖程序通过全静态编译而来所以无需再添加依赖,只需机器支持需要的指令集即可。

公司内部使用



公司内部使用相对简单,用户可以在奇麟平台编辑正则表达式,或者把编写好的正则表达式文件上传到奇麟平台,奇麟平台负责把正则表达式编译为 database 上传到 HDFS,然后开发作业进行匹配。

使用示例

假设现在要匹配的是 HTTP 报文中的 Host 字段和 Referer 字段,如下图所示:



代码示例如下图:



整个逻辑分为四步,第一步要从数据源构建输入流,第二步把输入流转换为 Hyperscanstream,第三步调用 hyperscan 方法进而使用 Hyperscan 算子,在第一个参数 HyperscanFunction 中指定要匹配的是 Host 和 Referer 字段,第四步使用匹配返回的结果,返回的结果是 Tuple2 对象,其中第一个字段 Event 是原始记录,在本例中就是整个 HTTP 报文,第二个字段是 HyperScanRecord 组成的 List,HyperScanRecord 类中包括匹配的字段,例如本例中 Host 或 Referer,匹配命中的正则表达式 id(如果匹配命中的话)和匹配的最终状态。


使用 1 万条规则集以及不同大小的待匹配样本经过测试,方案达到了期望的性能,测试结果如下图,



使用 Hyperscan 算子的一些建议,如下图:



前面提到,在不使用 Chimera 库时,Hyperscan 有部分 PCRE 语法不支持的情况,在使用时要注意,下图列举了不支持的语法(使用 Chimera 库将会影响匹配性能)


未来展望

一方面,目前 Hyperscan 算子在安全、威胁感知的场景中已经得到运用,但希望能在更多场景中进行检验,理论上在所有正则匹配的场景中都能使用,比方说文本审核、内容提取等。

另一方面,也在提升 Hyperscan 算子易用性,比方说现在的规则发生变化时,需要重启作业才能生效,后续希望能做到规则的动态热加载。


本文转载自:360 技术(ID:qihoo_tech)

原文链接:如何提高大规模正则匹配的效能

2021-03-10 07:002949

评论

发布
暂无评论
发现更多内容

数据分析作业-用户分析-ReadHub

隋泽

产品经理训练营

如何学习数据结构与算法

C语言与CPP编程

c c++ 数据结构 程序人生 算法

智能时代的TCL之舞

脑极体

尤雨溪 Twitch 直播:下一代前端构建工具 ViteJS —— Open Source Friday

清秋

翻译 大前端 vite webpack 构建工具

第八章作业

LouisN

一文搞懂如何实现 Go 超时控制

万俊峰Kevin

微服务 超时 Go 语言

ONE MORE

吴小平

架构师训练营 4 期 第13周

引花眠

架构师训练营 4 期

Java 并发基础(一):synchronized 锁同步

看山

Java Java并发 并发编程

加密解密之 crypto-js 知识

浩浩子

推荐引擎概述

跳蚤

Mac下brew更新及安装Prometheus+Grafana

程序员架构进阶

容器 Prometheus 监控系统 28天写作 3月日更

使用 Typescript 的一些注意事项

浩浩子

力扣(LeetCode)刷题,简单+中等题(第26期)

不脱发的程序猿

面试 LeetCode 28天写作 算法面经 3月日更

Img、net & page新展望:连接感知

云小梦

JavaScript html 网络 用户体验 连接感知

SpringBoot + Mybatis + Druid + PageHelper在多数据源下如何配置并实现分页

北游学Java

Java mybatis spring Boot Starter

浅析 Fabric Peer 节点

Rayjun

散列(哈希)表算法学习

Nick

数据结构 算法 哈希算法

React 中后台系统多页签实现

清秋

Vue 大前端 React keepalive

用栈、回溯算法设计迷宫程序

不脱发的程序猿

回溯算法 28天写作 3月日更 迷宫程序

2021春招JAVA面试总结:Java+并发+Spring+MySQL+分布式+Redis+算法+JVM等

Java 编程 程序员 架构 面试

用户体验 | 页面阅读进度提示

云小梦

html css3 用户体验 页面进度提示

HTML5+CSS3高级动画的应用实践

云小梦

JavaScript html css3 浏览器API 网页动画

使用Flask Nginx Gunicorn和Supervisor部署一个简单的Restful API接口服务器

Langer

Python 部署与维护 服务器部署 web服务

Redis 作为缓存是如何工作的

escray

redis 学习 极客时间 3月日更 Redis 核心技术与实战

如何学习数据结构与算法

C语言与CPP编程

数据结构 算法

shell学习

我是程序员小贱

3月日更

位运算符在 JS 中的妙用

浩浩子

我对PageRank 算法的理解

跳蚤

浅析Node中间件Koa&Express:原理和实现

云小梦

JavaScript node.js 中间件 koa

Logstash 中 Ruby filter 使用指南

Langer

ruby Logstash ELK

如何提高大规模正则匹配的效能_文化 & 方法_360技术_InfoQ精选文章