文章目录
1.ElasticSearch概述
1.1 Elasticsearch 是什么
1.2 全文搜索引擎
Google,百度类的网站搜索,它们都是根据网页中的关键字生成索引,我们在搜索的时 候输入关键字,它们会将该关键字即索引匹配到的所有网页返回;还有常见的项目中应用日 志的搜索等等。对于这些非结构化的数据文本,关系型数据库搜索不是能很好的支持。
一般传统数据库,全文检索都实现的很鸡肋,因为一般也没人用数据库存文本字段。进 行全文检索需要扫描整个表,如果数据量大的话即使对 SQL 的语法优化,也收效甚微。建立了索引,但是维护起来也很麻烦,对于 insert 和 update 操作都会重新构建索引。
基于以上原因可以分析得出,在一些生产环境中,使用常规的搜索方式,性能是非常差 的。
1.3 Elasticsearch And Solr
Lucene 是 Apache 软件基金会 Jakarta 项目组的一个子项目,提供了一个简单却强大的 应用程式接口,能够做全文索引和搜寻。在 Java 开发环境里 Lucene 是一个成熟的免费开源 工具。就其本身而言,Lucene 是当前以及最近几年最受欢迎的免费 Java 信息检索程序库。 但 Lucene 只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的 服务框架搭建起来进行应用。
目前市面上流行的搜索引擎软件,主流的就两款:Elasticsearch 和 Solr,这两款都是基 于 Lucene 搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了 服务器安装、部署、管理、集群以外,对于数据的操作 修改、添加、保存、查询等等都十 分类似。
2. ElasticSearch安装
2.1 下载和安装
Elasticsearch 的官方地址:https://www.elastic.co/cn/
直接下载window版本即可
Windows 版的 Elasticsearch 的安装很简单,解压即安装完毕,解压后的 Elasticsearch 的 目录结构如下
解压后,进入 bin 文件目录,点击 elasticsearch.bat 文件启动 ES 服务
注意:9300端口为 Elasticsearch 集群间组件的通信端口,9200 端口为浏览器访问的 http 协议 RESTful 端口。
打开浏览器(推荐使用谷歌浏览器),输入地址:http://localhost:9200,测试结果
2.2 可能存在的问题
Elasticsearch 是使用 java 开发的,且 7.8 版本的 ES 需要 JDK 版本 1.8 以上,默认安装 包带有 jdk 环境,如果系统配置 JAVA_HOME,那么使用系统默认的 JDK,如果没有配 置使用自带的 JDK,一般建议使用系统配置的 JDK。
双击启动窗口闪退,通过路径访问追踪错误,如果是“空间不足”,请修改config/jvm.options 配置文件
2.3 RESTful
在 REST 样式的 Web 服务中,每个资源都有一个地址。资源本身都是方法调用的目 标,方法列表对所有资源都是一样的。这些方法都是标准方法,包括 HTTP GET、POST、 PUT、DELETE,还可能包括 HEAD 和 OPTIONS。简单的理解就是,如果想要访问互联 网上的资源,就必须向资源所在的服务器发出请求,请求体中必须包含资源的网络路径,以 及对资源进行的操作(增删改查)
get,put,delete具有幂等性,即向ES服务器多次发送同一个url,操作是相当的;而post没有幂等性,多次发送的请求不同
3. ES的基本使用
3.1 数据格式
Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。为了方便大家理解, 我们将 Elasticsearch 里存储文档数据和关系型数据库 MySQL 存储数据的概念进行一个类比
ES 里的 Index 可以看做一个库,而 Types 相当于表,Documents 则相当于表的行。 这里 Types 的概念已经被逐渐弱化,Elasticsearch 6.X 中,一个 index 下已经只能包含一个 type,Elasticsearch 7.X 中, Type 的概念已经被删除了。
文档以Json形式存储,比如一条用户信息
{
"name" : "John",
"sex" : "Male",
"age" : 25,
"birthDate": "1990/05/01",
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
3.2 分片概念
简单来讲就是咱们在ES中所有数据的文件块,也是数据的最小单元块,整个ES集群的核心就是对所有分片的分布、索引、负载、路由等达到惊人的速度
实列场景:假设 IndexA 有2个分片,我们向 IndexA 中插入10条数据 (10个文档),那么这10条数据会尽可能平均的分为5条存储在第一个分片,剩下的5条会存储在另一个分片中。
3.3 索引操作
(1)创建索引
对比关系型数据库,创建索引就等同于创建数据库
向 ES 服务器发 PUT 请求 :http://127.0.0.1:9200/shopping
{
"acknowledged"【响应结果】: true, # true 操作成功
"shards_acknowledged"【分片结果】: true, # 分片操作成功
"index"【索引名称】: "shopping"
}
# 注意:创建索引库的分片数默认 1 片,在 7.0.0 之前的 Elasticsearch 版本中,默认 5 片
如果重复添加索引,会返回错误信息
(2) 查看所有索引
向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/_cat/indices?v
这里请求路径中的_cat 表示查看的意思,indices 表示索引,所以整体含义就是查看当前 ES 服务器中的所有索引,就好像 MySQL 中的 show tables 的感觉,服务器响应结果如下
(3) 查看单个索引
向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/shopping
{
"shopping"【索引名】: {
"aliases"【别名】: {
},
"mappings"【映射】: {
},
"settings"【设置】: {
"index"【设置 - 索引】: {
"creation_date"【设置 - 索引 - 创建时间】: "1614265373911",
"number_of_shards"【设置 - 索引 - 主分片数量】: "1",
"number_of_replicas"【设置 - 索引 - 副分片数量】: "1",
"uuid"【设置 - 索引 - 唯一标识】: "eI5wemRERTumxGCc1bAk2A",
"version"【设置 - 索引 - 版本】: {
"created": "7080099"
},
"provided_name"【设置 - 索引 - 名称】: "shopping"
}
}
}
}
(4) 删除索引
向 ES 服务器发 DELETE 请求 :http://127.0.0.1:9200/shopping
重新访问索引时,服务器返回响应:索引不存在
3.4 文档操作
(1)创建文档
索引已经创建好了,接下来我们来创建文档,并添加数据。这里的文档可以类比为关系型数 据库中的表数据,添加的数据格式为 JSON 格式
向 ES 服务器发 POST 请求 :http://127.0.0.1:9200/shopping/_doc
请求体内容为:
{
"title":"小米手机",
"category":"小米",
"images":"http://www.gulixueyuan.com/xm.jpg",
"price":3999.00
}
此处发送请求的方式必须为 POST,不能是 PUT,否则会发生错误
{
"_index"【索引】: "shopping",
"_type"【类型-文档】: "_doc",
"_id"【唯一标识】: "Xhsa2ncBlvF_7lxyCE9G", #可以类比为 MySQL 中的主键,随机生成
"_version"【版本】: 1,
"result"【结果】: "created", #这里的 create 表示创建成功
"_shards"【分片】: {
"total"【分片 - 总数】: 2,
"successful"【分片 - 成功】: 1,
"failed"【分片 - 失败】: 0
},
"_seq_no": 0,
"_primary_term": 1
}
上面的数据创建后,由于没有指定数据唯一性标识(ID),默认情况下,ES 服务器会随机 生成一个。 如果想要自定义唯一性标识,需要在创建时指定:http://127.0.0.1:9200/shopping/_doc/1
此处需要注意:如果增加数据时明确数据主键,那么请求方式也可以为 PUT
(2) 查看文档
查看文档时,需要指明文档的唯一性标识,类似于 MySQL 中数据的主键查询
向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/shopping/_doc/1
(3)修改文档
和新增文档一样,输入相同的 URL 地址请求,如果请求体变化,会将原有的数据内容覆盖
向 ES 服务器发 POST 请求 :http://127.0.0.1:9200/shopping/_doc/1
请求体内容为:
{
"title":"华为手机",
"category":"华为",
"images":"http://www.gulixueyuan.com/hw.jpg",
"price":4999.00
}
(4) 修改字段
修改数据时,也可以只修改某一给条数据的局部信息
向 ES 服务器发 POST 请求 :http://127.0.0.1:9200/shopping/_update/1
注意全体修改为_doc,修改字段为_update
请求体内容为:
{
"doc": {
"price":3000.00
}
}
(6)删除文档
删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。
向 ES 服务器发 DELETE 请求 :http://127.0.0.1:9200/shopping/_doc/1
{
"_index": "shopping",
"_type": "_doc",
"_id": "1",
"_version"【版本】: 4, #对数据的操作,都会更新版本
"result"【结果】: "deleted", # deleted 表示数据被标记为删除
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 4,
"_primary_term": 2
}
(6) 条件删除文档
一般删除数据都是根据文档的唯一性标识进行删除,实际操作时,也可以根据条件对多条数据进行删除
首先分别增加多条数据:
{
"title":"小米手机",
"category":"小米",
"images":"http://www.gulixueyuan.com/xm.jpg",
"price":4000.00
}
{
"title":"华为手机",
"category":"华为",
"images":"http://www.gulixueyuan.com/hw.jpg",
"price":4000.00
}
向 ES 服务器发 POST 请求 :http://127.0.0.1:9200/shopping/_delete_by_query
请求体内容为:
{
"query":{
"match":{
"price":4000.00
}
}
}
3.5 映射操作
有了索引库,等于有了数据库中的 database。
接下来就需要建索引库(index)中的映射了,类似于数据库(database)中的表结构(table)。 创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,需要知道这个类型 下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)。
(1) 创建映射
先创建一个索引student,然后添加映射
向 ES 服务器发 PUT 请求 :http://127.0.0.1:9200/student/_mapping
请求体内容为:
{
"properties": {
"name":{
"type": "text",
"index": true
},
"sex":{
"type": "text",
"index": false
},
"age":{
"type": "long",
"index": false
}
}
}
映射数据说明:
-
字段名:任意填写,下面指定许多属性,例如:title、subtitle、images、price
-
type:类型,Elasticsearch 中支持的数据类型非常丰富,说几个关键的:
(1)String 类型,又分两种:
- text:可分词
- keyword:不可分词,数据会作为完整字段进行匹配
(2)Numerical:数值类型,分两类
- 基本数据类型:long、integer、short、byte、double、float、half_float
- 浮点数的高精度类型:scaled_floatwww
(3)Date:日期类型
(4)Array:数组类型
(5) Object:对象
-
index:是否索引,默认为 true,也就是说你不进行任何配置,所有字段都会被索引
true:字段会被索引,则可以用来进行搜索
false:字段不会被索引,不能用来搜索
-
store:是否将数据进行独立存储,默认为 false
原始的文本会存储在_source 里面,默认情况下其他提取出来的字段都不是独立存储 的,是从_source 里面提取出来的。当然你也可以独立的存储某个字段,只要设置 “store”: true 即可,获取独立存储的字段要比从_source 中解析快得多,但是也会占用 更多的空间,所以要根据实际业务需求来设置。
-
analyzer:分词器,这里的 ik_max_word 即使用 ik 分词器
(2) 查看映射
向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_mapping
(3) 索引映射关联
即在创建索引的时候即添加映射要求
创建一个student1索引,添加映射要求如下:
{
"settings": {
},
"mappings": {
"properties": {
"name":{
"type": "text",
"index": true
},
"sex":{
"type": "text",
"index": false
},
"age":{
"type": "long",
"index": false
}
}
}
}
向 ES 服务器发 PUT 请求 :http://127.0.0.1:9200/student1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6GCT91ui-1650284771661)(https://gitee.com/GOV_D/my-picture/raw/master/MyPicture/image-20220412142729141.png)]
3.6 文档高级查询
(1) 查询所有文档
向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/student/_search
或者带请求体,但全匹配,即查出所有文档
{
"query": {
"match_all": {
}
}
}
// "query":这里的 query 代表一个查询对象,里面可以有不同的查询属性
// "match_all":查询类型,例如:match_all(代表查询所有), match,term , range 等等
// {查询条件}:查询条件会根据类型的不同,写法也有差异
{
"took【查询花费时间,单位毫秒】" : 1116,
"timed_out【是否超时】" : false,
"_shards【分片信息】" : {
"total【总数】" : 1,
"successful【成功】" : 1,
"skipped【忽略】" : 0,
"failed【失败】" : 0
},
"hits【搜索命中结果】" : {
"total"【搜索条件匹配的文档总数】: {
"value"【总命中计数的值】: 3,
"relation"【计数规则】: "eq" // eq 表示计数准确, gte 表示计数不准确
},
"max_score【匹配度分值】" : 1.0,
"hits【命中结果集合】" : [
。。。
}
]
}
}
(2) 匹配查询
match 匹配类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是 or 的关系
向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/shooping/_search
{
"query": {
"match": {
"category":"小米"
}
}
}
多字段匹配查询,即查询条件可以在多个字段中去进行匹配
multi_match 与 match 类似,不同的是它可以在多个字段中查询。
向 ES 服务器发 GET 请求 :http://127.0.0.1:9200/shooping/_search
{
"query": {
"multi_match": {
"query": "小米",
"fields": ["tile","category"]
}
}
}
//即title或者category字段中包含小米即可被查出
(3)完全匹配查询
精确的关键词匹配查询,不对查询条件进行分词。
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search,
{
"query":{
"match_phrase":{
"category" : "华为"
}
}
}
(4)查询指定字段
如果只想查询指定的某个字段,比如只需要title
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search
{
"query":{
"match_all":{
}
},
"_source":["title"]
}
(5)分页查询
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search
{
"query":{
"match_all":{
}
},
"from":0, //起始页
"size":2 //页的大小
}
(6)查询排序
如果你想对查询结果进行排序,如根据价格降序排序
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search
{
"query":{
"match_all":{
}
},
"sort":{
"price":{
"order":"desc"
}
}
}
(7)多条件查询
假设想找出小米牌子并且价格为3999元的。(must相当于数据库的&&)
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search
{
"query":{
"bool":{
"must":[{
"match":{
"category":"小米"
}
},{
"match":{
"price":3999.00
}
}]
}
}
}
假设想找出小米或华为的牌子,即满足一个条件即可。(should相当于数据库的||)
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search
{
"query":{
"bool":{
"should":[{
"match":{
"category":"小米"
}
},{
"match":{
"category":"华为"
}
}]
}
}
}
(8)范围查询
假设想找出小米或华为的牌子,价格大于2000元的手机。
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search
{
"query":{
"bool":{
"should":[{
"match":{
"category":"小米"
}
},{
"match":{
"category":"华为"
}
}],
"filter":{
"range":{
"price":{
"gt":2000
}
}
}
}
}
}
(9)高亮查询
即在查询结果中将查询条件高亮
{
"query":{
"match_phrase":{
"category" : "华为"
}
},
"highlight":{
"fields":{
"category":{
}//<----高亮这字段
}
}
}
(10)聚合查询
聚合允许使用者对 es 文档进行统计分析,类似与关系型数据库中的 group by,当然还有很多其他的聚合,例如取最大值max、平均值avg等等。
如按price字段进行分组:
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search
{
"aggs":{
//聚合操作
"price_group":{
//名称,随意起名
"terms":{
//分组
"field":"price"//分组字段
}
}
}
}
上面返回结果会附带原始数据的。若不想要不附带原始数据的结果,只想要统计结果
向 ES 服务器发 GET请求 :http://127.0.0.1:9200/shopping/_search
{
"aggs":{
"price_group":{
"terms":{
"field":"price"
}
}
},
"size":0
}
若想对所有手机价格求平均值。
向 ES 服务器发 GET请求 : http://127.0.0.1:9200/shopping/_search
{
"aggs":{
"price_avg":{
//名称,随意起名
"avg":{
//求平均
"field":"price"
}
}
},
"size":0
}
4. Java操作ES
4.1 环境搭建
maven依赖:
<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.8.0</version>
</dependency>
<!-- elasticsearch 的客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.8.0</version>
</dependency>
<!-- elasticsearch 依赖 2.x 的 log4j -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<!-- junit 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
试试建立连接
public class Es_client {
public static void main(String[] args) throws IOException {
//创建客户端对象
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost",9200,"http"))
);
//释放连接
client.close();
}
}
Java通过API操作ES也不过是封装了原生的restful请求罢了,其响应结果也和直接使用restful去请求是一样的
4.2 索引操作
(1)创建索引
public class EsClient_index_create {
public static void main(String[] args) throws IOException {
//创建客户端对象
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost",9200,"http"))
);
// 创建索引 - 请求对象
CreateIndexRequest request = new CreateIndexRequest("user");
// 发送请求,获取响应
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
//输出创建状态
System.out.println(response.isAcknowledged());
//释放连接
client.close();
}
}
(2)查询索引
public class EsClient_index_get {
public static void main(String[] args) throws IOException {
//创建客户端对象
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost",9200,"http"))
);
// 要发送的某种类型的请求
GetIndexRequest request = new GetIndexRequest("user");
// 发送请求,获取响应
GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
//输出响应结果
System.out.println("aliases:"+response.getAliases());
System.out.println("mappings:"+response.getMappings());
System.out.println("settings:"+response.getSettings());
//释放连接
client.close()