@[TOC]
导读
当前文章构建在读者已经了解Hbase与ElasticSearch相关技术的前提下,如果读者对这两个数据库较为陌生,那么推荐以下两篇文章:
《可能是最易懂的Hbase架构原理解析》
《原来 Elasticsearch 还可以这么理解》
看到这个标题,了解ElasticSearch的同学可能就要说为什么做数据检索要加上Hbase,ElasticSearch本身的存储性能不是就足以支撑海量数据吗?
首先ElasticSearch针对海量数据的存储我认为存在两个较大的缺点:
1、写入效率相对较低,虽然和Hbase一样都是采用LSM树(LSM 通过将磁盘的随机写转化为顺序写来提高写性能 ,而付出的代价就是牺牲部分读性能。),但是ES在写入时需要消耗大量的时间去进行分词、主副本构建倒排索引等操作。
2、(深有体会) 在大数据领域,我们在使用数据的时候,经常用到的字段也就那么几个,剩下的字段要么没有价值要么使用频率极低。
例如安全日志数据中的攻击手段与攻击内容描述字段、用户行为日志中的行为描述与访问操作字段、系统运维日志的进程操作与网络请求内容等字段。
以上无用或低频字段会占用大量的索引空间,由于ES的倒排索引存储在文件中,为提高访问速度,在启动时都会把它加载到内存中。上述问题会导致倒排索引文件过大,从而导致集群性能下降并降低数据访问速度,且会增加不必要的内存硬件成本。
架构设计
为了解决上述中出现的问题,我们需要引入Hbase对上述出现的问题进行弥补。
1、写入效率问题:Hbase基于HDFS存储,其次因为没有索引,不需要考虑海量数据下因为索引导致的性能瓶颈,所以Hbase非常适合存储海量数据,写入速度快,可扩展性强。
2、字段过多导致的性能问题:我们可以将原始数据存储至Hbase中,仅将需要进行检索的字段抽取出来存储至ES中,这样可以大大降低ES因为倒排索引文件过大导致的性能压力。
其次Hbase支持根据rowkey进行模糊查询,rowkey我们可以把它看做成Hbase中单条数据的唯一ID,同时也可以作为一个简单的索引。rowkey的设计是一门学问,可以参考这篇文章:《用大白话彻底搞懂 HBase RowKey 详细设计》
有了上述知识之后,有经验的读者可能就知道如何将Hbase+ElasticSearch结合起来了:根据rowkey将Hbase的原始数据和ES中的检索数据关联起来。
在原始数据(你也可以叫源数据、贴源层数据、日志数据)入库时,我们可以根据数据生成rowkey(rowkey的设计因人而异,最好根据具体的业务来设计,注意要确保唯一性),之后将需要检索的字段抽取出来形成单独的一条索引数据,并将刚才设计好的rowkey放进去,之后将原始数据根据rowkey写入至Hbase,检索数据根据rowkey写入至ES中。
我们在进行检索时首先根据检索条件去ES中搜索,搜索出相对应的检索数据后,根据检索数据中的rowkey直接去Hbase中获取原始数据即可。
性能测试
有读者可能要问了,原来只使用ElasticSearch的时候,数据只需要写入一遍,也只需要查询一次ES就可返回想要的数据。现在使用了Hbase+ElasticSearch后,不仅数据需要写入两份,查询也需要先查ES,然后再根据ES的查询结果去Hbase中取数据。这样多此一举性能难道真的能提升吗?
但是经过我多次测试后,单ES架构和Hbase+ES架构的性能对比可由上图直观的展示出来:
在数据量小的时候,单ES架构的性能是优于Hbase+ES架构的。
数据量过了一定量级的时候,Hbase+ES架构的性能就远远的把单ES架构给甩开了。
实战举例
数据写入
{
"machine": {
"country": "",
"city": "",
"description": "",
"type": 2,
"operatingSystem": "Microsoft Windows Server 2008 R2 Enterprise Edition 64-bit",
"mac": "00-50-56-8D-E5-C8",
"installSource": 1,
"userIdentityCode": "",
"newMachineId": "7088160ee3dc39bc03b78f72397b35",
"osVersion": 102,
"province": "",
"osType": 0,
"id": 1068,
"thirdpartid": "",
"packageNum": "",
"updateDatetime": "2020-09-08 23:52:54",
"level": 1,
"ip": "192.168.1.11",
"packageId": 1,
"groupname": "",
"tempIp": "192.168.0.1",
"createDatetime": "2020-03-23 15:12:19",
"checkedflag": false,
"machineId": "0",
"name": "",
"time": "",
"softwareVersion": "win_3.1.18.10"
},
"eventLog": {
"logType": 1,
"standardTimestamp": 1588272682000,
"insScore": 0,
"operationExtra": "",
"dealSuggestion": "该类事件的报警,说明网站服务器正在被上传可执行文件。开启强制访问控制,并通过事件管理分析事件详情及日志,查找被上传可疑文件的漏洞,对其进行修补。",
"subject": {
"process": "C:\\Windos\\Softwretion\\Download\\Install\\NDP48-x86-x64-ENU.exe",
"file": "",
"filehash": "",
"type": "",
"user": "",
"webPagePhysicalPath": ""
},
"typeName": "应用上传二进制文件",
"description": "应用存在漏洞被利用上传了二进制文件",
"ucrc": 3755904924,
"machineName": "",
"standardDate": "2020-05-01",
"result": 0,
"score": 0,
"insLevel": 4,
"newMachineId": "7088ae9bc03b78f72397b35",
"action": {
"alarmText": "您的服务器 (192.168.1.11) 有可疑进程创建二进制文件,请及时处理。详情请登录云中心查看。",
"alarmHtml": "您的服务器 <span class='innerIp'>(192.168.1.11)</span> 有可疑进程创建二进制文件,请及时处理。详情请登录云中心查看。",
"html": "可疑进程 <span class='process'>C:\\Windows\\Softwibution\\Download\\Install\\NDP48-x86-x64-ENU.exe</span> 创建 二进制文件 <span class='file'>d:\\7c471f50a46c41ade1570cb55c\\1033\\setupresources.dll</span>",
"text": "可疑进程 C:\\Windows\\SoftwareDistribution\\Download\\Install\\NDP48-x86-x64-ENU.exe 创建二进制文件 d:\\7c471f50a46c41ad0cb55c\\1033\\setupresources.dll"
},
"localTimestamp": 1588273219000,
"id": "aTCBWUJFsoa9asfGWe9Xy",
"localDate": "2020-05-01",
"phase": 3,
"summary": "",
"alarmEventId": "432e2bed52967b4ebd0c_2020-05-01",
"eventId": 50001,
"backId": "3755904_2020-05-01",
"userIdList": [
16181
],
"srcip": "10.51.58.18",
"destip": "192.168.0.1",
"treepath": "",
"srcEventId": "00050001",
"srcIpAddress": {
"area": "朝阳区",
"country": "中国",
"city": "北京",
"county": "",
"region": "",
"type": "直辖市"
},
"groupName": "可疑二进制文件",
"eventLogList": [],
"phaseDesc": "控制",
"innerIp": "192.168.1.11",
"operation": "create",
"desc": "",
"object": {
"other": "",
"proc": "",
"regkey": "",
"dst": "",
"src": "",
"ip": "",
"command": "",
"url": "",
"webLeakageList": [],
"file": "d:\\7c471f50a46cde570cb55c\\1033\\setupresources.dll",
"webLoopholeList": [],
"regvalue": "",
"domain": "",
"host": "",
"portList": [],
"filehash": "abfe75ce71cc472461820b0ff1b5badc6451"
}
},
"id": "aTCBWUJFsoa9asfGWe9Xy"
}
以上是一条具体的网络安全日志(已做脱敏处理),当前日志日增量数亿条,存量近百亿条。我们可以看到数据中字段较多,且存在较多的描述性字段。
"alarmText": "您的服务器 (192.168.1.11) 有可疑进程创建二进制文件,请及时处理。详情请登录云中心查看。",
"alarmHtml": "您的服务器 <span class='innerIp'>(192.168.1.11)</span> 有可疑进程创建二进制文件,请及时处理。详情请登录云中心查看。",
"html": "可疑进程 <span class='process'>C:\\Windows\\Softwibution\\Download\\Install\\NDP48-x86-x64-ENU.exe</span> 创建 二进制文件 <span class='file'>d:\\7c471f50a46c41ade1570cb55c\\1033\\setupresources.dll</span>",
"text": "可疑进程 C:\\Windows\\SoftwareDistribution\\Download\\Install\\NDP48-x86-x64-ENU.exe 创建二进制文件 d:\\7c471f50a46c41ad0cb55c\\1033\\setupresources.dll"
根据之前提到的架构建设要求,我们首先需要根据原始数据生成一个rowkey,这里为了方便演示我们直接将原始数据的id字段作为rowkey。然后将原始数据根据当前rowkey写入至Hbase中。
其次我们需要抽取出常用的字段构成检索数据,在网络安全领域,我们最常用到的字段无非以下几个:
数据id
攻击源ip
攻击目的ip
攻击类型
攻击描述
攻击时间
于是我们就可以将以上字段在原始数据中找到并抽取出来,组成检索数据
id:aTCBWUJFsoa9asfGWe9Xy
src_ip: 10.51.58.18
dest_ip: 192.168.0.1
attack_type:应用上传二进制文件
attack_description:应用存在漏洞被利用上传了二进制文件
attck_time:2020-05-01 02:51:22
抽取完成之后,我们根据检索数据中的id将数据写入至ES中。这样我们的数据写入工作即可完成。
### 数据检索
数据写入完成后,我们输入检索条件:192.168.0.1 ,将当前检索条件输入至ES中,可以得到以下数据
id:aTCBWUJFsoa9asfGWe9Xy
src_ip: 10.51.58.18
dest_ip: 192.168.0.1
attack_type:应用上传二进制文件
attack_description:应用存在漏洞被利用上传了二进制文件
attck_time:2020-05-01 02:51:22
检索到当前数据后,我们再根据数据中的id,将此id的值作为rowkey去Hbase进行查询,即可获得原始数据。
优化意见
1、根据Hbase中rowkey可以模糊搜索的特性,我们不仅可以做到检索数据和原始数据的一对一关系,在某些特定的场景下,甚至可以做到一对多,多对多的关系。
2、Hbase和ElasticSearch集群不建议放在同一批机器上。因为ElasticSearch需要消耗大量的内存,Hbase占用大量的存储,为了更好的分配存储和计算资源,所以建议分开放。
3、因为Hbase默认不支持聚合操作,需要引入coprocessor特性实现,且效率较低。ElasticSearch原生自带聚合操作,且性能很好,所以我们也可以将一些需要进行聚合操作的字段也存入ElasticSearch中。
心得
网上有很多Hbase集成ElasitcSearch的文章,有些文章只是简单的介绍了一些优点、架构、搭建等内容,有的文章就写的很详细,包括业务上的一些背景和需求,甚至源代码都加上了,那为什么我还要写这篇文章呢,主要有以下几点
第一:别人的文章终究是别人的经验,只有自己了解学习后并且按照自己的实际情况实现才是自己的,才算是真正的融会贯通。
第二:每个技术人都有分享的冲动,自己实现了之后就想着把自己遇到的一些经验和好的点子分享出来,毕竟这篇文章也是通过别人分享的相关内容,自己学习透彻后才可以完成。
第三:写文章的过程也是对当前项目总结和反思的过程,哪怕过了很久有些关键点忘掉了,回过头来看之前写的文章也能很快的拾起来。