ElasticSearch操作指南

1 ElasticSearch简介

ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎。是使用Java开发的。

1.1 使用场景

  • 为用户提供关键字查询的全文搜索功能。

  • 实现企业海量数据的处理分析。ELK框架(ElasticSearch、Logstash、Kibana)

  • 作为OLAP数据库,对数据进行统计分析。

    OLTP:联机事务处理,是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,如银行交易。

    OLAP:联机分析处理,是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供只管易懂的查询结果。

1.2 几种数据库比较

redis mysql elasticsearch hbase hadoop/hive
容量容量扩展 较大 海量 海量
查询时效性 极高 较高(需要索引优化) 较高 较高(rowkey方式) 较低(scan方式)
查询灵活性 较差 k-v模式 非常好,支持sql 较好,关联查询较弱,但是可以全文检索,DSL语言可以处理过滤、匹配、排序、聚合等各种操作 较差,主要靠rowkey, scan的话性能不行,或者安装phoenix插件来实现sql及二级索引 非常好,支持sql
写入速度 极快 中等 较快 较快
一致性、事务

1.3 elasticsearch特点

①分片&集群

image-20201221125125363

上图es将数据分成3个shard切片,有两个副本。就算一台node节点挂掉了,也能够凑出来一份完整的数据。

随着数据的不断增加,集群可以增加多个分片,把多个分片放在多个机器上,达到负载均衡和横向扩展。

②es的读数据过程

image-20201221131438396

  1. 客户端发送请求给任意一个node,这个node成为coordinating node
  2. coordinating node对doc id进行哈希路由,将请求转发到对应的node(也就是primary shard所在的节点),此时会使用round-robin轮询算法,在primary shard以及其所有的副本中随机选择一个,让读请求负载均衡。
  3. 接收到请求的node,将数据document同步到coordinating node
  4. coordinating node返回document给客户端

③es写数据过程

  1. 客户端发送写请求给任意一个node,这个node成为coordinating node
  2. coordinating node对document进行路由,将请求转发给对应的node(有primary shard所在的节点)
  3. 实际的node(primary shard)处理写数据请求,然后将写完的数据同步给replication node。(同步数据)
  4. coordinating node发现,写数据过程primary node 和replication node数据都同步完成后。
  5. 将写数据成功发送给客户端。

④倒排索引

ES所有数据都是默认进行索引的。全文搜索引擎主流的索引技术:倒排索引

image-20201222182122106

什么是倒排索引?

  • 传统的数据库:记录-单词
  • 倒排索引:单词:记录

通过分词,然后记录单词在什么位置 单词在什么位置,对每个分词打分,然后根据评分倒序

image-20201222182144564

2 ElasticSearch安装配置

本次安装的版本是elasticsearch-6.6.0

Elasticsearch官网:

https://www.elastic.co/products/elasticsearch

https://www.elastic.co/cn/downloads/past-releases/elasticsearch-6-6-0

2.1 安装配置步骤

步骤1:上传安装包,将elasticsearch-6.6.0.tar.gz上传到/opt/software/目录下

步骤2:解压ES到/opt/module目录下

[atguigu@hadoop102 bin]$ tar -zxvf elasticsearch-6.6.0.tar.gz -C /opt/module/

步骤3:重命名elasticsearch

[atguigu@hadoop202 module]$ mv elasticsearch-6.6.0/ elasticsearch

步骤4:修改elasticsearch配置文件

elasticsearch.yml

注意yml文件key:后面要有一个空格!!!

# 集群名称,同一个集群名称需要相同
cluster.name: my-es
# 单个节点名称
node.name: node-1
# 关闭bootstrap自检程序
bootstrap.memory_lock: false
bootstrap.system_call_filter: false
# 网络部分,改为当前的ip地址,端口号默认9200即可
network.host: 192.168.255.102
# 自发现配置:新节点向集群报道的主机名
discovery.zen.ping.unicast.hosts: ["hadoop102", "hadoop103"]

步骤5:Java虚拟机优化(非生产环境)

elasticsearch运行在Java虚拟机中,虚拟机默认启动内存1G内存。但是装在个人PC机,实际用不了1个G。所以可以改小一点内存。但是实际生产环境一般128G内存是标配。

# Xms represents the initial size of total heap space
# Xmx represents the maximum size of total heap space
#-Xms1g
-Xms512m
-Xmx512m
#-Xmx1g

步骤6:分发elasticsearch

[atguigu@hadoop202 module]$ xsync elasticsearch/

步骤7:修改hadoop103、hadoop104的elasticsearch.yml的配置

node.name: node-2
network.host: 192.168.255.103
node.name: node-3
network.host: 192.168.255.104

步骤8:单台启动测试,会报错

这是因为elasticsearch是单机访问模式,就是只能自己访问自己。但是我们已经设置成允许应用服务器通过网络方式访问,而且生产环境也是这种方式。

这时候elasticsearch会嫌弃单机版的低端配置而报错!!!无法启动。需要修改服务器的一些限制,支持更多并发。

[atguigu@hadoop102 bin]$ ./elasticsearch

image-20201218225222872

  • 错误1:[1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]

    原因:系统允许ElasticSearch打开的最大文件数需要改成65536

    解决方法:

    [atguigu@hadoop102 bin]$ sudo vim /etc/security/limits.conf 
    

    添加内容:(注意:“*” 不要省略掉)

    * soft nofile 65536
    * hard nofile 131072
    * soft nproc 2048
    * hard nproc 65536
    

    分发文件:

    [atguigu@hadoop102 bin]$ sudo /home/atguigu/bin/xsync /etc/security/limits.conf 
    
  • 错误2:[2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]

    原因:一个进程可以拥有的虚拟内存区域的数量

    解决方法:

    [atguigu@hadoop102 bin]$ sudo vim /etc/sysctl.conf 
    

    在文件最后添加:

    vm.max_map_count=262144
    

    分发文件:

    [atguigu@hadoop102 bin]$ sudo /home/atguigu/bin/xsync /etc/sysctl.conf 
    

步骤9:重启Linux机器,使配置生效

2.2 启动elasticsearch

①单台启动测试

单独启动elasticsearch

ES天然就是集群状态,就算是只有一个节点,也会当做集群处理,

默认节点name=主机名,cluster_name=elasticsearch

[atguigu@hadoop102 bin]$ ./elasticsearch

测试方式1:http://hadoop102:9200/_cat/nodes?v

image-20201218231003362

测试方法2:http://hadoop102:9200/

image-20201218231050936

②集群启动脚本

profile.d下不用source /etc/profile也可以~

#!/bin/bash
es_home=/opt/module/elasticsearch
case $1 in
	"start") {
    
    
	for i in hadoop102 hadoop103 hadoop104; 
	do
		echo "=============== $i starting ES ==============="
		ssh $i "source /etc/profile;${es_home}/bin/elasticsearch > /dev/null 2>&1 &"
	done
};;
	"stop") {
    
    
	for i in hadoop102 hadoop103 hadoop104; 
	do
		echo "=============== $i stoping ES ==============="
		ssh $i "ps -ef | grep $es_home |grep -v grep|awk '{print \$2}'|xargs kill" >/dev/null 2>&1
	done
};;
esac

测试方式1:http://hadoop102:9200/_cat/nodes?v

image-20201218233051584

测试方式2:http://hadoop104:9200/

image-20201218233133490

3 Kibana安装配置

3.1 安装配置步骤

步骤1:上传安装包到/opt/software

步骤2:解压kibana-6.6.0-linux-x86_64到/opt/module下

[atguigu@hadoop102 module]$ tar -zxvf kibana-6.6.0-linux-x86_64.tar.gz -C /opt/module/

步骤3:重命名kibana

[atguigu@hadoop102 module]$ mv kibana-6.6.0-linux-x86_64/ kibana

步骤4:修改kibana配置文件

# 授权远程访问
#server.host: "localhost"
server.host: "0.0.0.0"

# 指定ElasticSearch地址,可以指定多个
#elasticsearch.hosts: ["http://localhost:9200"]
elasticsearch.hosts: ["http://hadoop102:9200","http://hadoop103:9200","http://hadoop104:9200"]

步骤5:启动、测试

[atguigu@hadoop102 kibana]$ bin/kibana

image-20201218234434663

步骤6:访问Web监听页面http://hadoop102:5601/

image-20201218234615255

kibana如果出现.kibana的索引red问题,导致kibana起不来!解决方式:

  • 删除.kibana_1这个索引:正常情况下删除索引是DELETE /.kibana_1
  • 如果kibana起不来:删除操作为curl -XDELETE http://hadoop102:9200/.kibana*

3.2 启动脚本

首先创建文件夹

[atguigu@hadoop102 kibana]$ mkdir -p /opt/module/kibana/logs
#!/bin/bash
es_home=/opt/module/elasticsearch
kibana_home=/opt/module/kibana
case $1 in
	"start") {
    
    
	for i in hadoop102 hadoop103 hadoop104; 
	do
		echo "=============== $i starting ES ==============="
		ssh $i "source /etc/profile;${es_home}/bin/elasticsearch > /dev/null 2>&1 &"
	done
	echo "=============== starting kibana =============="
	nohup ${kibana_home}/bin/kibana >${kibana_home}/logs/kibana.log 2>&1 &
};;
	"stop") {
    
    
	echo "=============== stoping kibana ==============="
	ps -ef|grep ${kibana_home} |grep -v grep|awk '{print $2}'|xargs kill
	for i in hadoop102 hadoop103 hadoop104; 
	do
		echo "=============== $i stoping ES ==============="
		ssh $i "ps -ef | grep $es_home |grep -v grep|awk '{print \$2}'|xargs kill" >/dev/null 2>&1
	done
};;
esac

3.3 Kibana console

image-20201218235736135

image-20201218235812709

4 ElasticSearch result API(DSL)

4.1 es名词概念

cluster 整个elasticsearch默认就是集群状态,整个集群是一份完整、互备的数据
node 集群中的一个节点,一般指一个进程就是一个node
shard 分片,即使是一个节点中的数据也会通过hash算法,分成多个片存放,默认是5片。(7.0默认1片)
index 索引,相当于table
type 区,对表的数据再进行划分。(6.0只允许建一个,7.0废弃)
document 类似于面向对象中的object、rdbms的row
field 字段、属性

文档document:相当于一条一条的 数据!

索引index里面是一条一条document。

  • 6.x版本中:index相当于database,type相当于table,document相当于一条条数据,field是字段。

  • 7.x版本中:index就相当于table了,废弃了type。

  • 6.x版本中默认5个分片,1个副本

  • 7.x版本中默认1个分片,1个副本

4.2 es中的数据结构

public class  Movie {
    
    
	 String id;
     String name;
     Double doubanScore;
     List<Actor> actorList;
}

public class Actor{
    
    
String id;
String name;
}

上面的两个对象如果放在关系型数据库保存,会被拆成2张表,但是在elasticsearch是用一个json来表示一个document文档

{
    
    
  "id":"1",
  "name":"operation red sea",
  "doubanScore":"8.5",
  "actorList":[  
		{
    
    "id":"1","name":"zhangyi"},
		{
    
    "id":"2","name":"haiqing"},
		{
    
    "id":"3","name":"zhanghanyu"}
	] 
}

image-20201221173628866

4.3 服务状态查询

其实在kibana中的get,可以在hadoop102:9200后面加上,比如查询集群健康状态:

http://hadoop102:9200/_cat/health?v

①查看各个索引的状态

GET _cat/indices

image-20201221083613675

②给索引添加头信息

GET _cat/indices?v

image-20201221084800789

  • health:索引的健康状态;
  • status:是否可用
  • index:索引名,也就是表名;
  • pri:索引主分片数量;
  • rep:索引复制分片数量;
  • store.size:索引主分片+复制分片,分片总占用空间;
  • pri+store.size:索引总占用空间,不计算复制分片占用空间。

③服务整体状态查询

GET _cat/health?v

image-20201221112738163

  • cluster:集群名称
  • status:集群健康状态,green所有主分片都正常而且每个主分片都至少有一个副本,集群状态;yellow代表分配了所有主分片,但至少缺少一个副本,此时集群数据仍旧完整,主片副本都在单点;red代表部分主分片不可用,可能已经丢失数据。
  • node.total:表示在线的节点总数
  • node.data:表示在新的数据节点数量
  • shards:表示存活的分片数量
  • pri:表示存活的主分片数量,正常情况下 shards的数量是pri的两倍
  • relo,表示迁移中的分片数量,正常情况下为0
  • init,表示初始化中的分片数量,正常情况下为0
  • unassign,未分配的分片,正常情况下为0
  • pending_tasks,准备中的任务,任务指迁移分片等,正常情况下为0
  • max_task_wait_time,任务的最长等待时间
  • active_shards_percent,正常分片百分比,正常情况为100%

④查询各个节点状态

GET _cat/nodes?v

image-20201221134233775

  • heap.percent 堆内存占用百分比
  • ram.percent 内存占用百分比
  • cpu CPU占用百分比
  • load_5m,每5分钟加载的情况
  • master *表示节点是集群中的主节点

⑤查询某个索引的分片情况

-- GET _cat/shards/索引名

GET _cat/shards/movie_index?v

image-20201221134539468

  • index:索引名称
  • shard:分片序号,默认是5个分片
  • prirep:p是主分片,r是复制分片
  • state:该分片的状态
  • docs:分片存放的文档数
  • store:该分片占用存储空间

4.4 对数据的操作

1 增加一个索引(put)

添加一张表

PUT student_index

image-20201221172717137

2 查看创建的索引(get)

get _cat/indices?v

3 删除一个索引(delete)

删除一张表

  • 删除索引会立即释放空间,不存在标记删除逻辑
  • 删除文档的时候,是将新文档写入,同时将就文档标记为已删除。
  • 也可以手动执行post/_forcemerge进行合并触发
DELETE student_index

image-20201221172821842

4 新增文档(put幂等性)

  • 添加一条数据,如果没有的index或type,elasticsearch会自动创建。

  • 其中的id字段如果不写:会自增!!!

  • 文档的id和文档中的id属性不是一回事!

-- put index/type/id
-- 这里的index是表;type是区,6.x只允许建一个,7.x废弃了;id是rowkey主键,一条数据的唯一标识 

PUT movie_index/movie/1
{
  "id":100,
  "name":"operation red sea",
  "doubanScore":8.5,
  "actorList":[
      {
   
   "id":1, "name":"zhang yi"},
      {
   
   "id":2, "name":"hai qing"},
      {
   
   "id":3, "name":"zhang han yu"}
    ]
}
PUT movie_index/movie/2
{
  "id":200,
  "name":"operation meigong river",
  "doubanScore":8.0,
  "actorList":[
      {
   
   "id":1, "name":"zhang han yu"}
    ]
}
PUT movie_index/movie/3
{
  "id":300,
  "name":"incident red sea",
  "doubanScore":5.0,
  "actorList":[
      {
   
   "id":1, "name":"zhang chen"}
    ]
}

image-20201221182131036

响应结果中看到的_shards=2,为啥不是5呢?这里创建的文档会在其中一个分片上存一份,副本上存一份。

幂等性检测:

幂等性操作:当数据出现问题的时候,数据要重发,幂等性是保证数据是一条数据,数据不能重复。

我又多次执行了put添加操作:

PUT movie_index/movie/3
{
  "id":3,
  "name":"incident red sea",
  "doubanScore":5.0,
  "actorList":[
      {
   
   "id":1, "name":"zhang chen"}
    ]
}

查询结果表示:数据不会重复!!!

5 新增文档(post非幂等性)

而post就是非幂等性的操作了,当执行多次post相同的数据,数据会重复;

会自动生成随机的id!!!

POST movie_index02/movie
{
  "id":1,
  "name":"incident red sea",
  "doubanScore":6.0,
  "actorList":[
      {
   
   "id":1,"name":"zhang han yu"},
      {
   
   "id":2,"name": "Tom"}
    ]
}

6 查询表中的所有数据(get + _search)

因为elasticsearch只允许有1个type,所以查询全部数据,下面两种都行

GET movie_index/_search

GET movie_index/movie/_search

​ 查询的结果如下:

  • took,表示查询的耗时时间。
  • time_out,是否超时
  • _shards,表示切片信息
  • hits,表示我成功插入一条,就命中一条!
{
    
    
  "took" : 1,	//耗费时间,毫秒
  "timed_out" : false,	//是否超时
  "_shards" : {
    
    		//分片信息
    "total" : 5,	//分片数
    "successful" : 5,	//成功的
    "skipped" : 0,	//跳过的
    "failed" : 0	//失败的
  },
  "hits" : {
    
    
    "total" : 1,	//命中的总条数,也就是成功插入的总条数
    "max_score" : 1.0,		//最大评分,评分规则:
    "hits" : [	//命中的结果
      {
    
    
        "_index" : "movie_index02",
        "_type" : "movie",
        "_id" : "W1DfhHYBrx9nWXZTCG2h",
        "_score" : 1.0,
        "_source" : {
    
    
          "id" : 1,
          "name" : "incident red sea",
          "doubanScore" : 6.0,
          "actorList" : [
            {
    
    
              "id" : 1,
              "name" : "zhang han yu"
            },
            {
    
    
              "id" : 2,
              "name" : "Tom"
            }
          ]
        }
      }
}

7 修改-整体替换(put)

和新增没有区别,要求是必须包括全部字段。 ==> 幂等性操作

最后面的77是主键,修改替换77这个主键的所有数据。

PUT movie_index/movie/77
{
  "name":"incident red sea",
  "doubanScore":5.0,
  "actorList":[
      {
   
   "id":1, "name":"zhang chen333"}
    ]
}

image-20201222164342470

8 修改某一个字段(post + doc)

修改某一字段,使用post!!!不能使用put!!!

语法:

post index/type/rowKey/_update
{
	“doc”:{
		修改的内容
	}
}
POST movie_index/movie/77/_update
{
  "doc":{
    "doubanScore":9.9
  }
}

9 删除一个文档(delete)

语法是 delete index/type/rowkey,就删掉某条主键的document

  • 删除索引会立即释放空间,不存在标记删除逻辑
  • 删除文档的时候,是将新文档写入,同时将就文档标记为已删除。
  • 也可以手动执行post/_forcemerge进行合并触发
DELETE movie_index/movie/77

10 查询指定id数据(get)

查找某个主键的document

GET movie_index/movie/1

11 查询 几条 数据(get + _search + size)

虽然能命中hit多条,但是可以指定显示size条数据!

GET movie_index/movie/_search
{
  "size": 2
}

12 按条件查询(全部)(query + match_all)

--方式1:
GET movie_index/_search
{
  "query": {
    "match_all": {}
  }
}

--方式2:简写形式
GET movie_index/_search

13 按分词查询(query + match)

根据匹配的程度,也就是结果中的"_score" 的数值排序。

评分公式的影响因素:

  • 正向因素:命中次数、命中长度的比例。
  • 负面因素:关键词在该字段的其他词条中出现的次数。
GET movie_index/_search
{
  "query": {
   
   "match": {
    "name": "red"
  }}
}

14 按分词子属性查询(query + match)

elasticsearch默认是按照分词查询的 ==> text,这样会根据分词,查询出所有匹配的结果,按照_score排序

如果想精确的查找某个条件 ==> keyword,这样不会分词,会精确匹配结果。

-- 这个时候的查询的是按照分词区查的!!!text
GET movie_index/_search
{
  "query": {
   
   "match": {
    "actorList.name": "zhang"
  }}
}
--上面查询的结果就是所有匹配zhang的演员名,会有多个~
-- 这个是不按照分词查询的,就是精确的查keyword
GET movie_index/_search
{
  "query": {
   
   "match": {
    "actorList.name.keyword": "zhang"
  }}
}
--上面查询的结果就是[],空,因为没有叫zhang的演员名

15 不适用分词匹配查询 (query + match_phrase)

不再利用分词技术。直接用短语在原始数据中匹配。

-- 普通的使用match 会将"operation red"分词成:"operation"和"red",然后根据倒排索引匹配。
GET movie_index/_search
{
  "query": {
   
   "match": {
    "name": "operation red"
  }}
}

-- 而使用match phrase不使用分词技术,直接在原数据中匹配。
GET movie_index/_search
{
  "query": {
   
   "match_phrase": {
    "name": "operation red"
  }}
}

--使用keyword,也可以使用分词匹配
GET movie_index/_search
{
  "query": {
    "match": {
      "actorList.name.keyword": "zhang san feng"
    }
  }
}
--也可以使用精准匹配,term一定要和keyword一起使用
GET movie_index/_search
{
  "query": {
    "term": {
      "actorList.name.keyword": {
        "value": "zhang san feng"
      }
    }
  }
}

16 容错匹配查询(query + fuzzy)

校正匹配分词,当一个单词无法精确匹配,es会通过一种算法对非常接近的单词进行评分,查询出来,但是会消耗更多的性能。

– 默认是仅可以允许有一个差别

GET movie_index/_search
{
  "query": {
   
   "fuzzy": {
    "name": "rad"
  }}
}

17 查询后过滤(query + post_filter)

查询是用“query”:{“match”}

过滤式用“post_filter”:{“term”:{}},其中term是直等

-- 查询电影名中含有red,并且演员id=3的document
GET movie_index/_search
{
  "query": {
   
   "match": {
    "name": "red"
  }},
  "post_filter": {
   
   "term": {
    "actorList.id": "3"
  }} 
}

18 查询前过滤(bool + filter + must)

--先过滤保留下演员id含有1和含有3的document,然后匹配电影名中含有red的
GET movie_index/movie/_search
{ 
    "query":{
        "bool":{
          "filter":[ {
   
   "term": {  "actorList.id": "1"  }},
                     {
   
   "term": {  "actorList.id": "3"  }}
           ], 
           "must":{
   
   "match":{
   
   "name":"red"}}
         }
    }
}

19 按照范围过滤

gt 大于
lt 小于
gte 大于等于 great than or equals
lte 小于等于 less than or equals
GET movie_index/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "doubanScore": {
            "gte": 6,
            "lte": 9
          }
        }
      }
    }
  }
}
-- 这样也可以啊!
GET movie_index/_search
{
  "query": {
    "range": {
      "doubanScore": {
        "gte": 6,
        "lte": 9
      }
    }
  }
}

20 直等(term)

term直等一般是和不分词的keyword一起使用。

-- term和text一起使用,下面的查询结果为[],这是因为分词将zhang chen分成了zhang和chen就无法直等
GET movie_index/_search
{
  "query": {
    "term": {
      "actorList.name":"zhang chen"
    }
  }
}
-- term一般是和keyword一起使用的!这样就能查出来。
GET movie_index/_search
{
  "query": {
    "term": {
      "actorList.name.keyword":"zhang chen"
    }
  }
}

20 根据查询条件进行删除、修改(_delete_by_query)(updete_by_query)

根据查询条件删除:post index/type/_delete_by_query{查询条件}

POST movie_index/movie/_delete_by_query
{
  "query":{
    "term":{
      "actorList.name.keyword":"zhang chen"
    }
  }
}

根据查询结果修改

POST movie_index/movie/_update_by_query
{
  "script":"ctx._source['actorList '][0][ 'name']='zhang san feng'",
  "query":{
    "term": {
      "actorList.name.keyword": "zhang chen"
    }
  }
}

21 排序(sort + order)

-- 先查询,然后对结果进行降序排序:查询电影名含有red的电影,按照豆瓣评分降序排序
GET movie_index/movie/_search
{
  "query": {
    "match": {
      "name": "red"
    }
  },
  "sort": [
    {
      "doubanScore": {
        "order": "desc"
      }
    }
  ]
}

--和范围一起使用:查询豆瓣评分7.0-8.5之间的电影,按照豆瓣评分降序排序
GET movie_index/_search
{
  "query": {
    "range": {
      "doubanScore": {
        "gte": 7.0,
        "lte": 8.5
      }
    }
  },
  "sort": [
    {
      "doubanScore": {
        "order": "desc"
      }
    }
  ]
}

22 分页查询(query + from + size)

-- from:表示从第几个document开始,size:表示分页显示几个
GET movie_index/_search
{
  "query": {
   
   "match_all": {}},
  "from": 2,
  "size": 2
}

23 指定查询的字段(query + _source)

-- 查询豆瓣评分这一条件
GET movie_index/_search
{
  "query": {
   
   "match_all": {}},
  "_source": "doubanScore"
}
-- 查询电影名和豆瓣评分这两个条件
GET movie_index/_search
{
  "query": {
   
   "match_all": {}},
  "_source": ["name", "doubanScore"]
}

24 高亮显示(highlight)

-- 不分词情况下,电影名为"incident red sea",电影名字段高亮显示
GET movie_index/_search
{
  "query": {
   
   "match": {
    "name.keyword": "incident red sea"
  }},
  "highlight": {
   
   "fields": {
   
   "name": {}}}
}

--分词情况下,电影名中匹配有“red”,的电影名字段高亮显示
GET movie_index/_search
{
  "query": {
   
   "match": {
    "name": "red"
  }},
  "highlight": {
   
   "fields": {
   
   "name": {}}}
}

自定义高亮显示标签

GET movie_index/movie/_search
{
    "query":{
      "match": {
   
   "name":"red sea"}
    },
    "highlight": {
      "fields": {
   
   "name":{
   
   "pre_tags": "<span color='red'>","post_tags":"</span>"}}
    }
    
}

25 聚合(aggs)

聚合操作是:“aggs”{“分组新生成字段名”:{分组条件}}

terms分组查询

GET movie_index/movie/_search
{ 
  "aggs": {
    "groupby_actor": {
      "terms": {	
        "field": "actorList.name.keyword"  
      }
    }
  }
}

GET movie_index/_search
{
  "aggs": {
    "groupBy_actor": {
      "terms": {
        "field": "actorList.name.keyword",
        "size": 10
      }
    }
  }
}

image-20201221203242313

每个演员参演电影的平均分是多少,并按评分排序

-- 
GET movie_index/_search
{
  "aggs": {
    "groupby_actor_id": {
      "terms": {
        "field": "actorList.name.keyword",
        "order": {
          "avg_score": "desc"
        }
      },
      "aggs": {
        "avg_score": {
          "avg": {
            "field": "doubanScore"
          }
        }
      }
    }
  }
}

image-20201221204151317

26 批量操作(_bulk)

--批量创建两个文档
POST movie_index/movie/_bulk
{
   
   "index":{
   
   "_id":44}}
{
   
   "id":300,"name":"incident red sea44","doubanScore":5.0,"actorList":[{
   
   "id":44,"name":"zhang san feng"}]}
{
   
   "index":{
   
   "_id":55}}
{
   
   "id":301,"name":"incident red sea44","doubanScore":5.0,"actorList":[{
   
   "id":55,"name":"zhang san feng"}]}

--批量操作:更新 + 删除
POST movie_index/movie/_bulk
{
   
   "update":{
   
   "_id":44}}
{
   
   "doc":{
   
   "name":"zhang sui shan"}}
{
   
   "delete":{
   
   "_id":55}}

4.5 SQL的使用

ElasticSearch SQL是6.3版本以后的功能,能够支持一些最基本的SQL查询语句。

  • 只支持select操作,不支持update、insert、delete
  • 6.3版本之前不支持
  • 不支持窗口函数
  • SQL比DSL有丰富的函数
  • SQL少一些特殊功能,比如高亮、分页
GET _xpack/sql?format=txt
{
  "query":"select actorList.name.keyword, avg(doubanScore) from movie_index  where match(name,'red') group by actorList.name.keyword limit 1 " 
}

image-20201221204705879

4.6 中文分词

  • elasticsearch支持的英文分词,是按照空格分词的
  • elasticsearch本身自带的中文分词,就是单纯把中文一个字一个字的分开,根本没有词汇的概念。

分词器下载网址:https://github.com/medcl/elasticsearch-analysis-ik

还有的分词:smart chinese analysis(官方)、结巴

①安装分词器

步骤1:下载号的zip包,解压后放到…/elasticsearch/plugins/ik

image-20201221210038919

步骤2:关闭es集群

myes.sh stop

步骤3:分发插件到其他的节点

[atguigu@hadoop102 module]$ xsync /opt/module/elasticsearch/plugins/

步骤4:重启es集群

myes.sh start

②测试使用

-- 默认使用:
GET movie_index/_analyze
{  
  "text": "我是中国人"
}

image-20201221210921265

-- 使用ik中文分词器,ik_smart,不重复
GET movie_index/_analyze
{  "analyzer": "ik_smart", 
  "text": "我是中国人"
}
-- ik_max_word,会重复
GET movie_index/_analyze
{
  "analyzer": "ik_max_word",
  "text": "我是中国人"
}

image-20201221210954197

image-20201221211017686

③自定义分词(本地)

步骤1:修改配置文件 IKAnalyzer.cfg.xml

[atguigu@hadoop102 config]$ vim IKAnalyzer.cfg.xml 
  • 自己的扩展字典:可以添加新扩展的词
  • 扩展停止词字典:一些不想作为词的词,比如:我 的 了
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict">./myword.txt</entry>
         <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords"></entry>
        <!--用户可以在这里配置远程扩展字典 -->
        <!--<entry key="remote_ext_dict">http://192.168.255.102:8090/cict</entry> -->
        <!--用户可以在这里配置远程扩展停止词字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>

步骤2:添加新增的扩展词字典

[atguigu@hadoop102 config]$ vim myword.txt 
# 添加内容:
蓝瘦香菇
蓝瘦

步骤3:分发配置config到集群

xsync /opt/module/elasticsearch/plugins/ik/config

步骤4:重启es集群

步骤5:测试

GET _analyze
{
  "text": "蓝瘦香菇",
  "analyzer": "ik_max_word"
}

image-20201222193509594

④自定义分词(远程)

使用nginx

4.7 mapping表定义类型

  • elasticsearch中的type分区的概念,其实也没什么作用,在7.x就取消了。

  • elasticsearch中的类型是通过mapping定义的。

  • elasticsearch的mapping会自动推断出数据格式。默认只有text会进行分词,keyword不会分词

查看mapping

GET movie_index/_mapping/movie

GET movie_index/_mapping

查询结果

{
    
    
  "movie_index" : {
    
    
    "mappings" : {
    
    
      "movie" : {
    
    
        "properties" : {
    
    
          "actorList" : {
    
    
            "properties" : {
    
    
              "id" : {
    
    
                "type" : "long"
              },
              "name" : {
    
    
                "type" : "text",
                "fields" : {
    
    
                  "keyword" : {
    
    
                    "type" : "keyword",
                    "ignore_above" : 256
                  }
                }
              }
            }
          },
          "doubanScore" : {
    
    
            "type" : "float"
          },
          "id" : {
    
    
            "type" : "long"
          },
          "name" : {
    
    
            "type" : "text",
            "fields" : {
    
    
              "keyword" : {
    
    
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    }
  }
}

③建立mapping

PUT movie_chn
{  "settings": {                                               
    "number_of_shards": 1	//设置分片数!
  },
  "mappings": {
    "movie":{
      "properties": {
        "id":{
          "type": "long"
        },
        "name":{
          "type": "text"
          , "analyzer": "ik_smart"	//指定ik中文分词器
        },
        "doubanScore":{
          "type": "double"
        },
        "actorList":{
          "properties": {
            "id":{
              "type":"long"
            },
            "name":{
              "type":"keyword"
            }
          }
        }
      }
    }
  }
}

插入数据

PUT /movie_chn/movie/1
{ "id":1,
  "name":"红海行动",
  "doubanScore":8.5,
  "actorList":[  
  {
   
   "id":1,"name":"张译"},
  {
   
   "id":2,"name":"海清"},
  {
   
   "id":3,"name":"张涵予"}
 ]
}
PUT /movie_chn/movie/2
{
  "id":2,
  "name":"湄公河行动",
  "doubanScore":8.0,
  "actorList":[  
{
   
   "id":3,"name":"张涵予"}
]
}

PUT /movie_chn/movie/3
{
  "id":3,
  "name":"红海事件",
  "doubanScore":5.0,
  "actorList":[  
{
   
   "id":4,"name":"张晨"}
]
}

查询测试

GET /movie_chn/movie/_search
{
  "query": {
    "match": {
      "name": "红海战役"
    }
  }
}

GET /movie_chn/movie/_search
{
  "query": {
    "term": {
      "actorList.name": "张译"
    }
  }
}

4.8 分割索引

分割索引就是根据时间间隔把一个业务索引切分成多个索引。

比如把order_info变成:order_info_20200101、order_info_20200102 …

优点:

  1. 查询范围优化:因为一般情况并不会查询全部时间周期的数据,那么通过切分索引,物理上减少了扫描数据的范围,也是对性能的优化。
  2. 结构变化的灵活性:因为elasticsearch索引不允许对数据结构进行修改。但是实际使用中索引的结构和配置难免变化,最常见的是以天为间隔分割索引,这样每天就有了一次修改索引结构的机会。

4.9 索引别名 _aliases

索引别名可以指向一个或多个索引,也可以让多个索引指向。像mysql中的视图create view aaa as select …

优点:

  1. 给多个索引分组(例如:last_three_months):扩大

    image-20201222195950671

  2. 给索引的一个子集创建视图:缩小

  3. 在运行的集群中可以无缝的从一个索引切换到另一个索引

    -- 类似于mysql中的视图,在代码中写视图名,那么如果业务修改,只需要修改视图和表结构即可。
    create view 'v_base_category' as 
    select * from base_category where id < 10;
    
    select * from base_category where id < 10;
    
    select * from v_base_category where id < 10;
    
    --如果修改了业务数据
    drop view v_base_category;	--删掉原来视图
    --重新创建该视图
    create view 'v_base_category' as 
    select * from base_category11 where id < 10;
    

方式1:建索引的时候创建别名

aliases:{别名} + mapping定义表结构

PUT movie_chn_2020
{
  "aliases": {
    "movie_chn_2020_query": {}
  },
  "mappings": {
    "movie":{
      "properties":{
        "id":{
          "type":"long"
        },
        "name":{
          "type":"text"
        },
        "doubanScore":{
          "type":"double"
        },
        "actorList":{
          "properties":{
            "id":{
              "type":"long"
            },
            "name":{
              "type":"keyword"
            }
          }
        }
      }
    }
  }
}

方式2:为已存在的索引增加别名

POST _aliases
{
  "actions": [
    {
      "add": {
        "index": "teacher_index",
        "alias": "teacher_index_aliases"
      }
    }
  ]
}

-- 这个时候就可以使用别名,查询了
GET teacher_index_aliases/_search

应用1:分组别名

POST _aliases
{
  "actions": [
    {
      "add": {
        "index": "movie_chn",
        "alias": "movie_1001"
      }},
    {
      "add": {
        "index": "movie_index",
        "alias": "movie_1001"
    }}
  ]
}

-- 这样就能通过查询movie_1001查询到movie_chn和movie_index这两个索引中的数据了
GET movie_1001/_search

应用2:通过加过滤条件缩小查找范围,建立一个子集视图

POST _aliases
{
  "actions": [
    {
      "add": {
        "index": "movie_index",
        "alias": "movie_index_aliases",
        "filter": {
          "term": {
            "actorList.id": "3"
          }
        }
        
      }
    }
  ]
}

--查看查询结果
GET movie_index_aliases/_search

应用3:为某个别名进行无缝连接

在运行的集群中可以无缝的从一个索引切换到另一个索引。

POST _aliases
{
  "actions": [
    {
   
   "remove": {
        "index": "movie_chn",
        "alias": "movie_1001"
    }},
    {
      "add": {
        "index": "movie_index",
        "alias": "movie_1001"
      }
    }
  ]
}

①查看别名列表

GET _cat/aliases?v

image-20201222090921841

②删除某个索引的别名

POST _aliases
{
  "actions": [
    {
      "remove": {
        "index": "movie_index",
        "alias": "movie_index_aliases_idg2"
      }
    }
  ]
}

4.10 索引模板 _template

Index Template索引模板,创建索引的模具,可以定义一系列规则来构建符合特定业务需求的索引的mappings(表结构)和settings(分片数和副本数)。

①创建模板

  • “index_patterns:[“movie_test*”] 表示:凡是往movie_test开头的索引写入数据时,如果索引不存在,那么es会根据此模板自动创建索引。
  • “settings”: { “number_of_shards”: 1} 表示:索引的分片数
  • “aliases” : { “{index}-query”: {},“movie_test-query”:{}} 表示:设置两个别名为"{index}-query"和"movie_test-query"
  • mappings:表结构类型
PUT _template/template_movie2020
{
  "index_patterns": ["movie_test*"],
  "settings": {                                               
    "number_of_shards": 1
  },
  "aliases" : { 
    "{index}-query": {},
    "movie_test-query":{}
  },
  "mappings": {                                          
"_doc": {
      "properties": {
        "id": {
          "type": "keyword"
        },
        "movie_name": {
          "type": "text",
          "analyzer": "ik_smart"
        }
      }
    }
  }
}

-- 测试添加数据
POST movie_test_2020/_doc
{
  "id":"333",
  "name":"zhang3"
}

-- 因为设置了两个别名,可以查询这两个别名
GET movie_test_2020-query/_search
GET movie_test-query/_search

-- 也可以直接查这个索引
GET movie_test_2020/_search

②查看所有的模板清单

GET _cat/templates?v

image-20201222123723114

③查看某个模板详情

GET _template/template_movie2020

GET _template/template_movie*

5 shard划分

elasticsearch中的索引一般都是以天为单位建立的,每个索引的shard要合理控制

5.1 shard太多的危害

每个分片都有Lucene索引,这些索引都会消耗cpu和内存。同样的数据,分片越多,额外消耗的cpu和内存就越多。

shard的目的是为了负载均衡让每个节点的硬件充分发挥,但是如果分片太多,在单个节点上的多个shard同时接受请求,并对本节点的资源形成了竞争,实际上反而造成了内耗。

6 IDEA中操作ElasticSearch

客户端:

  • TransportClient,ES的原生客户端,不能执行DSL语句必须使用Java的API方法。(7.0排除)
  • jset,可以使用DSL语句拼成字符串,直接传给服务器,然后返回json字符串再解析。

6.1 导入pom依赖

        <dependency>
            <groupId>io.searchbox</groupId>
            <artifactId>jest</artifactId>
            <version>5.3.3</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            <version>2.11.0</version>
        </dependency>

        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <version>4.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.codehaus.janino</groupId>
            <artifactId>commons-compiler</artifactId>
            <version>2.7.8</version>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>6.6.0</version>
        </dependency>

6.2 获取jset客户端对象

  //定义获取客户端的工厂
  private var factory:JestClientFactory = null

  //获取操作es的java客户端工具类jestClient
  def getJestClient = {
    
    
    if (factory == null){
    
    
      factory = new JestClientFactory

      val httpClientConfig = new HttpClientConfig.Builder("http://hadoop102:9200")
        .multiThreaded(true)  //是否开启多线程处理
        .maxTotalConnection(20) //最多连接数
        .connTimeout(10000)   //连接超时时间
        .readTimeout(1000)    //读取超时时间
        .build()

      factory.setHttpClientConfig(httpClientConfig)

    }
    factory.getObject
  }

6.3 向es插入数据

①方式1:

  • 步骤1:获取客户端连接

  • 步骤2:通过客户端对象执行execute(action: Action)

  • 步骤3:根据业务逻辑,创建action

    Action是一个接口

    image-20201222230604893

    • 这里使用了构造器设计模式:在Index里面有Builder的内部类。
  //向es中的index索引中插入数据
  def putIndex() = {
    
    
    //获取客户端连接
    val jestClient = getJestClient

    val source : String =
      """
        |{
        |  "id":1001,
        |  "name":"天龙八部",
        |  "doubanScore":8.9,
        |  "actorList":[
        |    {"id":1, "name":"张三"},
        |    {"id":2, "name":"李四"}
        |    ]
        |}
        |""".stripMargin

    //创建插入操作对象
    val index = new Index.Builder(source)
      .index("movie_chn_1")
      .`type`("movie")
      .id("1")
      .build()

    //执行插入操作
    jestClient.execute(index)

    //关闭连接
    jestClient.close()
  }

②方式2:封装样例类

根据方式1:写出来document之后,再封装样例类,插入数据。

生产环境规范是方式2.

case class Movie(
                id: Long,
                name: String,
                doubanScore: Double,
                actorList:java.util.List[java.util.Map[String, Any]]
                )
  //方法2:使用样例类(用来封装document中的属性) 向Index中插入数据
  def putIndex2 = {
    
    
    //获取客户端连接
    val jestClient = getJestClient

    val actorList = new util.ArrayList[util.Map[String, Any]]()

    val actor1 = new util.HashMap[String, Any]()
    actor1.put("id", 111)
    actor1.put("name", "张三丰")
    val actor2 = new util.HashMap[String, Any]()
    actor2.put("id", 222)
    actor2.put("name", "张无忌")

    actorList.add(actor1)
    actorList.add(actor2)

    //
    val movie = Movie(
      666,
      "一部好看的电影",
      10.0,
      actorList
    )

    val index = new Index.Builder(movie)
      .index("movie_chn_1")
      .`type`("movie")
      .id("2")
      .build()

    //执行插入操作
    jestClient.execute(index)


    //关闭资源
    jestClient.close()
  }

6.4 从es查询一条数据

  def getIndexById() = {
    
    

    //获取连接
    val jestClient = getJestClient

    //创建Get查询操作对象
    val get = new Get.Builder("movie_chn_1", "2").build()


    //执行查询操作
    val documentResult: DocumentResult = jestClient.execute(get)
    println(documentResult.getJsonString)

    //关闭连接
    jestClient.close()

  }

image-20201223002011280

6.5 从es查询多条数据

方式1


  //根据条件,从es索引中查询出符合条件的所有文档
  def getIndexByCondition() = {
    
    
    //获取连接
    val jestClient = getJestClient


    val query: String =
      """
        |{
        |    "query":{
        |        "bool":{
        |          "filter":[ {"term": {  "actorList.id": "1"  }},
        |                     {"term": {  "actorList.id": "3"  }}
        |           ],
        |           "must":{"match":{"name":"red"}}
        |         }
        |    }
        |}
        |""".stripMargin


    val search = new Search.Builder(query)
      .addIndex("movie_index")
      .build()


    //执行查询操作
    val searchResult: SearchResult = jestClient.execute(search)

    //对查询结果进行处理
    val resList: util.List[SearchResult#Hit[util.Map[String, Any], Void]] = searchResult.getHits(classOf[util
    .Map[String, Any]])

    //将java集合转换成Scala集合
    import scala.collection.JavaConverters._
    val list: List[util.Map[String, Any]] = resList.asScala.map(_.source).toList
    println(list.mkString("\n"))


    //关闭连接
    jestClient.close()
  }

方式2:利用SearchSourceBuilder

  //方式2:根据条件,从es索引中查询出符合条件的所有文档:利用SearchSourceBuilder
  def getIndexByCondition2() = {
    
    

    //获取jest客户端对象
    val jestClient = getJestClient


    //创建SearchSourceBuilder对象
    val searchSourceBuilder = new SearchSourceBuilder()

    //创建QueryBuilder对象
    val boolQueryBuilder = new BoolQueryBuilder()
    boolQueryBuilder.must(new MatchQueryBuilder("name", "red"))
    boolQueryBuilder.filter(new TermQueryBuilder("actorList.name.keyword", "zhang han yu"))


    searchSourceBuilder.query(boolQueryBuilder)
    searchSourceBuilder.from(0)
    searchSourceBuilder.size(20)
    searchSourceBuilder.sort("doubanScore", SortOrder.DESC)
    searchSourceBuilder.highlighter(new HighlightBuilder().field("name"))


    val query : String = searchSourceBuilder.toString()
//    println(query)

    //创建查询对象
    val search = new Search.Builder(query).build()


    //执行查询操作:对结果处理
    val searchResult = jestClient.execute(search)
    val resList: util.List[SearchResult#Hit[util.Map[String, Any], Void]] = searchResult.getHits(classOf[util
    .Map[String, Any]])

    //将java集合转化为scala集合进行操作
    import scala.collection.JavaConverters._
    val list = resList.asScala.map(_.source).toList

    println(list.mkString("\n"))


    //关闭客户端连接
    jestClient.close()

  }

猜你喜欢

转载自blog.csdn.net/qq_43523503/article/details/112549788