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。然后自定义的分词策略,我们每一个业务的日志格式都不一样,分词策略需要特别灵活;然后故障转移节点负载均衡,自动恢复,支持原始日志的批量下载。


图1

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


图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所示。


图3

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


图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所示。


图5

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


图6

怎样应用

第一个瓶颈。 我们团队的基础组件全是 c++,我们团队核心业务,以及在线引擎、核心引擎都是c++
来做的。我们用到 gdb 进行调试,进程过多,用 c++ 组件一开始想偷懒,然后编辑进C,再放到 Go 里面去。每一个读取 Docid
中,每一个文件都会去读,我们的运用程序经常就挂,当时也没有原因,最后我们才看到执行 CGO 的时候,我们收到一个信号,就是 signal
exit,然后我们进行GDB调试,说是进程太多,因为CGO在执行的时候会新建一个M。

解决方案:用Go重新实现一遍,将组件作为http服务,Go Client调用,做集中式处理。

第二个瓶颈。 在系统中,我们大量使用 Goroutine,子写程 panic 在主写程不能被处理掉。

解决方案:我们在通道类型里面为struct,封装正常数据和error,在主协程取取出数据,统一做处理。

经验小结。

  • 即使精通很多语言,最好不要混用,要非常谨慎引入其他语言的解决方案。
  • 不要完全相信recover,它不能恢复runtime的一些panic。

看一下我们的Proxy多天并发查询设计。 如图7所示。要做 多天查询有两种方案。第一种方案把多天查询加上,这样使我们核心查询引擎变得非常臃肿,我们还是那句话,加一个中间层。把多天变成单天,然后在Proxy 拿到所有的单天数据,就形成了多天查询。


图7

我们还有另外一个项目,请求Poseidon的数据,我们想到两种解决方案,第一种解决方案,你在自己第三方系统里面做缓存,要不我们做缓存,我们是这样取舍。如果第三方系统里面做缓存,所有的查询,缓存只能在第三方系统里面用。如果在我们这里缓存,他们发了请求到我们这来,其他所有第三方里面都有可能能用上。我们是这样做的,首先请求
Searcher 拿到当天的数据,比如查一个月的数据,请求 Searcher 单天的数据,如果每一个Goroutine 去查一天,每一个
Goroutine 拿到 Searcher
单天数据之后,把它解出来,看一下是不是错误数据。如果是错误数据的话,直接给客户端把这条数据返回错误,并不是给客户端整个错误,因为只是这一天某一条数据有错误。而不至于我们在查询
30
天数据的时候,里面只要某一天某一条数据有错误,就直接返回给用户,我这个系统不可用。如果不是错误数据,会根据请求参数,请求参数有很多。除了这些之外,还有查询的时间,根据这个来做一个Cace
Key,然后打回给前端。

我们遇到一个问题,每一个用户会把整个索引流程都跑一遍,也就是说用户会给我们实时测试。在同一个时间之内,同一份数据在缓存时间之内不会走完整个
readhdfs 流程。build index
程序化,我们会有监控,如果程序化我们会知道,程序挂了会报警感知,但是数据错误却是未知,我们现在还没有做到这种监控。但是这个数据错误是未知的,我们修复索引就会花费大量时间,去重新写日志,跑
Docid,还要解决漏洞。

我们的解决方案,第一个减少缓存时间,在可容忍错误数据时间之内,用户查询能及时发现问题,恢复一天两天数据还可以,不至于缓存 30
天或者一、两个月,到最后错误数据会越来越多。第二个解决方案,参考 NSQ,利用 for+select 的不确定性来分馏,随机流量到 chanel
和 hdfs 做热测试。缺点,就是开发成本相对第一种方案来说有点高。这块要注意,开发成本并不是非常高,因为 select 而只能从
chanel 拿数据。

第二个经验小结。 不要选择非常高大上的一些技术,或者说一些我们所说的黑科技,简单、有效、够用能解决问题完全可以。利用 Goroutine
设计并发程序很方便,但是并发运行模型一定要 hold 住。我们之前Gopher 群里面发过一个博客,里面发了很多动态图,一些 Go 的
Goroutine 和 channel 如何并发,动态图画的非常炫。我们在写自己业务的时候,我们看了 Goroutine 以及
Goroutine 和 channel
怎么联动,我们自己有概念。我要表达观点的时候,我一时也找不到非常恰当的名词来描述,我不知道这个名词之前有没有,或者有没有其他的意义。

Proxy多天异步下载。
如图8所示。前端发起请求,要选择下载多少天,下载多少数据,服务端接受到请求之后,马上给客户端返回,我已经收到了,把这个消息写到channel。刚开始我们已经说过在readHDFS是是用JAVA写的,Goroutine太多,底层挂掉。两个Searcher到HDFS的时候,一个分词对应上百个Docid,可能对应着上百个文件,因为每一个Docid不一定在一个文件里面。在Searcher里面的时候,看起来进来一个请求,实际上往后会越来越大,到最后可能就是指数级的增长,像我们滚雪球一样。


图8

首先JAVA做了简单的连接池,然后有熔断机制,如果超出一定的连接数,直接返回error。像我们很早之前的时候,保险丝,家里面的电率大的时候,保险丝是用铅丝做的,铅丝会熔化掉。

再说一下GC的变化。 首先我说一下GC在我们整个系统中,从来都不是瓶颈。在这里说的几点,是我们升级之后简单做的测试,在这里和大家交流一下。如果有其他做测试比我们更细的同学,可以交流一下。

Go 1.7。 我们之前用的 1.5,升级到 1.7 之后,我们的 GC 下降到了三分之一。

nginx 代理问题,之前我做分享的时候,有同学问我在 Go 前端要不要加nginx代理。我之前做的系统面向海量用户,我们只把
GoServer 打包成二进制的可执行包,请求打到 lvs 的80 端口然后再转发到 GoServer 8080,非常简单。在这个项目我们用了
nginx,我们有用它的理由。

访问控制和负载均衡。 负载均衡我们可以用 LVS
做,我们这个项目的场景,使用的人非常少。第一我们是一个内部项目,权限问题,我们所在前端端口只能让开放的一些机器来访问,除了我们自己的前端器会访问以外,其实还有其他的一些团队,会过来直接写脚本请求我们的数据。我们nginx里面直接用了这两个,这样我不需要在Go里面做,前面就可以直接用nginx做了简单的负载均衡。要不要nginx,完全取决于自己业务的场景。因为在这个场景中,加了nginx也只是给运维稍微增加了负担,但是ip限制和负载均衡不需要重新开发了,之前没有用因为它没有在里面起到任何作用,而且之前是对外的服务,不需要有任何的限制,任何人都可以过来请求。

开源的改变

我们考虑开源。
在去年11月份的时候,我们开源了系统,系统有66%代码是用Golang写的。我们有两个问题需要解决,第一个问题第三方依赖的问题,我们开源主体方案没有用到我们自己的内部依赖包,这些第三方的组件,我们应该如何维护它,我当时和很多人交流过,这种方式也比较多,但是他们各有各的优点和缺点,几乎没有一个非常完美的方案,能解决到依赖里面再套依赖,以及多层依赖关系,至少我没有找到,既然没有的话,就选择最大众化,最简单的方案,用这个方式来解决。

在我们整个服务里面,我们自己开发了几个服务,一共有五个。我们当时考虑过,如果让用户部署五个服务,即使我们写好了脚本,部署起来在每个用户端操作系统不同,CPU位数不同等等,都会出各种各样的问题。排查起问题来,不知道排查哪一个服务,对于我们这些开发者来说,我们排查问题的时候,也会根据日志一个服务一个服务去找。我们考虑到,我们把所有的服务打成一个ALL
in One一个包。在实际交流试用中,我们了解到有很多人没有选择All in One而选择这五个服务独立部署。

我们开源有五个月,有很多人想让我们把模糊查询以及过滤开源出来。模糊查询我们做的非常简单,我们用了一个数据库,有并发能力。我们先把我们需要模糊查询的分词给分出来,放到数据库里面,在数据库里面我就可以操作,我们平常用到的模糊查询关键词,也就是几十亿左右,几十亿的量做一个操作,那简直太简单了,查到之后就知道关键词,拿到关键词之后,接下来的方案就是一个用多个关键词查询多天的场景,用多个关键词和单个关键词是一样的。多个关键词去查询和用多天查询是一样的,每个关键词分一个Goroutine去查询,就可以解决问题了。

总结回顾

首先Go的开发体验比较好,性能比较高,服务很稳定,我们除了线上有一次事故之后,好像就再也没有过。我们线上是用自己写的做监控,如果它挂掉就会自动拉起来,当然这是一种比较low的方式,因为它可能没有挂,但是它的确死掉了。可以满足大部分的需求场景,GO语言程序开发需要在代码可读性和性能之间做平衡取舍,应用程序并发模型需要在控制之内。我们有很多人在群里面问连接池以及对象池,连接池我们不说,因为很多客户端都会实现连接池这个功能,我们考虑对象池。对象池优点的确很大,因为它可以复用对象减轻压力,这是最核心的功能。复用对象解决了gc压力,但还有一个代码可读性的问题,引进对象池,对象池和业务没有关系,你要看对象池怎么做,代码可读性会非常差。还要说的是,对象池这种解决方案,在Go1.2的时候,用起来很爽,但是目前为止1.4到1.7的时候,对象池这种方案已经远远用不到了,因为gc已经不是那么明显。除非在非常极端的情况下,我们可能会用到这种非常极端的方式解决问题,但是我想大部分的公司都不太会遇到这种问题。我们知道Go在开发安卓,我们现在用的最多就是它和c++以及c的配合然后在用CGO引入到GO,谨慎与其他语言合用,即使对语言都非常熟,你也并不知道他们两个结合起来说不定引发一个问题,可能是你永远解决不了的问题。要合理引进第三方解决方案,在运维成本和系统维护成本要做平衡。

本文作者:郭军

来源:51CTO

时间: 2024-10-08 23:38:05

Go在百万亿级搜索引擎中的应用的相关文章

下一个万亿级技术产业?不是物联网,是无法翻译的CPS

都说物联网是下一个万亿级产业的技术引擎,物联网也热了很多年,被各路大神热议了很多年,但是物联网的产业还是没有兴旺起来,还不温不火. 又有人说下一个万亿级的市场是云计算,云计算也很火,私有云.公共云.物流云.仓储云.制造云等等,"云"山"物"罩,但还是被很多人当成了概念被炒作,还是难以看到云计算的庞大产业. 有人又说,下一个万亿级产业是大数据,数据量大得不得了,数据是资源,可是数据从哪里来?数据怎么被挖掘?数据怎么被利用?单单大数据好像也难以成为万亿级产业. 是的,物

扛起万亿级市场 物联网深藏功与名

6月8日消息,近日5G又传来新进展:工信部公开征求5G拟使用3300-3600MHz和4800-5000MHz频段的意见,中国移动牵头提出SBA架构成为5G网络统一基础架构.5G的来临进一步催动了物联网应用的落地,运营商.芯片商.制造商等一众产业链,无不充满了期待和干劲儿,要在这场万亿级市场中拔得头筹. 全球物联网产业生态竞争拉开序幕 麦肯锡预测,2025年物联网对全球经济影响将达11.1万亿美元.随着物联网巨大的产业发展空间逐渐显现,各大巨头纷纷加大投入.<物联网白皮书(2016年)>统计,

中移动打造百亿级造富计划征集手机开发软件

8月11日消息,中国移动宣布,8月7日-12日开始征集各类手机应用,中国移动将此称为"创富计划",并要打造百亿级产业链. 打造百亿级产业链 8月7日-12日提交应用作品将以绿色通道优先测试,8月12日后提交应用作品将以"排队"形式进行测试.过前期深度市场调研,中国移动甄选出目前市场重点关注.能为用户带来充分良好体验的典型 终端机型. 现诚邀您开发并提供适配以下终端的应用商品并参与Mobile Market合作.简要流程:注册-提交作品-申请销售-申请合作-应用测试-

亿级Web系统搭建:单机到分布式集群

当一个Web系统从日访问量10万逐步增长到1000万,甚至超过1亿的过程中,Web系统承受的压力会越来越大,在这个过程中,我们会遇到很多的问题.为了解决这些性能压力带来问题,我们需要在Web系统架构层面搭建多个层次的缓存机制.在不同的压力阶段,我们会遇到不同的问题,通过搭建不同的服务和架构来解决. Web负载均衡 Web负载均衡(Load Balancing),简单地说就是给我们的服务器集群分配"工作任务",而采用恰当的分配方式,对于保护处于后端的Web服务器来说,非常重要. 负载均衡

很不错的文章---【问底】徐汉彬:亿级Web系统搭建——单机到分布式集群

原文:很不错的文章---[问底]徐汉彬:亿级Web系统搭建--单机到分布式集群 [导读]徐汉彬曾在阿里巴巴和腾讯从事4年多的技术研发工作,负责过日请求量过亿的Web系统升级与重构,目前在小满科技创业,从事SaaS服务技术建设.  大规模流量的网站架构,从来都是慢慢"成长"而来.而这个过程中,会遇到很多问题,在不断解决问题的过程中,Web系统变得越来越大.并且,新的挑战又往往出现在旧的解决方案之上.希望这篇文章能够为技术人员提供一定的参考和帮助.  以下为原文 当一个Web系统从日访问量

Redis百亿级Key存储方案

1 需求背景 该应用场景为AdMaster DMP缓存存储需求,DMP需要管理非常多的第三方id数据,其中包括各媒体cookie与自身cookie(以下统称admckid)的mapping关系,还包括了admckid的人口标签.移动端id(主要是idfa和imei)的人口标签,以及一些黑名单id.ip等数据. 在hdfs的帮助下离线存储千亿记录并不困难,然而DMP还需要提供毫秒级的实时查询.由于cookie这种id本身具有不稳定性,所以很多的真实用户的浏览行为会导致大量的新cookie生成,只有

亿级 Web 系统的容错性建设实践

[本文转载于亿级 Web 系统的容错性建设实践] 三年多前,我在腾讯负责的活动运营系统,因为业务流量规模的数倍增长,系统出现了各种各样的异常,当时,作为开发的我,7*24小时地没日没夜处理告警,周末和凌晨也经常上线,疲于奔命.后来,当时的老领导对我说:你不能总扮演一个"救火队长"的角色, 要尝试从系统整体层面思考产生问题的根本原因,然后推进解决. 我幡然醒悟,"火"是永远救不完的,让系统能够自动"灭火",才是解决问题的正确方向.简而言之,系统的异

如何搭建亿级社交信息分享平台?

由于移动互联网的兴起,人与人之间的交流.信息分享能够以电子信号的速度传递在各个终端设备之间,每个人也都能够成为一个信息发布平台和信息接收平台,像朋友圈.微博.Twitter等社交平台的出现,大大方便和丰富了人们的日常生活.人人分享.人人参与也必然要求社交平台能够具有大规模.高并发.低延时的能力.通过本文,我们来看看如何搭建一个能够承受亿级活跃用户的社交信息分享平台. 一个最基础的方案 我们先来定义一个社交平台的基础的功能: 用户能够发布自己的状态.心情.照片.小视频等信息 用户能够关注自己的朋友

5G研发验证紧锣密鼓 产业链市场迎来万亿级机遇

在Polar Code被确定为5GeMBB控制信道标准方案后,中国IMT-2020(5G)推进组随即于20日在京召开了5G技术研发试验第二阶段规范发布会.据透露,第二阶段的验证预计将于2017年底完成.业内人士表示,随着中国5G技术研发试验的推进,将带动包括通信.电子元器件.芯片.终端应用等全产业链的升级和投资机会,未来市场空间可能达到"万亿级". 日前,中国企业(以华为为代表)主导的Polar Code被确定为5GeMBB控制信道标准方案,引起业内一片沸腾.而兵贵神速,中国IMT-2