https://zhuanlan.zhihu.com/Elasticsearch
阿里云TableStore团队出品的ElasticSearch技术研讨学习总结
ES定位
搜索领域:基于lucene
Json文档数据库: 相对于MongoDB读写性能更佳
时序数据分析:如日志处理,监控数据存储,分析和数据化
基本概念:
节点(Node): 一个ES实例
索引(Index): 逻辑概念,配置信息mapping和倒排索引,正排索引文件。索引可以分布在一台或者多台机器
分片(Shard): 为了支持更大量的数据,索引一般会按照某个维度分成多个部分。为了可靠性和可用性,同一个索引的分片会尽量分布在不同节点上以保证高可用。分片有两种分别是主分片和副本分片
副本: 同一个分片的备份数据,一个分片可能会有0或者多个副本。
- 建立索引流程
doc先经过路由规则定位到主Shard,发送这个doc到主Shard建索引,成功之后再发送这个doc到Shard的副本上建立索引,等到副本上建立索引成功后才返回成功。
这时候,如果某个副本Shard或者主Shard丢失的时候,需要从其他副本全量拷贝到这个节点上构造新的分片。
副本(Replica)存在的一个理由,避免数据丢失,提高数据可靠性。副本(Replica)存在的另一个理由是读请求量很大的时候,一个Node无法承载所有流量,这个时候就需要一个副本来分流查询压力,目的就是扩展查询能力。
部署方式
1.混合部署
如下面左图:
-
如果不考虑主节点,还会有Data Node和Transport Node,这种部署模式下,这两种不同类型Node角色都会位于同一个Node中。
-
当有query请求到达的时候,会把请求随机发送给任何一个node,这个node会有一个全局的路由表,然后再通过路由表选择合适的Node,将请求发送给这些Node.然后等所有请求都返回之后,把结果合并返回给用户。
-
缺点:
多种类型的请求会相互影响,如果某个节点出现热点,那么会影响经过这个节点的所有跨Node请求。
2.分层部署
- 通过配置来隔离开节点的功能。
- 设置部分节点为传输结点,专门用来做请求转发与结果合并
- 其他节点为数据节点,专门用来处理数据
- 好处是角色相互独立,不会相互影响。一般Transport Node流量平均分配,很少会出现单台机器CPU或者流量被打满的情况。
- 支持热更新: 指的是可以先一台台地升级DataNode,升级完成之后再升级Transport Node,整个过程可以做到用户无感知。
- 角色独立后,只需要Transport Node连接所有的DataNode,而DataNode则不需要和其他DataNode有连接。一个集群中DataNode的数量远大于Transport Node,这样集群的规模可以更大。
Es数据层架构
数据存储
Es的Index数据和meta存储在本地文件系统,这带来的问题就是如果当一台机器宕机或者磁盘损坏的时候,数据就会丢失。
可以使用副本机制来解决这个问题。
副本
1.服务可用性: 当设置多个副本的时候,如果某一个副本不可用,那么请求可能会流向其他的副本,服务很快就可以恢复
2.保证数据可靠: 如果没有副本机制,当主分片的存在磁盘的数据丢失的时候,这个节点的所有分片数据都会丢失。
3.提供更强大的查询能力: 当分片提供的查询能力无法满足业务需求的时候,可以把流量负载到副本上提高查询的并发度。
基于本地文件系统的分布式系统
这种架构的特点就是存储和计算是合并起来的。
每个节点的本地文件系统保存着分片数据,当一个节点发生宕机的时候,发生主备切换,然后需要再找一台机器当作备份,这就涉及到分片数据从一台机器的本地磁盘传输到另外一台机器的本地磁盘。在这个期间分片不能提供服务。
此外这种架构还有其他缺点:
1.多副本机制带来成本浪费。当作副本的Shard的计算能力往往会被浪费。
2.写性能和吞吐量的下降,每次索引或者更新的时候,需要先去更新Primary Replica,然后异步更新其他从副本来保证数据的一致性,写入性能会下降。
3.当出现热点或者需要紧急扩容的时候动态增加Replica慢。新Shard的数据需要完全从其他Shard拷贝,拷贝时间较长。
ElasticSearch,Kafka就是采用这种架构。
基于分布式文件系统的分布式系统(共享存储)
这种架构的特点就是存储和计算是分离的。
采取存储和计算分离的思想,上一种思路的问题主要在跨节点拷贝数据十分耗时。
一种解决方法的思路就是使用分布式文件系统作为底层的存储。每一个分片只需要连接到一个分布式文件系统的目录或者文件,分片本身不含有数据。
相当于节点存放着指向真实数据的指针,只负责计算不负责存储。
扩容的时候,只需要新节点的分片指向分布式文件系统即可。
但可能访问分布式文件系统的性能会比不过本地文件系统。
Hbase采用这种架构方式。
Lucene数据模型
-
Index: 索引,由很多的Document组成,相当于关系型数据库的Database
-
Document: 由很多的Field组成,是Index和Search的最小单位 相当于关系型数据库的Row
-
Field: 由很多的Term组成,包括Field Name和Field Value
-
Term 由很多的字节组成,可以分词
ElasticSearch拥有全部上面四种数据类型
Lucene中存储的索引主要分为三种类型:
1.Invert Index:倒排索引
可以配置为是否分词,如果分词的话可以设置不同的分词器。
DOCS:只存储DocID。
DOCS_AND_FREQS:存储DocID和词频(Term Freq)。
DOCS_AND_FREQS_AND_POSITIONS:存储DocID、词频(Term Freq)和位置。
DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS:存储DocID、词频(Term Freq)、位置和偏移
根据字段查询包含该字段的文档ID以及词频,位置。
Key: Term
Value: DocID 的链表
2.DocValues: 正排索引
采用列式存储,通过DocID可以快速读取该文档所有的term
Key: DocID和Filed Name
Value:Field Value
3.Store:字段原始内容存储
同一篇文章的多个Field的Store会存储在一起,使用场景:一次读取少量且多个字段内存的场景。
Key: DocID
Value: Field Key和Field Value
Lucene中提供索引和搜索的最小组织形式是Segment,Segment按照索引类型不同,会分为上面介绍的三种,每一类都是按照文档为最小单位存储。
Lucene的不足
1.Lucene是一个单机的搜索库,不支持分布式搜索
2.没有更新操作,每次都是Append一个新文档,那如何做部分字段的更新?
3.Lucene没有主键索引,如何处理同一个Doc的多次写入
4.在稀疏列数据中,如何判断某些文档是否存在特定字段
5.Lucene生成完整的Segment之后,这个Segment不能再被更改,这个时候Segment才能被搜索。那如何做实时搜索?
ElasticSearch 如何支持分布式?
增加了一个系统字段 ”_routing“(路由)
通过这个字段把文档分发到不同的Shard,那么不同的Shard可以位于不同的机器上,这样就能实现简单的分布式。
下面是Es相对于Lucene增加了一些新的字段。
1._id
Lucene中没有主键索引,要保证系统中同一个Doc不会重复,Elasticsearch引入了_id字段来实现主键。每次写入的时候都会先查询id,如果有,则说明已经有相同Doc存在了。
解决了Lucene没有主键索引,如何处理同一个Doc的多次写入
2._version
Es中每个文档都会有版本号,可以通过doc_id来读取Version。所以Version只要存储为DocValues即可。类似于KeyValue存储。
Es使用version,一种乐观锁的思想来保证文档的变更以正确的顺序执行。
1.首次写入文档的时候,为文档分配一个初始的版本号
2.再次写入文档,分两种情况:
- 请求没有指定Version
先加锁,然后去读取该Doc最大版本V1,然后把V1+1新版本写入Lucene
- 请求指定了Version
先加锁,然后读取最大版本V2,只有当指定版本V1==V2的时候再写入,否则发生版本冲突
3.部分更新的时候,会先通过GetRequest读取当前id的完整Doc和V1,接着和当前Request中的Doc合并为一个完整Doc。
再执行一些逻辑后加锁,查看最大版本号是否等于请求时候指定的版本号,假如不是,说明其他线程更改了这个文档,需要报错之后重试。如果相等,说明没有其他线程修改当前文档,则可以继续写入lucene.
解决Lucene问题:“没有更新操作,每次都是Append一个新文档,那如何做部分字段的更新?”
3… _routing
路由规则,写入和查询的routing是需要一致的。
引入_routing字段的作用:
1.查询使用某种_routing的文档有哪些,当路由规则发生变化的时候,可以根据routing读取文档对其reindex,存储为倒排索引
2.查询到文档后,要在Response里面展示文档使用的路由规则,存储为Store
解决Lucene问题: 支持分布式搜索引擎
4._field_names
用来在稀疏文档中查询那些文档存在Field
因此这里需要倒排索引Index
解决Lucene问题:在稀疏列数据中,如何判断某些文档是否存在特定字段 。
5. _source
存储原始文档。这个字段的功能在于通过doc_id读取这个文档的原始内容,所以只需要存储Store。
Elasticsearch中使用_source字段可以实现以下功能:
Update:部分更新时,需要从读取文档保存在_source字段中的原文,然后和请求中的部分字段合并为一个完整文档。如果没有_source,则不能完成部分字段的Update操作。
Rebuild:最新的版本中新增了rebuild接口,可以通过Rebuild API完成索引重建,过程中不需要从其他系统导入全量数据,而是从当前文档的_source中读取。如果没有_source,则不能使用Rebuild API。
Script:不管是Index还是Search的Script,都可能用到存储在Store中的原始内容,如果禁用了_source,则这部分功能不再可用。
Summary:摘要信息也是来源于_source字段。
6._seq_no
每个文档有一个单调递增的序号,序列号的一个作用是可以用来实现checkpoint.
每个文档在使用Lucene的document操作接口之前,会得到一个seq_no,然后文档写入Lucene之后,会用这个seq_no来更新本地checkpoint
local_checkpoint和global_checkpoint,主要用于保存有序性,另一方面减少数据拷贝时候的数据拷贝量。
引入该字段主要作用:
一是通过doc_id查询到该文档的seq_no,
二是通过seq_no范围查找相关文档,所以也就需要存储为Index和DocValues(或者Store)。
由于是在冲突检测时才需要读取文档的_seq_no,而且此时只需要读取_seq_no,不需要其他字段,这时候存储为列式存储的DocValues比Store在性能上更好一些。
7.primary_term
_primary_term也和_seq_no一样是一个整数,每当Primary Shard发生重新分配时,比如重启,Primary选举等,_primary_term会递增1。
_primary_term主要是用来恢复数据时处理当多个文档的_seq_no一样时的冲突,避免Primary Shard上的写入被覆盖。
内核解析——写入
ES有两个身份: 一个是分布式搜索系统,另外一个是分布式NoSQL数据库。
目前的ES有两种身份:一是分布式搜索引擎,二是分布式NoSQL。
写操作:
- 实时
1.搜索系统的Index一般是近实时的,Index的实时性是由refresh控制的,默认是1s,也就是说文档写入后进行索引后,需要等待至少1s才能被搜索到。
2.NoSQL数据库的写操作基本是实时的。写入成功后,立即是可见的。Es中的Index请求也能保证是实时的,因为Get请求会直接读内存中尚未Flush到外存上的TransLog
- 可靠
1.搜索系统的可靠性要求不高,般数据的可靠性通过将原始数据存储在另一个存储系统来保证,当搜索系统的数据发生丢失时,再从其他存储系统导一份数据过来重新rebuild就可以了。在Es中,可以通过设置TransLog的Flush频率来保证可靠性。一般Flush的时间间隔越长,可靠性会越低。
2.NoSQL数据库如果作为一款数据库,需要保证数据的强可靠性。TransLog的Flush策略需要为每一个请求进行flush。当前Shard写入成功后,数据能尽量持久化下来。
什么是refresh和flush操作?
当我们向ES发送请求的时候,我们发现es貌似可以在我们发请求的同时进行搜索。而这个实时建索引并可以被搜索的过程实际上是一次es 索引提交(commit)的过程,如果这个提交的过程直接将数据写入磁盘(fsync)必然会影响性能,所以es中设计了一种机制,即:先将index-buffer中文档(document)解析完成的segment写到filesystem cache之中,这样避免了比较损耗性能io操作,又可以使document可以被搜索。以上从index-buffer中取数据到filesystem cache中的过程叫做refresh。
而flu