文章目录
  1. 1. ES的分布式架构原理是什么(是如何实现分布式的)?
  2. 2. ES写入、读取、查询、删除的原理是什么?
    1. 2.1. 写数据过程
    2. 2.2. 读数据过程
    3. 2.3. 搜索数据过程
    4. 2.4. 写数据底层原理
  3. 3. ES在数据量很大的情况下(数十亿级别)如何提高查询效率?

目前业界对于分布式搜索引擎一般都使用Elasticsearch,早些年还有不少企业使用solr,这两个都是基于lucene。lucene的一个重要的底层原理是倒排索引。

关于ES,有一些常见的问题。

注:本系列所有文字、图片等其他信息均来自石杉老师视频教程,本人仅做整理工作。


ES的分布式架构原理是什么(是如何实现分布式的)?

Elasticsearch设计的理念就是分布式搜索引擎,底层其实还是基于lucene的。核心思想是在多台机器上启动多个ES进程实例,组成了一个ES集群。ES中存储数据的基本单位是索引,如要在ES中存储一些订单数据,就应该在ES中创建一个索引,order_idx,所有的订单数据就都写到这个索引里面去,一个索引差不多就是相当于是mysql里的一张表。ES的层级如下:index -> type -> mapping -> document -> field。

1、index:mysql里的一张表

2、type:没法跟mysql里去对比,一个index里可以有多个type,每个type的字段都是差不多的,但是有一些略微的差别。

好比说,有一个index,是订单index,里面专门是放订单数据的。如在mysql中建表,有些订单是实物商品的订单,如一件衣服,一双鞋子;有些订单是虚拟商品的订单,如游戏点卡,话费充值。就两种订单大部分字段是一样的,但是少部分字段可能有略微的一些差别。

所以就会在订单index里,建两个type,一个是实物商品订单type,一个是虚拟商品订单type,这两个type大部分字段是一样的,少部分字段是不一样的。

很多情况下,一个index里可能就一个type,但是如果说是一个index里有多个type的情况,你可以认为index是一个类别的表,具体的每个type代表了具体的一个mysql中的表。

3、mapping就代表了这个type的表结构的定义,定义了这个type中每个字段名称,字段是什么类型的,然后还有这个字段的各种配置

4、实际上你往index里的一个type里面写的一条数据,叫做一条document,一条document就代表了mysql中某个表里的一行,每个document有多个field,每个field就代表了这个document中的一个字段的值

如上图,ES中的一个索引可以拆分成多个shard,每个shard存储部分数据。这个shard的数据实际是有多个备份,就是说每个shard都有一个primary shard,负责写入数据,但是还有几个replica shard。primary shard写入数据之后,会将数据同步到其他几个replica shard上去。

通过这个replica的方案,每个shard的数据都有多个备份,如果某个机器宕机了,没关系啊,还有别的数据副本在别的机器上呢,这样就实现了高可用。

ES集群多个节点,会自动选举一个节点为master节点,这个master节点其实就是干一些管理的工作的,比如维护索引元数据拉,负责切换primary shard和replica shard身份拉,之类的。要是master节点宕机了,那么会重新选举一个节点为master节点。

如果是非master节点宕机了,那么会由master节点,让那个宕机节点上的primary shard的身份转移到其他机器上的replica shard。接着如果修复了那个宕机机器,重启之后,master节点会控制将缺失的replica shard分配过去,同步后续修改的数据之类的,让集群恢复正常。


ES写入、读取、查询、删除的原理是什么?

写数据过程

1)客户端选择一个node发送请求过去,这个node就变为coordinating node(协调节点)

2)coordinating node,对document进行路由,将请求转发给对应的node(有primary shard)

3)实际的node上的primary shard处理请求,然后将数据同步到replica node

4)coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端

读数据过程

查询,GET某一条数据,写入了某个document,这个document会自动给你分配一个全局唯一的id,doc id,同时也是根据doc id进行hash路由到对应的primary shard上面去。也可以手动指定doc id,比如用订单id,用户id。

1)客户端发送请求到任意一个node,成为coordinate node

2)coordinate node对document进行路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡

3)接收请求的node返回document给coordinate node

4)coordinate node返回document给客户端

搜索数据过程

1)客户端发送请求到一个coordinate node

2)协调节点将搜索请求转发到所有的shard对应的primary shard或replica shard也可以

3)query phase:每个shard将自己的搜索结果(其实就是一些doc id),返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果

4)fetch phase:接着由协调节点,根据doc id去各个节点上拉取实际的document数据,最终返回给客户端

写数据底层原理

1)先写入buffer,在buffer里的时候数据是搜索不到的;同时将数据写入translog日志文件

2)如果buffer快满了,或者到一定时间(每隔1秒钟),就会将buffer数据refresh到一个新的segment file中,但是此时数据不是直接进入segment file的磁盘文件的,而是先进入os cache的。这个过程就是refresh

为什么叫es是准实时的?NRT,near real-time,准实时。默认是每隔1秒refresh一次的,所以es是准实时的,因为写入的数据1秒之后才能被看到。

可以通过es的restful api或者java api,手动执行一次refresh操作,就是手动将buffer中的数据刷入os cache中,让数据立马就可以被搜索到。

只要数据被输入os cache中,buffer就会被清空了,因为不需要保留buffer了,数据在translog里面已经持久化到磁盘去一份了

3)只要数据进入os cache,此时就可以让这个segment file的数据对外提供搜索了

4)重复1~3步骤,新的数据不断进入buffer和translog,不断将buffer数据写入一个又一个新的segment file中去,每次refresh完buffer清空,translog保留。随着这个过程推进,translog会变得越来越大。当translog达到一定长度的时候,就会触发commit操作

5)commit操作发生第一步,就是将buffer中现有数据refresh到os cache中去,清空buffer

6)将一个commit point写入磁盘文件,里面标识着这个commit point对应的所有segment file

7)强行将os cache中目前所有的数据都fsync到磁盘文件中去

8)将现有的translog清空,然后再次重启启用一个translog,此时commit操作完成。默认每隔30分钟会自动执行一次commit,但是如果translog过大,也会触发commit。整个commit的过程,叫做flush操作。我们可以手动执行flush操作,就是将所有os cache数据刷到磁盘文件中去。

补充:

A、translog日志文件的作用是什么?

在你执行commit操作之前,数据要么是停留在buffer中,要么是停留在os cache中,无论是buffer还是os cache都是内存,一旦这台机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件,translog日志文件中,一旦此时机器宕机,再次重启的时候,es会自动读取translog日志文件中的数据,恢复到内存buffer和os cache中去。

B、commit操作三步走?

写commit point===>将os cache数据fsync强刷到磁盘上去===>清空translog日志文件

C、flush与commit喵喵喵?

ES中的flush操作,就对应着commit的全过程。我们也可以通过es api,手动执行flush操作,手动将os cache中的数据fsync强刷到磁盘上去,记录一个commit point,清空translog日志文件。

9)translog其实也是先写入os cache的,默认每隔5秒刷一次到磁盘中去,所以默认情况下,可能有5秒的数据会仅仅停留在buffer或者translog文件的os cache中,如果此时机器挂了,会丢失5秒钟的数据。但是这样性能比较好,最多丢5秒的数据。也可以将translog设置成每次写操作必须是直接fsync到磁盘,但是性能会差很多。

10)如果是删除操作,commit的时候会生成一个.del文件,里面将某个doc标识为deleted状态,那么搜索的时候根据.del文件就知道这个doc被删除了

11)如果是更新操作,就是将原来的doc标识为deleted状态,然后新写入一条数据

12)buffer每次refresh一次,就会产生一个segment file,所以默认情况下是1秒钟一个segment file,segment file会越来越多,此时会定期执行merge。当segment file多到一定程度的时候也会自动触发merge操作。

13)每次merge的时候,会将多个segment file合并成一个,同时这里会将标识为deleted的doc给物理删除掉,然后将新的segment file写入磁盘,这里会写一个commit point,标识所有新的segment file,然后打开segment file供搜索使用,同时删除旧的segment file。

ES里的写流程,有4个底层的核心概念,refresh、flush、translog、merge


ES在数据量很大的情况下(数十亿级别)如何提高查询效率?

es性能优化是没有什么银弹的,不要期待着随手调一个参数,就可以万能的应对所有的性能慢的场景。也许有的场景是你换个参数,或者调整一下语法,就可以搞定,但是绝对不是所有场景都可以这样。

(1)性能优化的杀手锏——filesystem cache

es的搜索引擎严重依赖于底层的filesystem cache,你如果给filesystem cache更多的内存,尽量让内存可以容纳所有的indx segment file索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高。

要让es性能要好,最佳的情况下,就是你的机器的内存,至少可以容纳你的总数据量的一半,比如说,你一共要在es中存储1T的数据,那么你的多台机器留个filesystem cache的内存加起来综合,至少要到512G,至少半数的情况下,搜索是走内存的,性能一般可以到几秒钟。

如果最佳的情况下,是仅仅在es中只存少量的数据,就是你要用来搜索的那些索引,内存留给filesystem cache的,就100G,那么你就控制在100G以内,相当于是,你的数据几乎全部走内存来搜索,性能非常之高,一般可以在1秒以内。

比如说你现在有一行数据

id name age ….30个字段

但是你现在搜索,只需要根据id name age三个字段来搜索

如果你傻乎乎的往es里写入一行数据所有的字段,就会导致70%的数据是不用来搜索的,结果硬是占据了es机器上的filesystem cache的空间,单条数据的数据量越大,会导致filesystem cahce能缓存的数据就越少

仅仅只是写入es中要用来检索的少数几个字段就可以了,比如说,就写入es id name age三个字段就可以了,然后你可以把其他的字段数据存在mysql里面,一般是建议用es + hbase的这么一个架构。

hbase的特点是适用于海量数据的在线存储,从es中根据name和age去搜索,拿到的结果可能就20个doc id,然后根据doc id到hbase里去查询每个doc id对应的完整的数据,给查出来,再返回给前端。

(2)数据预热

对于那些你觉得比较热的,经常会有人访问的数据,最好做一个专门的缓存预热子系统,就是对热数据,每隔一段时间,你就提前访问一下,让数据进入filesystem cache里面去。这样期待下次别人访问的时候,一定性能会好一些。

举个例子,就比如说,微博,你可以把一些大v,平时看的人很多的数据给提前你自己后台搞个系统,每隔一会儿,你自己的后台系统去搜索一下热数据,刷到filesystem cache里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索了,很快。

电商,你可以将平时查看最多的一些商品,比如说iphone XS max,热数据提前后台搞个程序,每隔1分钟自己主动访问一次,刷到filesystem cache里去。

(3)冷热分离

关于ES性能优化,数据拆分,之前说将大量不搜索的字段,拆分到别的存储中去,类似于mysql分库分表的垂直拆分。

ES也可以做类似于mysql的水平拆分,将大量的访问很少,频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。这样可以确保热数据在被预热之后,尽量都让他们留在filesystem os cache里,不让冷数据给冲刷掉。

(4)document模型设计

ES里面的复杂的关联查询,复杂的查询语法(join之类的),尽量别用,一旦用了性能一般都不太好。

很多复杂的乱七八糟的一些操作,如何执行

两个思路,在搜索/查询的时候,要执行一些业务强相关的特别复杂的操作:

1)在写入数据的时候,就设计好模型,加几个字段,把处理好的数据写入加的字段里面

2)自己用java程序封装,es能做的,用es来做,搜索出来的数据,在java程序里面去做,用java封装一些特别复杂的操作

比如,如果一定要执行join操作,可以采用如下策略:

写入es的时候,搞成两个索引,order索引,orderItem索引。order索引,里面就包含id order_code total_price。orderItem索引,里面写入进去的时候,就完成join操作,id order_code total_price id order_id goods_id purchase_count price。

即写入es的java系统里,就完成关联,将关联好的数据直接写入es中,搜索的时候,就不需要利用es的搜索语法去完成join来搜索了。

(5)分页性能优化

es的分页是较坑的,为啥呢?举个例子吧,假如你每页是10条数据,你现在要查询第100页,实际上是会把每个shard上存储的前1000条数据都查到一个协调节点上,如果你有个5个shard,那么就有5000条数据,接着协调节点对这5000条数据进行一些合并、处理,再获取到最终第100页的10条数据。

翻页的时候,翻的越深,每个shard返回的数据就越多,而且协调节点处理的时间越长。非常坑爹。所以用es做分页的时候,会发现越翻到后面,就越是慢。

那么该如何解决呢?

1)不允许深度分页或者告诉用户/PM默认深度分页性能很惨

2)类似于app里的推荐商品不断下拉出来一页一页的,可以用scroll api

scroll会一次性给你生成所有数据的一个快照,然后每次翻页就是通过游标移动,获取下一页下一页这样子,性能会比上面说的那种分页性能也高很多很多,无论翻多少页,性能基本上都是毫秒级的。

但是唯一的一点就是,这个适合于那种类似微博下拉翻页的,不能随意跳到任何一页的场景。同时这个scroll是要保留一段时间内的数据快照的,你需要确保用户不会持续不断翻页翻几个小时。

因为scroll api是只能一页一页往后翻的,是不能说,先进入第10页,然后去120页,回到58页,不能随意乱跳页。所以现在很多产品,都是不允许你随意翻页的,app,也有一些网站,做的就是你只能往下拉,一页一页的翻。


如果觉得文章很有趣或对你带来了帮助,欢迎请我喝杯咖啡哦~

文章目录
  1. 1. ES的分布式架构原理是什么(是如何实现分布式的)?
  2. 2. ES写入、读取、查询、删除的原理是什么?
    1. 2.1. 写数据过程
    2. 2.2. 读数据过程
    3. 2.3. 搜索数据过程
    4. 2.4. 写数据底层原理
  3. 3. ES在数据量很大的情况下(数十亿级别)如何提高查询效率?