Learn how to get started with ElasticSearch in Grain Mall

1. Introduction to ElasticSearch

1.1 Basic introduction of ES

1.1.1 Why ES

For full-text retrieval, there are many tools that can be used, such as Apache Lucene, which is a full-text search engine library, but when using Apache Lucene, its complexity far exceeds the knowledge of a normal programmer. In order to solve this problem, based on Apache Lucene, ElasticSearch was written in Java language, which hides the underlying complexity of Apache Lucene and develops a set of simple RESTful API.

1.1.2 Basic concepts

① Index index

index has two meanings in elasticsearch, one is as new data, and the other is as an index.

②Type type

In an index index, one or more types can be defined.

③ Document document

A piece of data of a certain type stored under a certain index, which is stored in JSON format. The above three concepts can be compared with MySQL:

ES concept MySQL concept
index database
type table
document Record

1.1.3 MySQL can be searched, why use ES

For MySQL search, the underlying layer can use equal for full-value search or like for fuzzy search, but this operation is relatively slow. It will judge whether each piece of data matches one by one, and if it matches, it will be used as the search result. In the process of inserting data, MySQL will insert one piece of data one piece of data in order, even if some parts of several pieces of data have similar values, they will be discharged in order. For ES, the bottom layer will maintain an inverted index table when inserting. In this inverted index table, there will be two fields, one field is the corresponding word, and the other field is all records containing the word. And these words, when inserting data, will split the data, split it into several words and record them in this inverted index table. Example: If the data we want to insert now is as follows:

1-红海行动
2-探索红海行动
3-红海特别行动
4-红海记录篇
5-特工红海特别探索

那么,ES会将每条记录进行拆分,如红海行动,可以将其拆分为红海行动,也可以将其拆分为四个词,然后将其存入倒排索引表中,这里我们使用第一种方式。

记录
红海 1
行动 1

上述的表格是在插入第一条数据之后所形成的倒排索引表 接着我们插入第二条数据,会将其拆分为探索红海行动三个词 于是此时的倒排索引表如下

记录
红海 1,2
行动 1,2
探索 2

后面的三条数据也是按照这样的操作逐一划分并存入倒排索引表中

记录
红海 1,2,3,4,5
行动 1,2,3
探索 2,5
特别 3,5
记录篇 4
特工 5

这就是最终生成的倒排索引表,而假如我们要进行查找时,假设我们要查找 红海特别行动 那么,同样的,在查找时,也会将这个词拆分为多个词,拿着拆分出来的词去倒排索引表中去查找,在倒排索引表如果有某条数据的词等于拆分出来的一个或多个词,那么就将该词对应的记录纳入结果集中。 此时将 红海特别行动拆分为三个词,红海特别行动。 于是乎,就去查找红海,发现有该词,且记录为1,2,3,4,5,于是这5条记录就作为结果,接着去查找特别,发现也有,记录为3,5,此时发现结果集中有这些于是不重复添加,同样的对于行动也是,因此,最终找到的结果集为1,2,3,4,5。然后将这些数据进行显示。

1.2 ES的安装以及配置启动

1.2.1 ES的安装

docker pull elasticsearch:7.4.2

1.2.2 ES的配置及启动

创建出两个文件用于挂载ES内部的文件内容

mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data

创建出对应的配置文件,并在配置文件中添加信息

echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

镜像的运行以及相应文件的挂载

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e  "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx128m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v  /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2 

修改对应的文件让所有人都可以访问ES

chmod -R 777 /mydata/elasticsearch

启动ES

docker start elas

可以设置开启虚拟机时默认启动ES

docker update elasticsearch --restart=always

最终在浏览器中输入对应的虚拟机地址以及端口号,如192.168.78.10:9200即可获取如下的界面,这样子就说明ES已经安装成功

{
  "name" : "72ed6c8d6e0b",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "_q-EIRuaQbOvJIfFkgbcfg",
  "version" : {
    "number" : "7.4.2",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "2f90bbf7b93631e52bafb59b3b049cb44ec25e96",
    "build_date" : "2019-10-28T20:40:44.881551Z",
    "build_snapshot" : false,
    "lucene_version" : "8.2.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

2、Kibana介绍

2.1 Kibana介绍

kibana是一个可视化界面,可以用于查看ES中的数据 kibana和ES的关系类似于Mysql和Sqlyog的关系,一个用于保存数据,一个用于使用图形化的界面查看当前保存的所有数据内容

2.2 Kibana的安装

kibana的版本要与ES对应,如,这里我们使用的ES版本是7.4.2,那么我们的Kibana的版本也要7.4.2才可以。

docker pull kibana:7.4.2

接着启动我们的kibana

docker run --name kibana \
-e ELASTICSEARCH_HOSTS=http://ip:9200 \
-p 5601:5601 \
-d kibana:7.4.2

注意,前面的http://ip需要填写自己虚拟机的ip地址 我们可以在本地主机中测试一下当前是否安装成功,在浏览器中输入ip:5601,如果能弹出如下的界面即说明已经成功安装了

img165.png

2.3 Kibana的相关操作

2.3.1 查询节点的状态_cat

① 查看所有节点的状态

http://{ip}:9200/_cat/nodes

127.0.0.1 55 99 9 0.58 0.38 0.30 dilm * 72ed6c8d6e0b
② 查询ES的健康状态

http://{ip}:9200/_cat/health

1687004907 12:28:27 elasticsearch green 1 1 4 4 0 0 1 0 - 80.0%
③ 查看主节点的状态

http://{ip}:9200/_cat/master

HzW2sAa0QTO9Vt1VZTuuFg 127.0.0.1 127.0.0.1 72ed6c8d6e0b
④ 查看所有索引的状态

http://{ip}:9200/_cat/`indices`

green open .kibana_task_manager_1   1X5FwZ7zT1ODNuOo7c96tg 1 0 2 0 38.2kb 38.2kb
green open .apm-agent-configuration PUnTh1VuQSKEQ3h37K4XOA 1 0 0 0   283b   283b
green open .kibana_1                yoL51QcrQO-xtZ51WqsOfA 1 0 7 0 25.2kb 25.2kb

2.3.2 新增文档

下面的操作使用postman软件进行模拟,后续再转为kibana自带的

① put请求

第一次发送put请求时http:192.168.78.10/customer/external/1 附上的json数据为

{
	"name": "John Doe"
}

返回的数据如下

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

接着我们再发送一次当前请求

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 2,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 1,
    "_primary_term": 1
}

可以发现,对于_id而言,其值没有发生变化,这个id是唯一标识这条数据的。 而我们可以观察_version和result两个字段的值,第一次的时候是1和created,第二次的时候就变成了2和updated。说明_version字段是动态更新的,每次修改该条数据的值都会变动一次,而如果本身有这条数据,那么就会是修改的状态,如果本身没有这条数据,那么就会是新增的状态。

② post请求

第一次发送post请求时http:192.168.78.10/customer/external 附上的json数据为

{
	"name": "John Doe"
}

返回的数据如下:

{
    "_index": "customer",
    "_type": "external",
    "_id": "VrVZyYgBfKt3Fbn5XnLP",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 2,
    "_primary_term": 1
}

而我们再次发送同样的请求时

{
    "_index": "customer",
    "_type": "external",
    "_id": "V7VayYgBfKt3Fbn5WHLl",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 3,
    "_primary_term": 1
}

可以发现,这两次返回的数据,从id上来看就不一样了。

2.3.3 查询文档

get请求用于查询文档,发送的请求路径与put的请求路径是一样的 返回的数据如下

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 2,
    "_seq_no": 1,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "name": "John Doe"
    }
}

2.3.4 更新文档

① post带_update

http://{ip}:9200/customer/external/1/_update 请求体所携带的数据为:

{
    "doc": {
        "name": "John"
    }
}

返回的数据为:

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 3,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 4,
    "_primary_term": 1
}

第二次发送同样请求且请求体中携带相同数据的时候,返回的数据为:

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 5,
    "result": "noop",
    "_shards": {
        "total": 0,
        "successful": 0,
        "failed": 0
    },
    "_seq_no": 6,
    "_primary_term": 1
}

可以看到,对比上述两次操作,第一次操作的结果为updated,第二次操作的结果为noop,也就是说,当发送数据要去更新文档时,会先去检测所携带的数据是否相同,如果相同的话,则不操作既noop,如果不同的话才会去进行更新操作即updated。

② post不带_update

http://{ip}:9200/customer/external/1 请求体所携带的数据为:

{
    "name": "John"
}

返回的数据为:

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 5,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 6,
    "_primary_term": 1
}

这里的post即是前面新增文档时的post

③ put

这里同样也是

④ put和post请求的对比
Ⅰ、区别一:请求路径的不同

对于put和post请求的区别在于,put请求时需要携带当前是第几条数据,而post则不携带当前是第几条数据 如

put: http:192.168.78.10/customer/external/1
post: http:192.168.78.10/customer/external
Ⅱ、区别二:新建还是修改

put请求会先去判断当前所带参数的数据是否存在,如果存在,那么会进行修改操作,如果不存则新增该条数据 而post请求则是不管有没有都是新增操作

Ⅲ、区别三:新建数据的id

从前面的例子可以看到,如果是put请求的话,最终生成的数据id会是携带参数的值 而如果是post请求的话,最终生成的数据id会是一个随机数

Ⅳ、更新范围

post请求是会将对应添加的数据进行修改 当前数据

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 13,
    "_seq_no": 14,
    "_primary_term": 1,
    "found": true,
    "_source": {
		"name": "John",
        "age": 20
    }
}

post请求携带了_update参数时,此时携带的请求体的数据如下:

{
    "doc": {
        "age": 22
    }
}

此时重新查询文档

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 13,
    "_seq_no": 14,
    "_primary_term": 1,
    "found": true,
    "_source": {
		"name": "John",
        "age": 22
    }
}

如果是携带了_update请求参数,那么,只会修改请求参数中不同的值,对于请求参数中没有携带的字段的,是不进行操作的。 post请求不携带_update参数时,此时重新更新,请求体携带的数据如下:

{
    "age": 20
}

此时重新查询文档

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 13,
    "_seq_no": 14,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "age": 20
    }
}

可以发现,此时的name字段已经消失了,只有age这一个字段。 此时恢复数据,我们来测试一下put

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 13,
    "_seq_no": 14,
    "_primary_term": 1,
    "found": true,
    "_source": {
		"name": "John",
        "age": 22
    }
}

put请求体携带的数据为:

{
    "age": 20
}

此时重新查询,其结果为

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 18,
    "_seq_no": 19,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "age": 20
    }
}

可以发现,此时的put与之前的post不带_update参数时是一样的。 所以,put和不带_update参数的post一样都是全量更新,其底层会先把这条数据删掉,然后重新添加一条新的数据。 而带了_update参数的post是增量更新,底层是会对请求体的参数中所携带的同名的字段的值进行修改,再添加原本数据中没有的字段。

2.3.5 删除文档

前面我们都是使用的postman软件用来执行的这些操作,接下来我们使用Kibana自带的工具来使用。 Kibana自带的工具如下图所示打开

img166.png 点击后可以进入如下的界面:

img167.png 整个界面分为两部分,左边为要执行的请求,右边为执行的结果。 每个请求都会有一个播放按钮,它会执行当前的请求,会以每个action作为分割,不会去执行其他的action

① 删除单个文档

使用DELETE /customer/external/1

{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "_version" : 19,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 20,
  "_primary_term" : 1
}

此时删除再次查询时返回的数据信息为:

{
  "_index" : "customer",
  "_type" : "external",
  "_id" : "1",
  "found" : false
}

可以发现,found字段的值为false,说明当前并没有找到id为1的文档。

② 删除整个索引

使用DELETE /customer 此时返回的数据为:

{
  "acknowledged" : true
}

此时重新查找,会报如下的错误信息

{
  "error" : {
    "root_cause" : [
      {
        "type" : "index_not_found_exception",
        "reason" : "no such index [customer]",
        "resource.type" : "index_expression",
        "resource.id" : "customer",
        "index_uuid" : "_na_",
        "index" : "customer"
      }
    ],
    "type" : "index_not_found_exception",
    "reason" : "no such index [customer]",
    "resource.type" : "index_expression",
    "resource.id" : "customer",
    "index_uuid" : "_na_",
    "index" : "customer"
  },
  "status" : 404
}

此时的状态为404,说明未找到当前的索引 我们可以删除单条文档和整个索引,但是不能删除中间那个

2.3.6 批量Api

批量操作时,不同于mysql数据库中的某条执行出错全部回滚的操作,这里每一条操作的运行是独立的,彼此互不干扰,上一条数据的结果不会影响下一条数据的执行。 批量api可以进行的操作是:indexcreatedeleteupdate 其中index和create是添加操作,可以添加文档,需要在后面添加一些参数 delete是删除操作,可以删除一个文档,不要求在后面添加 update是修改操作,可以修改一个文档,要求在下一行中指定部分 doc、更新插入(upsert)、脚本及其选项。

其语法格式为:

{"action": {metadata}}
{requestbody }
{"action": {metadata}}
{requestbody }

其中的action就是对应于前面四种操作,metadata就是需要进行此操作的某个索引下的某个类型下的某个文档,都需要唯一标识。 而下面的requestbody就是我们要使用到的数据。 如前面我们使用的是Postman用来执行的一些操作中,我们在请求体中添加的数据,就是这里的requestbody。 下面使用两个案例来体验一下使用批量Api进行操作

① 批量添加数据

我们执行的语句为:

POST /customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"Jane Doe"}

此时返回的数据为:

{
  // 执行本次操作所花费的时间,单位是ms
  "took" : 606, 
   // 有无出错,这里值为false,说明全部都成功了,如果有一个没成功则为true
  "errors" : false, 
  // 每个操作所执行的结果
  "items" : [
    {
     // 执行的是index操作
      "index" : {
        // 操作的索引名
        "_index" : "customer",
        // 操作的类型名
        "_type" : "external",
        // 操作的文档id
        "_id" : "1",
        // 该文档对应的版本号,默认值为1,每次对其进行修改时都会加1
        "_version" : 1,
        // 本次操作执行的结果是created表明为新建,如果是修改则为updated
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        // 并发控制字段,每次更新就会加1,用来做乐观锁
        "_seq_no" : 0,
        // 同上,主分片重新分配,如重启就会变化
        "_primary_term" : 1,
        // 当前执行的结果的状态码
        "status" : 201
      }
    },
    {
      "index" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "2",
        "_version" : 1,
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 1,
        "_primary_term" : 1,
        "status" : 201
      }
    }
  ]
}

② 批量执行不同的操作

此时的执行语句为:

POST /_bulk
{"delete": {"_index": "website", "_type": "blog", "_id": "123"}}
{"create": {"_index": "website", "_type": "blog", "_id": "123"}}
{"title": "My first blog post"}
{"index": {"_index": "website", "_type": "blog"}}
{"title": "My second blog post"}
{"update": {"_index": "website", "_type": "blog", "_id": "123"}}
{"doc": {"title": "My updated blog post"}}

返回的数据信息为:

{
  "took" : 3373,
  "errors" : true,
  "items" : [
    {
      "delete" : {
        "_index" : "website",
        "_type" : "blog",
        "_id" : "123",
        "_version" : 2,
        "result" : "not_found",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 1,
        "_primary_term" : 1,
        "status" : 404
      }
    },
    {
      "create" : {
        "_index" : "website",
        "_type" : "blog",
        "_id" : "123",
        "status" : 409,
        "error" : {
          "type" : "version_conflict_engine_exception",
          "reason" : "[123]: version conflict, document already exists (current version [4])",
          "index_uuid" : "H_4_xzfKR4GnkIEcGXdovQ",
          "shard" : "0",
          "index" : "website"
        }
      }
    },
    {
      "index" : {
        "_index" : "website",
        "_type" : "blog",
        "_id" : "WbW2yYgBfKt3Fbn5W3Kx",
        "_version" : 1,
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 5,
        "_primary_term" : 1,
        "status" : 201
      }
    },
    {
      "update" : {
        "_index" : "website",
        "_type" : "blog",
        "_id" : "123",
        "_version" : 4,
        "result" : "noop",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 4,
        "_primary_term" : 1,
        "status" : 200
      }
    }
  ]
}

2.3.8 添加测试数据

测试数据:es测试数据.json · 坐看云起时/common_content - Gitee.com 将这些测试数据存进/bank/account/_bulk中 此时重新查看当前的所有索引

yellow open website                  H_4_xzfKR4GnkIEcGXdovQ 1 1    3 3   8.8kb   8.8kb
yellow open bank                     UPeWuTxBRv61vW_WGjoULg 1 1 1000 0 422.1kb 422.1kb
green  open .kibana_task_manager_1   1X5FwZ7zT1ODNuOo7c96tg 1 0    2 0  38.2kb  38.2kb
green  open .apm-agent-configuration PUnTh1VuQSKEQ3h37K4XOA 1 0    0 0    283b    283b
green  open .kibana_1                yoL51QcrQO-xtZ51WqsOfA 1 0    8 0  28.6kb  28.6kb
yellow open customer                 PL1lo4qzQtGiT4Y4K4OdbA 1 1    2 0   3.5kb   3.5kb

学习谷粒商城时,学到了ElasticSearch技术,因此记录下学习过程中的一些笔记以及一些补充。将我做的笔记分享出来,希望能够帮助到其他人,如果有不足的地方也希望大家能指出,谢谢!!

Guess you like

Origin juejin.im/post/7245567988249952293