分布式搜索引擎elasticsearch的架构原理

分布式搜索引擎:把大量的索引数据拆散成多块,每台机器放一部分,然 后利用多台机器对分散之后的数据进行搜索,所有操作全部是分布在多台机器上进行,形成了 完整的分布式的架构。

1、核心概念

1.1 Near realtime 近实时

近实时,有两层意思:

  • 从写入数据到数据可以被搜索到有一个小延迟(大概是 1s)
  • 基于 es 执执行搜索和分析可以达到秒级
1.2 cluster集群 & Node节点

集群包含多个节点,每个节点属于哪个集群都是通过一个配置来决定的,
Node 是集群中的一个节点,节点也有一个名称,默认是随机分配的。默认节点会去加入一个名 称为 elasticsearch 的集群。如果直接启动一堆节点,那么它们会自动组成一个elasticsearch 集群,当然一个节点也可以组成 elasticsearch 集群。

1.3 Index & Type & Mapping
  • Index : 索引包含了一堆有相似结构的文档数据,比如商品索引。1个索引包含很多 document,1个
    索引就代表了1类相似或者相同的 ducument。

  • Type : 类型,每个索引里可以有1个或者多个 type,type 是 index 的1个逻辑分类,比如商品 index 下有多个 type:日化商品 type、电器商品 type、生鲜商品 type。每个 type 下的 document 的 field 可能不太一样。
    但是在ES7中已废弃,但还在用(默认一个index包含一个type),ES8才真正的去掉了type。(在ElasticSearch中,不同的type,如果包含相同的字段名,它们是一样的,es会认为是一个字段,模糊掉不同type的概念。所以在es里边,type这个概念没必要存在,所以es7就废弃了。)

  • Mapping:类似于数据库的schema的定义,mapping会把文档映射成lucene需要的扁平格式。作用是定义索引这里面的字段和名称、定义字段的数据类型(字符串、布尔、数字)、字段的倒排索引相关的配置,是否分词。

1.4 Document & field

文档是 es 中最小的数据单元,一个 document 可以是1条客户数据、1条商品分类数据、1条 订单数据,通常用json 数据结构来表示。每个 index 下的 type,都可以存储多条 document。
1个 document 里面有多个 field,每个 field 就是1个数据字段。

{ "product_id": "1",
 "product_name": "iPhone X",
 "product_desc": "苹果手机", 
"category_id": "2",
 "category_name": "电子产品" }
1.5 shard & replica
  • shard :单台机器无法存储海量数据,es 可以将1个索引中的数据切分为多个 shard,分布在多台服务 器上存储。有了 shard 就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务 器上去执行,提升吞吐量和性能。每个 shard 都是1个 lucene index。

  • replica:任何1个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建 多个 replica 副本。replica 可以在 shard 故障时提供备份服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard(建入索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个),默认每个索引 10 个 shard,5 个 primary shard,5个 replica shard,最小的高可用配置,是 2 台服务器。 这么说吧,shard 分为 primary shard 和 replica shard。而 primary shard 一般简称为 shard,而 replica shard 一般简称为 replica。

image.png
image.png

2、分布式架构原理

es 集群多个节点,会自动选举1个节点为 master 节点,这个 master 节点其实就是干一些管理 的工作的,比如维护索引元数据、负责切换 primary shard 和 replica shard 身份等。要是 master 节点宕机了,那么会重新选举1个节点为 master 节点。 如果是非 master节点宕机了,那么会由 master 节点,让那个宕机节点上的 primary shard 的身 份转移到其他机器上的 replica shard。接着你要是修复了那个宕机机器,重启了之后,master 节点会控制将缺失的 replica shard 分配过去,同步后续修改的数据之类的,让集群恢复正常。 说得更简单1点,就是说如果某个非 master 节点宕机了,那么此节点上的 primary shard 不就 没了。那好,master 会让 primary shard 对应的 replica shard(在其他机器上)切换为 primary shard。如果宕机的机器修复了,修复后的节点也不再是 primary shard,而是 replica shard。

索引可以拆分成多个 shard ,每个 shard 存储部分数据。拆分多个 shard是有好处的,一是支持横向扩展,比如你数据量是 3T,3 个 shard,每个 shard 就 1T 的数据, 若现在数据量增加到 4T,怎么扩展,很简单,重新建1个有 4 个 shard 的索引,将数据导进 去;二是提高性能,数据分布在多个 shard,即多台服务器上,所有的操作,都会在多台机器 上并行分布式执行,提高了吞吐量和性能。 接着就是这个 shard 的数据实际是有多个备份,就是说每个 shard 都有1个 primary shard ,负责写入数据,但是还有多个 replica shard 。 primary shard 写入数据之后, 会将数据同步到其他几个 replica shard上去。
通过这个 replica 的方案,每个 shard 的数据都有多个备份,如果某个机器宕机了,没关系啊, 还有别的数据副本在别的机器上,这样子就高可用了。

总结:分布式就是两点,1.通过shard切片实现横向扩展;2.通过replica副本机制,实现高可用

3、ES写入数据原理 & 搜索数据原理

基本概念

  • segment:大的逆向索引拆分成了多个小的段segment。每个segment本质上就是一个逆向索引;
  • commit point:用来记录当前所有可用的segment,当我们在这个commit point上进行搜索时,就相当于在它下面的segment中进行搜索,每个* segment返回自己的搜索结果,然后进行汇总返回给用户;
  • translog:记录segment日志,为了避免断电时,内存中的数据丢失;
  • in-memory buffer:ES的内存缓存;
  • os cache:操作系统缓存,filesystem cache;
3.1 es写数据底层原理

写数据过程:客户端通过hash选择一个node发送请求,这个node被称做coordinating node(协调节点),协调节点对docmount进行路由,将请求转发给到对应的primary shard,primary shard 处理请求,将数据同步到所有的replica shard,此时协调节点,发现primary shard 和所有的replica shard都处理完之后,就反馈给客户端。

  • write
    新增的文档首先会被存放在in-memory buffer中,同时会把日志记录写入os cache 的translog中;

  • refresh 刷新
    每隔1s,in-memory bufferr中的数据会生成一个segment file并写入os cache,此时os cache中的segment file被打开并供search使用,并清空buffer。
    文档经过 refresh 后, segment file暂时写到文件系统缓存os cache,这样避免了性能 IO 操作,又可以使文档搜索到。refresh 默认 1 秒执行一次,性能损耗太大。一般建议稍微延长这个 refresh 时间间隔,比如 5 s。因此,ES 其实就是准实时,达不到真正的实时。

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

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

    commit操作发生的第一步,就是将buffer中现有的数据refresh到os cache中去,清空buffer。然后将一个commit point写入磁盘文件,里面标识者这个commit point 对应的所有segment file,同时强行将os cache中目前所有的数据都fsync到磁盘文件中去。最后清空现有 translog日志文件,重启一个translog,此时commit操作完成。

    这个commit操作叫做flush。默认30分钟自动执行一次flush,但如果translog过大,也会触发flush。flush操作就对应着commit的全过程,我们可以通过es api,手动执行flush操作,手动将os cache中数据fsync强刷到磁盘上去。

  • merge过程
    buffer 每refresh一次,就会产生一个segment file,所以默认情况下是1秒钟一个segment file,这样下来segment file会越来越多,但是每个segment都会占用独立的文件句柄/内存/消耗cpu资源,而且,在查询的时候,需要在每个segment上都执行一次查询,这样是很消耗性能的。

    此时会定期执行merge, 每次merge的时候,会将多个segment file合并成一个,同时这里会将标识为 deleted的doc给物理删除掉,然后将新的segment file写入磁盘,这里会写一个commit point,标识所有新的 segment file,然后打开segment file供搜索使用,当新合并后的segment完全写入磁盘之后,es就会自动删除掉那些零碎的segment,之后的查询都在新合并的segment上执行。Segment的合并会消耗大量的IO和cpu资源,这会影响查询性能。

image.png
image.png
image.png

3.2 es读数据过程

客户端发送get请求到任意一个node节点,然后这个节点就称为协调节点,协调节点对document进行路由,将请求转发到对应的node,此时会使用随机轮询算法,在primary shard 和replica shard中随机选择一个,让读取请求负载均衡,接收请求的node返回document给协调节点,协调节点,返回document给到客户端

3.3 es搜索数据过程

es最强大的是做全文检索,就是比如你有三条数据
1.java真好玩儿啊
2.java好难学啊
3.j2ee特别牛

你根据java关键词来搜索,将包含java的document给搜索出来。

  • 客户端发送请求到一个coordinate node(随意选择的)
  • 协调节点将搜索请求转发到所有的shard对应的primary shard或replica shard也可以
  • query phase:每个shard将自己的搜索结果(其实就是一些doc id),返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果
  • fetch phase:接着由协调节点,根据doc id去各个节点上拉取实际的document数据,最终返回给客户端。

3.4 es更新/删除数据过程

更新/删除数据过程,首先还是write、merge操作,然后flush过程中:
1、write过程和上面的一致;
2、refresh过程有点区别

    1. 如果是删除操作,refresh 的时候会生成一个.del文件,里面将某个doc标识为deleted状态,那么搜索的时候根据.del文件就知道这个doc被删除了;
    1. 如果是更新操作,就是将原来的doc标识为deleted状态,然后新写入一条数据;
  1. flush过程和上面一致
    buffer每次refresh一次,就会产生一个segment file,所以默认情况下是1秒钟一个segment file,segment file会越来越多,此时会定期执行merge。

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

4、倒排索引

所谓的倒排索引,就是把你的数据内容先分词,每句话分成一个一个的关键词,然后记录好每一个关键词对应出现在了哪些 id 标识的数据。
然后你可以从其他地根据这个 id 找到对应的数据就可以了,这个就是倒排索引的数据格式 以及搜索的方式,这种利倒排索引查找数据的式,也被称之为全文检索。

5、segment file文件的数据结构

  • Inverted Index
  • Stored Fields
  • Document Values
  • Cache
5.1 Inverted Index

Inverted Index就是我们常见的倒排索引, 主要包括两部分:
一个有序的数据字典 Dictionary(包括单词 Term 和它出现的频率)。
与单词 Term 对应的 Postings(即存在这个单词的文件)
当我们搜索的时候,首先将搜索的内容分解,然后在字典里找到对应 Term,从而查找到与搜索相关的文件内容。

5.2 Stored Field

本质上,Stored Fields 是一个简单的键值对 key-value。默认情况下,Stored Fields是为false的,ElasticSearch 会存储整个文件的 JSON source。

哪些情形下需要显式的指定store属性呢?大多数情况并不是必须的。从_source中获取值是快速而且高效的。如果你的文档长度很长,存储 _source或者从_source中获取field的代价很大,你可以显式的将某些field的store属性设置为yes。缺点如上边所说:假设你存 储了10个field,而如果想获取这10个field的值,则需要多次的io,如果从Stored Field 中获取则只需要一次,而且_source是被压缩过 的。

这个时候你可以指定一些字段store为true,这意味着这个field的数据将会被单独存储(实际上是存两份,source和 Stored Field都存了一份)。这时候,如果你要求返回field1(store:yes),es会分辨出field1已经被存储了,因此不会从_source中加载,而是从field1的存储块中加载。

5.3 Document Values

Doc_values 本质上是一个序列化的 列式存储,这个结构非常适用于聚合(aggregations)、排序(Sorting)、脚本(scripts access to field)等操作。而且,这种存储方式也非常便于压缩,特别是数字类型。这样可以减少磁盘空间并且提高访问速度,ElasticSearch 可以将索引下某一个 Document Value 全部读取到内存中进行操作.

Doc_values是存在磁盘的

在es中text类型字段默认只会建立倒排索引,其它几种类型在建立倒排索引的时候还会建立正排索引,当然es是支持自定义的。在这里这个正排索引其实就是Doc Value。

5.4 Cache

即上文所描述的动态索引

6、提高ES性能

6.1、filesystem cache

往 es 写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到 filesystem cache 中去。

es 的搜索引擎严重依赖于底层的 filesystem cache ,你如果给 filesystem cache 更多的 内存,尽量让内存可以容纳所有的 idx segment file 索引数据文件,那么你搜索的时候就 基本都是走内存的,性能会非常高。 性能差距究竟可以有多大?我们之前很多的测试和压测,如果走磁盘一般肯定上秒,搜索性能 绝对是秒级别的,1秒、5秒、10秒。但如果是走 filesystem cache ,是走纯内存的,那么一 般来说性能比走磁盘要高一个数量级,基本上就是毫秒级的,从几毫秒到几百毫秒不等。

那如何才能节约filesystem cache这部分的空间呢?
当写数据到ES时就要考虑到最小化数据,当一行数据有30几个字段,并不需要把所有的数据都写入到ES,只需要把关键的需要检索的几列写入。这样能够缓存的数据就会越多。 所以需要控制每台机器写入的数据最好小于等于或者略大于filesystem cache空间最好。 如果要搜索海量数据,可以考虑用ES+Hbase架构。用Hbase存储海量数据,然后ES搜索出doc id后,再去Hbase中根据doc id查询指定的行数据。

6.2、数据预热

当每台机器写入的数据大于cache os太多时,导致太多的数据无法放入缓存,那么就可以把一部分热点数据刷入缓存中。

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

6.3、冷热分离

把热数据和冷数据分开,写入不同的索引里,然后确保把热索引数据刷到cache里。

6.4、document模型设计

在ES里最好不要用复杂的关联表的操作。当需要这样的场景时,可以在创建索引的时候,就把数据关联好。比如在mysql中需要根据关联ID查询两张表的关联数据:select A.name ,B.age from A join B where A.id = B.id,在写入ES时直接去把相关联数据放到一个document就好。

6.5、分页性能优化

es 的分页是较坑的,为啥呢?举个例子吧,假如你每页是 10 条数据,你现在要查询第 100 页,实际上是会把每个 shard 上存储的前 1000 条数据都查到1个协调节点上,如果你有个 5 个 shard,那么就有 5000 条数据,接着协调节点对这 5000 条数据进行一些合并、处理,再获取到 最终第 100 页的 10 条数据。
分布式的,你要查第 100 页的 10 条数据,不可能说从 5 个 shard,每个 shard 就查 2 条数据, 最后到协调节点合并成 10 条数据吧?你必须得从每个 shard 都查 1000 条数据过来,然后根据 你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 100 页的数据。你翻页的时 候,翻的越深,每个 shard 返回的数据就越多,而且协调节点处理的时间越长,非常坑爹。所 以用 es 做分页的时候,你会发现越翻到后面,就越是慢。

我们之前也是遇到过这个问题,用 es 作分页,前几页就几十毫秒,翻到 10 页或者几十页的时 候,基本上就要 5~10 秒才能查出来一页数据了。

解决方案吗?
1)不允许深度分页:跟产品经理说,你系统不允许翻那么深的页,默认翻的越深,性能就越差;
2)在APP或者公众号里,通过下拉来实现分页,即下拉时获取到最新页,可以通过scroll api来实现;
scroll 会1次性给你生成所有数据的1个快照,然后每次滑动向后翻页就是通过游标 scroll_id 移动获取下一页,性能会比上面说的那种分页性能要高很多很 多,基本上都是毫秒级的。 但是,唯1的缺点就是,这个适合于那种类似微博下拉翻页的,不能随意跳到任何一页的场 景。也就是说,你不能先进到第 10 页,然后去第 120 页,然后再回到第 58 页,不能随意乱跳 页。所以现在很多APP产品,都是不允许你随意翻页的,也有一些网站,做的就是你只能往 下拉,一页一页的翻。
初始化时必须指定 scroll 参数,告诉 es 要保存此次搜索的上下文多长时间。你需要确保用户不会持续不断翻页翻几个小时,否则可能因为超时而失败。
除了用 scroll api ,也可以用 search_after 来做, search_after 的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不允许你随意翻页,你只能一页一页往后 翻。初始化时,需要使用一个唯1值的字段作为 sort 字段。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336

推荐阅读更多精彩内容