1、Elasticsearch是什么
基于restful
web接口并且构建在apache
lucene之上的开源分布式搜索引擎。
属于一个分布式文档数据库,其中每个字段均可被索引,而且每个字段的数据均可被搜索。
优势:天然分布式,能够横向扩展至数以百计的服务器存储以及处理pb级的数据。
2、document
document即为文档的意思,一个文档为一条json数据,可以简单对比为MySQL中的一行数据。
2.1、document 结构
假设有订单表,mapping如下:

进行一次搜索结果如下:


_source:该部分为文档的json数据。
_id:每个文档都有唯一ID,可自定义也可系统生成。
hits:结果集合,每个元素(外层红框部分)可视为一个文档。
此处给正在阅读的你留一个问题:为什么status要设置成keyword类型?为什么不是long、integer、short?(答案会在之后不定期写的另一篇博客中讲到,暂时借鉴博客elasticsearch.cn/article/446)
2.2、分词
上图有一个_score字段:代表的本次搜索的得分,因为该字段设置了可分次,且我们的搜索可是根据分次进行搜索的,越高的得分证明文档匹配程度越高。如果不需要分次可将字段设置为keyword以提高读写性能并节省存储空间。
2.3、并发锁
在MySQL中有事务可以确保数据更新有序,最常见的场景就是秒杀抢购,库存100,如何涉及才能保证不超卖?在MySQL中可以通过for update 行锁、或者其它的方式实现。但是ES中并没有事务,那么如何处理呢?
可以使用乐观锁的思想, 并且ES也是支持的,ES的每个文档增、删、改 时都会记录一个_version、一个_seq_no来确保数据版本,
_version:可用户指定也可系统生成,如果系统生成,每次修改数据 + 1。
_seq_no:严格递增的顺序号,保证同一个doc中,后写入版本的_seq_no > 前者。
下面示例可以较清晰的看出:_seq_no不一致ES会直接返回更新失败及详细报错信息。


2.4、倒排索引
在ES2.0版本中,文档的存储是基于倒排索引实现的,在后面的5.0+有所改进,先简单看下什么是倒排索引,以下示例摘自维基百科:

倒排索引内部是以B+Tree的方式实现的,类似MySQL中InnoDB的索引;不同点是叶子节点存储的不是全部数据,而是对应文档的索引。且倒排索引是存储在内存中的(这也是ES检索快的根本原因)。
这里借用两张图简单说明下:


倒排索引由两部分组成,参考上图:一部分为词典,即我们看到的B+Tree,另外一部分为倒排列表,即每个分词对应索引的 Posting List
2.5、B+Tree 内存合理化
按照上面我们了解的内容,B+Tree 是存储在内存中,那么存储的数据越来越大,机器内存是不是也需要越来越大,这个增长是很快的,合理的涉及就显得很重要了。
1、只为搜索
可以尝试ES+Hbase、ES+MySQL的方案,ES只存储一些基础的数据,剩余的冗余数据通过外部存储的方式实现。
2、数据预热
将常用的字段预热刷到内存中。
3、冷热分离
高频访问数据单独放到一个index内,假设6台机器,2个index。热index占用4台机器,冷index占用2台。
4、document模型简单化
模型设计时尽量避免复杂操作,在应用层接入时就进行一些冗余业务计算,保证搜索简单。
2.6、分页的坑
ES中的分页性能极差,最好不要进行深度分页,其实在MySQL中 limit 1000000,10 同样会存在此问题,主要表现就是搜索慢。
为什么要少用ES的分页 scroll 呢?举个例子:
假设每页10条数据,现在需要查询第101页的数据,实际在ES内部会将每个shard 中存储的前1000条数据都查询到一个协调节点上,如果是5个shard,则一共5K条数据。
可能会有一个疑惑点,为什么每个shard都要查1000条数据呢?
因为ES是分布式分片存储,ES并不知道哪个分片存了哪些数据,可能A分片存的数据ID 为0-1000,B分片存的是1001-2000。
如何解决呢?
ES提供了scroll_id 的搜索,每个文档都会存在一个唯一的 scroll_id,当搜索到一定数据范围后,如果需要继续向下搜索,直接根据 scroll_id 检索在此之后的数据即可,类似上拉加载;但有一个弊端是无法进行跳页,必须顺序加载。
3、mapping
mapping(映射)类似关系数据库中的schema definition(表结构),每个索引都有一个映射,主要定义了某个 index 中的文档字段、类型等。
3.1、字段类型
每个字段都有一个type:text,keyword,date,long, double,boolean、ip等。
并且支持json的层次嵌套,比如某个字段是个json结构,json内部又有N个字段。
3.2、mapping方式
mappin如何创建呢?分两种方式:动态mapping、显式mapping。
1、动态mapping:预先不需要定义,直接写入即可,ES会根据写入的字段自动创建。
2、显式mapping:预先定义好mapping,按照设置的字段写入数据、搜索数据。
但需要注意的是:无论哪种方式,mapping建好之后是不允许更改字段类型的。如果真的有诉求的话,可以尝试重建index、重建mapping、复制索引、使用别名 alias 到新索引上即可,之后就可以删除旧索引了。
4、index
index(索引)即为一组文档的集合,可以对比MySQL中的一个大表进行了分表,index值的是这个大集合,单个shard 为单个表。
5、shard
shard(分片)简单理解就是:文档越来越多,由于内存的限制、磁盘处理能力不足,已经不能及时的响应客户端的请求了,干脆就把这些数据分成多个块,每个块就是一个分片,每个分片都放到不同的节点上。每个分片又分为主分片、副分片。
5.1、主分片
每个文档都存储在一个主分片中,默认一个索引5个主分片,创建index时可调,创建完毕不可更改。不可更改的原因是这是一个基于hash函数的分配策略,如果创建之后再修改的话无异于将数据重新写入一遍,成本极高。
5.2、副分片
每个主分片可以拥有N个副分片,且后期可调。主要是为了避免单点、故障转移、提升查询效率。可以对于MySQL的从库。
5.3、分片数量合理化
那么问题来了,既然是数据拆分,我也不知道我的数据会有多大,并且创建之后也无法修改分片数量了,那么我应该拆多少个分片呢?干脆越多越好吧。。。(错错错!!!)
分片是底层是 Lucene 索引,每个分片都会消耗一定的CPU、内存、文件句柄等,分片数量并不是越多越好;如果恰巧一次搜索的数据在每个分片中都有,那么就会存在机器资源竞争了。可能会直接导致集群瘫痪。
既然也不是越多越好,那么我怎么知道到底应该设置多少个呢?
还是按照模型预估下数据的增速、查询范围。然后模拟一下生产环境的配置,简单进行一次压测吧,最好保证单次普通查询能在50ms左右完成。然后基于压测的结果考虑分片数量。
5.4、分片间数据如何update
首先请求会发送到主节点,主节点通过计算得出数据存储在哪个主分片中,然后将update请求发送给主分片,主分片在update之后将请求也发送给所有的副分片进行数据更新。

5.5、分片间数据如何查找
1、对于单文档的查找比较简单:
接收到客户端请求后,通过hash计算得出所在的分片及数据节点,将请求进行转发,数据节点进行数据查询后将结果返回协调节点,协调节点处理后返回客户端。

2、对于多文档的查找稍微有点麻烦:
因为一个搜索可能涉及保存在不同节点上的数据,此时由协调节点进行协调(收到客户端请求的节点即为协调节点)。
协调节点将请求转发到保存数据的数据节点上,然后每个节点本地执行之后都将结果返回给协调节点,协调节点再将所有的结果压缩为需要返回的结果集。所以ES也是多线程并发查询的。

6、node、cluster
一台服务器即为一个node(节点),多个 node 组成一个 cluster(集群)
6.1、主节点
集群一旦启动,所有节点之间会选取一个master节点,其余的为slave节点。
主节点的作用主要是负责创建索引、删除索引、分配分片、追踪集群中其它节点的工作状态等。
6.2、节点异常
如果集群中某个slave节点服务异常了,需要确认是否开启replicate,如果已开启的话,当前节点的数据肯定在其它节点还有一份。如果当前异常节点数据为主分片,则正常节点上的副分片会自动升级为主分片(此时会有一小段时间集群的status为yellow)。
如果集群中的mater节点异常,当前集群会再选举出一个主节点,但容易出现脑裂问题。
6.3、脑裂
举例来看:5台机器,ABC机器在M机房,DE机器在N机房,两个机房之前光纤被挖断。那么每个机房都会选举出一个独立的主节点,这时就有两个主节点了。当机房之间联系恢复之后,就可能出现数据冲突了。针对脑裂的问题也有预防的方案:
1、角色调整
调整参数设置为主节点只负责管理,不负责存储;禁用自动选举主节点功能。
2、选举参数调整
原配置监听3S后无响应自动选举新节点,增大参数为10S或者一个合理时间,依赖参数:discovery.zen.ping_timeout
副节点监听主节点无响应数量达到 (N+1)/2 个才进行选举。针对上例,达到3个后才进行选举。依赖参数:discovery.zen.minimum_master_nodes、discovery.zen.minimum_master_nodes。
(N+1)/2 是官方给出的一个推荐值。
6.4、扩容
数据越来越多,场景越来越复杂,搜索越来越频繁,集群快扛不住了怎么办?bingo,就是要扩容了。
垂直/纵向扩容:提升机器能力、存储能力。说白了就是换好的、贵的机器上。
水平/横向扩容:增加节点数量。加机器治百病。
假设我们有如下三个主分片:M0、M1、M2
1、ES单机,类似MySQL单机,容易出现单点。
2、扩容到两个节点:副本分片将会对应分布到新增的节点上。
3、扩容到三个节点:为了分散负载会进行重新分配,每个节点拥有2个分片,分配策略有点类似MySQL中的 RAID10,此处不做深入解释。
4、扩容到九个节点:副分片2份,分别为S0、S1、S2、P0、P1、P2,每个节点上存储一个分片。
7、DSL
DSL的查询相对复杂,我们会在另外一篇博客中进行详细介绍。暂时借鉴博客www.elastic.co/cn/blog/ela…
8、如何选择
说到NoSQL,redis、MongoDB也是呀,说到大数据存储,HBase也可以呀,那么如何取舍呢?借用一张其它博客的图:
