ELASTICSEARCH(弹性搜索):是一款开源的分布式、RESTful风格的搜索和数据分析引擎,它底层基于Apache Lucene开源库进行封装,其不仅仅提供分布式多用户能力的全文搜索引擎,还可以被准确形容为:
1、一个分布式的实时文档存储,每个字段可以被索引与搜索;
2、一个分布式实时分析搜索引擎;
3、能胜任上百个节点的扩展,并支持PB级别额结构化和非结构化数据。
全文检索是指计算机索引程序通过扫描文档中的每一个词,并对每一个词建立一个索引,指明该词在文档中出现的次数和相对位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
在全文搜索的世界中,目前主流的工具包括:
Apache Lucene
Elasticsearch
Solr
Ferret
Elasticsearch 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个Elasticsearch实例。单个Elasticsearch实例称为一个节点(Node),一组节点构成一个集群(Cluster)。
Elasticsearch(ES) | Index | Document | Type | Field |
---|---|---|---|---|
MySQL | Database | Row | Table | Column |
ES是面向文档的,意味着它存储整个对象或文档,同时索引每个文档内容,使之可以被检索。索引实际上是指向一个或多个物理分片的逻辑命名空间,分片是一个底层的工作单元,他保存了全部数据中的一部分,一个分片是一个Lucene的实例,本身就是一个完整的搜索引擎。ES利用分片来实现分布式。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。 当集群规模扩大或者缩小时, ES会自动在各节点中迁移分片,使得数据仍然均匀分布在集群里。一个分片可以是主分片或者副本分片。 索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量。一个副本分片只是一个主分片的拷贝。 副本分片为保护数据不丢失的冗余备份,并为搜索和返回文档等读操作提供服务。我们如何得知一个文档应该被分片到什么位置呢?则利用如下公式:
shard = hash (routing) % number_of_primary_shards
routing是一个可变值,默认为文档的_id,也可以自定义值。routing通过hash函数生成一个数字,然后这个数字在除以number_of_primary_shards(主分片的数量)得到的余数,这个分布到0到number_of_primary_shards-1之间的余数,就是所求文档的分片位置。
Elasticsearch索引表中每一项包含一个属性值和该属性值的各记录地址(非记录确定属性值),即倒排索引原理。1)用户将数据提交到Elasticsearch数据库中,通过分词控制器将对应的语句分词,将权重和分词结果一并存入数据库;2)搜索时,根据权重将结果排名,打分,再将返回结果呈现给用户。索引建立过程需要向主节点发出建立请求,主节点根据负载情况以及分片规则将请求分发给对应主slave,主slave执行完成后,信息同步至所有备份slave。数据写入根据Routing规则可以在任意slave执行。
建立索引(请求)——>master——>主slave(信息同步)——>备份slave
数据写入(Routing规则)——>任意slave
如需要存储如下几条数据:
ID | Name | Age | Sex |
---|---|---|---|
1 | Kate | 24 | Female |
2 | John | 24 | Male |
3 | Bill | 25 | Male |
则ES根据倒排索引原理为Name,Sex两个Field建立如下索引:
1 | Term | Posting List |
---|---|---|
2 | Kate | 1 |
3 | John | 2 |
4 | Bill | 3 |
1 | Term | Posting List |
---|---|---|
2 | Female | 1 |
3 | Male | 2,3 |
其中,Kate,John,Female,Male叫Term,[1,2,3]为Posting List,存储符合某个Term的文档Id,通过Posting List很快定位到查询文档。(1)但当数据量很大时,为快速找到某个Term,将所有的Term排序,利用二分查找,即通过字典查找,生成Term Dictionary。(2)为进一步加快查找速度,通过内存查找,不读磁盘,减少磁盘寻道次数。但Term太多,无法全部读入内存,建立如字典的索引页,如A开头有哪些Term,通过Term Index快速定位Term Dictionary的位置。(3)最后利用FST,利用字节的方式存储所有的Term Index。
同样对Posting List也运用相关压缩技术,例如(1)增量编码压缩技术,如:
(2)位图压缩(Roaring bitmap),其中bitmap是一种数据结构,例如Posting List为[1,3,4,7,10],则利用Roaring bitmap技术压缩后为[1,0,1,1,0,0,1,0,0,1],对应位置0/1表示某值是否存在,压缩前20字节,压缩后10位,2字节。
索引新文档(Create)
当用户向一个节点提交了一个索引新文档的请求,节点会计算新文档应该加入到哪个分片(shard)中。每个节点都存储有每个分片存储在哪个节点的信息,因此协调节点会将请求发送给对应的节点。注意这个请求会发送给主分片,等主分片完成索引,会并行将请求发送到其所有副本分片,保证每个分片都持有最新数据。
每次写入新文档时,都会先写入内存中,并将这一操作写入一个translog文件(transaction log)中,此时如果执行搜索操作,这个新文档还不能被索引到。
图1、新文档被写入内存,操作被写入translog
ES会每隔1秒时间(这个时间可以修改)进行一次刷新操作(refresh),此时在这1秒时间内写入内存的新文档都会被写入一个文件系统缓存(filesystem cache)中,并构成一个分段(segment)。此时这个segment里的文档可以被搜索到,但是尚未写入硬盘,即如果此时发生断电,则这些文档可能会丢失。
图2、在执行刷新后清空内存,新文档写入文件系统缓存
不断有新的文档写入,则这一过程将不断重复执行。每隔一秒将生成一个新的segment,而translog文件将越来越大。
图3、translog不断加入新文档记录
每隔30分钟或者translog文件变得很大,则执行一次fsync操作。此时所有在文件系统缓存中的segment将被写入磁盘,而translog将被删除(此后会生成新的translog)。
图4、执行fsync后segment写入磁盘,清空内存和translog
由上面的流程可以看出,在两次fsync操作之间,存储在内存和文件系统缓存中的文档是不安全的,一旦出现断电这些文档就会丢失。所以ES引入了translog来记录两次fsync之间所有的操作,这样机器从故障中恢复或者重新启动,ES便可以根据translog进行还原。
当然,translog本身也是文件,存在于内存当中,如果发生断电一样会丢失。因此,ES会在每隔5秒时间或是一次写入请求完成后将translog写入磁盘。可以认为一个对文档的操作一旦写入磁盘便是安全的可以复原的,因此只有在当前操作记录被写入磁盘,ES才会将操作成功的结果返回发送此操作请求的客户端。
此外,由于每一秒就会生成一个新的segment,很快将会有大量的segment。对于一个分片进行查询请求,将会轮流查询分片中的所有segment,这将降低搜索效率。因此ES会自动启动合并segment的工作,将一部分相似大小的segment合并成一个新的大segment。合并的过程实际上是创建了一个新的segment,当新segment被写入磁盘,所有被合并的旧segment被清除。
图5、合并segment
图6、合并完成后删除旧segment,新segment可供搜索
ES的索引是不能修改的,因此更新和删除操作并不是直接在原索引上直接执行。
每一个磁盘上的segment都会维护一个del文件,用来记录被删除的文件。每当用户提出一个删除请求,文档并没有被真正删除,索引也没有发生改变,而是在del文件中标记该文档已被删除。因此被删除的文档依然可以被检索到,只是在返回检索结果时被过滤掉了。每次在启动segment合并工作时,那些被标记为删除的文档才会被真正删除。
更新文档会首先查找原文档,得到该文档的版本号。然后将修改后的文档写入内存,此过程与写入一个新文档相同。同时,旧版本文档被标记为删除,同理,该文档可以被搜索到,只是最终被过滤掉。
读操作(Read):查询过程
查询的过程大体上分为查询(query)和取回(fetch)两个阶段。这个节点的任务是广播查询请求到所有相关分片,并将它们的响应整合成全局排序后的结果集合,这个结果集合会返回给客户端。
查询阶段
当一个节点接收到一个搜索请求,则这个节点就变成了协调节点。
图7、查询过程分布式搜索
第一步是广播请求到索引中每一个节点的分片拷贝。 查询请求可以被某个主分片或某个副本分片处理,协调节点将在之后的请求中轮询所有的分片拷贝来分摊负载。
每个分片将会在本地构建一个优先级队列。如果客户端要求返回结果排序中从第from名开始的数量为size的结果集,则每个节点都需要生成一个from+size大小的结果集,因此优先级队列的大小也是from+size。分片仅会返回一个轻量级的结果给协调节点,包含结果集中的每一个文档的ID和进行排序所需要的信息。
协调节点会将所有分片的结果汇总,并进行全局排序,得到最终的查询排序结果。此时查询阶段结束。
取回阶段
查询过程得到的是一个排序结果,标记出哪些文档是符合搜索要求的,此时仍然需要获取这些文档返回客户端。
协调节点会确定实际需要返回的文档,并向含有该文档的分片发送get请求;分片获取文档返回给协调节点;协调节点将结果返回给客户端。
图8、分布式搜索的取回阶段
ES的API分为REST Client API(Representational state transfer,表述性状态转移,即http请求形式)以及Java API(即transportClient)两种。相比transportClient API效率更高,通过ES内部的RPC调用形式进行请求,连接可以是一个长连接,相当于是把客户端的请求当成ES 集群的一个节点。ES 7中移除transportClient,主要原因是难以向下兼容,改为High-Level Rest Client和Low-Level Rest Client。
REST Client API命令的格式如下:
curl –X ‘://:/?’ –d ‘’
部件说明:
VERB | 适当的HTTP方法(GET、PUT、POST、DELETE、HEAD |
---|---|
PROTOCOL | http或https |
HOST | ES集群中任意节点的主机名 |
PORT | 运行服务端口,默认9200 |
PATH | API的终端路径 |
QUERY_STRING | 任意可选的查询字符串参数 |
BODY | 一个JSON格式的请求体 |
对于HTTP方法,具体作用:
HTTP方法 | 说明 |
---|---|
GET | 获取请求对象的当前状态 |
POST | 改变对象的当前状态 |
PUT | 创建一个对象 |
DELETE | 销毁对象 |
HEAD | 获取请求对象的当前状态 |
eg:curl –X PUT ‘localhost:9200/weather’ //新建一个名叫weather的Index
JAVA API操作:
ES 7.0版本弃用TransportClient对Elasticsearch进行操作,并在8.0版本过后彻底删除,在新版本中通过创建Java Low Level REST Client和Java High Level REST Client,其中High Level基于Low Level进行了更上层的封装,Low Level本身不负责数据的编解码,需用户编解码,而High Level直接调用相应API,两者都基于http协议。
ES常用的API分类如下:
1)文档Document API:提供对文档的增删改查操作;
2)搜索Search API:提供对文档进行某个字段的查询;
3)索引Index API:提供对索引进行操作,查看索引信息等;
4)查看Watcher API:提供更加直观的形式返回数据,适合控制台请求展示;
5)集群Cluster API:对集群进行查看和操作的API。
接下来将对ES 5.x和ES 7.x版本下利用Java API对ES操作讲解:
(1)Client初始化:
ES 5.X:
//on startup
TransportClient client = new PreBuiltTransportClient(Settings.EMPTY)
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host1"), 9300));
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("host2"), 9300));
//on shutdown
client.close();
ES 7.X:
//on startup
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("host1",9200,"http"),
new HttpHost("host2",9200,"https")
)
);
//on shutdown
client.close();
所有操作都提供同步和异步两种操作,同步方法客户端等待结果返回,异步方法后添加Async后缀,该过程客户端直接返回,通过侦听器传递异步结果。其中,侦听器listen:
ActionListener listener = new ActionListener<IndexResponse>() {
@Override
public void onResponse(IndexResponse indexResponse) {
//执行成功
}
@Override
public void onFailure(Exception e) {
//执行失败
}
};
(2)Index API
ES 5.X:
IndexResponse response = client.prepareIndex("twitter", "tweet", "1")
.setSource(builder).get(); // builder可以替换为json
//.setSource(json,XContentType.JSON).get();
//.setSource(jsonMap).get();
ES 7.X:
IndexRequest indexRequest = new IndexRequest().index("posts")
.id("1")
.source( "user","kimchy",
"postDate",new Date(),
"message","trying out Elasticsearch");
//.source(builder)
//.source(jsonMap)
//异步请求
IndexResponse indexResponse = client.indexAsync(indexRequest, RequestOptions.DEFAULT,listener);
//同步请求
IndexResponse indexResponse1 = client.index(indexRequest,RequestOptions.DEFAULT);
通常日志由服务器生成,输出到不同的文件中,一般会有系统日志、 应用日志、安全日志。这些日志分散地存储在不同的机器上。在没有日志系统的情况下,若系统发生故障,首先需要定位处理请求的服务器,如果这台服务器部署了多个实例,则需要去每个应用实例的日志目录下查找日志文件。每个应用实例还会设置日志滚动策略(如:每天生成一个文件),还有日志压缩归档策略等。整个流程相对比较繁琐,因此,如果我们能把这些日志集中管理,并提供集中检索功能,不仅可以提高诊断的效率,同时对系统情况有个全面的理解。
针对这些问题,为了提供分布式的实时日志搜集和分析的监控系统,提出ELK,实践之后进而优化为EFK,即ELK(Elasticsearch+Logstash+Kibana)优化—> EFK(Elasticsearch+Beat+Kibana)
Elasticsearch——>存储数据;Logstash/ Beat——>数据处理引擎/轻量级数据收集引擎(针对 不同的数据有Filebeat,Packetbeat,Functiongbeat etc);Kibana——>可视化平台。
为解决大并发情况的数据丢失,利用Redis/Kafka,其中Redis保证高实时性,低可靠性(队列容 量取决于内存);Kafka保证高可靠性,有延迟(队列容量取决于磁盘)。