Go 在百万亿级搜索引擎中的应用


本文摘自网络,作者,侵删。


Poseidon 系统是由 360 开源的日志搜索平台,目前已经用到了生产环节中,可以在数百万亿条、数百 PB 大小的日志数据中快速分析和检索特定字符串。因为 Golang 得天独厚的支持并发编程,Poseidon 的核心搜索引擎、发报器、查询代理是用 Golang 开发的,在核心引擎查询、多天查询、多天数据异步下载中大量使用了 goroutine+channel 。

大家上午好,我是郭军,很高兴今天在这里和大家交流。我今天演讲题目,Golang 在百万亿搜索引擎中的应用。Poseidon在希腊意思是海神,在这里是海量数据集的主宰者。

之前我的工作一直面向海量用户,去年年中我接触大数据以及海量数据这样的场景,在今天的演讲中,主要会涉及以下几方面内容:

  • 设计目标

  • Go 应用场景与遭遇的挑战

  • 怎样应对?

  • 开源的改变

  • 总结

设计目标

首先说一下为什么要做这个系统。这是一个安全公司,APT (高危威胁持续性事件)。在追查APT事件的时候,我们通常会找一个样本在某一样时间之内到底做了什么事情。在海量日志中找这些信息的话,运气好不堵塞的时候,大约两、三小时可以跑出来,如果运气不好,跑的任务太多堵塞的话就要修复,可能一天两天才能出来数据,显然这样的效率是不高的。

我们的设计目标,我们总的数据量保留三年的历史数据,一共有一百万亿条,大小有 100 PB。秒级交互式搜索响应,从前端发起请求到某一天数据,我们会在几秒钟之内给你返回。我们之前设定秒级60秒返回就可以,实际上做完之后测试的结果都在3秒到5秒之内,90%请求在10秒之内。每天要支持两千亿数据量灌入,原始数据仅存一份,对现有 MR 任务无侵略。ES 原始数据不止存一份,会再存一份,我们这么大数据量来说,再存副本的话,维护成本以及代价是非常大的。ES 支持不了百万亿级数据量,现在业界做到一千亿,我们只做到300多G。然后自定义的分词策略,我们每一个业务的日志格式都不一样,分词策略需要特别灵活;然后故障转移节点负载均衡,自动恢复,支持原始日志的批量下载。

Go 在百万亿级搜索引擎中的应用

图1

图1是我们总体流程,这个图比较复杂,我们之前有同事分享过这个架构。如果今天再分享架构可能时间会不够,图2是它的一个非常简单的粗略图。

Go 在百万亿级搜索引擎中的应用

图2

Go 应用场景与遭遇的挑战

首先原始日志。在转化的时候我们把每 128 行原始日志抽取出来作为一个文档,多个文档联结在一起形成一个文件。这里会有人问为什么选择 128 行,我们每天日志量是700亿,按照每一行一个文档我们有700 亿文档。一行日志一个文档,700 亿文档占用空间太大;700 亿数据会膨胀。选择 128 行是因为:第一,700 亿除 128 ,大约是 5.46 亿左右,在一定范围内可以承受;第二,因为我们的ID都是数字形式,以发号器形式发出来的,我们压缩数字的时候,肯定要采取各种各样的压缩办法,我们在这个地方用的插分,对于128 数字的压缩是比较好的。压缩 128 行日志对比压缩1行日志高很多。我们每天原始日志,我说的业务每天原始日志有 60 ,压缩之后我们能打成 10 左右,这是每天的数据。我们在输出的时候,这个是原始的日志,最后就要到原始日志里面找,最后就要构建数据。因为我们要存入进去的时候,刚刚我说的一句话,很多人不明白,多个连接起来形成一个文件。有一个非常大的优势,里面的数据我放到另外一个文件里面,我一直叠加,最后这个文件可以被解压。换一种方式来说,把文件都输出到一个文件里面,作为这一个文件,我从这个文件里面取出某一段来,我就可以解压出来,这是一个非常大的特性。因为我需要读一段日志,我肯定要知道这个我从哪个地方读到哪个地方,我要知道我读的压缩文件,解压出来就是128行日志。我们把整个原数据放到这里面,去建索引以及原数据,大体就是这样一个流程。首先看一下离线引擎,客户端请求日志,包括 PC 卫士、网络以及浏览器等等,这块相当于传统搜索引擎的爬虫。下面会具体讲到,离线生成 DocGz 、DocGzmeta ,然后构建原数据。在线引擎,web 我们做简单的页面开发,到 proxy 集群,再发到 searcher 集群,然后走到 readHDFS ,readHDFS这个服务是用 Java开发,用 Java 开发有很多坑,但是又不得不用,因为java仍然是操作hadoop最合适的语言。

来说一下数据结构。我们用 ProtrBuffer 描述核心数据结构。每一个 ID 下面分为两段,那个 docID 就是我这个文档的编号;第二是 rowIndex,每个里面都会对应多行日志,我这里面对应 128 行里面哪一行日志,就是这个做的定位。我们用 map 的形式描述出来,这个是由 DocID 形成的列表,每一个里面会对应多个DocIDList。map 和 string 里面,我要先找到 map ,然后再把数据拿出来。如图3所示。

Go 在百万亿级搜索引擎中的应用Go 在百万亿级搜索引擎中的应用

图3

说一下搜索引擎的核心技术。首先倒排索引,倒排索引有一个趋势,DocidList 非常长。我们一个分词会先计算出来 hashid ,知道 hashid 之后要查询的时候我们要做一个平台,给出要查询哪一个业务,比如我要查网络等等这些,我们以业务的简写拼接上hashid,然后要查询的时间,查询哪一天的数据,我们引擎不是实时,因为数据量太大做不了实时,只能做到今天查昨天。然后解析 invertedindex 拿到对应的文档信息在里面,找到这个位置之后,把我们所有的需要的原数据抽出来,然后解压。我们就知道某一个分词对应着 DocidList 是哪一个,根据 DocidList 去查要查的 map 信息在哪个地方,获取之后再拼一个路径,把原始数据拿出来。拿出原始数据之后,一个文件里面会有 128 行日志,这 128 行日志Doc里面rowindx 找到文档在哪一行,做过滤就可以了。用非常简单的话来总结一下,因为 Docid 比较长,我们存一个位置,我们的 DocidList 每一个 Docid 对应的文档也比较多,我们读原始文档的时候,也会存一个位置,在计算机领域中,各种难以解决的问题都可以添加一个间接的中间层来解决这个问题。如图4所示。这句话在我们系统中有了很好的尝试,不仅是这一块。

Go 在百万亿级搜索引擎中的应用

图4

再来说一下 idgeneratror 。按照每天业务 27700 亿来算,分词以后是 100 亿,每一个分词对应 277 行日志,这是平均数,每天 Docid 有 27700 亿个。按照每个 4 字节来计算,光是 Docid 数字将近 11TB。在这里进行了处理,采用分段区间获取降低 qps,每天的 id 重新从 0 开始分配。我们每天 Docid 倒排索引量在2.4T。每天 27700 亿我们做起来也稍微有点发怵,我们想了一个办法,我们业务名加时间作为 key,每天id 从零开始重新分配,这样就可以保证我每天的量不至于太高,而且分出来的 Docid 不用太大,如果太大的话,可能数据就会比较膨胀。我现在建了索引是哪个业务,什么时间段,哪一天的,我这次要请求哪一个区段,如果说我请求了 1 到 100 个这个区段,在 idgeneratro 会提前预留出 1 到 100 这个空隙。

Proxy/Searcher详细设计。Searcher核心引擎就是走四级索引里面做的事情,其中包括过滤和模糊查询等等,这些不是主干业务我没有说。从里面拿出map数据,然后再取原始数据,取完数据以后,我们有很多原始数据非常大,大约有几十兆左右,如果放在处理器前端,前面会直接卡死,我们会把原始数据比较大的业务,在页面上面给大家展示,点击查看原始数据这么一个链接,点了以后再过来请求一遍,这是一个非常简单的架构。如图5所示。

Go 在百万亿级搜索引擎中的应用

图5

Searcher并发模型。因为读 四级索引的时候,读 Docid 的过程一模一样,所以我在这里用读 Docid 举例子,比如我拿到 DocidList 的数据,我会给每一个 Docid 分配一个 Goroutine ,拼接出来 doc path ,读取原始日志,然后做过滤,最后返回给前端。如图6所示。

Go 在百万亿级搜索引擎中的应用

图6

怎样应用

阅读剩余部分

相关阅读 >>

未编译的python代码比Go慢100倍,编译后呢?

如何使用Golang的pprof包对程序进行性能分析

Golang文件复制

test

分享一个Golang http 验证码示例

Go语言简介

手撸Golang 行为型设计模式 状态模式

Golang怎样读取json数据

关于Golang之排序使用

Golang可以调用lua吗?

更多相关阅读请进入《Go》频道 >>




打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...