image.png
安装:安装elasticsearch.md

1、面临问题

在模糊查询中,由于索引失效,mysql的查询效率太过低下。

2、解决方案-ElasticSearch

2.1、简介

【elasticsearch】

:::tips elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容. :::

【ELK】

:::tips elasticsearch结合kibana、Logstash、Beats,也就是elastic stack(ELK)。被广泛应用在日志数据分析、实时监控等领域:而elasticsearch是elastic stack的核心,负责存储、搜索、分析数据。 :::

【Lucene】

:::tips elasticsearch底层是基于lucene来实现的。
Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发。
官网:https://lucene.apache.org/ :::

2.2、优缺点

【优点】

:::tips 检索数据非常快 :::

【缺点】

:::tips 数据量小不建议使用 :::

2.3、运行原理-倒排索引

【正向索引】

:::tips 正向索引是最传统的,根据id索引的方式。但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程.
优点:
可以给多个字段创建索引
根据索引字段搜索、排序速度非常快
缺点:
根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。 :::

【倒排索引】

:::tips 倒排索引则相反,是先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程。
优点:
根据词条搜索、模糊搜索时,速度非常快
缺点:
只能给词条创建索引,而不是字段
无法根据字段做排序 :::

2.4、相关名词

【索引库】

:::tips 索引(Index),就是相同类型的文档的集合。类似于mysql中的表 :::

【mapping映射】

:::tips 映射(mapping),是索引中文档的字段约束信息,类似mysql表的结构约束。 :::

【文档】

:::tips 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 :::

【词条】

:::tips 词条(Term),对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。 :::

2.5、Kibana

:::tips 可视化工具,在浏览器输入DSL语句即可查看ES中的内容。 :::

3、使用步骤

3.1、Kibana浏览器操作-索引库&文档

【索引库操作】

【创建索引库】
  1. PUT /索引库名称
  2. {
  3. "mappings": {
  4. "properties": {
  5. "字段名":{
  6. "type": "text",
  7. "analyzer": "ik_smart"
  8. },
  9. "字段名2":{
  10. "type": "keyword",
  11. "index": "false"
  12. },
  13. "字段名3":{
  14. "properties": {
  15. "子字段": {
  16. "type": "keyword"
  17. }
  18. }
  19. }
  20. }
  21. }
  22. }

【查看索引库】

:::tips GET /索引库名 :::

【删除索引库】

:::tips DELETE /索引库名 :::

【修改索引库(追加)】
  1. PUT /索引库名/_mapping
  2. {
  3. "properties":{
  4. "新字段":{
  5. "type":"数据类型(integer)"
  6. }
  7. }
  8. }

【文档操作】

【新增文档】
  1. POST /索引库名/_doc/文档id(不给会随机生成id)
  2. {
  3. "字段1": "值1",
  4. "字段2": "值2",
  5. "字段3": {
  6. "子属性1": "值3",
  7. "子属性2": "值4"
  8. }
  9. }

【查看文档】

:::tips GET /索引库名/_doc/文档id :::

【删除文档】

:::tips DELETE /索引库名/_doc/文档id :::

【修改文档】

【全量修改】

  1. PUT /索引库名/_doc/文档id
  2. {
  3. "字段1": "值1",
  4. "字段2": "值2"
  5. }

【增量修改】

  1. POST /索引库名/_update/文档id
  2. {
  3. "doc": {
  4. "字段名": "新的值"
  5. }
  6. }

3.2、java代码操作-索引库&文档

【导入依赖】

  1. <dependency>
  2. <groupId>org.elasticsearch.client</groupId>
  3. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  4. </dependency>

【与版本对应】

  1. <properties>
  2. <java.version>1.8</java.version>
  3. <elasticsearch.version>7.12.1</elasticsearch.version>
  4. </properties>

【初始化连接&关闭连接】

  1. 第一种模式:
  2. private RestHighLevelClient client;
  3. // 创建连接
  4. @BeforeEach
  5. void setUp() {
  6. this.client = new RestHighLevelClient(RestClient.builder(
  7. HttpHost.create("http://192.168.248.222:9200")
  8. ));
  9. }
  10. // 关闭连接
  11. @AfterEach
  12. void tearDown() throws IOException {
  13. this.client.close();
  14. }
  15. 第二种模式:
  16. /**
  17. * 在单元测试方法执行前执行
  18. */
  19. @BeforeEach
  20. public void init(){
  21. client = new RestHighLevelClient(
  22. RestClient.builder(
  23. new HttpHost("192.168.200.129", 9200, "http")
  24. )
  25. );
  26. }
  27. /**
  28. * 在单元测试类执行后执行
  29. */
  30. @AfterEach
  31. public void destory() throws IOException {
  32. if (client!=null){
  33. client.close();
  34. }
  35. }

【索引库操作】

【创建索引库】
  1. // 创建索引库
  2. @Test
  3. public void createIndex() throws IOException {
  4. // 1.创建请求语义对象
  5. CreateIndexRequest request = new CreateIndexRequest("hotel");
  6. // 添加 source
  7. request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
  8. // 2.发送请求
  9. CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
  10. // 3.解析响应
  11. boolean flag = response.isAcknowledged();
  12. // 打印结果
  13. System.out.println(flag);
  14. }

【删除索引库】
  1. @Test
  2. public void deleteIndex() throws IOException {
  3. // 1.创建请求语义对象
  4. DeleteIndexRequest request = new DeleteIndexRequest("hotel");
  5. // 2.发送请求
  6. AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
  7. // 3.解析响应
  8. boolean flag = response.isAcknowledged();
  9. // 打印结果
  10. System.out.println(flag);
  11. }

【查看索引库是否存在】
  1. // 查看索引库是否存在
  2. @Test
  3. public void existsIndex() throws IOException {
  4. // 1.创建请求语义对象
  5. GetIndexRequest request = new GetIndexRequest("hotel");
  6. // 2.发送请求
  7. boolean flag = client.indices().exists(request, RequestOptions.DEFAULT);
  8. // 3.解析响应
  9. // 打印结果
  10. System.out.println(flag);
  11. }

【文档操作】

【添加文档数据】
  1. // 添加文档数据
  2. @Test
  3. public void add() throws IOException {
  4. // 1.根据id获取数据
  5. Hotel hotel = hotelService.getById(36934L);
  6. // 2.转换成 ES 对应的数据
  7. HotelDoc hotelDoc = new HotelDoc(hotel);
  8. // 3.转换成JSON
  9. String hotelDocJson = JSON.toJSONString(hotelDoc);
  10. // 4.创建请求语义对象
  11. IndexRequest request = new IndexRequest("hotel");
  12. request.id(hotel.getId() + "");
  13. request.source(hotelDocJson, XContentType.JSON);
  14. // 5.发送请求
  15. IndexResponse response = client.index(request, RequestOptions.DEFAULT);
  16. // 6.解析响应结果
  17. DocWriteResponse.Result result = response.getResult();
  18. System.out.println(result);
  19. }

【查询文档数据】
  1. @Test
  2. public void select() throws IOException {
  3. // 1.创建请求语义对象
  4. GetRequest request = new GetRequest("hotel","36934");
  5. // 2.发送请求
  6. GetResponse response = client.get(request, RequestOptions.DEFAULT);
  7. // 3.解析响应结果
  8. String json = response.getSourceAsString();
  9. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  10. System.out.println(hotelDoc);
  11. }

【修改文档数据】
  1. // 修改文档数据
  2. @Test
  3. public void update() throws IOException {
  4. // 1.创建请求语义对象
  5. UpdateRequest request = new UpdateRequest("hotel","36934");
  6. request.doc(
  7. "name", "7天连锁酒店(上海宝山路地铁站店)"
  8. );
  9. // 2.发送请求
  10. UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
  11. // 3.解析响应结果
  12. DocWriteResponse.Result result = response.getResult();
  13. System.out.println(result);
  14. }

【删除文档数据】
  1. // 删除文档数据
  2. @Test
  3. public void delete() throws IOException {
  4. // 1.创建请求语义对象
  5. DeleteRequest request = new DeleteRequest("hotel","36934");
  6. // 2.发送请求
  7. DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
  8. // 3.解析响应结果
  9. DocWriteResponse.Result result = response.getResult();
  10. System.out.println(result);
  11. }

【批量添加】

  1. // 批量添加文档数据
  2. @Test
  3. public void add() throws IOException {
  4. // 1.批量查询酒店数据
  5. List<Hotel> hotels = hotelService.list();
  6. // 2.创建请求语义对象
  7. BulkRequest request = new BulkRequest();
  8. for (Hotel hotel : hotels) {
  9. // 转换格式
  10. HotelDoc hotelDoc = new HotelDoc(hotel);
  11. String hotelDocJson = JSON.toJSONString(hotelDoc);
  12. IndexRequest indexRequest = new IndexRequest("hotel");
  13. indexRequest.id(hotel.getId() + "");
  14. indexRequest.source(hotelDocJson, XContentType.JSON);
  15. request.add(indexRequest);
  16. }
  17. // 5.发送请求
  18. BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
  19. // 6.解析响应结果
  20. RestStatus status = response.status();
  21. System.out.println(status);
  22. }

3.3、Kibana浏览器-查询&结果处理

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "match_all": {
  5. }
  6. }
  7. }

【全文检索查询】

【多字段】
  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "multi_match": {
  5. "query": "内容",
  6. "fields": ["字段1", "字段2"]
  7. }
  8. }
  9. }

【单字段】
  1. GET /hotel/_search
  2. {
  3. "query": {
  4. "match": {
  5. "字段": "值"
  6. }
  7. }
  8. }

【精确查询】

【term查询】
  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "term": {
  5. "字段": {
  6. "value": "值"
  7. }
  8. }
  9. }
  10. }

【range查询】
  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "range": {
  5. "字段": {
  6. "gte": 10, // 这里的gte代表大于等于,gt则代表大于
  7. "lte": 20 // lte代表小于等于,lt则代表小于
  8. }
  9. }
  10. }
  11. }

【地理坐标查询】

  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "geo_bounding_box": {
  5. "字段": {
  6. "top_left": { // 左上点
  7. "lat": 31.1,
  8. "lon": 121.5
  9. },
  10. "bottom_right": { // 右下点
  11. "lat": 30.9,
  12. "lon": 121.7
  13. }
  14. }
  15. }
  16. }
  17. }

【附近/距离查询】
  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "geo_distance": {
  5. "distance": "15km", // 半径
  6. "字段": "31.21,121.5" // 圆心
  7. }
  8. }
  9. }

【复合查询】

【算分函数查询】
  1. GET /hotel/_search
  2. {
  3. "query": {
  4. "function_score": {
  5. "query": {
  6. // 原始查询条件 根据BM25算法进行算分
  7. "match": {
  8. "all": "外滩"
  9. }
  10. },
  11. "functions": [
  12. {
  13. // 过滤条件 符合条件重新算分
  14. "filter": {
  15. "term": {
  16. "brand": "如家"
  17. }
  18. },
  19. //算分函数 weight是常量值
  20. "weight": 10
  21. }
  22. ],
  23. //加权模式 默认是multiply
  24. "boost_mode": "sum"
  25. }
  26. }
  27. }

【布尔查询】
  1. GET /hotel/_search
  2. {
  3. "query": {
  4. "bool": {
  5. //必须匹配
  6. "must": [
  7. {
  8. "match": {
  9. "all": "上海"
  10. }
  11. }
  12. ],
  13. //选择匹配
  14. "should": [
  15. {
  16. "term": {
  17. "brand": {
  18. "value": "皇冠假日"
  19. }
  20. }
  21. },
  22. {
  23. "term": {
  24. "brand": "华美达"
  25. }
  26. }
  27. ],
  28. //必须不匹配 不参与算分
  29. "must_not": [
  30. {
  31. "range": {
  32. "price": {
  33. "lte": 400
  34. }
  35. }
  36. }
  37. ],
  38. //必须匹配 不参与算分
  39. "filter": [
  40. {
  41. "geo_distance": {
  42. "distance": "10km",
  43. "location": {
  44. "lat": 31.21,
  45. "lon": 121.5
  46. }
  47. }
  48. }
  49. ]
  50. }
  51. }
  52. }

【排序】

【普通字段】
  1. GET /索引库名/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "sort": [
  7. {
  8. "字段": "desc" // 排序字段、排序方式ASC、DESC
  9. }
  10. ]
  11. }

【地理坐标】
  1. GET /hotel/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "sort": [
  7. {
  8. "_geo_distance": {
  9. "地理坐标相关字段": {
  10. "lat": 31.21,
  11. "lon": 121.5
  12. },
  13. "order":"asc",
  14. //单位
  15. "unit":"km"
  16. }
  17. }
  18. ]
  19. }

【分页】

【基本分页】
  1. GET /hotel/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "from": 0, // 分页开始的位置,默认为0
  7. "size": 10, // 期望获取的文档总数
  8. "sort": [
  9. {"price": "asc"}
  10. ]
  11. }

【深度分页】

【高亮】

  1. GET /hotel/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. },
  6. "from": 0, // 分页开始的位置,默认为0
  7. "size": 10, // 期望获取的文档总数
  8. "sort": [
  9. {"price": "asc"}
  10. ]
  11. }

3.4.ES相关的RESTClient代码实现

  1. @Test
  2. public void search01() throws IOException {
  3. //1.创建请求语义对象
  4. SearchRequest request = new SearchRequest("hotel");
  5. // QueryBuilders: 构建查询类型
  6. //查询所有
  7. request.source().query(QueryBuilders.matchAllQuery());
  8. //条件查询
  9. searchRequest.source().query(QueryBuilders.matchQuery("name","如家酒店"));
  10. searchRequest.source().query(QueryBuilders.multiMatchQuery("如家酒店","name","brand"));
  11. //精准查询
  12. //term查询
  13. searchRequest.source().query(QueryBuilders.termQuery("brand","如家"));
  14. //range范围查询
  15. searchRequest.source().query(QueryBuilders.rangeQuery("price").gte("100").lte("200"));
  16. //布尔查询
  17. // 2.1.准备BooleanQuery
  18. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  19. // 2.2.添加term
  20. boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
  21. // 2.3.添加range
  22. boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
  23. request.source().query(boolQuery);
  24. //排序、分页
  25. // 2.2.排序 sort
  26. request.source().sort("price", SortOrder.ASC);
  27. // 2.3.分页 from、size
  28. request.source().from((page - 1) * size).size(5);
  29. //2.发送请求给ES
  30. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  31. //3.处理返回结果
  32. handleResponse(response);
  33. }
  34. 高亮显示
  35. @Test
  36. void testHighlight() throws IOException {
  37. // 1.准备Request
  38. SearchRequest request = new SearchRequest("hotel");
  39. // 2.准备DSL
  40. // 2.1.query
  41. request.source().query(QueryBuilders.matchQuery("all", "如家"));
  42. // 2.2.高亮
  43. request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
  44. // 3.发送请求
  45. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  46. // 4.解析响应
  47. handleResponse(response);
  48. }
  1. 1controller
  2. @Autowired
  3. private IHotelService service;
  4. /**
  5. * 根据关键字查询
  6. * @param requestParams
  7. * @return
  8. * @throws IOException
  9. */
  10. @PostMapping("/list")
  11. public PageResult searchAll(@RequestBody RequestParams requestParams) throws IOException {
  12. return service.search(requestParams);
  13. }
  14. 2public interface IHotelService extends IService<Hotel> {
  15. /**
  16. * 根据关键字搜索酒店信息
  17. * @param requestParams 请求参数对象,包含用户输入的关键字
  18. * @return 酒店文档列表
  19. */
  20. PageResult search(RequestParams requestParams) throws IOException;
  21. }
  22. 3 @Autowired
  23. private RestHighLevelClient client;
  24. /**
  25. * 根据关键字搜索酒店信息
  26. *
  27. * @param requestParams 请求参数对象,包含用户输入的关键字
  28. * @return 酒店文档列表
  29. */
  30. @Override
  31. public PageResult search(RequestParams requestParams) throws IOException {
  32. //创建请求对象
  33. SearchRequest request = new SearchRequest("hotel");
  34. //根据条件进行查询
  35. buildBasicQuery(request, requestParams);
  36. //分页查询
  37. request.source().from((requestParams.getPage() - 1) * requestParams.getSize()).size(requestParams.getSize());
  38. //处理地址位置
  39. if (requestParams.getLocation() != null && requestParams.getLocation().length() > 0) {
  40. request.source().sort(SortBuilders.geoDistanceSort("location", new GeoPoint(requestParams.getLocation())).order(SortOrder.ASC)
  41. .unit(DistanceUnit.KILOMETERS)
  42. );
  43. }
  44. //发送请求到es
  45. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  46. //处理响应
  47. return handlerResponse(response);
  48. }
  49. 4//响应结果处理
  50. private PageResult handlerResponse(SearchResponse response) {
  51. SearchHits hits = response.getHits();
  52. //获取总条数
  53. long count = hits.getTotalHits().value;
  54. //获取数据
  55. SearchHit[] hitsHits = hits.getHits();
  56. //创建一个已经用来装HotelDoc
  57. List<HotelDoc> docs = new ArrayList<>();
  58. //遍历
  59. for (SearchHit hit : hitsHits) {
  60. String json = hit.getSourceAsString();
  61. //反序列化
  62. HotelDoc doc = JSON.parseObject(json, HotelDoc.class);
  63. //获取距离
  64. Object[] sortValue = hit.getSortValues();
  65. for (Object val : sortValue) {
  66. doc.setDistance(val);
  67. }
  68. docs.add(doc);
  69. }
  70. return new PageResult(count, docs);
  71. }
  72. 5、查询过滤条件
  73. //查询过滤条件
  74. private void buildBasicQuery(SearchRequest request, RequestParams requestParams) {
  75. // 1.构建BooleanQuery
  76. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  77. //判断关键字是否存在
  78. if (requestParams.getKey() == null || requestParams.getKey() == "") {
  79. boolQuery.must(QueryBuilders.matchAllQuery());
  80. } else {
  81. boolQuery.must(QueryBuilders.matchQuery("all", requestParams.getKey()));
  82. }
  83. //条件过滤
  84. //城市过滤
  85. if (requestParams.getCity() != null && requestParams.getCity().length() > 0) {
  86. boolQuery.filter(QueryBuilders.termQuery("city", requestParams.getCity()));
  87. }
  88. //品牌过滤
  89. if (requestParams.getBrand() != null && requestParams.getBrand().length() > 0) {
  90. boolQuery.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));
  91. }
  92. //星级过滤
  93. if (requestParams.getStarName() != null && requestParams.getStarName().length() > 0) {
  94. boolQuery.filter(QueryBuilders.termQuery("starName", requestParams.getStarName()));
  95. }
  96. //价格过滤
  97. if (requestParams.getMinPrice() != null && requestParams.getMaxPrice() != null) {
  98. boolQuery.filter(QueryBuilders.rangeQuery("price").gte(requestParams.getMinPrice()).lte(requestParams.getMaxPrice()));
  99. }
  100. //添加算分函数控制权重
  101. FunctionScoreQueryBuilder functionScoreQueryBuilder =
  102. QueryBuilders.functionScoreQuery(
  103. boolQuery, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
  104. //其中一个function score元素
  105. new FunctionScoreQueryBuilder.FilterFunctionBuilder(
  106. //过滤条件
  107. QueryBuilders.termQuery("isAD", true),
  108. //算分函数
  109. ScoreFunctionBuilders.weightFactorFunction(10)
  110. )
  111. }
  112. );
  113. request.source().query(functionScoreQueryBuilder);
  114. }