4.LogStash同步数据库到ElasticSearch(可跳过,使用docker部署失败,用二进制安装包本地同步可以)
注:文章默认你已经安装好了docker环境,且了解springboot和mysql的开发。
1.ElasticSearch安装与运行
docker search elasticsearch
docker pull elasticsearch:7.8.0
docker images
docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -e "discovery.type=single-node" -d -p 9200:9200 -p 9300:9300 --name es_docker elasticsearch:7.8.0
docker ps -a #拿到运行容器elasticsearch 的 id
docker exec -it es_docker /bin/bash
cd ./config
vi elasticsearch.yml
在elasticsearch.yml中添加:
http.cors.enabled: true
http.cors.allow-origin: "*"
重启 elasticsearch容器
docker restart es_docker
2.ElasticSearch中加入中文分词器
安装 IK 分词器
进入到容器内部安装插件
docker exec -it es_docker/bin/bash
执行命令
/usr/share/elasticsearch/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.8.0/elasticsearch-analysis-ik-7.8.0.zip
成功标志:-> Installed analysis-ik
退出容器:exit
重启容器:
docker restart search
打开kibana界面
http://localhost:5601/
测试分词效果
或者
安装elasticsearch-head 容器 或者 直接谷歌插件elasticsearch-head(推荐)
通过Dockerfile安装elasticsearch-analysis-ik-7.x.x插件
FROM docker.elastic.co/elasticsearch/elasticsearch:7.8.0
ADD elasticsearch-analysis-ik-7.8.0 /usr/share/elasticsearch/plugins/elasticsearch-analysis-ik-7.8.0
生成的docker镜像
docker build -t="elasticsearch-with-ik:7.8.0" /Users/xubh/WorkSpace/DockerFiles
运行刚才生成的docker镜像:
# 开放9200端口,-e中的设置为允许跨域访问,因为我要使用head插件访问
docker run -p 9200:9200 -p 9300:9300-e ES_JAVA_OPTS="-Xms256m -Xmx256m" -e "http.cors.enabled=true" -e "http.cors.allow-origin=*" elasticsearch-with-ik:7.8.0
成功启动后就能使用head插件访问
3.安装kibana测试并测试中文分词(可跳过)
安装kibana
https://blog.csdn.net/weixin_38106322/article/details/107828973
3.1.拉取镜像docker pull kibana:7.8.0
3.2.启动容器
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://宿主主机ip:9200-p5601:5601 -d kibana:7.8.0
3.3.配置kibana.yml
docker exec -it kibana /bin/bash
cd config
vi kibana.yml
server.name: kibana
server.host: "0.0.0.0"
elasticsearch.hosts: [ "http://search:9200" ]
xpack.monitoring.ui.container.elasticsearch.enabled: true
i18n.locale: "zh-CN"
4.LogStash同步数据库到ElasticSearch(可跳过,使用docker部署失败,用二进制安装包本地同步可以)
docker pull logstash:7.8.0
docker run -d --restart=always --log-driver json-file --log-opt max-size=10m --log-opt max-file=2 -p 5044:5044 --name logstash -v /Users/xubh/WorkSpace/DockerSpace/logstash/config/logstash.yml:/usr/share/logstash/config/logstash.yml -v /Users/xubh/WorkSpace/DockerSpace/logstash/conf.d/:/usr/share/logstash/conf.d/ logstash:7.8.0
创建容器后,需要安装logstash-input-jdbc插件。
docker exec -it logstash /bin/bash
docker run -d --restart=always --log-driver json-file --log-opt max-size=10m --log-opt max-file=2 -p 5044:5044 --name logstash -v /Users/xubh/WorkSpace/DockerSpace/logstash/logstash.yml:/usr/share/logstash/config/logstash.yml -v /Users/xubh/WorkSpace/DockerSpace/logstash/pipeline:/usr/share/logstash/pipeline logstash:7.8.0
5.搜索代码实现
springboot
加入依赖包
<!-- https://mvnrepository.com/artifact/org.elasticsearch/elasticsearch -->
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.8.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.8.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
增加ElasticSearchConfig
package com.example.esdemo.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author xubh
*/
@Configuration
public class ElasticSearchConfig {
/**
* 配置RestHighLevelClient对象
* 将该对象交给Spring容器去管理
*
* @return RestHighLevelClient对象
*/
@Bean
public RestHighLevelClient restHighLevelClient() {
return new RestHighLevelClient(
RestClient.builder(
//若有多个,可以传一个数组
new HttpHost("127.0.0.1", 9200, "http")));
}
}
新增业务对象User
package com.example.esdemo.entity;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class User {
private Integer id;
private String username;
private String remark;
}
测试代码ElasticSearchApiTests
package com.example.esdemo;
import com.alibaba.fastjson.JSONObject;
import com.example.esdemo.entity.User;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.core.CountRequest;
import org.elasticsearch.client.core.CountResponse;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.index.query.WildcardQueryBuilder;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@SpringBootTest
public class ElasticSearchApiTests {
@Autowired
@Qualifier("restHighLevelClient")
RestHighLevelClient client;
private static String INDEX_NAME = "user_index";
/**
* 创建索引测试
*/
@Test
void createIndex() throws IOException {
//7.2版本的esString类型分为两种:text,keyword
//重要:此处字段配置为中文分词,不配置默认是分字
String createStructName = "{\n"
+ " \"properties\": {\n"
+ " \"" + "username" + "\":{\n"
+ " \"type\":\"text\",\n"
+ " \"analyzer\":\"ik_max_word\"\n"
+ " },\n"
+ " \"" + "remark" + "\":{\n"
+ " \"type\":\"text\",\n"
+ " \"analyzer\":\"ik_max_word\"\n"
+ " }\n"
+ " }\n"
+ " }";
//1、构建 创建索引的请求
CreateIndexRequest request = new CreateIndexRequest(INDEX_NAME);//索引名
request.settings(Settings.builder()
.put("index.number_of_shards", 1)
.put("index.number_of_replicas", 0)
.put("max_result_window", 600000000));
request.mapping(createStructName, XContentType.JSON);
//2、客户端执行请求,获取响应
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
//3、打印
System.out.println("创建成功,创建的索引名为:" + response.index());
}
/**
* 获取索引测试
*/
@Test
void getIndex() throws IOException {
//1、构建 获取索引的请求
GetIndexRequest request = new GetIndexRequest(INDEX_NAME);
//2、客户端判断该索引是否存在
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
//3、打印
System.out.println("该索引是否存在:"+exists);
}
/**
* 删除索引测试
*/
@Test
void deleteIndex() throws IOException {
//1、构建 删除索引请求
DeleteIndexRequest request = new DeleteIndexRequest(INDEX_NAME);
//2、客户段执行删除的请求
AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
//3、打印
System.out.println("是否删除成功:"+response.isAcknowledged());
}
/**
* 创建文档
*/
@Test
void createDocument() throws IOException {
User user = new User().setId(1).setUsername("张三");
//1、构建请求
IndexRequest request = new IndexRequest(INDEX_NAME);
//2、设置规则 PUT /user_index/user/_doc/1
request.id("1");//设置id
request.timeout(TimeValue.timeValueSeconds(1));//设置超时时间
//3、将数据放入到请求中,以JSON的格式存放
request.source(JSONObject.toJSONString(user), XContentType.JSON);
//4、客户端发送请求,获取响应结果
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
//5、打印
System.out.println("响应结果:"+response.toString());
}
/**
* 获取文档
*/
@Test
void getDocument() throws IOException {
//获取id为1的文档的信息
GetRequest request = new GetRequest(INDEX_NAME,"1");
boolean exists = client.exists(request, RequestOptions.DEFAULT);
System.out.println("文档是否存在:"+exists);
//如果存在,获取文档信息
if (exists){
GetResponse response = client.get(request, RequestOptions.DEFAULT);
System.out.println("文档内容为:"+response.getSourceAsString());
}
}
/**
* 更新文档
*/
@Test
void updateDocument() throws IOException {
//更新id为1的文档的信息
UpdateRequest request = new UpdateRequest(INDEX_NAME, "1");
User user = new User().setUsername("李四");
request.doc(JSONObject.toJSONString(user), XContentType.JSON);
//客户端执行更新请求
UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
System.out.println("更新状态:" +response.status());
}
/**
* 删除文档
*/
@Test
void deleteDocument() throws IOException {
//构建删除请求
DeleteRequest request = new DeleteRequest(INDEX_NAME, "1");
//客户端执行删除请求,并获取响应结果
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
//打印
System.out.println("删除状态:"+response.status());
}
/**
* 批量插入数据
*/
@Test
void createBulkDocument() throws IOException {
//构建批量插入的请求
BulkRequest request = new BulkRequest();
//设置超时时间
request.timeout("10s");
//设置数据
List<User> list = new ArrayList<>();
list.add(new User().setId(1).setUsername("张三").setRemark("张三"));
list.add(new User().setId(2).setUsername("李四").setRemark("李四"));
list.add(new User().setId(3).setUsername("王五").setRemark("王五"));
list.add(new User().setId(4).setUsername("赵六").setRemark("赵六"));
list.add(new User().setId(4).setUsername("中国张三").setRemark("中国张三"));
list.add(new User().setId(4).setUsername("中国李四").setRemark("中国李四"));
list.add(new User().setId(4).setUsername("王中国").setRemark("王中国"));
list.add(new User().setId(4).setUsername("王嫌烦").setRemark("王嫌烦"));
list.add(new User().setId(4).setUsername("赵国").setRemark("赵国"));
list.add(new User().setId(4).setUsername("赵国中").setRemark("赵国中"));
list.add(new User().setId(4).setUsername("赵嫌犯").setRemark("赵嫌犯"));
//批量插入请求设置
for (int i = 0; i < list.size(); i++) {
request.add(
new IndexRequest(INDEX_NAME)//设置索引
.id(String.valueOf(i+1))//设置文档的id,如果没有指定,会随机生成,自己测试
.source(JSONObject.toJSONString(list.get(i)), XContentType.JSON)//设置要添加的资源,类型为JSON
);
}
BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
System.out.println("批量插入是否失败:"+response.hasFailures());
}
/**
* 查询
*/
@Test
void query() throws IOException {
//1、构建搜索请求
SearchRequest request = new SearchRequest(INDEX_NAME);
//2、设置搜索条件,使用该构建器进行查询
SearchSourceBuilder builder = new SearchSourceBuilder();//生成构建器
//查询条件我们可以用工具类QueryBuilders来构建
//QueryBuilders.termQuery():精确匹配
//QueryBuilders.matchAllQuery():全文匹配
String value = "李四";
String fieldName = "username";
String fieldNameKeyword = fieldName+".keyword";
//构建精确匹配查询条件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(fieldNameKeyword, value);
// MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
// WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("username", "张");
builder.query(termQueryBuilder);
//3、将搜索条件放入搜索请求中
request.source(builder);
//4、客户端执行搜索请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//5、打印测试
SearchHit[] hits = response.getHits().getHits();
System.out.println("共查询到"+hits.length+"条数据");
System.out.println("查询结果:");
for (int i = 0; i < hits.length; i++) {
System.out.println(hits[i].getSourceAsString());
}
}
/**
* 一般查询
*/
@Test
void query2() throws IOException {
queryIndexDataByKeyword("index",new String[]{"content"},"中国嫌犯");
}
/**
* 高亮查询
*/
@Test
void query3() throws IOException {
queryHighlightIndexDataByKeyword(new String[]{"index","user_index"},new String[]{"content","username","remark"},"中国",0,2);
}
/**
* 模糊查询
* @param indexName 索引名称
* @param keyword 查询关键字
* @return path list
*/
public List<String> queryIndexDataByKeyword(String indexName,String[] filedNames,String keyword) {
try {
//查询总数
CountRequest countRequest = new CountRequest();
SearchSourceBuilder searchCountBuilder = new SearchSourceBuilder();
/*searchSourceBuilder.query(
QueryBuilders.queryStringQuery(keyword).field(config.getFiledName()));*/
searchCountBuilder.query(
QueryBuilders.wildcardQuery(filedNames[0],"*" + keyword + "*"));
countRequest.source(searchCountBuilder);
countRequest.indices(indexName);
CountResponse countResponse = client.count(countRequest, RequestOptions.DEFAULT);
long count = countResponse.getCount();
long pageMax = count / 10000;
long remainder = count % 10000;
if (remainder > 0) {
pageMax++;
}
List<String> list = new ArrayList<>();
for (int pageIndex = 0;pageIndex < pageMax;pageIndex++) {
WildcardQueryBuilder wildcardQueryBuilder =
new WildcardQueryBuilder(filedNames[0],"*" + keyword + "*");
/*builder.query(QueryBuilders
.queryStringQuery(keyword).field(config.getFiledName()));*/
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.from(pageIndex * 10000);
builder.size(10000);
builder.timeout(new TimeValue(60, TimeUnit.MINUTES));
builder.query(wildcardQueryBuilder);
SearchRequest request = new SearchRequest(indexName);
request.source(builder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHit[] hits = response.getHits().getHits();
for (SearchHit hit : hits) {
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String sentence = (String)sourceAsMap.get(filedNames[0]);
list.add(sentence);
}
}
System.out.println("存入文件数据数量:"+list.size()+"条数据");
for (String s:
list) {
System.out.println(""+s+"");
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void queryHighlightIndexDataByKeyword(String[] indexNames,String[] filedNames,String keyword,int from,int size) throws IOException {
// 1、创建search请求
SearchRequest searchRequest = new SearchRequest(indexNames);
// 2、用SearchSourceBuilder来构造查询请求体 ,请仔细查看它的方法,构造各种查询的方法都在这。
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//构造QueryBuilder
QueryBuilder matchQueryBuilder = QueryBuilders.multiMatchQuery(keyword,filedNames);
sourceBuilder.query(matchQueryBuilder);
//分页设置
sourceBuilder.from(from);
sourceBuilder.size(size); ;
// 高亮设置
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.requireFieldMatch(false);
//不同字段可有不同设置,如不同标签
for (String filedName: filedNames) {
HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field(filedName);
highlightTitle.preTags("<strong>").postTags("</strong>");
highlightBuilder.field(highlightTitle);
}
sourceBuilder.highlighter(highlightBuilder);
searchRequest.source(sourceBuilder);
//3、发送请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//4、处理响应
if(RestStatus.OK.equals(searchResponse.status())) {
//处理搜索命中文档结果
SearchHits hits = searchResponse.getHits();
TotalHits totalHits = hits.getTotalHits();
System.out.println("totalHits.value : " + totalHits.value);
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
String index = hit.getIndex();
String type = hit.getType();
String id = hit.getId();
float score = hit.getScore();
//取_source字段值
//String sourceAsString = hit.getSourceAsString(); //取成json串
Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 取成map对象
//从map中取字段值
/*String title = (String) sourceAsMap.get("title");
String content = (String) sourceAsMap.get("content"); */
System.out.println("index:" + index + " type:" + type + " id:" + id);
System.out.println("sourceMap : " + sourceAsMap);
//取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField highlight = null;
for (String filedName: filedNames) {
highlight = highlightFields.get(filedName);
if (highlight != null) {
if(highlight != null) {
Text[] fragments = highlight.fragments(); //多值的字段会有多个值
if(fragments != null) {
String fragmentString = fragments[0].string();
System.out.println(filedName+" highlight : " + fragmentString);
//可用高亮字符串替换上面sourceAsMap中的对应字段返回到上一级调用
sourceAsMap.put(filedName, fragmentString);
}
}
}
}
}
System.out.println("<===============>");
SearchHit[] searchHitsWWWW = hits.getHits();
for (SearchHit hit : searchHitsWWWW) {
Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 取成map对象
System.out.println("sourceMap : " + sourceAsMap);
}
}
}
}
{
"query": {
//"match": {
//"content": "中国",
//"username": "中国"
//},
"multi_match": {
"query": "中国",
"fields": [
"content",
"username"
]
}
},
"highlight": {
"pre_tags": [
"<tag1>",
"<tag2>"
],
"post_tags": [
"</tag1>",
"</tag2>"
],
"fields": {
"content": {},
"username": {}
}
}
}