一、基础概念

1、lucene和elasticsearch的前世今生

lucene,最先进、功能最强大的搜索库,直接基于lucene开发,非常复杂,api复杂(实现一些简单的功能,写大量的java代码),需要深入理解原理(各种索引结构)
elasticsearch,基于lucene,隐藏复杂性,提供简单易用的restful api接口、java api接口(还有其他语言的api接口)
(1)分布式的文档存储引擎
(2)分布式的搜索引擎和分析引擎
(3)分布式,支持PB级数据
开箱即用,优秀的默认参数,不需要任何额外设置,完全开源
关于elasticsearch的一个传说,有一个程序员失业了,陪着自己老婆去英国伦敦学习厨师课程。程序员在失业期间想给老婆写一个菜谱搜索引擎,觉得lucene实在太复杂了,就开发了一个封装了lucene的开源项目,compass。后来程序员找到了工作,是做分布式的高性能项目的,觉得compass不够,就写了elasticsearch,让lucene变成分布式的系统。

2、elasticsearch的核心概念

(1)Near Realtime(NRT):近实时,两个意思,从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级
(2)Cluster:集群,包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常
(3)Node:节点,集群中的一个节点,节点也有一个名称(默认是随机分配的),节点名称很重要(在执行运维管理操作的时候),默认节点会去加入一个名称为“elasticsearch”的集群,如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群
(4)Document&field:文档,es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field,每个field就是一个数据字段。

  1. product document
  2. {
  3. "product_id": "1",
  4. "product_name": "高露洁牙膏",
  5. "product_desc": "高效美白",
  6. "category_id": "2",
  7. "category_name": "日化用品"
  8. }

(5)Index:索引,包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。
(6)Type:类型,每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。
商品index,里面存放了所有的商品数据,商品document
但是商品分很多种类,每个种类的document的field可能不太一样,比如说电器商品,可能还包含一些诸如售后时间范围这样的特殊field;生鲜商品,还包含一些诸如生鲜保质期之类的特殊field
type,日化商品type,电器商品type,生鲜商品type
日化商品type:product_id,product_name,product_desc,category_id,category_name
电器商品type:product_id,product_name,product_desc,category_id,category_name,service_period
生鲜商品type:product_id,product_name,product_desc,category_id,category_name,eat_period
每一个type里面,都会包含一堆document

  1. {
  2. "product_id": "2",
  3. "product_name": "长虹电视机",
  4. "product_desc": "4k高清",
  5. "category_id": "3",
  6. "category_name": "电器",
  7. "service_period": "1年"
  8. }
  9. {
  10. "product_id": "3",
  11. "product_name": "基围虾",
  12. "product_desc": "纯天然,冰岛产",
  13. "category_id": "4",
  14. "category_name": "生鲜",
  15. "eat_period": "7天"
  16. }

(7)shard:单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index。
(8)replica:任何一个服务器随时可能故障或宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。

3、elasticsearch核心概念 vs. 数据库核心概念

Elasticsearch 数据库
————————————————————-
Document 行
Type 表
Index 库

4、shard和replica的解释

image.png

二、简单命令

1、document数据格式

面向文档的搜索分析引擎
(1)应用系统的数据结构都是面向对象的,复杂的
(2)对象数据存储到数据库中,只能拆解开来,变为扁平的多张表,每次查询的时候还得还原回对象格式,相当麻烦
(3)ES是面向文档的,文档中存储的数据结构,与面向对象的数据结构是一样的,基于这种文档数据结构,es可以提供复杂的索引,全文检索,分析聚合等功能
(4)es的document用json数据格式来表达

  1. public class Employee {
  2. private String email;
  3. private String firstName;
  4. private String lastName;
  5. private EmployeeInfo info;
  6. private Date joinDate;
  7. }
  8. private class EmployeeInfo {
  9. private String bio; // 性格
  10. private Integer age;
  11. private String[] interests; // 兴趣爱好
  12. }
  13. EmployeeInfo info = new EmployeeInfo();
  14. info.setBio("curious and modest");
  15. info.setAge(30);
  16. info.setInterests(new String[]{"bike", "climb"});
  17. Employee employee = new Employee();
  18. employee.setEmail("zhangsan@sina.com");
  19. employee.setFirstName("san");
  20. employee.setLastName("zhang");
  21. employee.setInfo(info);
  22. employee.setJoinDate(new Date());

employee对象:里面包含了Employee类自己的属性,还有一个EmployeeInfo对象
两张表:employee表,employee_info表,将employee对象的数据重新拆开来,变成Employee数据和EmployeeInfo数据
employee表:email,first_name,last_name,join_date,4个字段
employee_info表:bio,age,interests,3个字段;此外还有一个外键字段,比如employee_id,关联着employee表

  1. {
  2. "email": "zhangsan@sina.com",
  3. "first_name": "san",
  4. "last_name": "zhang",
  5. "info": {
  6. "bio": "curious and modest",
  7. "age": 30,
  8. "interests": [ "bike", "climb" ]
  9. },
  10. "join_date": "2017/01/01"
  11. }

我们就明白了es的document数据格式和数据库的关系型数据格式的区别

2、简单的集群管理

(1)快速检查集群的健康状况
es提供了一套api,叫做cat api,可以查看es中各种各样的数据
GET /_cat/health

  1. {
  2. "cluster_name" : "elasticsearch_mingjing",
  3. "status" : "yellow",
  4. "timed_out" : false,
  5. "number_of_nodes" : 1,
  6. "number_of_data_nodes" : 1,
  7. "active_primary_shards" : 7,
  8. "active_shards" : 7,
  9. "relocating_shards" : 0,
  10. "initializing_shards" : 0,
  11. "unassigned_shards" : 5,
  12. "delayed_unassigned_shards" : 0,
  13. "number_of_pending_tasks" : 0,
  14. "number_of_in_flight_fetch" : 0,
  15. "task_max_waiting_in_queue_millis" : 0,
  16. "active_shards_percent_as_number" : 58.333333333333336
  17. }

健康状况

如何快速了解集群的健康状况?green、yellow、red?
green:每个索引的primary shard和replica shard都是active状态的
yellow:每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态
red:不是所有索引的primary shard都是active状态的,部分索引有数据丢失了

为什么现在会处于一个yellow状态?
我们现在就一个笔记本电脑,就启动了一个es进程,相当于就只有一个node。现在es中有一个index,就是kibana自己内置建立的index。由于默认的配置是给每个index分配5个primary shard和5个replica shard,而且primary shard和replica shard不能在同一台机器上(为了容错)。现在kibana自己建立的index是1个primary shard和1个replica shard。当前就一个node,所以只有1个primary shard被分配了和启动了,但是一个replica shard没有第二台机器去启动。

做一个小实验:此时只要启动第二个es进程,就会在es集群中有2个node,然后那1个replica shard就会自动分配过去,然后cluster status就会变成green状态。

快速查看集群中有哪些索引

GET /_cat/indices

  1. yellow open test2 CYNTU4W9TGmfoeAp7AAoRA 1 1 0 0 208b 208b
  2. yellow open test3 5bhqo33ERdCW8ywZzK-Ymg 1 1 1 0 4kb 4kb
  3. yellow open test j_a2-y0ZQLC4QnG8l56tkQ 1 1 1 0 4.1kb 4.1kb
  4. yellow open testdb 8qDkDbONRz2Lbq5IgMXaXg 1 1 2 0 7.2kb 7.2kb
  5. green open .kibana_1 kY2GKa8MRrGMqqdjDnRHhQ 1 0 191 2 109.4kb 109.4kb
  6. green open kibana_sample_data_flights 2CSDTjwTR9mqGL59M8HP4g 1 0 13059 0 6.2mb 6.2mb
  7. yellow open mingjing RDemQT6HTQCPlz029LGiEg 1 1 4 0 14.7kb 14.7kb

简单的索引操作

创建索引:PUT /test_index?pretty
删除索引:DELETE /test_index?pretty
查看索引:GET /_cat/indices

3、商品的CRUD操作

新增商品:新增文档,建立索引

PUT /index/type/id { “json数据” }

  1. PUT /hlj/product/2
  2. {
  3. "name" : "测试美甲2",
  4. "desc" :"gaoxiao meibai",
  5. "price" :300,
  6. "tags": [ "美甲", "蓝色海洋" ]
  7. }
  8. PUT /hlj/product/3
  9. {
  10. "name" : "美容测试商品",
  11. "desc" :"不要下单哦",
  12. "price" :255,
  13. "tags": [ "测试" ]
  14. }
  15. PUT /hlj/product/4
  16. {
  17. "name" : "Diro子弹口红小红书同款",
  18. "desc" :"迪奥",
  19. "price" :260,
  20. "tags": [ "Diro" ]
  21. }

es会自动建立index和type,不需要提前创建,而且es默认会对document每个field都建立倒排索引,让其可以被搜索

查询商品:检索文档

GET /index/type/id

  1. GET /hlj/product/3
  2. {
  3. "_index" : "hlj",
  4. "_type" : "product",
  5. "_id" : "3",
  6. "_version" : 1,
  7. "_seq_no" : 2,
  8. "_primary_term" : 1,
  9. "found" : true,
  10. "_source" : {
  11. "name" : "美容测试商品",
  12. "desc" : "不要下单哦",
  13. "price" : 255,
  14. "tags" : [
  15. "测试"
  16. ]
  17. }
  18. }

修改商品:替换文档

  1. PUT /hlj/product/1
  2. {
  3. "name" : "测试美甲3",
  4. "desc" :"测试2",
  5. "price" :320,
  6. "tags": [ "美甲1", "蓝色海3洋" ]
  7. }
  8. PUT /hlj/product/1
  9. {
  10. "name" : "jiaqiangban gaolujie yagao"
  11. }

替换方式有一个不好,即使必须带上所有的field,才能去进行信息的修改

修改商品:更新文档

  1. POST /hlj/product/1/_update
  2. {
  3. "doc":{
  4. "name" : "测试美甲3"
  5. }
  6. }

删除商品:删除文档

  1. DELETE /ecommerce/product/1

三、多种搜索方式

1、搜索全部商品

GET /hlj/product/_search
image.png
took:耗费了几毫秒
timed_out:是否超时,这里是没有
_shards:数据拆成了5个分片,所以对于搜索请求,会打到所有的primary shard(或者是它的某个replica shard也可以)
hits.total:查询结果的数量,3个document
hits.max_score:score的含义,就是document对于一个search的相关度的匹配分数,越相关,就越匹配,分数也高
hits.hits:包含了匹配搜索的document的详细数据

query string search的由来,因为search参数都是以http请求的query string来附带的
搜索商品名称中包含【美甲】的商品,而且按照售价降序排序:GET /hlj/product/_search?q=name:美甲&sort=price:desc
适用于临时的在命令行使用一些工具,比如curl,快速的发出请求,来检索想要的信息;但是如果查询请求很复杂,是很难去构建的
在生产环境中,几乎很少使用query string search

2、query DSL

DSL:Domain Specified Language,特定领域的语言
http request body:请求体,可以用json的格式来构建查询语法,比较方便,可以构建各种复杂的语法,比query string search肯定强大多了
查询所有的商品

  1. GET /hlj/product/_search
  2. {
  3. "query": { "match_all": {} }
  4. }

查询名称包含【测试】的商品,同时按照价格降序排序

  1. GET /hlj/product/_search
  2. {
  3. "query":{
  4. "match":{
  5. "name":"测试"
  6. }
  7. },
  8. "sort":[
  9. {
  10. "price":"desc"
  11. }
  12. ]
  13. }

分页查询商品,总共3条商品,假设每页就显示1条商品,现在显示第2页,所以就查出来第2个商品

  1. GET /hlj/product/_search
  2. {
  3. "query":{
  4. "match_all": {}
  5. },
  6. "from":1,
  7. "size":2
  8. }

指定要查询出来商品的名称和价格就可以

  1. GET /hlj/product/_search
  2. {
  3. "query":{
  4. "match_all": {}
  5. },
  6. "_source":["name","price"]
  7. }

更加适合生产环境的使用,可以构建复杂的查询

3、query filter

搜索商品名称包含yagao,而且售价大于25元的商品

  1. GET /hlj/product/_search
  2. {
  3. "query":{
  4. "bool": {
  5. "must": [
  6. {"match": {
  7. "name": "测试"
  8. }}
  9. ],
  10. "filter": [
  11. {
  12. "range": {
  13. "price": {
  14. "gte": 200
  15. }
  16. }
  17. }
  18. ]
  19. }
  20. },
  21. "_source":["name","price"]
  22. }

4、full-text search(全文检索)

  1. GET /hlj/product/_search
  2. {
  3. "query":{
  4. "match": {
  5. "name": "美甲 测试"
  6. }
  7. },
  8. "_source":["name","price"]
  9. }

name 这个字段,会先被拆解,建立倒排索引

5、phrase search(短语搜索)

跟全文检索相对应,相反,全文检索会将输入的搜索串拆解开来,去倒排索引里面去一一匹配,只要能匹配上任意一个拆解后的单词,就可以作为结果返回
phrase search,要求输入的搜索串,必须在指定的字段文本中,完全包含一模一样的,才可以算匹配,才能作为结果返回

  1. GET /hlj/product/_search
  2. {
  3. "query":{
  4. "match_phrase": {
  5. "name": "口红"
  6. }
  7. },
  8. "_source":["name","price"]
  9. }

6、highlight search(高亮搜索结果)

  1. GET /hlj/product/_search
  2. {
  3. "query":{
  4. "match": {
  5. "name": "口红"
  6. }
  7. },
  8. "_source":["name","price"],
  9. "highlight":{
  10. "pre_tags": "<p class='key' style='color:red'>",
  11. "post_tags": "</p>",
  12. "fields": {"name":{}}
  13. }
  14. }

四、聚合

1、分析需求:计算每个tag下的商品数量

  1. GET /hlj/product/_search
  2. {
  3. "aggs":{
  4. "group_by_tags":{
  5. "terms":{
  6. "field":"tags.keyword"
  7. }
  8. }
  9. }
  10. }

2、对名称中包含【测试】的商品,计算每个tag下的商品数量

  1. GET /hlj/product/_search
  2. {
  3. "query":{
  4. "match": {
  5. "name": "测试"
  6. }
  7. },
  8. "aggs":{
  9. "group_by_tags":{
  10. "terms":{
  11. "field":"tags.keyword"
  12. }
  13. }
  14. }
  15. }

3、计算每个tag下的商品的平均价格并排序

  1. GET /hlj/product/_search
  2. {
  3. "aggs":{
  4. "group_by_tags":{
  5. "terms":{
  6. "field":"tags.keyword",
  7. "order": {
  8. "avg": "desc"
  9. }
  10. },
  11. "aggs": {
  12. "avg": {
  13. "avg": {
  14. "field": "price"
  15. }
  16. }
  17. }
  18. }
  19. }
  20. }

4、按照指定的价格范围区间进行分组,然后在每组内再按照tag进行分组,最后再计算每组的平均价格

  1. GET /hlj/product/_search
  2. {
  3. "aggs":{
  4. "group_by_price":{
  5. "range": {
  6. "field": "price",
  7. "ranges": [
  8. {
  9. "from": 0,
  10. "to": 100
  11. },
  12. {
  13. "from": 100,
  14. "to": 200
  15. },
  16. {
  17. "from": 200,
  18. "to": 300
  19. },
  20. {
  21. "from": 300,
  22. "to": 400
  23. }
  24. ]
  25. },
  26. "aggs": {
  27. "group_by_tags": {
  28. "terms": {
  29. "field": "tags.keyword"
  30. },
  31. "aggs": {
  32. "average_price": {
  33. "avg": {
  34. "field": "price"
  35. }
  36. }
  37. }
  38. }
  39. }
  40. }
  41. }
  42. }

5、精确查询

term 查询是根据通过倒排索引指定的词进行精确查询的
关于分词
term,直接进行精确查询
match,会使用分词解析器。
keyword 字段类型不会被分词器解析
text字段类型会被分词器解析

五、Elasticsearch特点

1、Elasticsearch对复杂分布式机制的透明隐藏特性

Elasticsearch是一套分布式的系统,分布式是为了应对大数据量,隐藏了复杂的分布式机制
分片机制(我们之前随随便便就将一些document插入到es集群中去了,我们有没有care过数据怎么进行分片的,数据到哪个shard中去)
cluster discovery(集群发现机制,我们之前在做那个集群status从yellow转green的实验里,直接启动了第二个es进程,那个进程作为一个node自动就发现了集群,并且加入了进去,还接受了部分数据,replica shard)
shard负载均衡(举例,假设现在有3个节点,总共有25个shard要分配到3个节点上去,es会自动进行均匀分配,以保持每个节点的均衡的读写负载请求)
shard副本请求路由集群扩容shard重分配

2、Elasticsearch的垂直扩容与水平扩容

垂直扩容:采购更强大的服务器,成本非常高昂,而且会有瓶颈,假设世界上最强大的服务器容量就是10T,但是当你的总数据量达到5000T的时候,你要采购多少台最强大的服务器啊
水平扩容:业界经常采用的方案,采购越来越多的普通服务器,性能比较一般,但是很多普通服务器组织在一起,就能构成强大的计算和存储能力
普通服务器:1T,1万,100万
强大服务器:10T,50万,500万
扩容对应用程序的透明性

3、增减或减少节点时的数据rebalance

保持负载均衡

4、master节点

(1)创建或删除索引
(2)增加或删除节点

5、节点对等

(1)节点对等,每个节点都能接收所有的请求,如果当前索引不在该节点,节点会查找匹配请求数据的节点,并返回;
(2)自动请求路由
(3)响应收集

六、核心数据元

  1. {
  2. "_index": "test_index",
  3. "_type": "test_type",
  4. "_id": "1",
  5. "_version": 1,
  6. "found": true,
  7. "_source": {
  8. "test_content": "test test"
  9. }
  10. }

1、_index元数据

(1)代表一个document存放在哪个index中
(2)类似的数据放在一个索引,非类似的数据放不同索引:product index(包含了所有的商品),sales index(包含了所有的商品销售数据),inventory index(包含了所有库存相关的数据)。如果你把比如product,sales,human resource(employee),全都放在一个大的index里面,比如说company index,不合适的。
(3)index中包含了很多类似的document:类似是什么意思,其实指的就是说,这些document的fields很大一部分是相同的,你说你放了3个document,每个document的fields都完全不一样,这就不是类似了,就不太适合放到一个index里面去了。
(4)索引名称必须是小写的,不能用下划线开头,不能包含逗号:product,website,blog

2、_type元数据

(1)代表document属于index中的哪个类别(type)
(2)一个索引通常会划分为多个type,逻辑上对index中有些许不同的几类数据进行分类:因为一批相同的数据,可能有很多相同的fields,但是还是可能会有一些轻微的不同,可能会有少数fields是不一样的,举个例子,就比如说,商品,可能划分为电子商品,生鲜商品,日化商品,等等。
(3)type名称可以是大写或者小写,但是同时不能用下划线开头,不能包含逗号

3、_id元数据

(1)代表document的唯一标识,与index和type一起,可以唯一标识和定位一个document
(2)我们可以手动指定document的id(put /index/type/id),也可以不指定,由es自动为我们创建一个id

手动指定

根据应用情况来说,是否满足手动指定document id的前提;
一般来说,是从某些其他的系统中,导入一些数据到es时,会采取这种方式,就是使用系统中已有数据的唯一标识,作为es中document的id。举个例子,比如说,我们现在在开发一个电商网站,做搜索功能。这个时候,数据首先会在网站系统或者IT系统内部的数据库中,会先有一份,此时就肯定会有一个数据库的primary key(自增长,UUID,或者是业务编号)。如果将数据导入到es中,此时就比较适合采用数据在数据库中已有的primary key。
如果说,我们是在做一个系统,这个系统主要的数据存储就是es一种,也就是说,数据产生出来以后,可能就没有id,直接就放es一个存储,那么这个时候,可能就不太适合说手动指定document id的形式了,因为你也不知道id应该是什么,此时可以采取下面要讲解的让es自动生成id的方式。

  1. put /index/type/id
  2. PUT /product/tag_mei_rong/2
  3. {
  4. "name": "科技美容深度补水"
  5. }

自动生成

自动生成的id,长度为20个字符,URL安全,base64编码,GUID,分布式系统并行生成时不可能会发生冲突

  1. post /index/type
  2. POST /product/tag_mei_rong
  3. {
  4. "name": "科技美容深度补水"
  5. }
  6. {
  7. "_index": "product",
  8. "_type": "tag_mei_rong",
  9. "_id": "AVp4RN0bhjxldOOnBxaE",
  10. "_version": 1,
  11. "result": "created",
  12. "_shards": {
  13. "total": 2,
  14. "successful": 1,
  15. "failed": 0
  16. },
  17. "created": true
  18. }

4、_source

  1. put /product/tag_mei_rong/1
  2. {
  3. "name": "科技美容深度补水",
  4. "price": 200
  5. }
  6. get /product/tag_mei_rong/1
  7. {
  8. "_index": "test_index",
  9. "_type": "test_type",
  10. "_id": "1",
  11. "_version": 2,
  12. "found": true,
  13. "_source": {
  14. "name": "科技美容深度补水",
  15. "price": "200"
  16. }
  17. }

_source元数据:就是说,我们在创建一个document的时候,使用的那个放在request body中的json串,默认情况下,在get的时候,会原封不动的给我们返回来。

2、定制返回结果
定制返回的结果,指定_source中,返回哪些field

  1. GET /product/tag_mei_rong/1?_source=name,xxx

5、document

1、document的全量替换
(1)语法与创建文档是一样的,如果document id不存在,那么就是创建;如果document id已经存在,那么就是全量替换操作,替换document的json串内容
(2)document是不可变的,如果要修改document的内容,第一种方式就是全量替换,直接对document重新建立索引,替换里面所有的内容
(3)es会将老的document标记为deleted,然后新增我们给定的一个document,当我们创建越来越多的document的时候,es会在适当的时机在后台自动删除标记为deleted的document

2、document的强制创建
(1)创建文档与全量替换的语法是一样的,有时我们只是想新建文档,不想替换文档,如果强制进行创建呢?
(2)PUT /index/type/id?op_type=create 或者 PUT /index/type/id/_create

3、document的删除
(1)DELETE /index/type/id
(2)不会物理删除,只会将其标记为deleted,当数据越来越多的时候,在后台自动删除