赞
踩
ELK(Elastic Stack)是以Elastic为核心的技术栈,如下图所示:
这个Lucene使用java写成的,其实就是个jar包,我们引入之后就可以使用这个Lucene的API。而ES就是基于Lucene的二次开发,对其API进行进一步封装:
基于文档id创建索引。查询词条时必须先找到文档,而后判断是否包含词条
就是传统的关系型数据中的表,通过id或通过某个值来查询记录。
对文档内容分词,对词条创建索引,并记录词条所在文档的信息。查询时先根据词条查询到文档id,而后获取到文档
示例如下:
这个倒排索引其实和生活中字典相当像,你拿到一本字典的目录,肯定不会傻到先找页码,你肯定是先大略看一眼目录的关键字,然后找到关键字之后,去看关键字旁边的页码,最后再根据页码翻到书对应的那一页。
搜索过程图
先倒排索引查出文档的ID值,然后根据ID使用正排索引查出结果。
尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整形。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称为映射类型。
创建网络
因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:
docker network create es-net
加载es镜像
直接拉去镜像即可
docker pull elasticsearch:7.12.1
运行es
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms256m -Xmx256m" \ # 建议512m++ 这里因为阿里云内存有限
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
-v es-logs:/usr/share/elasticsearch/logs \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
命令解释:
-e "cluster.name=es-docker-cluster"
:设置集群名称-e "http.host=0.0.0.0"
:监听的地址,可以外网访问-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:内存大小-e "discovery.type=single-node"
:非集群模式-v es-data:/usr/share/elasticsearch/data
:挂载逻辑卷,绑定es的数据目录-v es-logs:/usr/share/elasticsearch/logs
:挂载逻辑卷,绑定es的日志目录-v es-plugins:/usr/share/elasticsearch/plugins
:挂载逻辑卷,绑定es的插件目录--privileged
:授予逻辑卷访问权--network es-net
:加入一个名为es-net的网络中-p 9200:9200
:端口映射配置拉镜像
docker pull kibana:7.12.1
运行
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
--network es-net
:加入一个名为es-net的网络中,与elasticsearch在同一个网络中-e ELASTICSEARCH_HOSTS=http://es:9200"
:设置elasticsearch的地址,因为kibana已经与elasticsearch在一个网络,因此可以用容器名直接访问elasticsearch-p 5601:5601
:端口映射配置kibana启动一般比较慢,需要多等待一会,可以通过命令:
docker logs -f kibana
打开看看
测试
分词:即把一段中文或者别的划分成一个个的关键字,我们在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一一个匹配操作,默认的中文分词是将每个字看成一个词(不使用用IK分词器的情况下)
es在创建倒排索引时需要对文档分词;在搜索时,需要对用户输入内容分词。但默认的分词规则对中文处理并不友好。
我们在kibana的DevTools中测试:
可以看到每一词都被分出来了,不符合预期,我们需要使用其他分词软件
1)查看数据卷目录
安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:
docker volume inspect es-plugins
显示结果:
[
{
"CreatedAt": "2022-05-06T10:06:34+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/es-plugins/_data",
"Name": "es-plugins",
"Options": null,
"Scope": "local"
}
]
说明plugins目录被挂载到了:/var/lib/docker/volumes/es-plugins/_data
这个目录中。
2)解压分词器安装包
下面我们需要把课前资料中的ik分词器解压缩,重命名为ik
链接:https://pan.baidu.com/s/1oS-Hf5vWFAi5mH76ehDcrg?pwd=rki9
提取码:rki9
3)上传到es容器的插件数据卷中
也就是/var/lib/docker/volumes/es-plugins/_data
:
4)重启容器
# 4、重启容器
docker restart es
# 查看es日志
docker logs -f es
5)测试
IK分词器包含两种模式:
ik_smart
:最少切分ik_max_word
:最细切分,分得词更多,空间占用率高GET /_analyze
{
"analyzer": "ik_max_word",
"text": "阿果程序员学习java太棒了"
}
GET /_analyze
{
"analyzer": "ik_smart",
"text": "阿果程序员学习java太棒了"
}
docker目录下:/var/lib/docker/volumes/es-plugins/_data/ik/config/
中打开IKAnalyzer.cfg.xml文件
aguo.dic aguo.dic就可以存放在当前文件,作为自定义词汇
<?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">aguo.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
aguo.dic文件示例
奥利给
阿果
更新这个词典文件后,需要docker restart es重启es服务
一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
method | url地址 | 描述 |
---|---|---|
PUT(创建,修改) | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
POST(创建) | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST(修改) | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE(删除) | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
GET(查询) | localhost:9200/索引名称/类型名称/文档id | 查询文档通过文档ID |
POST(查询) | localhost:9200/索引名称/类型名称/文档id/_search | 查询所有数据 |
PUT /索引名/类型名/ID
{
DSL语句
}
//新建索引
PUT /aguoindex/1
{
"name":"阿果爱Java",
"age":3
}
指定类型时创建规则
PUT /aguoindex { "mappings":{ "properties":{ "info":{ "type": "text", "analyzer": "ik_smart" }, "name":{ #对象类型 "type":"object", "properties": { "firstname":{ "type":"keyword" }, "lastname":{ "type":"keyword" } } }, "age":{ #不参与倒排索引 "type":"long", "index": false }, "birthday":{ "type":"date" } } } }
禁止修改索引库,允许添加新的字段。
POST /aguoindex/_mapping
{
"properties":{
"sex":{
"type":"keyword"
}
}
}
DElETE /aguoindex
新增
POST /aguoindex/_doc/1
{
"info":"阿果爱学Java",
"age":21,
"brithday":"2000-08-31",
"name":{
"firstname":"阿",
"lastname":"果"
}
}
查询
GET /aguoindex/_doc/1
删除
DELETE /aguoindex/_doc/1
修改
#存在则修改,不存在则新增,全量修改 PUT /aguoindex/_doc/1 { "info":"阿果爱学Java", "age":21, "brithday":"2000-08-31", "name":{ "firstname":"阿", "lastname":"果" } } #局部修改 POST /aguoindex/_update/1 { "doc": { "age":22 } }
ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是组装DSL语句,通过http即请求发送给ES。
链接:https://pan.baidu.com/s/1xkAW_U7_rwAhkSeuMC8BHQ?pwd=bg9q
提取码:bg9q
准备好数据
创建索引
PUT /hotel { "mappings":{ "properties": { "id":{ "type": "keyword" }, "name":{ "type": "text", "analyzer": "ik_max_word", "copy_to": "all" }, "address":{ "type": "keyword", "index": false }, "price":{ "type": "integer" }, "score":{ "type": "integer" }, "brand":{ "type": "keyword", "copy_to": "all" }, "city":{ "type": "keyword", "copy_to": "all" }, "starName":{ "type": "keyword" }, "business":{ "type": "keyword" }, "location":{ "type": "geo_point" }, "pic":{ "type": "keyword", "index": false }, "all":{ "type": "text", "analyzer": "ik_max_word" } } } }
“all” 字段是为了方便搜索而存在,聚集了 酒店名字、品牌、城市等信息的集合体
导入依赖
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.17.4</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>7.17.4</version>
</dependency>
创建es连接
RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://120.76.137.145:9200")
));
创建索引库
//1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
//2、准备请求的参数:DSL语句
request.source(HoTelConstant.createIndexDSL, XContentType.JSON);
//3.发起请求
client.indices().create(request, RequestOptions.DEFAULT);
删除索引库
@Test
void deleteHotelIndex() throws IOException {
//1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
//2.发起请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
判断索引库
@Test
void isExitsHotelIndex() throws IOException {
//1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
//2.发起请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
插入文档
//获取request对象
IndexRequest request = new IndexRequest("hotel").id("123456");
//设置请求的body
request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
//发起请求
client.index(request, RequestOptions.DEFAULT);
查询文档
//获取request对象,索引与id
GetRequest request = new GetRequest("hotel","36934");
//得到response
GetResponse response = client.get(request, RequestOptions.DEFAULT);
//从response得到对象的JSON字符串形式
String hotelJson = response.getSourceAsString();
//反序列化为对象
HotelDoc hotelDoc = JSON.parseObject(hotelJson, HotelDoc.class);
System.out.println(hotelDoc);
更新文档
//获取request
UpdateRequest request = new UpdateRequest("hotel", "36934");
//封装body
request.doc(
"price","999888"
);
//发起更新
client.update(request, RequestOptions.DEFAULT);
删除文档
//获取request
DeleteRequest request = new DeleteRequest("hotel","36934");
//发起删除
client.delete(request, RequestOptions.DEFAULT);
批量导入
List<Hotel> list = iHotelService.list();
LinkedList<HotelDoc> link = new LinkedList<>();
for (Hotel hotel : list) {
link.add(new HotelDoc(hotel));
}
//以上是封装数据
//获取request对象
BulkRequest request = new BulkRequest();
//遍历对象,全部放到request中
for (HotelDoc hotelDoc : link) {
request.add(new IndexRequest("hotel").id(hotelDoc.getId().toString())
.source(JSON.toJSONString(hotelDoc),XContentType.JSON));
}
//提交
client.bulk(request, RequestOptions.DEFAULT);
Elasticsearch:提供了基于SON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:
match查询
全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索,语法:
GET /hotel/_search
{
"query":{
"match":{
"name":"李四"
}
}
}
multi_match 查询
GET /hotel/_search
{
"query":{
"multi_match":{
"query":"外滩如家",
"fields":["brand","name","business"]
}
}
}
term查询:
根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "上海"
}
}
}
}
range查询:
根据数值范围查询,可以是数值、日期的范围
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 10, # 大于等于,去掉e就只是大于
"lte": 2000
}
}
}
}
distance 查询
指定圆心与半径,检索出圆形中所有的点
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance":"15km",
"location":"31.21,121.5"
}
}
}
根据结果集的命中词频进行排序
DSL的模板如下,加权模式可以忽略
示例如下:
让“如家”这个品牌更加靠前。
GET /hotel/_search { "query": { "function_score": { "query": { #query_socre "match": { "all": "外滩" } }, "functions": [ { "filter": { #对“如家”的品牌结果施加权重 "term": { "brand": "如家" } }, "weight": 10 } ] } } }
逻辑运算
模板如下
GET /hotel/_search { "query": { "bool": { "must": [ {} ], "should": [ {} ], "must_not": [ {} ], "filter": [ {} ] } } }
示例
GET /hotel/_search { "query": { "bool": { "must": [ { "term": { "brand": { "value": "如家" } } } ], "should": [ { "match": { "city": "北京" } }, { "match": { "city": "上海" } } ], "must_not": [ { "range": { "price": { "gte": 3000 } } } ], "filter": [ { "geo_distance": { "distance": "15km", "location": { "lat": 31.21, "lon": 121.5 } } } ] } } }
对结果集更加keyword、数值、地理坐标、日期排序
数值排序
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"FIELD": {
"order": "asc/desc"
},
#...
}
]
}
地理排序
距离据点的距离排序
GET /hotel/_search { "query": { "match_all": {} }, "sort": [ { "_geo_distance": { "location": { "lat": 40, "lon": -70 }, "order": "desc", "unit": "km" } } ] }
官方限制1w条分页数据,需要在应用层保证,超出的部分需要通过其他更多条件来筛选结果
模板
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"FIELD": {
"order": "asc"
}
}
],
"from": 10,
"size": 100
}
因为es是把10+100=110的结果全部检索出来,最后截取10条,所以对于大数据量的情况下,需要更多条件来筛选。
就是在搜索结果中把搜索关键字突出显示
原理:
- query域不能使用match_all
- 如果query的待查字段与高亮字段不一致,需要加"require_field_match": “false”
模板
GET /hotel/_search { "query": { "match": { "all": "如家" } }, "highlight": { "fields": {#fields关键字 "name": { "require_field_match": "false", #默认是<em></em> "pre_tags": "<aguo>" , "post_tags": "</aguo>" } } } }
核心就是SearchRequest
解析数据的通用方法
private void analyticalData(SearchResponse search) {
//获取一个列表数据
SearchHits hitList = search.getHits();
long total = hitList.getTotalHits().value;
System.out.printf("共找到"+total+"条数据");
//获取一个hit数组
SearchHit[] hits = hitList.getHits();
//反序列化
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
System.out.println(hotelDoc);
}
}
示例
//获取request对象
SearchRequest request = new SearchRequest("hotel");
//封装body
request.source()
.query(QueryBuilders.matchAllQuery());
//发起请求
SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);
//解析
analyticalData(search);
//request
SearchRequest request = new SearchRequest("hotel");
//封装body
request.source()
.query(QueryBuilders.multiMatchQuery("如家","brand","all"));
SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);
//解析
analyticalData(search);
@Test void booleanQuery() throws IOException { //获取request SearchRequest request = new SearchRequest("hotel"); //使用Bool查询 BoolQueryBuilder builder = new BoolQueryBuilder(); //封装Bool builder.must(QueryBuilders.termQuery("city","北京")); builder.mustNot(QueryBuilders.rangeQuery("price").gt(1000)); builder.filter(QueryBuilders.termQuery("brand","如家")); //封装body request.source().query(builder); //发起请求 SearchResponse search = this.client.search(request, RequestOptions.DEFAULT); //解析请求 analyticalData(search); }
与以下完全对应
GET /hotel/_search { "query": { "bool": { "must": [ { "term": { "city": { "value": "北京" } } } ], "must_not": [ { "range": { "price": { "gt": 1000 } } } ], "filter": [ { "term": { "brand": "如家" } } ] } } }
source()含有query、sort、highlighter,支持链式编程,很舒服
int page = 1;
int pageSize = 10;
//获取request
SearchRequest request = new SearchRequest("hotel");
request.source()
.query(QueryBuilders.termQuery("city","北京"))
//排序
.sort("price", SortOrder.ASC)
//分页
.from((page-1)*pageSize).size(10);
//发起请求
SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);
//解析请求
analyticalData(search);
与query同级别,所以直接在query后面链式
//获取request
SearchRequest request = new SearchRequest("hotel");
request.source()
.query(QueryBuilders.matchQuery("all","二钻"))
.highlighter(new HighlightBuilder()
.field("name")
.requireFieldMatch(false)
.field("city")
.requireFieldMatch(false)
);
//发起请求
SearchResponse search = this.client.search(request, RequestOptions.DEFAULT);
//解析请求
analyticalData(search);
自己写了个工具类
/** * * @param search response结果 * @param type 反序列化的对象类型 * @return 高亮标签插入后对象 * @param <T> * @throws Exception 可能的反射异常,包括反射方法失败、调用失败 */ private <T> Collection<T> analyticalData(SearchResponse search, Class<T> type) throws Exception { //获取一个列表数据 SearchHits hitList = search.getHits(); long total = hitList.getTotalHits().value; System.out.println("共找到"+total+"条数据"); //获取一个hit数组 SearchHit[] hits = hitList.getHits(); //开始反序列化 Collection<T> collection = new LinkedList<>(); for (SearchHit hit : hits) { String sourceAsString = hit.getSourceAsString(); //反序列化 T bean = JSON.parseObject(sourceAsString, type); //获取高亮map Map<String, HighlightField> highlightFields = hit.getHighlightFields(); //遍历高亮field,如name,city要求的高亮 for (String key : highlightFields.keySet()) { //根据field获取高亮的对象,text为,如<em>北京</em> HighlightField highlightField = highlightFields.get(key); Text[] fragments = highlightField.fragments(); //经过不断测试,这里Text.length===1,时间复杂度:O(1) for (Text fragment : fragments) { StringBuilder sb = new StringBuilder(); sb.append("set"); sb.append(key.substring(0,1).toUpperCase()); sb.append(key.substring(1)); Method setMethod = bean.getClass().getMethod(sb.toString(), String.class); setMethod.invoke(bean,fragment.string()); } } collection.add(bean); } return collection; }
聚合(aggregations)可以实现对文档数据的统计、分析、运算。
聚合有三类:
聚合三要素
聚合可配置属性有:
aggs与query同级别!
参与聚合的字段必须是keyword类型
默认聚合所有数据,因此大数据时候需要使用query过滤下
语法
GET /hotel/_search
{
"size": 0,#表示不显示文档
"aggs": {
"brandeggs": {#自定义聚合名
"terms": {
"field": "name",
"size": 10,#聚合结果数量
"order": {#自定义排序规则
"_count": "asc"
}
}
}
}
}
对桶聚合的结果再次聚合
语法
GET /hotel/_search { "size": 0, "aggs": { "brandeggs": { "terms": { "field": "brand", "size": 20, "order": { "scoreAgg.avg": "desc" } }, "aggs": { "scoreAgg": {#自定义聚合名 "stats": {#stats是max,min全计算 "field": "score" } } } } } }
"a":{
"terms:{..}//关键字分桶
"aggs":{
"b":{}
"c":{}
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。