一、介绍
Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elasticsearch 并不仅仅是 Lucene 那么简单,它不仅包括了全文搜索功能,还可以进行以下工作:
分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
实时分析的分布式搜索引擎。
可以扩展到上百台服务器,处理PB级别(约1000个TB)的结构化或非结构化数据。
二、与关系数据库的对应关系
关系数据库 ⇒ 数据库 ⇒ 表 ⇒ 行 ⇒ 列(Columns)
Elasticsearch ⇒ 索引(Index) ⇒ 类型(type) ⇒ 文档(Docments) ⇒ 字段(Fields)
三、存储结构
索引 Index
ES将数据存储于一个或多个索引中,索引是具有类似特性的文档的集合。
它是个逻辑命名空间,类似于数据库概念中的数据库。
类型 Type
类型是索引内部的逻辑分区(category/partition),然而其意义完全取决于用户需求。因此,一个索引内部可定义一个或多个类型(type)。
类似于数据库概念中的一张表。
文档 Document
文档是索引和搜索的原子单位,它是包含了一个或多个域(Field)的容器,基于JSON格式进行表示。文档由一个或多个域组成,每个域拥有一个名字及一个或多个值,有多个值的域通常称为“多值域”。
每个文档可以存储不同的域集,但同一类型下的文档至应该有某种程度上的相似之处。
它是具体事实数据的体现,类似于数据库概念中的一条记录。
SHARD 分片
index包含多个shard,每个shard都是一个最小工作单元,承载部分数据
每个shard都是一个lucene实例,有完整的建立索引和处理请求的能力。
增减节点时,shard会自动在nodes之间负载均衡
shard分为primary shard和replica shard,每个document只存在于某一个primary shard以及其对应的replica shard中
replica shard是primary shard的副本,负责容错,以及承担读请求负载提高搜索性能。
primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改,primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard
primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上
SEGMENT
elasticsearch中每个shard每隔1秒都会refresh一次,每次refresh都会生成一个新的segment,一个segment对应着硬盘或者缓存(内存充当文件系统的)的一个文件,占用一个文件描述符。
translog
事务日志,在每一次对 Elasticsearch 进行操作时均进行了日志记录。作为临时文件存储在硬盘上。理想中应该是任何一次写入都刷入磁盘,但是性能考虑不可能。实际上是每几秒中调用一次fsync刷到磁盘来提高吞吐量。这当然带来了丢失数据的可能。
四、数据写入过程
第一步,新文档被写入内存,操作被写入translog
第二步,refresh操作
ES中默认每1秒中进行一次refresh。
refresh操作会清空buffer中的数据(translog数据不变),在这1秒时间内写入内存的新文档都会被写入一个文件系统缓存(filesystem cache)中,并构成一个分段(segment)新建segment并式segment可以被检索的到,但是尚未写入硬盘。
第三步,不断累积数据,重复数据写到buffer和translog,每隔一秒将生成一个新的segment,而translog文件将越来越大。
第四步,一定时间后,执行flush操作。清空buffer,translog,所有segment处于commit状态。
第五步 segment合并写入磁盘,一旦合并,旧的segment和translog将被删除。
segment合并极为消耗资源,所以一般情况下ES会对段合并消耗的资源加以限制
五、索引
Elasticsearch最关键的就是提供强大的索引能力,一切设计都是为了提高搜索性能为了提高读,难免牺牲写性能。
前面看到往Elasticsearch里插入一条记录,其实就是直接PUT一个json的对象,这个对象有多个fields,插入这些数据到Elasticsearch的同时,Elasticsearch还默默1的为这些字段建立索引--倒排索引,因为Elasticsearch最核心功能是搜索。
为啥Elasticsearch使用的倒排索引比关系型数据库的B-Tree索引快?
复习一下mysql索引:
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。
数据库查询是数据库的主要功能之一,最基本的查询算法是顺序查找(linear search)时间复杂度为O(n),显然在数据量很大时效率很低。优化的查找算法如二分查找(binary search)、二叉树查找(binary tree search)等,虽然查找效率提高了。但是各自对检索的数据都有要求:二分查找要求被检索数据有序,而二叉树查找只能应用于二叉查找树上,但是数据本身的组织结构不可能完全满足各种数据结构(例如,理论上不可能同时将两列都按顺序进行组织)。所以,在数据之外,数据库系统还维护着满足特定查找算法的数据结构。这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构就是索引。
什么是B-Tree索引?
上大学读书时老师教过我们,二叉树查找效率是logN,同时插入新的节点不必移动全部节点,所以用树型结构存储索引,能同时兼顾插入和查询的性能。因此在这个基础上,再结合磁盘的读取特性(顺序读/随机读),传统关系型数据库采用了B-Tree/B+Tree这样的数据结构:
为了提高查询的效率,减少磁盘寻道次数,将多个值作为一个数组通过连续区间存放,一次寻道读取多个数据,同时也降低树的高度。
描述B-Tree:
首先定义一条数据记录为一个二元组[key, data],key为记录的键值,对于不同数据记录,key是互不相同的;data为数据记录除key外的数据。那么B-Tree是满足下列条件的数据结构:
1.d为大于1的一个正整数,称为B-Tree的度;
2.h为B-Tree的高;
3.每个非叶子结点由n-1个key和n个指针组成,其中d<=n<=2d;
4.每个叶子结点至少包含一个key和两个指针,最多包含2d-1个key和2d个指针,叶结点的指针均为NULL;
5.所有叶结点都在同一层,深度等于树高h;
6.key和指针相互间隔,结点两端是指针;
7.一个结点中的key从左至右非递减排列;
8.如果某个指针在结点node最左边且不为null,则其指向结点的所有key小于v(key1),其中v(key1)为node的第一个key的值。
9.如果某个指针在结点node最右边且不为null,则其指向结点的所有key大于v(keym),其中v(keym)为node的最后一个key的值。
10.如果某个指针在结点node的左右相邻key分别是keyi和keyi+1且不为null,则其指向结点的所有key小于v(keyi+1)且大于v(keyi)。
检索,首先从根节点进行二分查找,如果找到则返回对应节点的data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到null指针,前者查找成功,后者查找失败
BTree_Search(node, key) {
if (node == null) return null;
foreach(node.key) {
if (node.key[i] == key) return node.data[i];
if (node.key[i] > key) return BTree_Search(point[i]->node);
} return BTree_Search(point[i + 1]->node);
}
data =BTree_Search(root, my_key);
关于B-Tree有一系列有趣的性质,例如一个度为d的B-Tree,设其索引N个key,则其树高h的上限为logd((N+1)/2),检索一个key,其查找结点个数的渐进复杂度为O(logdN)。从这点可以看出,B-Tree是一个非常有效率的索引数据结构。
B+Tree有以下不同点:
1.每个结点的指针上限为2d而不是2d+1。
2.内结点不存储data,只存储key;叶子结点不存储指针
优化:带有顺序访问指针的B+Tree
磁盘读取
磁盘示意图:
文件形式存储在磁盘上,索引检索需要磁盘I/O操作。与主存不同,磁盘I/O存在机械运动耗费,因此磁盘I/O的时间消耗是巨大的
同心环叫做磁道,半径划分一段叫扇区,每个扇区是磁盘的最小存储单元
确定要读的数据在哪个磁道,哪个扇区。需要将磁头放到这个扇区上方,为了实现这一点,磁头需要移动对准相应磁道,这个过程叫做寻道,所耗费时间叫做寻道时间,然后磁盘旋转将目标扇区旋转到磁头下,这个过程耗费的时间叫做旋转时间
磁盘预读,局部性原理
为了提高效率,要尽量减少磁盘I/O。想要达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存,既局部性原理
当一个数据被用到时,其附近的数据也通常会马上被使用。
程序运行期间所需要的数据通常比较集中。
预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。
从使用磁盘I/O次数评价索引结构的优劣性:根据B-Tree的定义,可知检索一次最多需要访问h个结点。数据库系统的设计者巧妙的利用了磁盘预读原理,将一个结点的大小设为等于一个页面,这样每个结点只需要一次I/O就可以完全载入。为了达到这个目的,在实际实现B-Tree还需要使用如下技巧:
每次新建结点时,直接申请一个页面的空间,这样可以保证一个结点的大小等于一个页面,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。
二叉查找树红黑树为啥不行?太高了,物理地址离太远,没法预读。
es索引对应表:
| ID | Name | Age | Sex |
| -- |:------------:| -----:| -----:|
| 1 | Kate | 24 | Female
| 2 | John | 24 | Male
| 3 | Bill | 29 | Male
ID是Elasticsearch自建的文档id,那么Elasticsearch建立的索引如下:
Name:
| Term | Posting List |
| -- |:----:|
| Kate | 1 |
| John | 2 |
| Bill | 3 |
Age:
| Term | Posting List |
| -- |:----:|
| 24 | [1,2] |
| 29 | 3 |
Sex:
| Term | Posting List |
| -- |:----:|
| Female | 1 |
| Male | [2,3] |
倒排索引:
Elasticsearch分别为每个field都建立了一个倒排索引,Kate, John, 24, Female这些叫term,而[1,2]就是Posting List。Posting list就是一个int的数组,存储了所有符合某个term的文档id。
看到这里,不要认为就结束了,精彩的部分才刚开始...
通过posting list这种索引方式似乎可以很快进行查找,如果这里有上千万的记录呢?如果是想通过name来查找呢?
Term Dictionary
Elasticsearch为了能快速找到某个term,将所有的term排个序,二分法查找term,logN的查找效率,就像通过字典查找一样,这就是Term Dictionary。现在再看起来,似乎和传统数据库通过B-Tree的方式类似啊,为什么说比B-Tree的查询快呢?
Term Index
B-Tree通过减少磁盘寻道次数来提高查询性能,Elasticsearch也是采用同样的思路,直接通过内存查找term,不读磁盘,但是如果term太多,term dictionary也会很大,放内存不现实,于是有了Term Index,就像字典里的索引页一样,A开头的有哪些term,分别在哪页,可以理解term index是一颗树:
这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。
所以term index不需要存下所有的term,而仅仅是他们的一些前缀与Term Dictionary的block之间的映射关系,再结合FST(Finite State Transducers)的压缩技术,可以使term index缓存到内存中。从term index查到对应的term dictionary的block位置之后,再去磁盘上找term,大大减少了磁盘随机读的次数。
六、压缩
1.压缩term index
FST(Finite State Transducers)
FSTs are finite-state machines that map a term (byte sequence) to an arbitrary output.
FST以字节的方式存储所有的term,这种压缩方式可以有效的缩减存储空间,使得term index足以放进内存,但这种方式也会导致查找时需要更多的CPU资源
2.压缩 Posting List
Frame Of Reference
增量编码压缩,将大数变小数,按字节存储
问题:
1.落数据,主分片,复制分片
交互过程:https://www.cnblogs.com/niutao/p/10909071.html
在ES5.0以前,按照ES的主副同步策略,主分片本地索引完成后,会将请求forward到副本分片,默认情况下超过半数副本回复后,主分片才响应client端操作成功。
在5.0以后,这个参数已经被wait_for_active_shards参数(默认值1)取代,即默认情况下只要主分片就可以完成写操作,否则阻塞至超时或者满足条件。
在主完成本地索引后,会forward所有 被称作in-sync copies 的列表里的分片,直到列表所有分片响应成功,才响应client。如果部分失败,则会通知master,将故障分片从in-sync copies 列表里剔除,主分片响应client,与此同时,master会分配一个新的分片来接替故障的分片。
in-sync copies 的内容可以通过API
ET /_cluster/state?filter_path=metadata.indices.your-indexname.in_sync_allocations.*,routing_table.indices.your-indexname.*
2.translog 文件提交
过程:https://blog.csdn.net/lijingjingchn/article/details/107673900
Translog 设置:https://www.jianshu.com/p/ffef3be04fd3
官方:https://www.elastic.co/guide/en/elasticsearch/reference/7.2/index-modules-translog.html
3,term index,term Dictionary 怎么创建的
数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。数据写入 segment file 之后,同时就建立好了倒排索引。