栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 大数据系统

Elasticsearch

Elasticsearch

Elasticsearch

1. 了解搜索技术

1.1. 什么是搜索1.2. 新业务需求1.3. 搜索引擎1.4. 倒排索引1.5. 认识lucene1.6. 什么是全文检索 2. elasticsearch

2.1. elastic2.2. elasticsearch2.3. 下载2.4. 安装elasticsearch

2.4.1. 安装2.4.2. 配置elasticsearch2.4.3. 重启2.4.4. 测试2.4.3. cluster_block_exception 2.5. 安装kibana2.6. IK分词器

2.6.1. 中文问题2.6.2. 中文分词器2.6.3. IK分词器2.6.4. 自定义词库 3. elasticsearch基本操作

3.1. 基本概念3.2. 索引操作(indeces)

3.2.1. 查询索引3.2.2. 创建索引3.2.3. 查看索引具体信息3.2.4. 删除索引 3.3. 映射配置(_mapping)

3.3.1. 创建映射字段3.3.2. 查看映射关系3.3.3. 字段属性详解

3.3.3.1. type3.3.3.2. index3.3.3.3. store 3.4. 新增文档(document)

3.4.1. 自动生成id3.4.2. 自定义id3.4.3. 智能判断 3.6. 修改数据

3.6.1. 整体覆盖3.6.2. 更新字段 3.7. 删除数据 4. 查询

4.1. 数据准备4.2. 匹配查询(match)4.3. 词条查询(term)4.4. 范围查询(range)4.5. 模糊查询(fuzzy)4.6. 布尔组合(bool)4.7. 过滤(filter)4.8. 排序(sort)4.9. 分页(from/size)4.10. 高亮(highlight)4.11. 结果过滤(_source) 5. 聚合(aggregations)

5.1 基本概念5.2 聚合为桶5.3 桶内度量5.4 桶内嵌套桶 6. Java高级别REST客户端1、新建SpringBoot项目整合es

1.1 引入:web和elasticsearch的场景启动器1.2 配置elasticsearch1.3 测试获取elasticsearch的高级别客户端 2、文档CRUD

2.1 同步新增文档2.2 异步新增文档2.3 判断文档是否存在2.4 指定id查询文档2.5 查询所有文档2.6 分页查询2.7 高亮2.8 更新文档

1. 了解搜索技术 1.1. 什么是搜索

什么是搜索, 计算机根据用户输入的关键词进行匹配,从已有的数据库中摘录出相关的记录反馈给用户。
线性匹配:
select * from item where title like ’%小米%‘

1.2. 新业务需求

比如,用户在百度文本框中输入,“吃饭睡觉写程序”,从结果可以发现百度搜索具备以下明显特点:
1、即使在相关结果数量接近3600万时,也能快速得出结果。

2、搜索的结果不仅仅局限于完整的“吃饭睡觉写程序”这一短语,而是将此短语拆分成,“写程序”,“吃饭”,“睡觉”,“程序”等关键字。

3、对拆分后的搜索关键字进行标红显示。

4、即使只满足部分关键字也能查询出来

5、即使输错一两个字母也能查询出来,例如:搜索facebool,能查询到Facebook

问题:上述功能,使用大家以前学过的数据库搜索能够方便实现吗?

1.3. 搜索引擎

搜索引擎(search engine)是一种信息检索系统,旨在协助搜索存储在计算机系统中的信息。

搜索引擎按照功能通常分为垂直搜索和综合搜索。

​ 1、垂直搜索是指专门针对某一类信息进行搜索。例如:会搜网 主要做商务搜索的,并且提供商务信息。除此之外还有爱看图标网、职友集等。

​ 2、综合搜索是指对众多信息进行综合性的搜索。例如:百度、谷歌、搜狗、360搜索等。

​ 3、站内搜索是指对网站内的信息进行的搜索。例如:京东、招聘网站等

​ 4、软件内部搜索,例如word、eclipse、idea等

1.4. 倒排索引

搜索引擎目前主流的实现原理:倒排索引技术

倒排索引又叫反向索引以字或词为关键字进行索引,表中关键字所对应的记录表项,记录了出现这个字或词的所有文档,每一个表项记录该文档的编号和关键字在该文档中出现的位置情况。

在实际的运用中,我们可以对数据库中原始的数据结构(如:商品表),在业务空闲时,事先生成文档列表(左图)及倒排索引区域(右图)。

用户有查询需求时,先访问倒排索引数据区域(右图),得出文档编号后,通过文档编号即可快速,准确的通过左图找到具体的文档内容。
例如:用户输入“跳槽”关键字,先到右图的索引区查询,找到1,4;再根据id=1和id=4到左图找到两条记录。整个过程走的都是索引,比传统的链式匹配更加快速。

1.5. 认识lucene

倒排索引技术只是底层原理,我们可以自己写代码实现。也可以使用开源组织写好的方案:lucene。

官网:http://lucene.apache.org

Lucene是一套用于全文检索和搜寻的开源程序库,由Apache软件基金会支持和提供

Lucene提供了一个简单却强大的应用程序接口(API),能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码工具

Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品。例如:solr和elasticsearch

1.6. 什么是全文检索

倒排索引是全文检索技术的一种实现方式。

2. elasticsearch

lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来的应用。

lucene是类似于servlet,而搜索引擎就是tomcat 。

目前市面上流行的搜索引擎软件,主流的就两款:elasticsearch和solr。这两款都是基于lucene的搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了服务器安装、部署、管理、集群以外,对于数据的操作,修改、添加、保存、查询等等都十分类似。就好像都是支持sql语言的两种数据库软件。只要学会其中一个另一个很容易上手。

从实际企业使用情况来看,elasticSearch的市场份额逐步在取代solr,国内百度、京东、新浪都是基于elasticSearch实现的搜索功能。国外就更多了 像维基百科、GitHub、Stack Overflow等等也都是基于ES的。

2.1. elastic

Elastic官网:https://www.elastic.co/cn/

中文文档:https://elasticsearch.apachecn.org/#/

Elastic有一条完整的产品线及解决方案:Elasticsearch、Kibana、Logstash等,前面说的三个就是大家常说的ELK技术栈。实现企业海量日志的处理分析的解决方案。大数据领域的重要一份子。

2.2. elasticsearch

这里使用6.8.1版本。

2.3. 下载

这里只学习elasticsearch和kibana,官网下载地址如下:

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

kibana:https://www.elastic.co/cn/downloads/past-releases#kibana

ik分词器:https://github.com/medcl/elasticsearch-analysis-ik/releases

可以去官网下载特定版本,建议和我的版本一致。6.8.1版本

注意:kibana、elasticsearch和IK分词器的版本号要一致,否则可能带来兼容性问题

另外,需要jdk1.8以上环境。

2.4. 安装elasticsearch

把课前资料中的elasticsearch和kibana拷贝到/opt目录下

2.4.1. 安装

    安装elasticsearch:rpm -ivh elasticsearch-6.8.1.rpm

    为elasticsearch配置jdk:vim /etc/sysconfig/elasticsearch

2.4.2. 配置elasticsearch

切换到/etc/elasticsearch目录下,看到两个配置文件jvm.options和elasticsearch.yml
首先修改jvm.options。elasticsearch默认占用所有内存,导致虚拟机很慢,可以改的小一点。

vim /etc/elasticsearch/jvm.options

两个值必须相等

若不想动,会报错

修改elasticsearch.yml配置文件

修改yml配置的注意事项:

​ 每行必须顶格,不能有空格

​ “:”后面必须有一个空格

    集群名称,同一集群名称必须相同(可选,集群必须配置)

    单个节点名称 (可选,集群必须配置)

    默认只允许本机访问,修改为0.0.0.0后则可以远程访问;端口使用默认:9200

    把bootstrap自检程序关掉

bootstrap.memory_lock: false
bootstrap.system_call_filter: false
    配置集群列表,这里只有一个。可以配置计算机名,也可以配置ip

主机名是你计算机名,一定不可以写错!!
可以通过hostname查看主机名


如果没有则可以自己写一个:

2.4.3. 重启

启动elasticsearch:systemctl start elasticsearch
centos6:service elasticsearch start

如果启动未成功,请去查看相关日志

vim /var/log/elasticsearch/{cluster-name}.log

例如:这里cluster-name配置的是my-es,那么就是指:vim /var/log/elasticsearch/my-es.log文件

2.4.4. 测试



如果访问失败:

​ 原因1:防火墙可能没有关闭

​ 原因2:配置文件 network.hosts修改错误或者 集群节点主机名配置错误

2.4.3. cluster_block_exception

如果elasticsearch服务器硬盘剩余空间不足5%,会抛出cluster_block_exception异常,解决方案参照官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/6.2/disk-allocator.html

在elasticsearch.yml中取消硬盘检查。

在elasticsearch.yml配置文件中末尾,追加这个配置:

vim /etc/elasticsearch/elasticsearch.yml 
cluster.routing.allocation.disk.threshold_enabled: false
2.5. 安装kibana

Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。

解压缩进入kibana的config目录:

tar -zxvf kibana-6.8.1-linux-x86_64.tar.gz

修改配置文件,允许访问的ip地址

​ 修改为虚拟机IP地址

配置elasticsearch服务器列表:

启动:切换到kibana的bin目录下

./kibana   # 会输出日志,并独占当前窗口
nohup ./kibana &  # 后台启动

日志文件就生成在bin目录下

注意:不要使用ps -ef | grep kibana查询kibana进程,因为kibana的进程不叫kibana

必须先启动elasticSearch 启动kibana后才可以访问

测试:通过浏览器访问kibana

点击左边菜单DevTools

在Console中,执行 get _cluster/health

右边的结果中,status为yellow或者green。

表示es启动正常,并且与kibana连接正常。

启动kibana

2.6. IK分词器 2.6.1. 中文问题

大家先不管语法,我们先测试一波。

在kibana控制台输入下面的请求:

GET _analyze
{
  "text":"中国人的态度"
}

结果:

{
  "tokens" : [
    {
      "token" : "中",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "",
      "position" : 0
    },
    {
      "token" : "国",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "",
      "position" : 1
    },
    {
      "token" : "人",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "",
      "position" : 2
    },
    {
      "token" : "的",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "",
      "position" : 3
    },
    {
      "token" : "态",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "",
      "position" : 4
    },
    {
      "token" : "度",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "",
      "position" : 5
    }
  ]
}

2.6.2. 中文分词器

elasticsearch本身自带的中文分词,就是单纯把中文一个字一个字的分开,根本没有词汇的概念。但是实际应用中,用户都是以词汇为条件,进行查询匹配的,如果能够把文章以词汇为单位切分开,那么与用户的查询条件能够更贴切的匹配上,查询速度也更加快速。

因此一般我们会用第三方提供的分词器
IK分词器使用最多。

2.6.3. IK分词器

Lucene的IK分词器早在2012年已经没有维护了,现在我们要使用的是在其基础上维护升级的版本,并且开发为ElasticSearch的集成插件了,与Elasticsearch一起维护升级,版本也保持一致:6.8.1。

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases

上传课前资料中的elasticsearch-analysis-ik-6.8.1.zip包到/usr/share/elasticsearch/plugins目录下,并解压到:/usr/share/elasticsearch/plugins/

unzip elasticsearch-analysis-ik-6.8.1.zip -d ik-analyzer

重启elasticsearch :

systemctl restart elasticsearch.service

再次测试:

GET _analyze
{
  "analyzer": "ik_smart", 
  "text": "中国人的态度"
}

ik分词器还提供了更详尽的分词方式:ik_max_word

结果:

{
  "tokens" : [
    {
      "token" : "中国人",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "的",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "态度",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 2
    }
  ]
}

2.6.4. 自定义词库

中文语言在不断发展,新的词汇不断涌现,怎么不断完善分词需求?

ik分词器提供了重要的扩展能力:

扩展词典(新创建词功能): 有些词IK分词器不识别 例如:“尚硅谷”,“蓝瘦香菇”,“碉堡了”

停用词典(停用某些词功能): 有些词不需要建立索引 例如:“哦”,“啊”,“的”

elasticsearch加载ik分词器插件时,ik会读取一个配置文件,这个配置文件在ik分词器根目录的config目录下:

打开该配置文件:

这里有两种方式配置扩展词典和停用词典:

    本地方式:ext_dict配置扩展词典,ext_stopwords配置停用词典。远程方式:tomcat或者nginx

第一种方式每次修改词典都要重启搜索服务,不推荐。推荐使用nginx的方式,并发量大,修改内容不需要重启。

第一步:利用nginx搭建远程词库。

vim /usr/local/nginx/conf/nginx.conf

需要在nginx根目录下创建对应的elasticsearch目录

mkdir -p /usr/local/nginx/elasticsearch/ext
# 创建扩展字典文件
vim /usr/local/nginx/elasticsearch/ext/ext_dict.txt

添加扩展词典,每行一个关键词

第二步:在ik分词器中引用远程词库

进入ik分词器的conf目录:cd /usr/share/elasticsearch/plugins/ik-analyzer/config/

vim /usr/share/elasticsearch/plugins/ik-analyzer/config/IKAnalyzer.cfg.xml

重启elasticsearch服务,再次测试

service elasticsearch restart 

分词效果就是好!​

添加新词条后,es只会对新增的数据用新词分词。历史数据是不会重新分词的。如果想要历史数据重新分词。需要执行:

POST {index}/_update_by_query?conflicts=proceed
3. elasticsearch基本操作 3.1. 基本概念

Elasticsearch也是基于Lucene的全文检索库,本质也是存储数据,很多概念与MySQL类似的。

说明
cluster整个elasticsearch 默认就是集群状态,整个集群是一份完整、互备的数据。
node集群中的一个节点,一般只一个进程就是一个node
shard分片,即使是一个节点中的数据也会通过hash算法,分成多个片存放,默认是5片。
index索引。相当于rdbms的database, 对于用户来说是一个逻辑数据库,虽然物理上会被分多个shard存放,也可能存放在多个node中。
type类似于rdbms的table,但是与其说像table,其实更像面向对象中的class , 同一Json的格式的数据集合。
document文档。类似于rdbms的 row、面向对象里的object
field字段。相当于字段、属性
mappings映射。字段的数据类型、属性、是否索引、是否存储等特性

对比关系:

索引(indices)----------------------Databases 数据库

  类型(type)--------------------------Table 数据表

     文档(document)----------------------Row 行

	    字段(Field)-------------------------Columns 列 

数据结构对比

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

public class Actor{
    String id;
    String name;
}

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

所以他保存到es

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

要注意的是:Elasticsearch本身就是分布式的,因此即便你只有一个节点,Elasticsearch默认也会对你的数据进行分片和副本操作,当你向集群添加新数据时,数据也会在新加入的节点中进行平衡。

3.2. 索引操作(indeces) 3.2.1. 查询索引

查看es中有哪些索引库:

GET /_cat/indices
GET /_cat/indices?v

es 中会默认存在一个名为.kibana和.kibana_task_manager的索引

表头的含义

字段名含义说明
healthgreen(集群完整) yellow(单点正常、集群不完整) red(单点不正常)
status是否能使用
index索引名
uuid索引统一编号
pri主节点几个
rep从节点几个
docs.count文档数
docs.deleted文档被删了多少
store.size整体占空间大小
pri.store.size主节点占
3.2.2. 创建索引
PUT /索引名


参数可选:指定分片及副本,默认分片为5,副本为2。

{
    "settings": {
        "number_of_shards": 3,
        "number_of_replicas": 3
      }
}

演示:说明索引创建成功

3.2.3. 查看索引具体信息
GET /索引名

或者,我们可以使用*来查询所有索引具体信息

3.2.4. 删除索引
DELETE /索引库名
3.3. 映射配置(_mapping)

索引有了,接下来肯定是添加数据。但是,在添加数据之前必须定义映射。

什么是映射?

映射是定义文档的过程,文档包含哪些字段,这些字段是否保存,是否索引,是否分词等

只有配置清楚,Elasticsearch才会帮我们进行索引库的创建(不一定)

3.3.1. 创建映射字段
PUT /索引库名/_mapping/类型名称
{
  "properties": {
    "字段名": {
      "type": "类型",
      "index": true,
      "store": true,
      "analyzer": "分词器"
    }
  }
}

类型名称:就是前面将的type的概念,类似于数据库中的不同表

字段名:类似于列名,properties下可以指定许多字段。

每个字段可以有很多属性。例如:

type:类型,可以是text、long、short、date、integer、object、keyword等

​ 注意:keyword也表示文本,用来保存类似邮箱、地址、状态等,不会分词但是常用。

index:是否索引,默认为true

store:是否存储,默认为false

analyzer:分词器,这里使用ik分词器:ik_max_word或者ik_smart

示例

3.3.2. 查看映射关系

语法:

GET /索引库名/_mapping

示例:

3.3.3. 字段属性详解 3.3.3.1. type

Elasticsearch中支持的数据类型非常丰富
几个关键的:

String类型,又分两种:

text:可分词,不可参与聚合keyword:不可分词,数据会作为完整字段进行匹配,可以参与聚合

Numerical:数值类型,分两类

基本数据类型:long、interger、short、byte、double、float、half_float浮点数的高精度类型:scaled_float

需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。

Date:日期类型

elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。

3.3.3.2. index

index影响字段的索引情况。

true:字段会被索引,则可以用来进行搜索。默认值就是truefalse:字段不会被索引,不能用来搜索

index的默认值就是true,也就是说你不进行任何配置,所有字段都会被索引。

但是有些字段是我们不希望被索引的,比如商品的图片信息,就需要手动设置index为false。

3.3.3.3. store

是否将数据进行额外存储。

Elasticsearch在创建文档索引时,会将文档中的原始数据备份,保存到一个叫做_source的属性中。而且我们可以通过过滤_source来选择哪些要显示,哪些不显示。

而如果设置store为true,就会在_source以外额外存储一份数据,多余,因此一般我们都会将store设置为false,事实上,store的默认值就是false。

3.4. 新增文档(document)

有了索引、类型和映射,就可以对文档做增删改查操作了。

3.4.1. 自动生成id

语法:

POST /索引库名/类型名
{
    "key":"value"
}

示例

POST /atguigu/goods
{
  "title": "华为P40PRO",
  "images": "https://huawei.com/pimages/10.jpg",
  "price": 4488
}

查询看看结果:

GET /atguigu/_search

_source:源文档信息,所有的数据都在里面。_id:这条文档的唯一标示,与文档自己的id字段没有关联

3.4.2. 自定义id

如果我们想要自己新增的时候指定id,可以这么做:

POST /索引库名/类型/id值
{
    ...
}


3.4.3. 智能判断

事实上Elasticsearch非常智能,你不需要给索引库设置任何mapping映射,它也可以根据你输入的数据来判断类型,动态添加数据映射。
测试一下:

POST /atguigu/goods/2
{
    "title":"小米手机",
    "images":"http://image.jd.com/12479122.jpg",
    "price":2899,
    "stock": 200,
    "saleable":true,
    "attr": {
        "category": "手机",
        "brand": "小米"
    }
}

我们额外添加了stock库存,saleable是否上架,attr其他属性几个字段。


stock,saleable,attr都被成功映射了。

如果是字符串类型的数据,会添加两种类型:text + keyword。如上例中的category 和 brand

3.6. 修改数据

修改数据分为,整体覆盖和修改某一个字段。

3.6.1. 整体覆盖

把刚才新增的请求方式改为PUT。不过必须指定id,

id对应文档存在,则修改id对应文档不存在,则新增

比如,我们把id为2的数据进行修改:

这种方式必须有所有字段,否则会导致更新后的数据字段缺失。

3.6.2. 更新字段

更新使用POST请求

语法:

POST /{index}/{type}/{id}/_update
{
	"doc": {
		字段名: 字段值
	}
}


3.7. 删除数据

删除使用DELETE请求,同样,需要根据id进行删除:

语法

DELETE /索引库名/类型名/id值

4. 查询

之前已经见识了查询功能

查询所有:

GET /{index}/_search

根据id查询:

GET /{index}/{type}/{id}

除了上述简单查询之外。elasticsearch作为搜索引擎,最复杂最强大的功能就是搜索查询功能。包括:匹配查询、词条查询、模糊查询、组合查询、范围查询、高亮、排序、分页等等查询功能。

基本查询语法如下:

GET /索引库名/_search
{
    "query":{
        "查询类型":{
            "查询条件":"查询条件值"
        }
    }
}

这里的query代表一个查询对象,里面可以有不同的查询属性

查询类型:

例如:match_all, match,term , range 等等 查询条件:查询条件会根据类型的不同,写法也有差异,后面详细讲解

查询结果:

took:查询花费时间,单位是毫秒time_out:是否超时_shards:分片信息hits:搜索结果总览对象

total:搜索到的总条数max_score:所有结果中文档得分的最高分hits:搜索结果的文档对象数组,每个元素是一条搜索到的文档信息

_index:索引库_type:文档类型_id:文档id_score:文档得分_source:文档的源数据 4.1. 数据准备

POST /atguigu/goods/_bulk
{"index":{"_id":1}}
{ "title":"小米手机", "images":"http://image.jd.com/12479122.jpg", "price":1999, "stock": 200, "attr": { "category": "手机", "brand": "小米" } }
{"index":{"_id":2}}
{"title":"超米手机", "images":"http://image.jd.com/12479122.jpg", "price":2999, "stock": 300, "attr": { "category": "手机", "brand": "小米" } }
{"index":{"_id":3}}
{ "title":"小米电视", "images":"http://image.jd.com/12479122.jpg", "price":3999, "stock": 400, "attr": { "category": "电视", "brand": "小米" } }
{"index":{"_id":4}}
{ "title":"小米笔记本", "images":"http://image.jd.com/12479122.jpg", "price":4999, "stock": 200, "attr": { "category": "笔记本", "brand": "小米" } }
{"index":{"_id":5}}
{ "title":"华为手机", "images":"http://image.jd.com/12479122.jpg", "price":3999, "stock": 400, "attr": { "category": "手机", "brand": "华为" } }
{"index":{"_id":6}}
{ "title":"华为笔记本", "images":"http://image.jd.com/12479122.jpg", "price":5999, "stock": 200, "attr": { "category": "笔记本", "brand": "华为" } }
{"index":{"_id":7}}
{ "title":"荣耀手机", "images":"http://image.jd.com/12479122.jpg", "price":2999, "stock": 300, "attr": { "category": "手机", "brand": "华为" } }
{"index":{"_id":8}}
{ "title":"oppo手机", "images":"http://image.jd.com/12479122.jpg", "price":2799, "stock": 400, "attr": { "category": "手机", "brand": "oppo" } }
{"index":{"_id":9}}
{ "title":"vivo手机", "images":"http://image.jd.com/12479122.jpg", "price":2699, "stock": 300, "attr": { "category": "手机", "brand": "vivo" } }
{"index":{"_id":10}}
{ "title":"华为nova手机", "images":"http://image.jd.com/12479122.jpg", "price":2999, "stock": 300, "attr": { "category": "手机", "brand": "华为" } }
4.2. 匹配查询(match)

匹配所有

GET /atguigu/_search
{
    "query":{
        "match_all": {}
    }
}

query:代表查询对象match_all:代表查询所有

条件匹配

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  }
}

查询出很多数据,不仅包括小米手机,而且与小米或者手机相关的都会查询到,说明多个词之间是or的关系。

某些情况下,我们需要更精确查找,我们希望这个关系变成and,可以这样做:

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": {
        "query": "小米手机",
        "operator": "and"
      }
    }
  }
}

查询结果:

{
  "took" : 26,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.7037868,
    "hits" : [
      {
        "_index" : "atguigu",
        "_type" : "goods",
        "_id" : "1",
        "_score" : 1.7037868,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://image.jd.com/12479122.jpg",
          "price" : 1999,
          "stock" : 200,
          "attr" : {
            "category" : "手机",
            "brand" : "小米"
          }
        }
      }
    ]
  }
}

子属性匹配

GET /atguigu/_search
{
  "query": {
    "match": {
      "attr.brand": "小米"
    }
  }
}

短句匹配

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

GET /atguigu/_search
{
  "query": {
    "match_phrase": {
      "title": "小米手机"
    }
  }
}

多字段匹配

match只能根据一个字段匹配查询,如果要根据多个字段匹配查询可以使用multi_match

GET /atguigu/_search
{
    "query":{
        "multi_match": {
            "query": "小米",
            "fields": ["title", "attr.brand.keyword"]
        }
	}
}
4.3. 词条查询(term)

查询时必须要保证匹配的字段建立了索引。

term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串。

GET /atguigu/_search
{
  "query": {
    "term": {
      "price": {
        "value": 4999
      }
    }
  }
}

多词条查询

terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:

GET /atguigu/_search
{
  "query": {
    "terms": {
      "price": ["1999","3999","4999"]
    }
  }
}
4.4. 范围查询(range)

range 查询找出那些落在指定区间内的数字或者时间

GET /atguigu/_search
{
    "query":{
        "range": {
            "price": {
                "gte":  1000,
                "lt":   3000
            }
    	}
    }
}

range查询允许以下字符:

操作符说明
gt大于
gte大于等于
lt小于
lte小于等于
4.5. 模糊查询(fuzzy)

fuzzy 允许用户搜索词条与实际词条的拼写出现偏差,但是偏差的编辑距离不得超过2:

GET /atguigu/_search
{
  "query": {
    "fuzzy": {
      "title": "oppe"
    }
  }
}

上面的查询,也能查询到apple手机

可以通过fuzziness来指定允许的编辑距离:

GET /atguigu/_search
{
  "query": {
    "fuzzy": {
      "title": {
        "value": "oppe",
        "fuzziness": 1
      }
    }
  }
}

编辑距离:从错误的词到正确词条需要修改的次数。例如:oppe—>oppo,需要修改一次,编辑距离就是1。

elasticsearch支持的最大编辑距离是2。

4.6. 布尔组合(bool)

布尔查询又叫组合查询

bool把各种其它查询通过must(与)、must_not(非)、should(或)的方式进行组合

GET /atguigu/_search
{
    "query":{
        "bool":{
        	"must": [
        	  {
        	    "range": {
        	      "price": {
        	        "gte": 1000,
        	        "lte": 3000
        	      }
        	    }
        	  },
        	  {
        	    "range": {
        	      "price": {
        	        "gte": 2000,
        	        "lte": 4000
        	      }
        	    }
        	  }
        	]
        }
    }
}

注意:一个组合查询里面只能出现一种组合,不能混用

4.7. 过滤(filter)

所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter方式:

GET /atguigu/_search
{
  "query": {
    "bool": {
      "must": {
        "match": { "title": "小米手机" }
      },
      "filter": {
        "range": {
          "price": { "gt": 2000, "lt": 3000 }
        }
      }
    }
  }
}

注意:filter中还可以再次进行bool组合条件过滤。

4.8. 排序(sort)

sort 可以让我们按照不同的字段进行排序,并且通过order指定排序的方式

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  },
  "sort": [
    {
      "price": { "order": "desc" }
    },
    {
      "_score": { "order": "desc"}
    }
  ]
}
4.9. 分页(from/size)
GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  },
  "from": 2,
  "size": 2
}

from:从那一条开始,索引值从0开始计算

size:取多少条

4.10. 高亮(highlight)

查看百度高亮的原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-clIrjUGk-1646445159596)(assets/1563258499361.png)]

发现:高亮的本质是给关键字添加了标签,在前端再给该标签添加样式即可。

GET /atguigu/_search
{
  "query": {
    "match": {
      "title": "小米"
    }
  },
  "highlight": {
    "fields": {"title": {}}, 
    "pre_tags": "",
    "post_tags": ""
  }
}

fields:高亮字段

pre_tags:前置标签

post_tags:后置标签

4.11. 结果过滤(_source)

默认情况下,elasticsearch在搜索的结果中,会把文档中保存在_source的所有字段都返回。

如果我们只想获取其中的部分字段,可以添加_source的过滤

GET /atguigu/_search
{
  "_source": ["title","price"],
  "query": {
    "term": {
      "price": 2699
    }
  }
}

返回结果,只有两个字段

5. 聚合(aggregations)

聚合可以让我们极其方便的实现对数据的统计、分析。例如:

什么品牌的手机最受欢迎?这些手机的平均价格、最高价格、最低价格?这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现实时搜索效果。

5.1 基本概念

Elasticsearch中的聚合,包含多种类型,最常用的两种,一个叫桶,一个叫度量:

桶(bucket)

桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个桶,例如我们根据国籍对人划分,可以得到中国桶、英国桶,日本桶……或者我们按照年龄段对人进行划分:010,1020,2030,3040等。

Elasticsearch中提供的划分桶的方式有很多:

Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组Histogram Aggregation:根据数值阶梯分组,与日期类似Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组……

bucket aggregations 只负责对数据进行分组,并不进行计算,因此往往bucket中往往会嵌套另一种聚合:metrics aggregations即度量

度量(metrics)

分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量

比较常用的一些度量聚合方式:

Avg Aggregation:求平均值Max Aggregation:求最大值Min Aggregation:求最小值Percentiles Aggregation:求百分比Stats Aggregation:同时返回avg、max、min、sum、count等Sum Aggregation:求和Top hits Aggregation:求前几Value Count Aggregation:求总数…… 5.2 聚合为桶

首先,我们按照手机的品牌attr.brand.keyword来划分桶

GET /atguigu/_search
{
    "size" : 0,
    "aggs" : { 
        "brands" : { 
            "terms" : { 
              "field" : "attr.brand.keyword"
            }
        }
    }
}

size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率aggs:声明这是一个聚合查询,是aggregations的缩写

brands:给这次聚合起一个名字,任意。

terms:划分桶的方式,这里是根据词条划分

field:划分桶的字段,从数据属性中挑选的

hits:查询结果为空,因为我们设置了size为0aggregations:聚合的结果brands:我们定义的聚合名称buckets:查找到的桶,每个不同的品牌字段值都会形成一个桶

key:这个桶对应的品牌字段的值doc_count:这个桶中的文档数量 5.3 桶内度量

前面的例子告诉我们每个桶里面的文档数量,这很有用。 但通常,我们的应用需要提供更复杂的文档度量。 例如,每种品牌手机的平均价格是多少?

因此,我们需要告诉Elasticsearch使用哪个字段,使用何种度量方式进行运算,这些信息要嵌套在桶内,度量的运算会基于桶内的文档进行

现在,我们为刚刚的聚合结果添加 求价格平均值的度量:

GET /atguigu/_search
{
    "size" : 0,
    "aggs" : { 
        "brands" : { 
            "terms" : { 
              "field" : "attr.brand.keyword"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                }
            }
        }
    }
}

aggs:我们在上一个aggs(brands)中添加新的aggs。可见度量也是一个聚合avg_price:聚合的名称avg:度量的类型,这里是求平均值field:度量运算的字段

可以看到每个桶中都有自己的avg_price字段,这是度量聚合的结果

5.4 桶内嵌套桶

刚刚的案例中,我们在桶内嵌套度量运算。事实上桶不仅可以嵌套运算, 还可以再嵌套其它桶。也就是说在每个分组中,再分更多组。

比如:我们想统计每个品牌都生产了那些产品,按照attr.category.keyword字段再进行分桶

GET /atguigu/_search
{
    "size" : 0,
    "aggs" : { 
        "brands" : { 
            "terms" : { 
              "field" : "attr.brand.keyword"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                },
                "categorys": {
                  "terms": {
                    "field": "attr.category.keyword"
                  }
                }
            }
        }
    }
}

我们可以看到,新的聚合categorys被嵌套在原来每一个brands的桶中。每个品牌下面都根据 attr.category.keyword字段进行了分组我们能读取到的信息:

华为有4中产品华为产品的平均售价是 3999.0美元。其中3种手机产品,1种笔记本产品 6. Java高级别REST客户端

Jest 客户端:java操作es的原生客户端 , 已经淘汰

Java Low Level RestClient: java操作es的低级别客户端,一般不用

Java高级别REST客户端(The Java High Level REST Client)以后简称高级客户端,内部仍然是基于低级客户端。它提供了更多的API,接受请求对象作为参数并返回响应对象,由客户端自己处理编码和解码。

创建module引入依赖,修改springboot版本为2.2.2

1、新建SpringBoot项目整合es 1.1 引入:web和elasticsearch的场景启动器


注意:由于使用的es版本为6.8.1,所以降级springboot项目版本为2.2.x

1.2 配置elasticsearch
spring.application.name=es-demo
server.port=9000
# es的地址+访问端口号
spring.elasticsearch.rest.uris=http://192.168.1.168:9200
1.3 测试获取elasticsearch的高级别客户端

RestHighLevelClient为es的高级别REST客户端,只要配置了es的参数,项目启动时自动配置会初始化客户端对象注入到容器中。Controller中可以自动装配获取使用。

@RestController
public class ESController {
    @Autowired
    RestHighLevelClient client;

    @GetMapping("/helloes")
    public Object helloes(){
        try {
            //info:获取es节点的信息
            return client.info(RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "fail";
    }
}
2、文档CRUD

es6.5以后一个索引下只能创建一个映射

kibana中手动创建index以及type映射:

PUT /guigu
PUT /guigu/_mapping/house
{
  "properties": {
    "id":{
      "type": "long"
    },
    "address":{
      "type": "text",
      "analyzer": "ik_max_word"
    },
    "user":{
      "properties": {
        "id":{
          "type":"long"
        },
        "username":{
          "type":"keyword"
        }
      }
    }
    
  }
}
2.1 同步新增文档

引入Gson依赖:


    com.google.code.gson
    gson

@GetMapping("/createDoc")
public Object createDoc() throws IOException {
    //IndexRequest:创建文档指定index和type
    IndexRequest indexRequest = new IndexRequest("guigu","house");
    //文档数据
    Map map = new HashMap();
    map.put("id",1001);
    map.put("address","百合公寓a011");
    map.put("user",new User(1,"国哥"));
    //将文档数据转为json并指定类型:
    indexRequest.source(new Gson().toJson(map), XContentType.JSON);
    //将封装好的indexRequest发送到es执行并接收响应结果
    IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT);

    return response;
}

2.2 异步新增文档
@GetMapping("/createDocAsync")
public Object createDocAsync() throws IOException {
    //IndexRequest:创建文档指定index和type
    IndexRequest indexRequest = new IndexRequest("guigu","house","2");
    //文档数据
    Map map = new HashMap();
    map.put("id",1003);
    map.put("address","百合公寓a012");
    map.put("user",new TAdmin(3,"小张"));
    //将文档数据转为json并指定类型:
    indexRequest.source(new Gson().toJson(map),XContentType.JSON);
    //将封装好的indexRequest异步发送到es执行并接收响应
    client.indexAsync(indexRequest, RequestOptions.DEFAULT, new ActionListener() {
        @Override
        public void onResponse(IndexResponse indexResponse) {
            System.out.println("新增成功: "+indexResponse.toString());
        }
        @Override
        public void onFailure(Exception e) {
            System.out.println("新增失败:"+ e);
        }
    });
    return "ok";
}
2.3 判断文档是否存在
@GetMapping("docIsExist")
public String isExist() throws IOException {
    GetRequest getRequest = new GetRequest("guigu","house","1");
    boolean b = client.exists(getRequest, RequestOptions.DEFAULT);
    return b+"";
}

2.4 指定id查询文档
@GetMapping("/queryById")
public Object queryById() throws IOException {
    GetRequest getRequest = new GetRequest("guigu", "house",
                                           "1");
    // 指定过滤的字段
    String[] includes = new String[]{"address", "id","user"};
    //指定排除的字段
    String[] excludes = Strings.EMPTY_ARRAY;
    FetchSourceContext fetchSourceContext =
        new FetchSourceContext(true, includes, excludes);
    getRequest.fetchSourceContext(fetchSourceContext);

    GetResponse response = client.get(getRequest,
                                      RequestOptions.DEFAULT);
    //获取查询的结果
    System.out.println("数据 -> " + response.getSourceAsMap());
    return  response.getSource();
}

2.5 查询所有文档
@GetMapping("/matchall")
public Object  matchAll() throws IOException {
    SearchRequest searchRequest = new SearchRequest();
    searchRequest.indices("guigu");//指定查询数据的  索引
    searchRequest.types("house");//指定查询 数据的类型
    //封装查询时的条件内容
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

    //封装query的方式的内容:MatchAllQueryBuilder  表示查询所有
    MatchAllQueryBuilder queryBuilder = new MatchAllQueryBuilder();
    sourceBuilder.query(queryBuilder);
    searchRequest.source(sourceBuilder);
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

    //解析查询到的结果
    SearchHits searchHits = searchResponse.getHits();
    //查询得到的文档的对象的集合
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        System.out.println("文档id:"+hit.getId());
        //将查询到的结果转为map集合
        Map map = hit.getSourceAsMap();
        System.out.println("记录id:"+map.get("id"));
        System.out.println("地址:"+map.get("address"));

        User user = new Gson().fromJson(map.get("user").toString(), User.class);
        System.out.println("用户id:"+ user.getId());
        System.out.println("用户姓名:"+ user.getUsername());
    }
    return searchHits;
}
2.6 分页查询
//6、分页查询
@GetMapping("/queryPage")
public Object queryPage() throws IOException {
    SearchRequest searchRequest = new SearchRequest();
    //查询的index和type
    searchRequest.indices("guigu");
    searchRequest.types("house");
    //构建查询的条件对象
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    //分页配置
    sourceBuilder.from(0);
    sourceBuilder.size(2);
    MatchQueryBuilder queryBuilder = new MatchQueryBuilder("address" , "北九峰");
    //设置本次查询的方式
    sourceBuilder.query(queryBuilder);
    //向查询的searchRequest对象中设置查询的条件对象
    searchRequest.source(sourceBuilder);
    //客户端发送查询请求:searchRequest用来封装查询请求的参数
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    SearchHit[] hits = searchResponse.getHits().getHits();

    for (SearchHit hit : hits) {
        System.out.println(hit.getSourceAsMap());
    }
    return hits;
}
2.7 高亮
@GetMapping("/queryHighlight")
public Object queryHighlight() throws IOException {
    SearchRequest searchRequest = new SearchRequest("guigu");
    //常见匹配查询对象
    MatchQueryBuilder queryBuilder = new MatchQueryBuilder("address","百合");
    //指定查询短语如何分词
    queryBuilder.analyzer("ik_max_word");
    //创建高亮匹配对象
    HighlightBuilder highlighter = new HighlightBuilder();
    highlighter.preTags("");//前缀
    highlighter.postTags("");//后缀
    highlighter.field("address");//高亮匹配的属性
    //查询构建对象
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(queryBuilder);//将匹配查询对象设置给查询构建对象
    sourceBuilder.highlighter(highlighter);//将高亮匹配对象设置给查询构建对象
    //将查询构建对象设置给查询对象
    searchRequest.source(sourceBuilder);
    //执行查询
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    SearchHits searchHits = searchResponse.getHits();
    //获取解析结果
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        Map sourceAsMap = hit.getSourceAsMap();
        System.out.println("sourceAsMap = " + sourceAsMap);
        //高亮解析结果
        Map highlightFields = hit.getHighlightFields();
        System.out.println("highlightFields = " + highlightFields);
    }
    return hits;
}
//7、高亮查询
@GetMapping("/queryHighlight")
public Object queryHighlight(String value) throws IOException {

    SearchRequest searchRequst = new SearchRequest();
    searchRequst.indices("guigu");
    searchRequst.types("house");


    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    QueryBuilder queryBuilder = new MatchQueryBuilder("address" , value);
    searchSourceBuilder.query(queryBuilder);//封装查询的条件
    HighlightBuilder highlighterBuilder = new HighlightBuilder();
    highlighterBuilder.field("address");
    highlighterBuilder.preTags("");
    highlighterBuilder.postTags("");
    searchSourceBuilder.highlighter(highlighterBuilder);//封装高亮显示的配置
    searchRequst.source(searchSourceBuilder);
    SearchResponse searchResponse = client.search(searchRequst, RequestOptions.DEFAULT);
    //解析相应结果
    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) {
        System.out.println(hit.getSourceAsMap());
        //获取高亮查询的address内容
        Map highlightFields = hit.getHighlightFields();
        HighlightField highlightField = highlightFields.get("address");
        Text[] texts = highlightField.getFragments();//Text数组表示高亮显示的一个address的内容,包含了em前后缀
        System.out.println("高亮查询到当前文档的address:  ");
        for (Text text : texts) {
            System.out.println(text);
        }
    }
    return hits;

}
2.8 更新文档
@GetMapping("/updateDoc")
    public Object updateDoc() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest();
        updateRequest.index("guigu");
        updateRequest.type("house");
        updateRequest.id("aTpiWX8BXaLRQYnnN4AV");
        HashMap map = new HashMap<>();
        map.put("address", "谷阳北路2一室一厅2");
        map.put("user", new User(1000,"张华"));
        updateRequest.doc(new Gson().toJson(map),XContentType.JSON);

        UpdateResponse update = client.update(updateRequest, RequestOptions.DEFAULT);
        System.out.println("version -> " + update.getVersion());
        return update;

    }
    ```
    ## 2.9 删除文档

```java
@GetMapping("/deleteDoc")
public Object deleteDoc() throws IOException {
    DeleteRequest deleteRequest = new DeleteRequest("guigu", "house",
                                                    "1");
    DeleteResponse response = client.delete(deleteRequest,
                                            RequestOptions.DEFAULT);

    System.out.println(response.status());// OK or NOT_FOUND
    return response;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/753243.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号