Elasticsearch
了解Elasticsearch
Elasticsearch 介绍
Elasticsearch 是一个开源的搜索引擎,建立在一个全文搜索引擎库 Apache Lucene™ 基础之上。 Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库—无论是开源还是私有。
Elasticsearch 也是使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单, 通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。
Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确的形容:
- 一个分布式的实时文档存储,每个字段 可以被索引与搜索
- 一个分布式实时分析搜索引擎
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
Elasticsearch 倒排索引结构
在网上找到了一篇介绍比较清晰的文档
https://www.cnblogs.com/cjsblog/p/10327673.html
Elasticsearch 安装
Elasticsearch 在5.x版本后依赖的jdk环境就是1.8,所以首先要保证自己本地的jdk环境是1.8
windows下安装包7.6.1,这里推荐在华为开源镜像站进行安装,官网下载超级慢。
地址:https://mirrors.huaweicloud.com/elasticsearch/7.6.1/
下载安装包后解压
进入bin 目录,看到 elasticsearch.bat文件,运行
看到started,表示启动成功,可以看到默认端口为9200
浏览器访问:
127.0.0.1:9200
Elasticsearch-head安装
PS:Elasticsearch-head依赖于node环境,安装es-head前需确保node环境安装完毕。
安装node
node.js安装包 找到msi后缀的安装
https://nodejs.org/en/download/
我安装的是当前最新版本
ps:此处可勾选,勾选后会安装两个模块工具,python 和 chocolatey,勾选后默认会安装到C盘,所以也可以不勾选,之后自己自定义安装。
安装完成后进入命令控制台 输入node 可以看到当前安装的版本号
安装grunt
npm install -g grunt-cli
准备工作ok了,可以安装head插件了
安装es-head(7.6.1)
官网下载地址:
:https://github.com/mobz/elasticsearch-head
百度网盘下载地址:
https://pan.baidu.com/s/1vWhDMf1zrWYun_72D3AjYA
提取码:a47u
1.下载,解压
2.命令控制台进入head目录
3.npm install (ps:安装速度比较慢)
4.npm run start 启动
es-head安装完毕
PS:修改elasticsearch 的配置文件,配置可以跨域访问
在文件结尾加上:
# 是否支持跨域
http.cors.enabled: true
# *表示支持所有域名
http.cors.allow-origin: "*"
重新启动es
在浏览器访问: 127.0.0.1:9100
IK分词器安装
github下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.6.1
将ik分词器压缩包解压到elasticsearch的plugins插件下:
重启es
Kibana安装
安装包地址:https://pan.baidu.com/s/1hjwohKNqMYj3lMvIj15gFg
提取码:hscx
下载 安装 解压 后进入kibana.bat 运行
默认连接的es地址就是 localhost:9200
可进入config/kibana.yml文件下看到:
配置kibana可视化界面汉化
在配置文件末尾加上:
i18n.locale: “zh-CN”
浏览器访问:localhost:5601进入kibana可视化界面
Elasticsearch DSL
ES常用数据类型
类型分类 | 子分类 | 具体类型 |
核心类型 | 字符串 | text,keyword |
整数 | byte,short,integer,long | |
浮点 | double,float,half_float,scale_float | |
逻辑 | boolean | |
日期 | date | |
范围 | range | |
二进制 | binary | |
复合类型 | 数组 | array |
对象 | object | |
嵌套 | nested | |
地理类型 | 经纬度 | ge_point |
特殊类型 | ip | ip |
join | join |
索引操作
创建索引
PUT /test01
{
"settings": {
"number_of_replicas": 1,
"number_of_shards": 1
},
"mappings": {
"properties": {
"name":{
"type": "keyword"
},
"phone":{
"type": "keyword"
},
"age":{
"type": "integer"
},
"ip":{
"type": "ip"
},
"dec":{
"type": "text"
},
"birth":{
"type": "date"
}
}
}
}
这里的setting主要是设置索引的分片和备份,mapping映射主要是配置文档中字段名(类似于mysql表的字段)以及对应的类型(字段类型)。
可以看到我们默认的文档类型是_doc。
ps:
在5版本及之前一个索引可以对应多个类型。
在6.x版本一个索引可以对应一个类型。
在7.x之后取消了类型。
当然映射其实不需要我们去手动映射,因为在我们添加文档的时候,elasticsearch会自动猜测我的文档数据为我们创建映射。
创建索引(这样也是ok的)
PUT /test01
删除索引
DELETE /test01
文档操作
创建文档
- 创建文档(指定文档id)
PUT /zhongtou_uat/_doc/1
{
"name": "张三",
"age": 18,
"score": 95
}
- 创建文档,使用默认id
POST /zhongtou_uat/_doc
{
"name": "王东林",
"age": 23
}
此处必须要用POST请求方式,使用PUT请求,则会报错
PUT /zhongtou_uat/_doc
{
"name":"王东林",
"age":23
}
{
"error" : "Incorrect HTTP method for uri [/zhongtou_uat/_doc?pretty=true] and method [PUT], allowed: [POST]",
"status" : 405
}
修改文档
- 覆盖修改
覆盖修改即采用添加的方式,覆盖掉原有的文档数据,但是有一个弊端,当漏掉属性时,这个属性也会去掉。
PUT /zhongtou_uat/_doc/1
{
"name": "李四",
"age": 18,
"score": 95
}
- 指定属性修改
此处需要指定POST类型 具体语法:
POST /索引/类型/文档id/_update
类型默认都是_doc
POST /zhongtou_uat/_doc/1/_update
{
"doc":{
"name": "王五"
}
}
删除文档
删除文档需要指定 DELETE类型 具体语法
DELETE /索引/文档类型/文档id
DELETE /zhongtou_uat/_doc/Jgt4T3UBlIJHe9k23-YO
查询文档
基本的条件查询
GET /zhongtou_uat/_doc/_search?q=name:"王"
复杂的条件查询
- match
match 查询类似于模糊查询,因为match查询的属性值(类型为keyword的除外) 首先会被分词器分词,然后去文档中匹配,例如 使用match操作搜索 name=王五的文档,可以搜到 name等于王五和王小二的两个文档。
可以使用_source来限制查询的字段
"_source": ["name","age"]
可以使用sort进行排序
"sort": [
{
"age": {
"order": "desc"
}
}
]
可以使用from size进行分页
"form": 0 #起始条数
"size": 10 #每页展示条数
- multi_match
匹配多个字段(age,score中有等于18的文档),注意fields中的类型必须保持一致。否则会出现异常。
GET /zhongtou_uat/_search
{
"query": {
"multi_match": {
"query": 18,
"fields": ["age","score"]
}
}
}
使用multi_match ,同一个关键字查询多个字段时,可以增加权重,可以使_score乘以相应的倍数,如图,增加score的权重,则id=2的文档的相关性就大大提高了。
GET /zhongtou_uat/_search
{
"query": {
"multi_match": {
"query": 18,
"fields": ["age","score^3"]
}
}
}
- must/must not /should
查询score等于60或者age等于20的数据,并且age不等于18的数据
must 相当于 and ,must not 就相当于 not 。should相当于or
GET /zhongtou_uat/_search
{
"query": {
"bool": {
"must": [
{
"bool": {
"should": [
{"match": {"score": "60"}},
{"match": {"age": "20"}}
]
}
}
]
, "must_not": [
{"bool": {
"filter": {
"term": {
"age": 18
}
}
}}
]
}
}
}
- 通配符查询
?代表匹配任意一个字符,*代表匹配0个或者多个字符
GET /zhongtou_uat/_search
{
"query": {
"wildcard": {
"job": {
"value": "*医*"
}
}
}
}
-
正则查询…
-
query_string
GET /zhongtou_uat/_search
{
"query": {
"query_string": {
"default_field": "job",
"query": "志愿医务工作者 OR 医生"
}
}
}
sort 排序
GET /zhongtou_uat/_search
{
"query": {
"match": {
"name": {
"query": "王五 赵四",
"operator": "and"
}
}
},
"_source": ["name","age"],
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
range查询 filter查询
查询job为医生且score大于等于60小于等于100的文档
GET /zhongtou_uat/_search
{
"query": {
"bool": {
"must": [
{"match": {
"job": "医生"
}}
],
"filter": {
"range": {
"score": {
"gte": 60,
"lte": 100
}
}
}
}
}
}
sum求和,avg求平均数
“aggs”代表聚合操作,一个aggs下面可以跟多个聚合操作。sum_score代表聚合后结果名字,“sum”代表聚合操作为统计求和,field:代表是对score进行求和
avg max min 同理
GET /zhongtou_uat/_search
{
"aggs": {
"sum_score": {
"sum": {
"field": "score"
}
},
"avg_score":{
"avg": {
"field": "score"
}
}
}
}
max最大值 top榜 查询score最大值
GET /zhongtou_uat/_search
{
"aggs": {
"max_score": {
"max": {
"field": "score"
}
}
}
}
min最小值 top 查询score最小值
GET /zhongtou_uat/_search
{
"aggs": {
"min_score": {
"min": {
"field": "score"
}
}
}
}
Elasticsearch Java API
准备工作(创建maven项目)
导入依赖(pom.xml)
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
创建es客户端连接
package com.pec.es.uitl;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
public class EsClient {
public static RestHighLevelClient getHighLevelClient() {
//指定elastic search的ip和端口
HttpHost host= new HttpHost("127.0.0.1",9200,"http");
return new RestHighLevelClient(RestClient.builder(host));
}
}
辅助实体类
package com.pec.es.entity;
public class User {
private String userName;
private Integer age;
private String sex;
public User() {
super();
}
public User(String userName, Integer age, String sex) {
super();
this.userName = userName;
this.age = age;
this.sex = sex;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
测试
测试客户端连接
/**
* 测试连接
*/
@Test
public void testConnect() {
RestHighLevelClient client=EsClient.getHighLevelClient();
System.out.println(client);
}
索引
创建索引
/**
* 创建索引,分片setting 映射 mapping 创建文档
* @throws IOException
*/
@Test
@SuppressWarnings("deprecation")
public void createIndex() throws IOException {
RestHighLevelClient client=EsClient.getHighLevelClient();
CreateIndexRequest createIndexRequest = new CreateIndexRequest("shop_jifen");
//配置Setting 分片 备份
Settings.Builder setting = Settings.builder()
.put("number_of_shards",1)
.put("number_of_replicas",1);
createIndexRequest.settings(setting);
//mapping映射 ...
CreateIndexResponse create = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
System.out.println(create);
}
查看索引是否存在
/**
* @throws IOException
* 查看索引是否存在
*/
@SuppressWarnings("deprecation")
@Test
public void getIndex() throws IOException {
RestHighLevelClient client=EsClient.getHighLevelClient();
GetIndexRequest getIndexRequest = new GetIndexRequest();
getIndexRequest.indices("shop_jifen");
boolean b = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
System.out.println(b);
}
删除索引
/**
*
* 删除索引
*/
@Test
public void deleteIndex() throws IOException {
RestHighLevelClient client=EsClient.getHighLevelClient();
DeleteIndexRequest request = new DeleteIndexRequest();
request.indices("shop_jifen");
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.toString());
}
文档
创建文档
/**
* @throws IOException
* 创建文档
*/
@Test
public void createDoc() throws IOException {
RestHighLevelClient client=EsClient.getHighLevelClient();
User u =new User("黎明",40,"男");
IndexRequest request = new IndexRequest("shop_jifen");
request.id("2");
request.timeout(TimeValue.timeValueSeconds(1));
request.source(JSON.toJSONString(u),XContentType.JSON);
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
System.out.println(response.status());
}
修改文档
/**
* 修改文档
* @throws IOException
* 此处修改文档,会修改文档中的某个属性,不会覆盖
*/
@Test
public void updateDoc() throws IOException {
RestHighLevelClient client=EsClient.getHighLevelClient();
UpdateRequest request = new UpdateRequest("shop_jifen","1");
request.timeout(TimeValue.timeValueSeconds(1));
User user = new User();
user.setUserName("张三");
user.setAge(18);
request.doc(JSON.toJSONString(user),XContentType.JSON);
UpdateResponse response =client.update(request,RequestOptions.DEFAULT);
System.out.println(response.status());
}
删除文档
/**
* 修改文档
* @throws IOException
* 此处修改文档,会修改文档中的某个属性,不会覆盖
*/
@Test
public void updateDoc() throws IOException {
RestHighLevelClient client=EsClient.getHighLevelClient();
UpdateRequest request = new UpdateRequest("shop_jifen","1");
request.timeout(TimeValue.timeValueSeconds(1));
User user = new User();
user.setUserName("张三");
user.setAge(18);
request.doc(JSON.toJSONString(user),XContentType.JSON);
UpdateResponse response =client.update(request,RequestOptions.DEFAULT);
System.out.println(response.status());
}
查询文档(根据文档id)
/**
* @throws IOException
* 查询文档 根据文档id
*/
@Test
public void getDoc() throws IOException {
RestHighLevelClient client=EsClient.getHighLevelClient();
GetRequest request = new GetRequest("shop_jifen","1");
GetResponse response = client.get(request, RequestOptions.DEFAULT);
System.out.println(response.toString());
// 1.若索引库里面没有数据{"_index":"shop_jifen","_type":"_doc","_id":"1","found":false}
// 2.若索引库里面有数据{"_index":"shop_jifen","_type":"_doc","_id":"1","_version":1,"_seq_no":3,"_primary_term":1,"found":true,"_source":{"age":40,"sex":"男","userName":"黎明"}}
System.ou.println(response.getSource());
}
查询文档
QueryBuilders.boolQuery();
QueryBuilders.rangeQuery(name);
QueryBuilders.existsQuery(name);
QueryBuilders.fuzzyQuery(name, value);
QueryBuilders.matchAllQuery();
QueryBuilders.matchQuery(name, text);
QueryBuilders.multiMatchQuery(text, fieldNames);
QueryBuilders.termQuery(name, value);
//获取相似文章,根据关键词匹配。
QueryBuilders.moreLikeThisQuery(likeTexts, likeItems);
QueryBuilders.prefixQuery(name, prefix)
…
term精准查询job为学生的,并且age大于等于18且小于等于20 并且name(text类型,默认会分词)不是王五的数据
/**
* boolQuery must must not filter termQuery range
* @throws IOException
* term精准查询job为学生的,并且age大于等于18且小于等于20 并且name不能包含王、五的数据
*/
@Test
public void search02() throws IOException {
RestHighLevelClient client=EsClient.getHighLevelClient();
BoolQueryBuilder builder = QueryBuilders.boolQuery();
//此处可以写多个builder
builder.must(QueryBuilders.termQuery("job", "学生"))
.filter(QueryBuilders.rangeQuery("age").gte(18))
.filter(QueryBuilders.rangeQuery("age").lte(20));
builder.mustNot(QueryBuilders.matchQuery("name", "王五"));
SearchSourceBuilder ssb =new SearchSourceBuilder();
query = ssb.query(builder);
SearchRequest sr =new SearchRequest().indices("zhongtou_uat").source(query);
SearchResponse response = client.search(sr, RequestOptions.DEFAULT);
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
高亮显示
@Test
public void search03() throws IOException {
RestHighLevelClient client=EsClient.getHighLevelClient();
SearchRequest request =new SearchRequest().indices("zhongtou_uat");
SearchSourceBuilder ssb =new SearchSourceBuilder();
//查询所有构造器
MatchAllQueryBuilder matchAllQuery = QueryBuilders.matchAllQuery();
ssb.query(matchAllQuery);
//高亮构造器
HighlightBuilder highlightBuilder=new HighlightBuilder();
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
highlightBuilder.field("job");
ssb.highlighter(highlightBuilder);
request.source(ssb);
SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);
for (SearchHit hit : searchResponse.getHits().getHits()) {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField jobValue = highlightFields.get("job");
//将值 前缀 后缀拼接在一起
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
if(jobValue !=null) {
StringBuilder newValue=new StringBuilder();
Text[] fragments = jobValue.getFragments();
for (Text text : fragments) {
newValue.append(text);
}
sourceAsMap.put("job", newValue.toString());
}
String string = JSON.toJSONString(sourceAsMap);
System.out.println(string);
}
}
未完待续…