写点什么

elasticsearch 打怪升级之基础篇

用户头像
程征
关注
发布于: 2020 年 12 月 18 日
elasticsearch打怪升级之基础篇

ES-基础篇



前言



作为一名后端coder,mysql无疑是我们日常开发中最常用的数据库,作为一个款有着二十几年悠久历史的关系型数据库有着自己鲜明的特征,支持事务的ACID特性,支持表间join.这些特征使得mysql很适合事务类业务,例如订单业务.



然而如果我们换个场景,在复杂场景下的检索需求,mysql还适合这种业务吗?如果我们用mysql去实现复杂场景的搜索问题,那么就需要开发人员熟悉mysql的索引机制,

对于搜索字段建立二级索引,必要时候需要建立联合索引,了解最左匹配原则,但是即便是针对所有的检索字段建立了索引,同样也会面对各种性能问题,各种慢查询等等,还记得我们面试时候经常问的sql性能调优吗,需要了解各种索引建立的原则,更何况索引越多,也会影响到插入性能,如果稍微了解数据库原理的朋友都知道mysql的索引是基于B+树实现的,当数据量达到千万级别的时候,mysql的检索同样也会很慢,通常做法我们就要对数据库进行拆分,分库分表一些列操作,分库分表之后之前单库支持的join以及全局有序都不得不在业务中实现.真是应了那句话"查询容易,优化不易,且写且珍惜".



那么有没有一种新的数据存储模型,使得在这种复杂检索需求下同样也能保持稳定的搜索性能,同时支持横向扩展数据分片,实际上elasticsearch就是为了这种场景下应运而生的全文检索工具。



本文是es的基础篇,要向熟悉一个工具,最快的方式就是实践并使用它,所以本文主要立足于es的基础知识,使读者在阅读完本文之后能够直观的感受到es能做什么. 其中包括了es的核心概念,索引的设置,以及CURD操作,



基本概念



es是一个基于Lucene的全文搜索和分析系统,既然是一种数据搜索系统,那么肯定有自己的数据存储方式. 例如mysql将数据组织成一行行记录的形式.同样我们也需要了解es中数据逻辑上是如何存储的.

es中对数据存储有这几种概念: 索引(index),类型(type),文档(document),属性(field).



索引 index



es中数据在哪里存储呢?在mysql中如果我们要存储用户的注册信息,我们通常创建一个用户user表,这里的user表就是mysql的存储模结构,这里的user表就是mysql中数据容器,同样es中的数据存放在成为索引(index)的地方,在es中索引是具有相同业务特征的文档集合。使用索引名称唯一标识此索引,并且通过引用此名称完成对索引的增加、删除、更新以及查询操作。



类型 type



es中的type类型在es6.x之后已经不推荐使用了,在将来的版本中es会完全去掉type



文档 document



有了用户表信息,那么表中的每一行记录同样在es中也有相应的抽象,叫做document,至于为什么叫文档,我想这主要是由于es的每一个记录的数据模型决定的,在mysql中每一行记录是由每一列决定的,是有固定关系模式.而在es中每一行记录是一个json对象,一个  可以是一个字段或字段的名称,一个  可以是一个字符串,一个数字,一个布尔值, 另一个对象,一些数组值,或一些其它特殊类型诸如表示日期的字符串,或代表一个地理位置的对象,而且json对象中的域可以非常灵活,每个文档可以由不同的域组成,但是实际开发中我们尽量保证在同一个索引内部的文档尽量保持相同的域集. es中的json对象被序列化成 JSON 并存储到 Elasticsearch 中,指定了唯一 ID。



我们从数据模型上就可以看出es并非关系数据库,而是一款nosql数据库、同样具有nosql数据库的各种优点,包括比关系数据库更好的扩展性需求,包括支持超大数据集或超高写入吞吐量。能更好的地支持一些特定的查询操作。



{
"name": "John Smith",
"age": 42,
"confirmed": true,
"join_date": "2014-06-01",
"home": {
"lat": 51.5,
"lon": 0.1
},
"accounts": [
{
"type": "facebook",
"id": "johnsmith"
},
{
"type": "twitter",
"id": "johnsmith"
}
]
}




映射 mapping



Mapping 定义了文档中字段的存储类型、分词方式、是否存储等信息。当然这一步操作我们可以不做,也就是说我们可以不定义mapping信息,让es动态帮我们创建映射mapping信息,具体来说就是每次插入文档的时候,es动态的将不存在的字段信息存入到mapping之中。当我们创建索引的时候我们就会指定mapping信息。例如以下操作我们创建一个user的索引信息,并且明确的设置mapping信息。



PUT /user-index
{
"mappings": {
"properties": {
"age": { "type": "integer" },
"email": { "type": "keyword" },
"password": { "type": "keyword" },
"name": { "type": "text" }
}
}
}




同样我们给已经存在的mapping信息中添加一个字段映射信息。



PUT /user-index/_mapping
{
"properties": {
"location": {
"type": "keyword"
}
}
}




因为es是一个分布式的全文检索工具,所以es肯定为我们提供了数据分片和高可用机制,方便我们对数据做横向扩展,以及容错机制,所以我们接下来了解一下es集群中相关的基本概念。



节点



运行单个ES实例的主机称为节点。实际上是一个运行Java虚拟机的主机。他是集群中的一个成员,可以存储数据,参与集群索引以及搜索操作。节点在启动的时候通过配置文件识别到自己需要加入的集群。 es中节点根据职责可以分为以下节点类型;



  • Master node: 主节点。当一个节点配置node.master: true(默认)的时候,它有资格被选作为主节点,控制整个集群。

  • Data node: 数据节点。node.data: true(默认)。该节点保存数据和执行数据相关的操作,如增删改查,搜索,和聚合。

  • Ingest node: 提取节点。node.ingest: true(默认)。Ingest node可以通过ingest pipeline对文档执行预处理操作,以便在索引文档之前对文档进行转换或者增强。

  • Tribe node: 部落节点。当一个节点配置tribe.*的时候,它是一个特殊协调节点,它可以连接多个集群,在所有连接的集群上执行搜索和其他操作。 一个节点默认情况下可以同时是master、data和ingest节点。当集群增大时,最好将其分开配置。



分片和副本



作为一款分布式搜索引擎,数据分片机制肯定是少不了的。如果所有的数据都存储一个索引(index)之中,那么随着数据量的增加,就不能通过扩展机器来扩充存储容量。es通过某种维度将数据分别存储在不同的部分中,每一个部分就是一个分片.一个节点通常会管理多个分片.其实分区这个概念在不同的系统中有不同的称谓,例如在mongodb和es中叫做shard,在hbase中叫做region.同理为了保征数据的高可用,同一个分片可以有多个副本,多个副本通常在不同的机器中, 这样当部分机器出现故障的时候,系统仍然可以工作,从而提高可用性.同样多个副本还能共同承担es的查询压力,提高查询吞吐量。



注意es的数据物理存储是基于本地文件系统的。是一种将计算和存储耦合在一起的设计方案。这种方案的优点就是提供更大的查询能力,当shard提供的查询能力无法满足业务需求的时候,可以继续增加N个副本分片,这样查询能力就能提高N倍。但是这种架构同样存在一些问题就是因为存储和计算耦合在一起,会带来计算成功的浪费,因为当一个分片满足查询要求时候,其他分片的计算能力就是在浪费。而且当es出现热点数据需要动态扩大副本数时候,新的分片的数据完全需要从其他分片中拷贝。这样可能时间会很长。拖慢了扩容的效率。



CURD操作



我们会以一个例如演示如何在es中进行数据增删改查操作。



安装:



elastic-search和kibana的安装,kibana是elasticsearch的可视化工具,通过它可以很方便的实现对es的管理.这里我默认已经下载好了es和kibana,具体如何安装请参考官方文档(https://www.elastic.co/cn/downloads/kibana).



注意kibana和es的版本要一致.否则会导致kibana无法启动问题.



添加索引相当于mysql中建表,既然是建表那么一定要有表的一些元信息,mysql的建表元信息就是表中有些列,主键是什么,同理es也需要指定一些元信息,当然在es中你可以你什么都不指定,那么一切都用es默认的索引设置,这里我演示创建一个具有三个分区,1个副本,并且手动设置mapping信息的user索引.



PUT /user
{
"settings": {
"number_of_shards" : 3, //这里指定user索引一共有三个分区
"number_of_replicas" : 1 //这里指定每一个分区都有1个副本分区
},
"mappings": { //设置user索引的映射信息
"properties": {
"id" : {
"type": "long"
},
"name": {
"type": "keyword"
},
"password": {
"type": "keyword"
}
}
}
}




同样我们可以为映射新增域,这里给用户索引新增email域



PUT /user/_mapping/tweet
{
"properties" : {
"email" : {
"type" : "keyword"
}
}
}






我们也可以删除索引信息



delete /user




索引创建好了之后,我们就可以给索引新增数据了.



由前文一直每一个es文档都有自己的ID信息,我们可以自己制定文档的id,也可以让es帮我们生成,这两种不同的方式对应着两种不同的索引文档方式



使用自定义的文档ID,注意这里如果id为123的文档已经存在了,会覆盖老的文档。



PUT /user/_doc/123
{
"id": 123,
"name": "zhangsan",
"password": "zhangsan123"
}




使用自动生成的id创建文档,使用post的方式会重新创建一个新的文档,es内部为此文档生成唯一的ID信息。



POST /user/_doc
{
"id": 456,
"name": "lisi",
"password": "lisi123"
}




文档已经成功的插入文档之中,我们就可以取回一个文档了.



GET /user/_doc/123

{
"_index" : "user",
"_type" : "_doc",
"_id" : "123",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"id" : 123,
"name" : "zhangsan",
"password" : "zhangsan123"
}
}




接下来我们看看如何更新一个文档



PUT /user/_doc/123
{
"id": 123,
"name": "zhangsan1",
"password": "zhangsan123"
}




这里更新一个es文档我们同样使用put方法,这里提前先扩展一下,es中文档是不可以变更,修改的,如果我们要修改已有的文档我们只能用新的文档去替换老的文档,es在内部是将老的文档标记为删除,然后重新索引新的文档.详细插入过程我们后面会详解es的插入过程.



总结:



本文主要讲解了es的应用场景和es中核心概念以及数据是如何输入到es中并且取回一个文档,没有涉及到es中与搜索有关的知识。下一节我们将聚焦es中的搜索过程包括搜索原理以及查询dsl语句的编写。



用户头像

程征

关注

还未添加个人签名 2017.12.22 加入

还未添加个人简介

评论 (1 条评论)

发布
用户头像
good
2020 年 12 月 18 日 22:43
回复
没有更多了
elasticsearch打怪升级之基础篇