一、Elasticsearch是什么?
Elasticsearch是一个基于文档的NoSQL数据库,是一个分布式
、RESTful
风格的搜索和数据分析引擎,同时也是Elastic Stack
的核心,集中存储数据。Elasticsearch、Logstash、Kibana经常被用作日志分析系统,俗称ELK。
说白了,就是一个数据库,搜索贼快(但是插入更新较慢,要不然其他数据库别玩了)。速度快,还可以进行分词,非常适合做搜索
,例如商城的商品搜索。为什么快,后面讲原理的时候会说,不单单是缓存的问题,原理非常精彩。而且它是nosql的,数据格式可以随便造。Elasticsearch还为我们提供了丰富的RESTful风格的API,写代码的成本极低。最后它支持分布式,高性能(搜索快),高可用(某些节点宕机可以接着用),可伸缩(可以方便的增加节点,解决物理内存上线问题),适合分布式系统开发。
优势:
-
分布式的实时文件存储,每个字段都被索引并可被搜索
-
实时分析的分布式搜索引擎
-
横向可扩展:支持上百台服务节点的扩展,集群增加机器简单,支持处理PB级数据
-
分片机制:
允许水平分割扩展数据,允许分片和并行操作从而提高性能和吞吐量
提供高性能:同一个索引可以分多个主分片(
primary shard
),每个主分片拥有自己的副本分片(replica shard
),每个副本分片都可以提供服务,提升系统搜索请求的吞吐量和性能提供高可用性:同一个索引可以分多个主分片,每个主分片拥有零个或者多个副本,如果主分片挂了,可以从副本分片中选择一个作为主分片继续提供服务
-
隐藏复杂实现:Elasticsearch 内部隐藏了分布式系统的复杂性,我们不用去关心它是如何做到高可用,可扩展,高性能的
-
易用开源:不需要额外配置,就可以运行一个Elasticsearch服务,开源
二、基本概念
为了快速了解Elasticsearch(后面可能会简称为ES),可以与mysql几个概念做个对比。
Elasticsearch | Mysql |
---|---|
字段(Filed) | 属性(列) |
文档(Document) | 记录(行) |
类型(Type) | 表 |
索引(Index) | 数据库 |
1、Cluster
:集群
一个集群包含多个节点,对外提供服务,每个节点属于哪个集群通过配置文件中的集群名称决定
2、Node
:节点
集群中的一个节点,每个节点也有一个名称,默认是随机分配,也可以自己指定,在es集群中通过节点名称进行管理和通信。
由于生产环境下ES基本都是集群部署的,所以一定少不了节点
的概念,一个节点就是一个ES实例,就是一个Java进程,这些Java进程部署在不同的服务器上,增加ES可用性。
ES节点根据功能可以分为三种:
- 主节点:职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。每个节点都可访问集群的状态,但是只有主节点可以修改集群的状态。
- 数据节点:数据节点主要是储存数据的节点,对文档进行增删改查,聚合操作等等,数据节点对cpu,内存,io要求较高,当资源不够的时候,可以增加新的节点,很方便的进行数据拓展。
- 客户端节点:本节点主要处理路由请求,分发索引的操作。实际上主节点和数据节点也有路由转发的功能,但是为了提高效率,还是建议生产环境单独创建客户端节点。
分片类似于mysql中的分表,在一个索引拆分成几个小索引,分布在不同的节点(不同服务器)上,每个小索引都具有完备的功能,当客户端发来请求的时候,客户端节点找到合适的分片上的小索引,进行数据查询,这一过程对于用户来说都是透明的,用户表面上看只是在操作一个索引。利用分片,可以避免单个节点的物理限制,还可以增加吞吐量。建议最开始一个索引要用多少分片设计好,因为修改分片数量是个相当麻烦的过程。
作为分布式的数据库,ES必须为咱们提供数据冗余功能,这就是分片副本,就是将某个分片copy一份放到其他节点上。注意,这里分片和分片副本 **必须在不同的节点上!** 分片副本也可以提高吞吐量。分片副本不同于分片,可以很方便的进行修改。
说完了所有概念,再去看上面的图,有一个索引,分了3分片在三个节点上,并且每个分片在不同的节点上有分片副本。
3、Index
:索引
索引是具有相同结构的文档集合,作用相当于mysql中的库。
ES中的索引类似于mysql中的数据库,我觉得未来索引有成为mysql中表概念的潜质。
我们把相同特征(Filed数量和类型基本相同)的文档放到同一个索引(index)里面。这样方便提前通过mapping来规定各个Filed的类型。另外,索引名称必须全部小写,所以不建议写成驼峰式。
4、Type
:类型
一个索引可以对应一个或者多个类型,类型可以当做是索引的逻辑分区,作用相当于mysql中的表。
关于Type
,类型概念,在6.x版本中,一个索引(Index)可以拥有多个Type
。在7.x版本(目前最新版本),一个索引只能拥有一个Type
,默认的type就是_doc
,在7.x版本中,已经建议删除了。在未来的8.x版本会彻底删除。但是在7.x版本中,一个文档还必须归属于一个类型。
5、Document
:文档
存储在es中的一个JSON
格式的字符串,每一个文档有一个文档ID,如果没有自己指定ID,系统会自动生成一个ID,文档的index/type/id必须是唯一的,作用相当于mysql中的行。
ES中的一个对象将来会和Java代码中的一个对象对应。文档的每一个Filed
可以是任意类型,但是一旦某索引(Index)(
我们描述的时候,略过Type
,但是Type
依然存在)
中插入了一个文档,某Filed
被第一次使用,ES就会设置好此Filed
的类型。
例如:你插入user的name是字符串类型,以后再插入文档,name字段必须是字符串类型。所以,建议在插入文档之前,先设置好每个Filed
的类型。
如果插入文档的时候,不指定id,ES会帮助我们自动生成一个id,建议id是数字类型,这样搜索会快速很多。商城系统中的商品id建议使用雪花算法生成,这样既避免了自增id的安全性问题,又解决了字符串id检索慢的问题。
6、field
:字段
一个文档会包含多个字段,每个字段都对应一个字段类型,类似于mysql中的列。
7、shard
:分片
es中分为primary shard
主分片和replica shard
副本分片。
主分片:当存一个文档的时候会先存储在主分片中,然后复制到不同的副本分片中,默认一个索引会有5个主分片,当然可以自己指定分片数量,当分片一旦建立,分片数量不能改变副本分片:每一个主分片会有零个或者多个副本,副本主要是主分片的复制,通过副本分片可以提供高可用性,当一个主分片挂了,可以从副本分片中选择一个作为主分片,还可以提高性能,所以主分片不能和副本分片部署在相同的节点上。
8、replica
:复制
复制是为了防止单点问题,可以做到对故障进行转移,保证系统的高可用。
9、映射
描述数据在每个字段内如何存储,是定义存储和索引的文档类型及字段的过程,索引中的每一个文档都有一个类型,每种类型都有它自己的映射,一个映射定义了文档结构内每个字段的数据类型。
使用GET /index/_mapping/type
获取对应的/index/type
的映射信息
三、Elasticsearch索引原理
首先,我们知道mysql底层数据结构使用的是B+Tree
,这种BTree
,将搜索时间复杂度变成了logN,已经很快了,我们Elasticsearch要比它还快。Elasticsearch是怎么做的呢?首先储存结构要优化,然后再提高下和磁盘的交互效率。
先说Elasticsearch索引结构,叫做倒排索引
,啥是倒排索引呢?它的大概逻辑如下:
为了讲清楚这个概念,我们先看个例子,如下为我们user的数据:
ID | Name | Age |
---|---|---|
1 | Kate | 24 |
2 | John | 24 |
3 | Bill | 29 |
4 | Kate | 26 |
5 | Brand | 29 |
Elasticsearch会为以上数据建立两个索引树:
Term | Posting List |
---|---|
Kate | 1,4 |
Brand | 5 |
John | 2 |
Bill | 3 |
Term | Posting List |
---|---|
24 | 1,2 |
26 | 4 |
29 | 3,5 |
以上的索引树就叫做倒排索引,每个Filed
字段对应着一组Term
,每个Term
后面跟着的id(这个主键用户不指定就会自动生成,所以一定存在)就是Posting List
,它是一组id,有了id再去磁盘中对应的文档就so fast了。
你有没有发现,Term
如果按序找会快点,将Term
按序排,在进行二分查找,是不是速度就跟BTree
一样了,时间复杂度为LogN。这个有序的Term
组就是Term Dictionary
。
那么问题又来了,比如:数据库中有name前缀为A的同学1000万个,前缀为Z的同学有3个,我要查前缀为Z的同学,那二分查找不也很多次吗,所以,Elasticsearch把每个开头的地方标记一下,拿出来,再放到一颗树里,速度不是就快了嘛,这棵树就是Term Index
。Term Index
前缀不一定是第一个字符,比如A、Ab、Abz,这种都可以在Term Index
树里。并且Term Dictionary
可能会太大,会被放到磁盘中,避免内存占用太多。
由于Term Index
被放到内存中,所以最好压缩一下,减少内存使用,压缩使用的是FST
,这个东西讲起来比较复杂,反正就是能压缩,内存变小就好了。
Term
压缩完了,那么Posting List
是不是也可以压缩一下,省省空间啊?既然都是id,使用过redis的同学瞬间会想到bitMap
,就是有个巨大的数组,储存着0或1,有就是1,没有就是0。例如上面的3、5放在BitMap中就是 1,0,1,0,0,0。虽说空间已经明显小多了,但是如果一个Posting List
只储存着1,10000001这两个id,最后产生的数字是不是过大呢。于是乎,Roaring bitmaps
就出来了,进行了一次指数降级,简单点说就是取商和余数储存,被除数是65535。
例如:1000,62101,131385,196658
, 这几个id,首先分组,分组规则就是商一样,例如上面id可分组为[(0,1000),(0,62101)],[],[(2,6915)],[(3,53)]。注意,没有商为1的值,我用空数组表示。此时,将某个组中的数字放到一个bitmap中。