说一说elasticsearch

es是一个服务于海量数据搜索的高性能的分布式搜索引擎,它天生就是分布式的,使用es我们需要搭建es集群,集群中的所有节点负责存储我们所有的数据,并提供跨整个集群的文档的索引和搜索服务。

什么时候需要用到es呢?

1、当关系型数据库因为存储的数据量过大而导致查询性能太低,而通过使用索引所带来的性能提升又是有限的,这个时候可以使用es来为数据提供快速检索能力,es的快速检索是从数据结构上来实现的,当搜索发生的时候,es能通过扫描倒排表快速地定位到文档,速度非常快。

2、当我们的业务需求要求实现分词搜索时。关系型数据库是没有分词搜索的能力的,而es会在索引文档时,将文本类型的字段分词,并将分词以后的词条与文档的映射关系记录在倒排表中,当根据一个搜索关键词搜索数据时,es也会先将这个关键词进行分词,并且根据分词后的关键词列表去扫描倒排表,搜索出这些关键词所在的文档返回。

3、当我们要频繁地在关系型数据库中执行多表联查,太多表的联查一个是实现起来比较繁琐,再一个就是性能也不会太高,这个时候我们也可以使用es来代替关系型数据库,因为es与关系型数据库存储数据的特点是不同的,关系型数据库存储的是扁平的、结构单一的数据,同一张表中存储的都是结构完全相同的数据,具有不同结构的两条数据会被保存在两张表中,并且为了保证单表的操作性能,我们通常会去限制表结构的大小,比如我们会将商品的基本信息存储在一张表中,而将商品的扩展信息又存储到另外一张表中。而es存储的是结构化的数据,数据具有更加丰富的结构,对于关系型数据库中的多个表结构,在es中完全可以合并为一个结构,这样就能将多个关联表中的多条数据合并为一条数据保存在es中的一个文档中了。所以可以使用es来取代关系型数据库中的多表联查。

除了以上不同之外,还应该展开说一下es作为一个分布式搜索引擎的特点,包括:集群、节点、索引、类型、文档、分片、Lucene的段、索引文档的流程、es的近实时搜索特性、Lucene提供的合并段的功能、文档的删除是如何实现的、es文档的搜索流程。还有es的使用,包括:es的结构化查询、查询体(query)的构建、es提供的查询类型。

集群:es集群是由多个es节点组成的集合,集群中所有节点共同承担所有数据的存储,并提供跨整个集群所有节点的文档的索引和搜索能力,我们可以通过为多个节点设置相同的cluster.name让它们加入到同一个集群中,es集群默认的cluster.name是elasticsearch。

节点:一个es服务实例就是一个es节点,我们虽然可以在同一台服务器上部署多个es实例,只要让它们使用不同的端口即可,但一般我们只会在一台服务器上部署一个es实例。

索引:es中的索引类似于关系型数据库中“数据库”的概念,es的一个索引是一个指向多个es分片的逻辑命名空间,我们在创建索引的时候,会为它设置主分片的数量以及每个主分片的副本分片数量,这些分片会被分散到集群中的各个节点上,每一个主分片可以设置0个或多个副本分片,副本分片的作用是备份主分片的数据,参与文档的搜索,并在主分片所在的主机宕机时,继续对外提供搜索服务,保证不丢失数据。

关于主分片的数量设置,有一点需要说明,一般我们在为索引设置了主分片数量之后是不会去轻易修改这个值的,但是如果我们必须要去修改它,比如当我们为es扩容之后,因为节点数量增加了,我们也想将某个索引的主分片数量调高一些,这时需要注意,在改变了主分片数量之后,要将之前索引到此索引下面的文档全部删除,并重新索引所有数据,因为主分片数量改变了,路由算法的结果就会改变,之前被索引的文档已经无法通过新的路由算法定位到了。

类型:类型是es索引的逻辑分区,类似于关系型数据库中“表”的概念,es的同一个索引下是可以存储具有不同结构的文档的,但通常我们会将具有相同或相似结构的文档保存在同一个类型下,在同一个类型下的不同文档的通用字段应该具有相同的数据类型。

文档:es中的一个文档就是一条数据,类似于关系型数据库表中的一行记录,只不过es文档的结构更加的丰富,并且文档中不只包含源数据,还包含源数据的描述字段,如:文档所属的索引、类型、文档的id【当然这个id不是源数据的一个字段,而是此文档在它所在的索引下的一个识别序列】、文档的版本号等。

es的分片

说到es的分片,就要往es的底层说了,先说es与Lucene的关系:es底层使用的是Lucene库,es的索引是多个es分片的集合,es分片就是Lucene索引,而一个Lucene索引又包含着多个段。下面通过文档的索引流程来描述这一个个概念。

es索引文档的流程

当索引文档的请求到达时,es首先要做的一个计算是定位此文档应该存储在哪个主分片上,es提供了一个路由算法来定位主分片:hash(id)%主分片数量,索引文档的请求可以往集群中的任意一个节点发送,因为集群中的任意一个节点都可以作为协调节点,当协调节点收到索引请求之后,就会用路由算法定位到主分片,然后向这个主分片转发这个索引请求,然后进入真正的索引流程。

文档首先会被保存到内存缓冲区,并写入事务日志,也就是translog,然后经历refresh和flush流程提交到磁盘保存。

refresh操作是将内存缓冲区中的新文档刷新到文件系统缓存中新的段,并将这个段打开,然后清空内存缓冲区。新的段被打开,段中的文档就可以被搜索到了。分片默认每秒执行一次自动的refresh操作,位于内存缓冲区中的文档是不能被搜索到的,但是一旦被刷新到文件系统缓存之后就能被搜索到了,而分片是每秒refresh一次的,所以es的搜索是近实时搜索,也就是,文档的变化并不会立即对搜索可见,但是会在一秒之内变为可见。

flush是将文件系统缓存中新的段往磁盘提交的一个操作,es默认每30分钟或者是当事务日志体积过大时执行一次flush操作,flush操作开始的时候,会先执行一次refresh操作,将内存缓冲区中所有的新文档刷新到文件系统缓存中的一个新段,并清空内存缓冲区,然后在磁盘上创建一个新的提交点【一个提交点就是一个列出了所有已知段的文件】,将文件系统缓存中所有未提交的新段提交到磁盘,并记录到这个新的提交点上,最后清除事务日志。

文件系统缓存存在的意义是什么呢?

es要将文档存盘,需要去调用操作系统的fsync函数,而fsync函数的执行是一个非常昂贵的操作,因此es使用了一种轻量级的开销更小的方式去索引文档,那就是在es与磁盘之间加入文件系统缓存,这样每次在索引文档的时候,都将这个新文档刷新到文件系统缓存中,而不是每次都调用fsync落入磁盘了,es每三十分钟一次或者是事务日志过大的时候才去往磁盘中写一次,大大减少了fsync函数调用的次数,减少了系统开销。

es的近实时搜索特性

上面说过了,es的搜索是近实时的,也就是,文档的变化并不会立即对搜索可见,但是会在一秒之内变为可见。如果我们的需求是,在一个新文档被索引之后,就能立即搜索到这个文档,那么我们可以在索引完成之后去调用一下refresh api,手动执行一次刷新。事实上,频繁的刷新操作是会影响es的搜索性能的,因为会导致Lucene创建出太多的段来,段的数量过多会让搜索性能下降,所以,在我们实际的业务需求中,如果没有必要让es每秒刷新一次的话,我们是可通过修改refresh_interval的设置去延长刷新间隔的,甚至在我们不需要es自动刷新的时候,我们也可以通过将refresh_interval的值设置为-1来将自动刷新关闭,比如当我们在生产环境中往es中索引大量的日志文件时。

Lucene的段合并

因为分片默认每秒自动刷新一次,而每次刷新时,只要内存缓冲区中有新文档,就会创建新的段去存储它们,这样段的数量很容易发生爆炸,每个段都会带来内存和cpu的开销,并且段的数量过多会影响到搜索性能,因为Lucene的段就是倒排索引,在搜索的时候Lucene会去扫描所有的倒排索引,所以段的数量会直接影响到搜索性能,因此Lucene提供了段的合并功能,将小段合并为大的段,将大段合并为更大的段。段合并功能虽然减少了段的数量,但是合并的过程会占用cpu,在此过程中也会影响到搜索性能,所以虽然Lucene提供了段合并的功能,却又会限制段合并。

es文档的删除

Lucene的段具有不可更改的特性,而文档是保存在段中的,所以es中的文档无法实现物理删除,只能实现逻辑删除,每一个提交点中都会有一个.del文件,这个文件中记录的就是段中删除的文档信息,当要删除一个文档时,会在.del文件中将此文档标记为已删除,被标记为删除的文档还是可以被搜索到的,不过会在最终结果返回之前被从结果集中移除。

文档的修改操作也是差不多的实现,因为段不支持修改,所以文档的修改操作实际上是由一次删除操作+一次新增操作来实现的,将旧段中的文档标记为删除,并在新的段中插入一个新的文档,并且新文档的版本号要在旧文档的版本号基础上加一。在搜索的时候,被删除的旧文档和新插入的新文档都是可以被搜索到的,但是会在最终结果返回之前,将旧文档在结果集中移除。

es文档的搜索流程

es文档的搜索实现被分成了两个阶段:query then fetch【搜索和取回】,搜索文档的请求可以发送到集群中的任意一个节点上,因为所有的节点都有作为协调节点的能力,当协调节点收到搜索请求后,它会将这个请求广播到每一个分片拷贝【主分片或者是副本分片】,每一个收到请求的分片都在自己本地执行一次搜索,并构建一个大小为from+size的匹配文档的优先队列,并将自己优先队列中所有文档的id和相关性评分返回给协调节点,协调节点整合所有的分片返回的数据到自己的优先队列中,按照这些数据的相关性评分做一个最终结果的排序,排序之后要取回的是哪些文档就确定了,然后进入取回阶段:协调节点向要取回的那一部分文档所在的那些分片发出取回请求,所有收到请求的分片在自己本地加载相关文档返回给协调节点,等所有的文档都取回之后,协调节点再将它们整合并排序,然后返回给客户端,至此,一次搜索就完成了。

es的结构化查询

es的查询是结构化的,意思是我们在查询的时候可以构建一个结构化的查询体,这个查询体是json格式的,我们可以在这个json内部定义查询条件,es提供了多种查询类型供我们使用,如:分词查询match_query、精确查询【不分词查询】term_query、通配符查询wildcard_query、前缀查询prefix_query、使用多个查询条件的bool_query,我们在实际的开发工作中,可以选择合适类型的查询,去构建查询体,实现查询功能。es还支持在查询体中设置排序方式、分页参数等。

最后说说es的倒排索引

倒排索引是搜索引擎的核心,它与正向索引不同,正向索引记录的是文档id到关键词的映射,当搜索发生时,是去扫描所有的文档id到关键词的映射关系,而网络上收录在搜索引擎中的文档数量庞大,使用正向索引无法实现实时地返回文档排序结果,所以es使用倒排索引,在倒排表中记录的是关键词到文档id的映射,也就是记录了每个关键词都在哪些文档中出现过,当搜索发生时,只要扫描倒排表就能知道搜索关键词都出现在了哪些文档中,然后快速返回目标文档。