目录
(14.2)实现elasticsearch与数据库数据同步。
注意:下面的两种id是完全不一样的id,一个是只用于es搜索的唯一标识(_id),一个是对应数据库的字段(_source里面的id字段)。
- 在 Elasticsearch 中,一个文档(Document)有两个 ID 相关的概念:_id 和 _source。
-
- 1._id 是文档的唯一标识符,由 Elasticsearch 自动分配或者用户显式指定。该字段在创建文档时生成,用于唯一标识每个文档。例如,在索引文档时可以指定 _id 值:
-
- json
- POST /my_index/_doc/1
- {
- "title": "Example",
- "content": "This is an example document."
- }
- 2._source 是文档的实际内容。它是一个存储了文档原始 JSON 内容的字段。默认情况下,当你检索文档时,会返回该文档的完整内容(即 _source 字段)。例如,以下请求会返回具有指定 ID 的文档的全部内容:
-
- json
- GET /my_index/_doc/1
- 返回结果如下所示:
-
- json
- {
- "_index": "my_index",
- "_type": "_doc",
- "_id": "1",
- "_version": 1,
- "_seq_no": 0,
- "_primary_term": 1,
- "found": true,
- "_source": {
- "title": "Example",
- "content": "This is an example document."
- }
- }

es是基于lucene写的。

es是基于lucene写的。


索引:对应数据表。
文档:对应数据表记录。
词条:一条数据表记录有若干词条。

因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:
docker network create es-net
这里我们采用elasticsearch的7.12.1版本的镜像,这个镜像体积非常大,接近1G。不建议大家自己pull。
大家将其上传到虚拟机中,然后运行命令加载即可:
docker load -i es.tar
同理还有kibana的tar包也需要这样做。
运行docker命令,部署单点es:
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1
数据卷挂载提示:docker run -v <宿主机路径>:<容器路径> <镜像名称>
命令解释:
--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:端口映射配置
在浏览器中输入:
http://192.168.150.101:9200
即可看到elasticsearch的响应结果。
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
查看运行日志,当查看到下面的日志,说明成功:

此时,在浏览器输入地址访问(注意该IP地址):
http://192.168.150.101:5601
即可看到结果
点击Dev tools

kibana中提供了一个DevTools界面:

这个界面中可以编写DSL来操作elasticsearch。并且对DSL语句有自动补全功能。

- # 进入容器内部
- docker exec -it elasticsearch /bin/bash
-
- # 在线下载并安装
- ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
-
- #退出
- exit
- #重启容器
- docker restart elasticsearch
安装插件需要知道elasticsearch的plugins目录位置,而我们用了数据卷挂载,因此需要查看elasticsearch的数据卷目录,通过下面命令查看:
docker volume inspect es-plugins
提示:只要将ik分词器放到挂载到容器的主机挂载目录下就行,当时运行容器的时候挂载了:
-v es-plugins:/usr/share/elasticsearch/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 这个目录中。
下面我们需要把课前资料中的ik分词器解压缩,重命名为ik

也就是/var/lib/docker/volumes/es-plugins/_data :

- # 4、重启容器
- docker restart es
- # 查看es日志
- docker logs -f es
IK分词器包含两种模式:
ik_smart:最少切分
ik_max_word:最细切分
- GET /_analyze
- {
- "analyzer": "ik_max_word",
- "text": "黑马程序员学习java太棒了"
- }
结果:
- {
- "tokens" : [
- {
- "token" : "黑马",
- "start_offset" : 0,
- "end_offset" : 2,
- "type" : "CN_WORD",
- "position" : 0
- },
- {
- "token" : "程序员",
- "start_offset" : 2,
- "end_offset" : 5,
- "type" : "CN_WORD",
- "position" : 1
- },
- {
- "token" : "程序",
- "start_offset" : 2,
- "end_offset" : 4,
- "type" : "CN_WORD",
- "position" : 2
- },
- {
- "token" : "员",
- "start_offset" : 4,
- "end_offset" : 5,
- "type" : "CN_CHAR",
- "position" : 3
- },
- {
- "token" : "学习",
- "start_offset" : 5,
- "end_offset" : 7,
- "type" : "CN_WORD",
- "position" : 4
- },
- {
- "token" : "java",
- "start_offset" : 7,
- "end_offset" : 11,
- "type" : "ENGLISH",
- "position" : 5
- },
- {
- "token" : "太棒了",
- "start_offset" : 11,
- "end_offset" : 14,
- "type" : "CN_WORD",
- "position" : 6
- },
- {
- "token" : "太棒",
- "start_offset" : 11,
- "end_offset" : 13,
- "type" : "CN_WORD",
- "position" : 7
- },
- {
- "token" : "了",
- "start_offset" : 13,
- "end_offset" : 14,
- "type" : "CN_CHAR",
- "position" : 8
- }
- ]
- }
随着互联网的发展,“造词运动”也越发的频繁。出现了很多新的词语,在原有的词汇列表中并不存在。比如:“奥力给”,“传智播客” 等。
所以我们的词汇也需要不断的更新,IK分词器提供了扩展词汇的功能。
1)打开IK分词器config目录:

2)在IKAnalyzer.cfg.xml配置文件内容添加:
- "1.0" encoding="UTF-8"?>
- properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
- <properties>
- <comment>IK Analyzer 扩展配置comment>
-
- <entry key="ext_dict">ext.dicentry>
- properties>
3)新建一个 ext.dic,可以参考config目录下复制一个配置文件进行修改
- 传智播客
- 奥力给
4)重启elasticsearch
- docker restart es
-
- # 查看 日志
- docker logs -f elasticsearch

日志中已经成功加载ext.dic配置文件
5)测试效果:
- GET /_analyze
- {
- "analyzer": "ik_max_word",
- "text": "传智播客Java就业超过90%,奥力给!"
- }
注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑
在互联网项目中,在网络间传输的速度很快,所以很多语言是不允许在网络上传递的,如:关于宗教、政治等敏感词语,那么我们在搜索时也应该忽略当前词汇。
IK分词器也提供了强大的停用词功能,让我们在索引时就直接忽略当前的停用词汇表中的内容。
1)IKAnalyzer.cfg.xml配置文件内容添加:
- "1.0" encoding="UTF-8"?>
- properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
- <properties>
- <comment>IK Analyzer 扩展配置comment>
-
- <entry key="ext_dict">ext.dicentry>
-
- <entry key="ext_stopwords">stopword.dicentry>
- properties>
3)在 stopword.dic 添加停用词
黑马
4)重启elasticsearch
- # 重启服务
- docker restart elasticsearch
- docker restart kibana
-
- # 查看 日志
- docker logs -f elasticsearch
日志中已经成功加载stopword.dic配置文件
5)测试效果:
- GET /_analyze
- {
- "analyzer": "ik_max_word",
- "text": "传智播客Java就业率超过95%,奥力给!"
- }
注意当前文件的编码必须是 UTF-8 格式,严禁使用Windows记事本编辑

分词器只对text类型的数据分词。(不分词代表整个内容就是一个词条,分词就是整个内容可能超过一个词条)。
index约束如果为真,则参与倒排索引,否则不参与倒排索引(即不成为词条)。
- PUT /itheima
- {
- "mappings": {
- "properties": {
- "info": {
- "type": "text",
- "analyzer": "ik_smart"
- },
- "email": {
- "type": "keyword",
- "index": false
- },
- "name": {
- "type": "object",
- "properties": {
- "firstName": {
- "type": "keyword"
- },
- "lastName": {
- "type": "keyword"
- }
- }
- }
- }
- }
- }






注意:测试了一下,这也是全量修改。
- POST /itheima/_doc/1
- {
- "info": "1黑马程序员java讲师",
- "email": "zy@itcast.cn",
- "name": {
- "firstName": "云",
- "lastName": "赵"
- }
- }







案例的mapping:
- # 酒店的mapping
- PUT /hotel
- {
- "mappings": {
- "properties": {
- "id": {
- "type": "keyword"
- },
- "name": {
- "type": "text",
- "analyzer": "ik_max_word"
- },
- "address": {
- "type": "keyword",
- "index": false
- },
- "price": {
- "type": "integer"
- },
- "score": {
- "type": "integer"
- },
- "brand": {
- "type": "keyword"
- },
- "city": {
- "type": "keyword"
- },
- "starName": {
- "type": "keyword"
- },
- "business": {
- "type": "keyword"
- },
- "location": {
- "type": "geo_point"
- },
- "pic": {
- "type": "binary",
- "index": false
- }
- }
- }
- }







- GET /hotel/_search
- {
- "query": {
- "match_all": {}
- }
- }

- GET /hotel/_search
- {
- "query": {
- "match": {
- "business": "交大/闵行经济开发区"
- }
- }
- }
-
- GET /hotel/_search
- {
- "query": {
- "multi_match": {
- "query": "上海滩",
- "fields": ["name","city","brand"]
- }
- }
- }

- # term查询
- GET /hotel/_search
- {
- "query": {
- "term": {
- "city": {
- "value": "上海"
- }
- }
- }
- }
-
- # range查询
- GET /hotel/_search
- {
- "query": {
- "range": {
- "price": {
- "gte": 100,
- "lte": 2000
- }
- }
- }
- }

- # 地理查询
- GET /hotel/_search
- {
- "query": {
- "geo_distance": {
- "distance": "150km",
- "location": "31.21,122.6"
- }
- }
- }


- # filter中 虽然指的是过滤,却是过滤出需要的数据(如下面,除了万怡,其他都不要)
- GET /hotel/_search
- {
- "query": {
- "function_score": {
- "query": {
- "match": {
- "name": "紫竹"
- }
- },
- "functions": [
- {
- "filter": {
- "term": {
- "brand": "万怡"
- }
- },
- "weight": 100
- }
- ],
- "boost_mode": "sum"
- }
- }
- }

- GET /hotel/_search
- {
- "query": {
- "bool": {
- "must": [
- {
- "match": {
- "name": "万怡"
- }
- }
- ],
- "must_not": [
- {
- "range": {
- "price": {
- "gt": 700
- }
- }
- }
- ],
- "filter": [
- {
- "geo_distance": {
- "distance": "10km"
- , "location": {
- "lat": 31.02,
- "lon": 121.46
- }
- }
- }
- ]
- }
- }
- }

- GET /hotel/_search
- {
- "query": {
- "match_all": {}
- },
- "sort": [
- {
- "score": "desc"
- },
- {
- "price": "asc"
- }
- ]
- }
-
- GET /hotel/_search
- {
- "query": {
- "match_all": {}
- },
- "sort": [
- {
- "_geo_distance": {
- "location": "31.02118, 121.465185",
- "order": "asc",
- "unit": "km"
- }
- }
- ]
- }


注意:高亮的字段必须是query查询语句中出现过的字段(如下面只有name能高亮,其他字段不会高亮,也就是说highlight里面的字段只能写name,写其他字段是没作用的) 。
- GET /hotel/_search
- {
- "query": {
- "match": {
- "name": "万怡"
- }
- },
- "highlight": {
- "fields": {
- "name": {
- "require_field_match": "false"
- }
- }
- }
- }

要构建查询条件,只要记住一个类:QueryBuilders。
- @Test
- void testMatchAll() throws IOException {
- //1.准备request
- SearchRequest request = new SearchRequest("hotel");
- //2.准备DSL
- request.source().query(QueryBuilders.matchAllQuery());
- //3.发送请求
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- //4.解析结果
- SearchHits searchHits = response.getHits();
- //4.1查询总条数
- long total = searchHits.getTotalHits().value;
- //4.2查询结果数组
- SearchHit[] hits = searchHits.getHits();
- for (SearchHit hit : hits) {
- //4.3获取source
- String json = hit.getSourceAsString();
- //4.4反序列化
- HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
- System.out.println(hotelDoc);
- }
- }





注意:高亮字段在文档里面(索引库包含文档,文档包含高亮字段,每个文档都有记录它的高亮内容),即要从文档里面获取高亮字段内容显示。
- GET /hotel/_search
- {
- "query": {
- "bool": {
- "should": [
- {
- "term": {
- "brand": {
- "value": "万怡"
- }
- }
- },
- {
- "match": {
- "name": "上海"
- }
- }
- ]
- }
- },
- "highlight": {
- "fields": {
- "name": {
- "require_field_match": "false"
- },
- "brand": {
- "require_field_match": "false"
- }
- }
- }
- }
- @Test
- void testHighLighter() throws IOException {
- //1.准备request
- SearchRequest request = new SearchRequest("hotel");
- //2.准备DSL
- //2.1 query
- request.source().query(QueryBuilders.matchQuery("name", "万怡"));
- //2.2 高亮
- request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
- //3.发送请求
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- //4.解析结果
- SearchHits searchHits = response.getHits();
- //4.1查询总条数
- long total = searchHits.getTotalHits().value;
- //4.2查询结果数组
- SearchHit[] hits = searchHits.getHits();
- for (SearchHit hit : hits) {
- //4.3获取source
- String json = hit.getSourceAsString();
- //4.4反序列化
- HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
- //获取高亮结果
- Map
highlightFields = hit.getHighlightFields(); - if (!CollectionUtils.isEmpty(highlightFields)){
- //根据字段名获取高亮结果
- HighlightField highlightField = highlightFields.get("name");
- if (highlightField != null){
- //获取高亮值
- String name = highlightField.getFragments()[0].string();
- //覆盖非高亮结果
- hotelDoc.setName(name);
- }
- }
- System.out.println(hotelDoc);
- }
- }


- @Service
- public class HotelService extends ServiceImpl
implements IHotelService { - @Autowired
- private RestHighLevelClient client;
-
- @Override
- public PageResult search(RequestParams params){
- try {
- //1.准备request
- SearchRequest request = new SearchRequest("hotel");
- //2.准备DSL
- //2.1 query
- //构建BooleanQuery
- BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
- //关键字搜索
- String key = params.getKey();
- if (key == null && "".equals(key)){
- boolQuery.must(QueryBuilders.matchAllQuery());
- }else {
- boolQuery.must(QueryBuilders.matchQuery("name",key));
- }
- //城市条件
- if (params.getCity() != null && !"".equals(params.getCity())){
- boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
- }
- //品牌条件
- if (params.getBrand() != null && !"".equals(params.getBrand())){
- boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
- }
- //品牌条件
- if (params.getStarName() != null && !"".equals(params.getStarName())){
- boolQuery.filter(QueryBuilders.termQuery("starName",params.getStarName()));
- }
- //价格
- if (params.getMinPrice() != null && params.getMaxPrice() != null){
- boolQuery.filter(QueryBuilders
- .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
- }
-
- //算分控制
- FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
- //原始查询
- boolQuery,
- //function score的数组
- new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
- //其中的一个function score元素
- new FunctionScoreQueryBuilder.FilterFunctionBuilder(
- //过滤条件
- QueryBuilders.termQuery("isAD",true),
- //算分函数
- ScoreFunctionBuilders.weightFactorFunction(10)
- )
- });
-
- request.source().query(functionScoreQuery);
- //2.2 分页
- Integer page = params.getPage();
- Integer size = params.getSize();
- request.source().from((page - 1) * size).size(size);
- //2.3排序
- String location = params.getLocation();
- if (location != null && !"".equals(location)){
- request.source().sort(SortBuilders.geoDistanceSort("location",new GeoPoint(location))
- .order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS));
- }
-
- //3.发送请求,得到响应
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- //4.解析响应
- handleResponse(response);
- return null;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private PageResult handleResponse(SearchResponse response) {
- //4.解析结果
- SearchHits searchHits = response.getHits();
- //4.1查询总条数
- long total = searchHits.getTotalHits().value;
- //4.2查询结果数组
- SearchHit[] hits = searchHits.getHits();
- //4.3遍历
- List
hotels = new ArrayList<>(); - for (SearchHit hit : hits) {
- //获取source
- String json = hit.getSourceAsString();
- //反序列化
- HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
- //获取排序值
- Object[] sortValues = hit.getSortValues();
- if (sortValues.length > 0){
- Object sortValue = sortValues[0];
- hotelDoc.setDistance(sortValue);
- }
- hotels.add(hotelDoc);
- }
- //4.4封装返回
- return new PageResult(total,hotels);
- }
- }



- @Test
- void testAggregation() throws IOException {
- // 1.准备request
- SearchRequest request = new SearchRequest("hotel");
- // 2.准备DSL
- // 2.1设置size
- request.source().size(0);
- // 2.2聚合
- request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(20));
- // 3.发送请求
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 4.解析结果
- Aggregations aggregations = response.getAggregations();
- // 4.1根据聚合名称获取聚合结果
- Terms brandTerms = aggregations.get("brandAgg");
- // 4.2获取buckets
- List extends Terms.Bucket> buckets = brandTerms.getBuckets();
- // 4.3遍历
- for (Terms.Bucket bucket : buckets) {
- // 4.4获取key
- String key = bucket.getKeyAsString();
- long docCount = bucket.getDocCount();
- System.out.println(key);
- }
- }

- @Override
- public Map
> filter() { - try {
- // 1.准备request
- SearchRequest request = new SearchRequest("hotel");
- // 2.准备DSL
- // 2.1设置size
- request.source().size(0);
- // 2.2聚合
- buildAggregation(request);
- // 3.发送请求
- SearchResponse response = null;
- response = client.search(request, RequestOptions.DEFAULT);
- // 4.解析结果
- Map
> result = new HashMap<>(); - Aggregations aggregations = response.getAggregations();
- //根据品牌名称,获取品牌结果
- List
brandList = getAggByName(aggregations,"brandAgg"); - result.put("品牌",brandList);
- List
cityList = getAggByName(aggregations,"cityAgg"); - result.put("城市",cityList);
- List
starList = getAggByName(aggregations,"starAgg"); - result.put("星级",starList);
- return result;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private List
getAggByName(Aggregations aggregations,String aggName) { - // 4.1根据聚合名称获取聚合结果
- Terms brandTerms = aggregations.get(aggName);
- // 4.2获取buckets
- List extends Terms.Bucket> buckets = brandTerms.getBuckets();
- // 4.3遍历
- List
brandList = new ArrayList<>(); - for (Terms.Bucket bucket : buckets) {
- // 4.4获取key
- String key = bucket.getKeyAsString();
- brandList.add(key);
- }
- return brandList;
- }
-
- private void buildAggregation(SearchRequest request) {
- request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(20));
- request.source().aggregation(AggregationBuilders.terms("cityAgg").field("city").size(20));
- request.source().aggregation(AggregationBuilders.terms("starAgg").field("starName").size(20));
- }

- @Override
- public Map
> filter(RequestParams params) { - try {
- // 1.准备request
- SearchRequest request = new SearchRequest("hotel");
- // 2.准备DSL
- //query
- request.source().query(QueryBuilders.matchQuery("city",params.getCity()));
- // 2.1设置size
- request.source().size(0);
- // 2.2聚合
- buildAggregation(request);
- // 3.发送请求
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- // 4.解析结果
- Map
> result = new HashMap<>(); - Aggregations aggregations = response.getAggregations();
- //根据品牌名称,获取品牌结果
- List
brandList = getAggByName(aggregations,"brandAgg"); - result.put("品牌",brandList);
- List
cityList = getAggByName(aggregations,"cityAgg"); - result.put("城市",cityList);
- List
starList = getAggByName(aggregations,"starAgg"); - result.put("星级",starList);
- return result;
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private List
getAggByName(Aggregations aggregations,String aggName) { - // 4.1根据聚合名称获取聚合结果
- Terms brandTerms = aggregations.get(aggName);
- // 4.2获取buckets
- List extends Terms.Bucket> buckets = brandTerms.getBuckets();
- // 4.3遍历
- List
brandList = new ArrayList<>(); - for (Terms.Bucket bucket : buckets) {
- // 4.4获取key
- String key = bucket.getKeyAsString();
- brandList.add(key);
- }
- return brandList;
- }
-
- private void buildAggregation(SearchRequest request) {
- request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(20));
- request.source().aggregation(AggregationBuilders.terms("cityAgg").field("city").size(20));
- request.source().aggregation(AggregationBuilders.terms("starAgg").field("starName").size(20));
- }


与ik分词器同级目录。


- DELETE /test
-
- // 自定义拼音分词器
- PUT /test
- {
- "settings": {
- "analysis": {
- "analyzer": {
- "my_analyzer": {
- "tokenizer": "ik_max_word",
- "filter": "py"
- }
- },
- "filter": {
- "py": {
- "type": "pinyin",
- "keep_full_pinyin": false,
- "keep_joined_full_pinyin": true,
- "keep_original": true,
- "limit_first_letter_length": 16,
- "remove_duplicated_term": true,
- "none_chinese_pinyin_tokenize": false
- }
- }
- }
- },
- "mappings": {
- "properties": {
- "name": {
- "type": "text",
- "analyzer": "my_analyzer",
- "search_analyzer": "ik_smart"
- }
- }
- }
- }
-
- POST /test/_doc/1
- {
- "id": 1,
- "name": "狮子"
- }
- POST /test/_doc/2
- {
- "id": 2,
- "name": "虱子"
- }
-
- POST /test/_doc/222
- {
- "name": "耗子"
- }
-
- POST /test/_doc
- {
- "id": 3,
- "name": "老鼠"
- }
-
- GET /test/_search
- {
- "query": {
- "match": {
- "name": "掉入狮子笼咋办"
- }
- }
- }

- // 自动补全的索引库
- PUT test2
- {
- "mappings": {
- "properties": {
- "title":{
- "type": "completion"
- }
- }
- }
- }
- // 示例数据
- POST test2/_doc
- {
- "title": ["Sony", "WH-1000XM3"]
- }
- POST test2/_doc
- {
- "title": ["SK-II", "PITERA"]
- }
- POST test2/_doc
- {
- "title": ["Nintendo", "switch"]
- }
-
- // 自动补全查询
- POST /test2/_search
- {
- "suggest": {
- "title_suggest": {
- "text": "s", // 关键字,补全前缀为s的title内容变成text字段
- "completion": {
- "field": "title", // 补全字段
- "skip_duplicates": true, // 跳过重复的
- "size": 10 // 获取前10条结果
- }
- }
- }
- }

- DELETE /hotel
-
- GET /hotel/_mapping
-
- // 酒店数据索引库
- PUT /hotel
- {
- "settings": {
- "analysis": {
- "analyzer": {
- "text_anlyzer": {
- "tokenizer": "ik_max_word",
- "filter": "py"
- },
- "completion_analyzer": {
- "tokenizer": "keyword",
- "filter": "py"
- }
- },
- "filter": {
- "py": {
- "type": "pinyin",
- "keep_full_pinyin": false,
- "keep_joined_full_pinyin": true,
- "keep_original": true,
- "limit_first_letter_length": 16,
- "remove_duplicated_term": true,
- "none_chinese_pinyin_tokenize": false
- }
- }
- }
- },
- "mappings": {
- "properties": {
- "id":{
- "type": "keyword"
- },
- "name":{
- "type": "text",
- "analyzer": "text_anlyzer",
- "search_analyzer": "ik_smart",
- "copy_to": "all"//all字段三个属性值必须和上面三个属性值一样,name字段才能复制到all字段
- },
- "address":{
- "type": "keyword",
- "index": false
- },
- "price":{
- "type": "integer"
- },
- "score":{
- "type": "integer"
- },
- "brand":{
- "type": "keyword",
- "copy_to": "all"
- },
- "city":{
- "type": "keyword"
- },
- "starName":{
- "type": "keyword"
- },
- "business":{
- "type": "keyword",
- "copy_to": "all"
- },
- "location":{
- "type": "geo_point"
- },
- "pic":{
- "type": "keyword",
- "index": false
- },
- "all":{
- "type": "text",
- "analyzer": "text_anlyzer",
- "search_analyzer": "ik_smart"
- },
- "suggestion":{//该字段是自动补全的字段
- "type": "completion",//该类型是自动补全需要的类型
- "analyzer": "completion_analyzer"//对该字段的值使用这个自定义分词器分词
- }
- }
- }
- }
-
- GET /hotel/_search
- {
- "suggest": {
- "suggestions": {
- "text": "sh",
- "completion": {
- "field": "suggestion",//注意:FIELD要改成小写,不然报错
- "skip_duplicates":true,
- "size": 10
- }
- }
- }
- }

- @Test
- void testSuggest() throws IOException {
- //1.准备Request
- SearchRequest request = new SearchRequest("hotel");
- //2.准备DSL
- request.source().suggest(new SuggestBuilder().addSuggestion(
- "suggestions",
- SuggestBuilders.completionSuggestion("suggestion")
- .prefix("h")
- .skipDuplicates(true)
- .size(10)));
- //3.发起请求
- SearchResponse response = client.search(request, RequestOptions.DEFAULT);
- //4.解析结果
- Suggest suggest = response.getSuggest();
- //4.1根据补全查询名称,获取补全结果
- CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
- //4.2获取options并遍历
- for (CompletionSuggestion.Entry.Option option : suggestions.getOptions()) {
- String text = option.getText().toString();
- System.out.println(text);
- }
- }


下面是部分代码:
- spring:
- datasource:
- url: jdbc:mysql://localhost:3306/test?useSSL=false
- username: root
- password: tan
- driver-class-name: com.mysql.cj.jdbc.Driver
- rabbitmq:
- host: 192.168.0.101
- port: 5672
- username: itcast
- password: 123321
- virtual-host: / #每个虚拟主机都有自己的队列、交换机、绑定和权限控制等配置。
- #假设有一个消息中间件服务器,其中运行着多个应用程序,每个应用程序都需要使用消息队列来进行异步通信。
- #如果所有应用程序都共享同一个消息队列空间,那么它们之间的消息可能会相互干扰,导致消息丢失或处理错误。
- #而如果为每个应用程序创建一个独立的虚拟主机,那么它们之间的消息就可以得到有效的隔离和保护,避免了潜在的冲突和错误。
-
- @Configuration
- public class MqConfig {
- @Bean
- public TopicExchange topicExchange(){
- return new TopicExchange(MqConstants.HOTEL_EXCHANGE,true,false);
- }
- @Bean
- public Queue insertQueue(){
- return new Queue(MqConstants.HOTEL_INSERT_QUEUE,true);
- }
- @Bean
- public Queue deleteQueue(){
- return new Queue(MqConstants.HOTEL_DELETE_QUEUE,true);
- }
- @Bean
- public Binding insertQueueBinding(){
- return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY);
- }
- @Bean
- public Binding deleteQueueBinding(){
- return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);
- }
- }
-
-
- @PostMapping
- public void saveHotel(@RequestBody Hotel hotel){
- hotelService.save(hotel);
- amqpTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE,MqConstants.HOTEL_INSERT_KEY,hotel.getId());
- }
-
- @PutMapping()
- public void updateById(@RequestBody Hotel hotel){
- if (hotel.getId() == null) {
- throw new InvalidParameterException("id不能为空");
- }
- hotelService.updateById(hotel);
- amqpTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE,MqConstants.HOTEL_INSERT_KEY,hotel.getId());
- }
-
- @DeleteMapping("/{id}")
- public void deleteById(@PathVariable("id") Long id) {
- hotelService.removeById(id);
- amqpTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE,MqConstants.HOTEL_DELETE_KEY,id);
- }
- @Component
- public class HotelListener {
- @Autowired
- private IHotelService hotelService;
- /**
- * 监听酒店新增或修改的业务
- */
- @RabbitListener(queues = MqConstants.HOTEL_INSERT_QUEUE)
- public void listenerHotelInsertOrUpdate(Long id){
- hotelService.insertById(id);
- }
- /**
- * 监听酒店删除的业务
- */
- @RabbitListener(queues = MqConstants.HOTEL_DELETE_QUEUE)
- public void listenerHotelDelete(Long id){
- hotelService.deleteById(id);
- }
- }
- @Override
- public void insertById(Long id) {
- try {
- //0.根据id查询酒店数据
- Hotel hotel = getById(id);
- //转换为文档类型
- HotelDoc hotelDoc = new HotelDoc(hotel);
- //1.准备Request
- IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
- //2.准备DSL
- request.source(JSON.toJSONString(hotelDoc),XContentType.JSON);
- //3.发送请求
- client.index(request,RequestOptions.DEFAULT);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public void deleteById(Long id) {
- try {
- //1.准备Request
- DeleteRequest request = new DeleteRequest("hotel",id.toString());
- //2.准备发送请求
- client.delete(request,RequestOptions.DEFAULT);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }

首先编写一个docker-compose.yml文件,内容如下:
- version: '2.2' # Docker Compose 版本号
-
- services: # 定义服务
- es01: # Elasticsearch 服务1
- image: elasticsearch:7.12.1 # 使用 elasticsearch:7.12.1 镜像
- container_name: es01 # 容器名称为 es01
- environment: # 设置环境变量
- - node.name=es01 # 节点名称为 es01
- - cluster.name=es-docker-cluster # 集群名称为 es-docker-cluster
- - discovery.seed_hosts=es02,es03 # 发现种子主机为 es02 和 es03
- - cluster.initial_master_nodes=es01,es02,es03 # 集群初始主节点为 es01、es02 和 es03
- - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # 设置 JVM 内存
- volumes: # 挂载数据卷
- - data01:/usr/share/elasticsearch/data # 将 data01 挂载到 /usr/share/elasticsearch/data 目录下
- ports: # 端口映射
- - 9200:9200 # 将主机的 9200 端口映射到容器的 9200 端口
- networks: # 指定使用的网络
- - elastic # 使用 elastic 网络
-
- es02: # Elasticsearch 服务2
- image: elasticsearch:7.12.1
- container_name: es02
- environment:
- - node.name=es02
- - cluster.name=es-docker-cluster
- - discovery.seed_hosts=es01,es03 # 发现种子主机为 es01 和 es03
- - cluster.initial_master_nodes=es01,es02,es03
- - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- volumes:
- - data02:/usr/share/elasticsearch/data
- ports:
- - 9201:9200 # 将主机的 9201 端口映射到容器的 9200 端口
- networks:
- - elastic
-
- es03: # Elasticsearch 服务3
- image: elasticsearch:7.12.1
- container_name: es03
- environment:
- - node.name=es03
- - cluster.name=es-docker-cluster
- - discovery.seed_hosts=es01,es02 # 发现种子主机为 es01 和 es02
- - cluster.initial_master_nodes=es01,es02,es03
- - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- volumes:
- - data03:/usr/share/elasticsearch/data
- ports:
- - 9202:9200 # 将主机的 9202 端口映射到容器的 9200 端口
- networks:
- - elastic
-
- volumes: # 定义数据卷
- data01:
- driver: local # 使用本地驱动
- data02:
- driver: local
- data03:
- driver: local
-
- networks: # 定义网络
- elastic:
- driver: bridge # 使用 bridge 网络模式
es运行需要修改一些linux系统权限,修改`/etc/sysctl.conf`文件:
vi /etc/sysctl.conf
添加下面的内容:
vm.max_map_count=262144是一条Linux系统内核参数的配置命令,用于设置系统中单个进程最大允许的内存区域数量。其作用是控制一个进程可以拥有多少个内存映射区域,以及每个内存映射区域能够映射的最大虚拟内存大小。
vm.max_map_count=262144
然后执行命令,让配置生效:
sysctl -p
通过docker-compose启动集群:
docker-compose up -d


默认情况下,每个节点同时具备这四种功能,但实际开发中一般不这样做,因为每种功能需要的硬件不一样。(比如data节点,处理这些数据需要内存大,硬盘大,性能好等等) 。








底层会自动的根据存入的数据判断数据类型,这种自动分析就叫动态映射。
即添加新字段时(mapping中没有定义的字段),自动帮你加上映射。
dynamic 属性有三种取值:
创建索引库时指定mapping映射。