Demo介绍

学习rabbitmq和elasticsearch后的小练习,主要功能点介绍:

1.elasticsearch实现搜索、条件查询和分页;

2.搜索周边酒店信息

3.酒店竞价排名;

4.后台管理;

RabbitMQ介绍

微服务间通讯有同步和异步两种方式:

同步通讯:就像打电话,需要实时响应(Feign调用就属于同步方式)。

异步通讯:就像发邮件,不需要马上回复(RabbitMQ)。

RabbitMQ中的一些角色:

  • publisher:生产者

  • consumer:消费者

  • exchange个:交换机,负责消息路由

  • queue:队列,存储消息

  • virtualHost:虚拟主机,隔离不同租户的exchange、queue、消息的隔离

RabbitMQ安装(基于Docker)

从docker仓库拉去

  1. docker pull rabbitmq:3-management

安装

  1. docker run \
  2. -e RABBITMQ_DEFAULT_USER=itcast \
  3. -e RABBITMQ_DEFAULT_PASS=123321 \
  4. --name mq \
  5. --hostname mq1 \
  6. -p 15672:15672 \
  7. -p 5672:5672 \
  8. -d \
  9. rabbitmq:3-management

  这里第二行是设置登录管理界面的用户名,第三行是密码,第四行是容器名字,第五行是主机名称,第六行是管理界面所需要暴露的端口,第七行是RabbitMQ进程端口。

Elasticsearch介绍

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

Elasticsearch 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

elasticsearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在elasticsearch中,而Json文档中往往包含很多的字段(Field),类似于数据库中的列。

索引(Index),就是相同类型的文档的集合。

数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库中就有映射(mapping),是索引中文档的字段约束信息,类似表的结构约束。

和MYSQL对比:

  • Mysql:擅长事务类型操作,可以确保数据的安全和一致性

  • Elasticsearch:擅长海量数据的搜索、分析、计算.

Elasticsearch安装(基于Docker)

在用elasticsearch之前可以安装kibana(可视化图形工具)

因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络:

  1. docker network create es-net

kibana拉取

  1. 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

第三行是kibana的地址,es是上面创建的网络,来确保两个在同一网络环境

es拉取

  1. docker pull elasticsearch:7.12.1

  es安装

  1. docker run -d \
  2. --name es \
  3. -e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
  4. -e "discovery.type=single-node" \
  5. -v es-data:/usr/share/elasticsearch/data \
  6. -v es-plugins:/usr/share/elasticsearch/plugins \
  7. --privileged \
  8. --network es-net \
  9. -p 9200:9200 \
  10. -p 9300:9300 \
  11. elasticsearch:7.12.1

第三行是指定es的运行内存大小,可根据自己的机器修改,第四行是指定为非集群模式,五六行是挂在数据卷的位置。

RabbitMQ使用

通过SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配。

导入SpringAMQP依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-amqp</artifactId>
  4. </dependency>

  

rabbitMQ---yml配置

  1. spring:
    rabbitmq:
    host: 你的服务器ip地址
    port: 5672
    username: 配置时设置的用户名
    password: 密码
    virtual-host: /

 

Basic Queue 简单队列模型

消息发送(publisher)

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {


  1. @Autowired
  2. private RabbitTemplate rabbitTemplate;
  3.  
  4. @Test
  5. public void testSimpleQueue() {
  6. // 队列名称
  7. String queueName = "simple.queue";
  8. // 消息
  9. String message = "hello, spring amqp!";
  10. // 发送消息
  11. rabbitTemplate.convertAndSend(queueName, message);
  12. }
    }

消息接收(consumer)

在consumer服务的listener包中新建一个类SpringRabbitListener

  1. import org.springframework.amqp.rabbit.annotation.RabbitListener;
  2. import org.springframework.stereotype.Component;
  3.  
  4. @Component
  5. public class SpringRabbitListener {
  6.  
  7. @RabbitListener(queues = "simple.queue")
  8. public void listenSimpleQueueMessage(String msg) throws InterruptedException {
  9. System.out.println("spring 消费者接收到消息:【" + msg + "】");
  10. }
  11. }

WorkQueue

WorkQueue也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息

消息发送

  1. @Test
  2. public void testWorkQueue() throws InterruptedException {
  3. // 队列名称
  4. String queueName = "simple.queue";
  5. // 消息
  6. String message = "hello, message_";
  7. for (int i = 0; i < 50; i++) {
  8. // 发送消息
  9. rabbitTemplate.convertAndSend(queueName, message + i);
  10. Thread.sleep(20);
  11. }
  12. }

消息接收

  1. @RabbitListener(queues = "simple.queue")
  2. public void listenWorkQueue1(String msg) throws InterruptedException {
  3. System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
  4. Thread.sleep(20);
  5. }
  6.  
  7. @RabbitListener(queues = "simple.queue")
  8. public void listenWorkQueue2(String msg) throws InterruptedException {
  9. System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
  10. Thread.sleep(200);
  11. }

  

发布/订阅模型

在订阅模型中,多了一个exchange角色,Exchange(交换机)只负责转发消息,不具备存储消息的能力。

订阅模型不像简单队列模型它需要将队列绑定在交换机上

Fanout模型

绑定队列和交换机在消费者consumer服务中

  1. import org.springframework.amqp.core.Binding;
  2. import org.springframework.amqp.core.BindingBuilder;
  3. import org.springframework.amqp.core.FanoutExchange;
  4. import org.springframework.amqp.core.Queue;
  5. import org.springframework.context.annotation.Bean;
  6. import org.springframework.context.annotation.Configuration;
  7.  
  8. @Configuration
  9. public class FanoutConfig {
  10. /**
  11. * 声明交换机
  12. * @return Fanout类型交换机
  13. */
  14. @Bean
  15. public FanoutExchange fanoutExchange(){
  16. return new FanoutExchange("itcast.fanout");
  17. }
  18.  
  19. /**
  20. * 第1个队列
  21. */
  22. @Bean
  23. public Queue fanoutQueue1(){
  24. return new Queue("fanout.queue1");
  25. }
  26.  
  27. /**
  28. * 绑定队列和交换机
  29. */
  30. @Bean
  31. public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
  32. return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
  33. }
  34.  
  35. /**
  36. * 第2个队列
  37. */
  38. @Bean
  39. public Queue fanoutQueue2(){
  40. return new Queue("fanout.queue2");
  41. }
  42.  
  43. /**
  44. * 绑定队列和交换机
  45. */
  46. @Bean
  47. public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
  48. return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
  49. }
  50. }

  消息发送

  1. @Test
  2. public void testFanoutExchange() {
  3. // 队列名称
  4. String exchangeName = "itcast.fanout";
  5. // 消息
  6. String message = "hello, everyone!";
  7. rabbitTemplate.convertAndSend(exchangeName, "", message);
  8. }

  消息接收

  1. @RabbitListener(queues = "fanout.queue1")
  2. public void listenFanoutQueue1(String msg) {
  3. System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
  4. }
  5.  
  6. @RabbitListener(queues = "fanout.queue2")
  7. public void listenFanoutQueue2(String msg) {
  8. System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
  9. }

  

Direct模型

基于注解声明队列和交换机和接收消息

  1. @RabbitListener(bindings = @QueueBinding(
  2. value = @Queue(name = "direct.queue1"),
  3. exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
  4. key = {"red", "blue"}
  5. ))
  6. public void listenDirectQueue1(String msg){
  7. System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
  8. }
  9.  
  10. @RabbitListener(bindings = @QueueBinding(
  11. value = @Queue(name = "direct.queue2"),
  12. exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
  13. key = {"red", "yellow"}
  14. ))
  15. public void listenDirectQueue2(String msg){
  16. System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
  17. }

  

消息发送

  1. @Test
  2. public void testSendDirectExchange() {
  3. // 交换机名称
  4. String exchangeName = "itcast.direct";
  5. // 消息
  6. String message = "message ";
  7. // 发送消息
  8. rabbitTemplate.convertAndSend(exchangeName, "red", message);
  9. }

配置JSON转换器

显然,JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。

在publisher和consumer两个服务中都引入依赖:

  1. <dependency>
  2. <groupId>com.fasterxml.jackson.dataformat</groupId>
  3. <artifactId>jackson-dataformat-xml</artifactId>
  4. <version>2.9.10</version>
  5. </dependency>

  

配置消息转换器。

在启动类中添加一个Bean即可:

  1. @Bean
  2. public MessageConverter jsonMessageConverter(){
  3. return new Jackson2JsonMessageConverter();
  4. }

  

小结:服务者publisher消息发送只需要管要发送的交换机名字、Binding和消息,而消费者需要监听是否收到消息

elasticsearch使用

索引库的CRUD

首先打开ip:5601

创建索引库和映射

  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.   }
  23. }

  

查询索引库

  1. GET /索引库名

  

修改索引库

  1. PUT /索引库名/_mapping
  2. {
  3.   "properties": {
  4.     "新字段名":{
  5.       "type": "integer"
  6.     }
  7.   }
  8. }

 

删除索引库

  1. DELETE /索引库名

 

文档操作

新增文档

  1. POST /索引库名/_doc/文档id
  2. {
  3.     "字段1": "值1",
  4.     "字段2": "值2",
  5.     "字段3": {
  6.         "子属性1": "值3",
  7.         "子属性2": "值4"
  8.     },
  9. // ...
  10. }

  例:

  1. POST /test/_doc/1
  2. {
  3.     "info": "Java讲师",
  4.     "email": "123@qq.cn",
  5.     "name": {
  6.         "firstName": "云",
  7.         "lastName": "赵"
  8.     }
  9. }

  

查询文档

  1. GET /{索引库名称}/_doc/{id}

  例:

  1. GET /test/_doc/1

删除文档

  1. DELETE /{索引库名}/_doc/id

  

修改文档

全量修改---全量修改是覆盖原来的文档

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

增量修改---增量修改是只修改指定id匹配的文档中的部分字段

  1. POST /heima/_update/1
  2. {
  3.   "doc": {
  4.     "email": "ZhaoYun@qq.cn"
  5.   }
  6. }

  

RestAPI---forJava

创建索引库

  1. PUT /hotel
  2. {
  3. "mappings": {
  4. "properties": {
  5. "id": {
  6. "type": "keyword"
  7. },
  8. "name":{
  9. "type": "text",
  10. "analyzer": "ik_max_word",
  11. "copy_to": "all"
  12. },
  13. "address":{
  14. "type": "keyword",
  15. "index": false
  16. },
  17. "price":{
  18. "type": "integer"
  19. },
  20. "score":{
  21. "type": "integer"
  22. },
  23. "brand":{
  24. "type": "keyword",
  25. "copy_to": "all"
  26. },
  27. "city":{
  28. "type": "keyword",
  29. "copy_to": "all"
  30. },
  31. "starName":{
  32. "type": "keyword"
  33. },
  34. "business":{
  35. "type": "keyword"
  36. },
  37. "location":{
  38. "type": "geo_point"
  39. },
  40. "pic":{
  41. "type": "keyword",
  42. "index": false
  43. },
  44. "all":{
  45. "type": "text",
  46. "analyzer": "ik_max_word"
  47. }
  48. }
  49. }
  50. }

  

几个特殊字段说明:

  • location:地理坐标,里面包含精度、纬度

  • all:一个组合字段,其目的是将多字段的值 利用copy_to合并,提供给用户搜索

初始化RestClient

引入es的RestHighLevelClient依赖

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

  因为SpringBoot默认的ES版本是7.6.2,所以我们需要覆盖默认的ES版本

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

  初始化RestHighLevelClient

  1. import org.apache.http.HttpHost;
  2. import org.elasticsearch.client.RestHighLevelClient;
  3. import org.junit.jupiter.api.AfterEach;
  4. import org.junit.jupiter.api.BeforeEach;
  5. import org.junit.jupiter.api.Test;
  6.  
  7. import java.io.IOException;
  8.  
  9. public class HotelIndexTest {
  10. private RestHighLevelClient client;
  11.  
  12. @BeforeEach
  13. void setUp() {
  14. this.client = new RestHighLevelClient(RestClient.builder(
  15. HttpHost.create("http://192.168.150.101:9200")
  16. ));
  17. }
  18.  
  19. @AfterEach
  20. void tearDown() throws IOException {
  21. this.client.close();
  22. }
  23. }

  

创建索引库

  1. @Test
  2. void createHotelIndex() throws IOException {
  3. // 1.创建Request对象
  4. CreateIndexRequest request = new CreateIndexRequest("hotel");
  5. // 2.准备请求的参数:DSL语句
  6. request.source(*DSL语句*, XContentType.JSON);
  7. // 3.发送请求
  8. client.indices().create(request, RequestOptions.DEFAULT);
  9. }

删除索引库

  1. @Test
  2. void testDeleteHotelIndex() throws IOException {
  3. // 1.创建Request对象
  4. DeleteIndexRequest request = new DeleteIndexRequest("hotel");
  5. // 2.发送请求
  6. client.indices().delete(request, RequestOptions.DEFAULT);
  7. }

  

判断索引库是否存在

  1. @Test
  2. void testExistsHotelIndex() throws IOException {
  3. // 1.创建Request对象
  4. GetIndexRequest request = new GetIndexRequest("hotel");
  5. // 2.发送请求
  6. boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
  7. // 3.输出
  8. System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
  9. }

  

RestClient操作文档

新增文档

  1. @Test
  2. void testAddDocument() throws IOException {
  3. // 1.根据id查询酒店数据
  4. Hotel hotel = hotelService.getById(61083L);
  5.  
  6. // 2.将hotel 转json
  7. String json = JSON.toJSONString(hotel );
  8.  
  9. // 1.准备Request对象
  10. IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
  11. // 2.准备Json文档
  12. request.source(json, XContentType.JSON);
  13. // 3.发送请求
  14. client.index(request, RequestOptions.DEFAULT);
  15. }

  

查询文档(根据id)

  1. @Test
  2. void testGetDocumentById() throws IOException {
  3. // 1.准备Request
  4. GetRequest request = new GetRequest("hotel", "61082");
  5. // 2.发送请求,得到响应
  6. GetResponse response = client.get(request, RequestOptions.DEFAULT);
  7. // 3.解析响应结果
  8. String json = response.getSourceAsString();
  9.  
  10. HotelDoc hotel = JSON.parseObject(json, Hotel.class);
  11. System.out.println(hotel);
  12. }

  

删除文档

  1. @Test
  2. void testDeleteDocument() throws IOException {
  3. // 1.准备Request
  4. DeleteRequest request = new DeleteRequest("hotel", "61083");
  5. // 2.发送请求
  6. client.delete(request, RequestOptions.DEFAULT);
  7. }

 

修改文档

 

  1. @Test
  2. void testUpdateDocument() throws IOException {
  3. // 1.准备Request
  4. UpdateRequest request = new UpdateRequest("hotel", "61083");
  5. // 2.准备请求参数
  6. request.doc(
  7. "price", "952",
  8. "starName", "四钻"
  9. );
  10. // 3.发送请求
  11. client.update(request, RequestOptions.DEFAULT);
  12. }

  

批量导入文档

  1. @Test
  2. void testBulkRequest() throws IOException {
  3. // 批量查询酒店数据
  4. List<Hotel> hotels = hotelService.list();
  5.  
  6. // 1.创建Request
  7. BulkRequest request = new BulkRequest();
  8. // 2.准备参数,添加多个新增的Request
  9. for (Hotel hotel : hotels) {
  10.  
  11. // 2.1.创建新增文档的Request对象
  12. request.add(new IndexRequest("hotel")
  13. .id(hotel.getId().toString())
  14. .source(JSON.toJSONString(hotel), XContentType.JSON));
  15. }
  16. // 3.发送请求
  17. client.bulk(request, RequestOptions.DEFAULT);
  18. }

  

DSL查询分类

  • 查询所有:查询出所有数据,一般测试用。例如:match_all

  • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:

    • match查询:单字段查询

    • multi_match查询:多字段查询,任意一个字段符合条件就算符合查询条件

      • match和multi_match的区别是什么?

        •   match:根据一个字段查询

        •   multi_match:根据多个字段查询,参与查询字段越多,查询性能越差

  • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:

    • ids 根据id查询

    • range  根据值的范围查询

    • term 根据词条精确值查询

  • 地理(geo)查询:根据经纬度查询。例如:

    • geo_distance 附近查询,也叫做距离查询

    • geo_bounding_box  矩形范围查询

  • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:

    •   fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名

    •   bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索

    相关性算分

    当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(_score),返回结果时按照分值降序排列

function score 查询中包含四部分内容:

  • 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)

  • 过滤条件:filter部分,符合该条件的文档才会重新算分

  • 算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数

    • weight:函数结果是常量

    • field_value_factor:以文档中的某个字段值作为函数结果

    • random_score:以随机数作为函数结果

    • script_score:自定义算分函数算法

  • 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:

    • multiply:相乘

    • replace:用function score替换query score

    • 其它,例如:sum、avg、max、min

function score的运行流程如下:

  • 1)根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)

  • 2)根据过滤条件,过滤文档

  • 3)符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)

  • 4)将原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分。

因此,其中的关键点是:

  • 过滤条件:决定哪些文档的算分被修改

  • 算分函数:决定函数算分的算法

  • 运算模式:决定最终算分结果

示例:

  1. GET /hotel/_search
  2. {
  3.   "query": {
  4.     "function_score": {
  5.       "query": { .... }, // 原始查询,可以是任意条件
  6.       "functions": [ // 算分函数
  7.         {
  8.           "filter": { // 满足的条件,品牌必须是如家
  9.             "term": {
  10.               "brand": "如家"
  11.             }
  12.           },
  13.           "weight": 2 // 算分权重为2
  14.         }
  15.       ],
  16. "boost_mode": "sum" // 加权模式,求和
  17.     }
  18.   }
  19. }

  

布尔查询(bool)

布尔查询是一个或多个查询子句的组合,每一个子句就是一个子查询。子查询的组合方式有:

  • must:必须匹配每个子查询,类似“与”

  • should:选择性匹配子查询,类似“或”

  • must_not:必须不匹配,不参与算分,类似“非”

  • filter:必须匹配,不参与算分

每一个不同的字段,其查询的条件、方式都不一样,必须是多个不同的查询,而要组合这些查询,就必须用bool查询了。

需要注意的是,搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:

  • 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分

  • 其它过滤条件,采用filter查询。不参与算分

示例

  1. GET /hotel/_search
  2. {
  3.   "query": {
  4.     "bool": {
  5.       "must": [
  6.         {"term": {"city": "上海" }}
  7.       ],
  8.       "should": [
  9.         {"term": {"brand": "皇冠假日" }},
  10. {"term": {"brand": "华美达" }}
  11.       ],
  12.       "must_not": [
  13.         { "range": { "price": { "lte": 500 } }}
  14.       ],
  15.       "filter": [
  16.         { "range": {"score": { "gte": 45 } }}
  17.       ]
  18.     }
  19.   }
  20. }

  

查询的语法基本一致:

  1. GET /indexName/_search
  2. {
  3.   "query": {
  4.     "查询类型": {
  5.       "查询条件": "条件值"
  6.     }
  7.   }
  8. }

  

搜索结果处理

排序

普通字段排序

排序条件是一个数组,也就是可以写多个排序条件。按照声明的顺序,当第一个条件相等时,再按照第二个条件排序,以此类推

  1. GET /indexName/_search
  2. {
  3.   "query": {
  4.     "match_all": {}
  5.   },
  6.   "sort": [
  7.     {
  8.       "FIELD": "desc"  // 排序字段、排序方式ASC、DESC
  9.     }
  10.   ]
  11. }

  

地理坐标排序

 

  1. GET /indexName/_search
  2. {
  3.   "query": {
  4.     "match_all": {}
  5.   },
  6.   "sort": [
  7.     {
  8.       "_geo_distance" : {
  9.           "FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点
  10.           "order" : "asc", // 排序方式
  11.           "unit" : "km" // 排序的距离单位
  12.       }
  13.     }
  14.   ]
  15. }

  

这个查询的含义是:

  • 指定一个坐标,作为目标点

  • 计算每一个文档中,指定字段(必须是geo_point类型)的坐标 到目标点的距离是多少

  • 根据距离排序

分页

基本的分页

  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": 990, // 分页开始的位置,默认为0
  7.   "size": 10, // 期望获取的文档总数
  8.   "sort": [
  9.     {"price": "asc"}
  10.   ]
  11. }

 

高亮

  1. GET /hotel/_search
  2. {
  3.   "query": {
  4.     "match": {
  5.       "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
  6.     }
  7.   },
  8.   "highlight": {
  9.     "fields": { // 指定要高亮的字段
  10.       "FIELD": {
  11.         "pre_tags": "<em>",  // 用来标记高亮字段的前置标签
  12.         "post_tags": "</em>" // 用来标记高亮字段的后置标签
  13.       }
  14.     }
  15.   }
  16. }

  

RestClient查询文档

基本步骤:

  • 第一步,创建SearchRequest对象,指定索引库名

  • 第二步,利用request.source()构建DSL,DSL中可以包含查询、分页、排序、高亮等

    • query():代表查询条件,利用QueryBuilders.matchAllQuery()构建一个match_all查询的DSL

  • 第三步,利用client.search()发送请求,得到响应

  • 第四步,解析响应

示例:

  1. @Test
  2. void testMatchAll() throws IOException {
  3. // 1.准备Request
  4. SearchRequest request = new SearchRequest("hotel");
  5. // 2.准备DSL
  6. request.source()
  7. .query(QueryBuilders.matchAllQuery());
  8. // 3.发送请求
  9. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  10.  
  11. // 4.解析响应
  12. handleResponse(response);
  13. }
  14.  
  15. private void handleResponse(SearchResponse response) {
  16. // 4.解析响应
  17. SearchHits searchHits = response.getHits();
  18. // 4.1.获取总条数
  19. long total = searchHits.getTotalHits().value;
  20. System.out.println("共搜索到" + total + "条数据");
  21. // 4.2.文档数组
  22. SearchHit[] hits = searchHits.getHits();
  23. // 4.3.遍历
  24. for (SearchHit hit : hits) {
  25. // 获取文档source
  26. String json = hit.getSourceAsString();
  27. // 反序列化
  28. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  29. System.out.println("hotelDoc = " + hotelDoc);
  30. }
  31. }

match查询

  1. @Test
  2. void testMatch() throws IOException {
  3. // 1.准备Request
  4. SearchRequest request = new SearchRequest("hotel");
  5. // 2.准备DSL
  6. request.source()
  7. .query(QueryBuilders.matchQuery("all", "如家"));
  8. // 3.发送请求
  9. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  10. // 4.解析响应
  11. handleResponse(response);
  12.  
  13. }

 

精确查询

同上换成

  1. QueryBuilders.termQuery(“字段”,“值”);
  1. QueryBuilders.rangeQuery(“字段”).gte(min).lte(max);
     

布尔查询

与其它查询的差别同样是在查询条件的构建,QueryBuilders,结果解析等其他代码完全不变。

  1. @Test
  2. void testBool() throws IOException {
  3. // 1.准备Request
  4. SearchRequest request = new SearchRequest("hotel");
  5. // 2.准备DSL
  6. // 2.1.准备BooleanQuery
  7. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
  8. // 2.2.添加term
  9. boolQuery.must(QueryBuilders.termQuery("city", "杭州"));
  10. // 2.3.添加range
  11. boolQuery.filter(QueryBuilders.rangeQuery("price").lte(250));
  12.  
  13. request.source().query(boolQuery);
  14. // 3.发送请求
  15. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  16. // 4.解析响应
  17. handleResponse(response);
  18.  
  19. }

  

 

排序、分页

  1. @Test
  2. void testPageAndSort() throws IOException {
  3. // 页码,每页大小
  4. int page = 1, size = 5;
  5.  
  6. // 1.准备Request
  7. SearchRequest request = new SearchRequest("hotel");
  8. // 2.准备DSL
  9. // 2.1.query
  10. request.source().query(QueryBuilders.matchAllQuery());
  11. // 2.2.排序 sort
  12. request.source().sort("price", SortOrder.ASC);
  13. // 2.3.分页 from、size
  14. request.source().from((page - 1) * size).size(5);
  15. // 3.发送请求
  16. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  17. // 4.解析响应
  18. handleResponse(response);
  19.  
  20. }

  

高亮

  1. @Test
  2. void testHighlight() throws IOException {
  3. // 1.准备Request
  4. SearchRequest request = new SearchRequest("hotel");
  5. // 2.准备DSL
  6. // 2.1.query
  7. request.source().query(QueryBuilders.matchQuery("all", "如家"));
  8. // 2.2.高亮
  9. request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
  10. // 3.发送请求
  11. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  12. // 4.解析响应
  13. handleResponse(response);
  14.  
  15. }

  高粱结果解析

  1. private void handleResponse(SearchResponse response) {
  2. // 4.解析响应
  3. SearchHits searchHits = response.getHits();
  4. // 4.1.获取总条数
  5. long total = searchHits.getTotalHits().value;
  6. System.out.println("共搜索到" + total + "条数据");
  7. // 4.2.文档数组
  8. SearchHit[] hits = searchHits.getHits();
  9. // 4.3.遍历
  10. for (SearchHit hit : hits) {
  11. // 获取文档source
  12. String json = hit.getSourceAsString();
  13. // 反序列化
  14. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  15. // 获取高亮结果
  16. Map<String, HighlightField> highlightFields = hit.getHighlightFields();
  17. if (!CollectionUtils.isEmpty(highlightFields)) {
  18. // 根据字段名获取高亮结果
  19. HighlightField highlightField = highlightFields.get("name");
  20. if (highlightField != null) {
  21. // 获取高亮值
  22. String name = highlightField.getFragments()[0].string();
  23. // 覆盖非高亮结果
  24. hotelDoc.setName(name);
  25. }
  26. }
  27. System.out.println("hotelDoc = " + hotelDoc);
  28. }
  29. }

  

数据聚合

聚合(aggregations可以让我们极其方便的实现对数据的统计、分析、运算。例如:

  • 什么品牌的手机最受欢迎?

  • 这些手机的平均价格、最高价格、最低价格?

  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索效果。

聚合常见的有三类:

  • 桶(Bucket)聚合:用来对文档做分组

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组

    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值

    • Max:求最大值

    • Min:求最小值

    • Stats:同时求max、min、avg、sum等

  • 管道(pipeline)聚合:其它聚合的结果为基础做聚合

注意:参加聚合的字段必须是keyword、日期、数值、布尔类型

 DSL实现

  1. GET /hotel/_search
  2. {
  3.   "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果
  4.   "aggs": { // 定义聚合
  5.     "brandAgg": { //给聚合起个名字
  6.       "terms": { // 聚合的类型,按照品牌值聚合,所以选择term
  7.         "field": "brand", // 参与聚合的字段
  8.         "size": 20 // 希望获取的聚合结果数量
  9.       }
  10.     }
  11.   }
  12. }

聚合结果排序

  1. GET /hotel/_search
  2. {
  3.   "size": 0, 
  4.   "aggs": {
  5.     "brandAgg": {
  6.       "terms": {
  7.         "field": "brand",
  8.         "order": {
  9.           "_count": "asc" // 按照_count升序排列
  10.         },
  11.         "size": 20
  12.       }
  13.     }
  14.   }
  15. }

  

限定聚合范围

默认情况下,Bucket聚合是对索引库的所有文档做聚合,但真实场景下,用户会输入搜索条件,因此聚合必须是对搜索结果聚合。那么聚合必须添加限定条件。

  1. GET /hotel/_search
  2. {
  3.   "query": {
  4.     "range": {
  5.       "price": {
  6.         "lte": 200 // 只对200元以下的文档聚合
  7.       }
  8.     }
  9.   }, 
  10.   "size": 0, 
  11.   "aggs": {
  12.     "brandAgg": {
  13.       "terms": {
  14.         "field": "brand",
  15.         "size": 20
  16.       }
  17.     }
  18.   }
  19. }

  RestAPI

  1. @Override
  2. public Map<String, List<String>> filters(RequestParams params) {
  3. try {
  4. // 1.准备Request
  5. SearchRequest request = new SearchRequest("hotel");
  6. // 2.准备DSL
  7. // 2.1.query
  8. buildBasicQuery(params, request);
  9. // 2.2.设置size
  10. request.source().size(0);
  11. // 2.3.聚合
  12. buildAggregation(request);
  13. // 3.发出请求
  14. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  15. // 4.解析结果
  16. Map<String, List<String>> result = new HashMap<>();
  17. Aggregations aggregations = response.getAggregations();
  18. // 4.1.根据品牌名称,获取品牌结果
  19. List<String> brandList = getAggByName(aggregations, "brandAgg");
  20. result.put("品牌", brandList);
  21. // 4.2.根据品牌名称,获取品牌结果
  22. List<String> cityList = getAggByName(aggregations, "cityAgg");
  23. result.put("城市", cityList);
  24. // 4.3.根据品牌名称,获取品牌结果
  25. List<String> starList = getAggByName(aggregations, "starAgg");
  26. result.put("星级", starList);
  27.  
  28. return result;
  29. } catch (IOException e) {
  30. throw new RuntimeException(e);
  31. }
  32. }
  33.  
  34. private void buildAggregation(SearchRequest request) {
  35. request.source().aggregation(AggregationBuilders
  36. .terms("brandAgg")
  37. .field("brand")
  38. .size(100)
  39. );
  40. request.source().aggregation(AggregationBuilders
  41. .terms("cityAgg")
  42. .field("city")
  43. .size(100)
  44. );
  45. request.source().aggregation(AggregationBuilders
  46. .terms("starAgg")
  47. .field("starName")
  48. .size(100)
  49. );
  50. }
  51.  
  52. private List<String> getAggByName(Aggregations aggregations, String aggName) {
  53. // 4.1.根据聚合名称获取聚合结果
  54. Terms brandTerms = aggregations.get(aggName);
  55. // 4.2.获取buckets
  56. List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
  57. // 4.3.遍历
  58. List<String> brandList = new ArrayList<>();
  59. for (Terms.Bucket bucket : buckets) {
  60. // 4.4.获取key
  61. String key = bucket.getKeyAsString();
  62. brandList.add(key);
  63. }
  64. return brandList;
  65. }

  

网络释义
Kibana: 可芭纳

RabbitMQ和Elasticsearch的使用笔记的更多相关文章

  1. .net Elasticsearch 学习入门笔记

    一. es安装相关1.elasticsearch安装  运行http://localhost:9200/2.head插件3.bigdesk插件安装(安装细节百度:windows elasticsear ...

  2. python采用pika库使用rabbitmq总结,多篇笔记和示例

    这一段时间学习了下rabbitmq,在学习的过程中,发现国内关于python采用pika库使用rabbitmq的资料很少,官网有这方面的资料,不过是都英文的.于是笔者结合自己的理解,就这方面内容写了一 ...

  3. python采用pika库使用rabbitmq总结,多篇笔记和示例(转)

    add by zhj:作者的几篇文章参考了Rabbitmq的Tutorials中的几篇文章. 原文:http://www.01happy.com/python-pika-rabbitmq-summar ...

  4. elasticsearch原理学习笔记

    https://mp.weixin.qq.com/s/dn1n2FGwG9BNQuJUMVmo7w 感谢,透彻的讲解 整理笔记 请说出 唐诗中 包含 前  的诗句 ...... 其实你都会,只是想不起 ...

  5. JAVA各种框架插件常用端口:redis、MySQL、rabbitmq、elasticsearch、tomcat等等

    默认端口号 应用 21 FTP(文件传输) 22 SSH(安全登录).SCP(文件传输).端口重定向 23 Telnet(远程登录) 80 HTTP服务器 1433 SQL Server数据库serv ...

  6. Elasticsearch一些使用笔记(持续更新)

    这篇博客记录这一些运维ES的一些经验. 1.节点磁盘使用率过高,导致ES集群shard无法分配,丢失数据? 有两个配置,分配副本的时候 参数名称 默认值 含义 cluster.routing.allo ...

  7. elasticsearch 分布式阅读笔记(二)

    说明 扩展分为 纵向扩展:购买更好的服务器 横向扩展:增加服务器(elasticsearch更适合横向扩展) elasticsearch可以用于构建高可用和可扩展的系统,elasticsearch天生 ...

  8. Elasticsearch数据建模笔记

    数据建模 数据建模是创建数据模型的过程 数据模型是对真实世界进行抽象描述的一种工具和方法,实现对现实世界的映射 三个过程:概念模型=>逻辑模型=>数据模型 数据模型:结合具体的数据库,在满 ...

  9. .NET 云原生架构师训练营(模块二 基础巩固 RabbitMQ Masstransit 介绍)--学习笔记

    2.6.6 RabbitMQ -- Masstransit 介绍 Masstransit 是什么 Quickstart 消息 Message Masstransit 是什么 Masstransit 是 ...

随机推荐

  1. webpack(10)webpack-dev-server搭建本地服务器

    前言 当我们使用webpack打包时,发现每次更新了一点代码,都需要重新打包,这样很麻烦,我们希望本地能搭建一个服务器,然后写入新的代码能够自动检测出来,这时候就需要用到webpack-dev-ser ...

  2. Kubernetes全栈架构师(二进制高可用安装k8s集群扩展篇)--学习笔记

    目录 二进制Metrics&Dashboard安装 二进制高可用集群可用性验证 生产环境k8s集群关键性配置 Bootstrapping: Kubelet启动过程 Bootstrapping: ...

  3. C语言:清空缓冲区

    缓冲区的优点很明显,它加快了程序的运行速度,减少了硬件的读写次数,让整个计算机变得流畅起来:但是,缓冲区也带来了一些负面影响,经过前面几节的学习相信读者也见识到了.那么,该如何消除这些负面影响呢?思路 ...

  4. 基于SSM框架的旅游网站

    介绍:spring+springmvc+mybatis三大框架,mysql数据库 功能结构图: 效果截图: 数据库表: CREATE TABLE `t_admin` ( `id` int(11) NO ...

  5. 关于Xpath定位方法知道这些基本够用

    一.写在前面 之前写过一些关于元素定位的文章,但是感觉都是很碎片,现在想做个整合,便有了这篇文章. 二.xpath的定位方法 关于xpath定位方法,网上写的已经很成熟了,现已百度首页为例,如下图: ...

  6. 前端开发入门到进阶第四集【使用sublime安装jshint和cssLint】

    参考:https://blog.csdn.net/qq_27965129/article/details/52786224 使用sublime安装JSHint插件: 1,解决不能使用package c ...

  7. 微信小程序云开发-云函数-云函数实现数据的查询、修改和删除功能

    一.云函数获取商品信息 1.创建云函数getData,云函数功能:获取商品信息 2.在本地小程序页面调用云函数getData  二.云函数修改商品信息 1.创建云函数updateData,云函数功能: ...

  8. erase

    erase详细解释及原理 我们先定义一个字符串string string.erase(iterator) iterator表示要删除元素的迭代器. string.erase(it_begin,it_e ...

  9. 第2天 第一个程序&IDEA安装&Java基础语法

    第一个程序 Hello,World! 随便新建一个文件夹,存放代码 新建一个Java文件 文件后缀名为java Hello.java [注意点]系统可能没有显示后缀名,必须手动打开 编写代码 publ ...

  10. Pycharm关联gitlab(http方式)

    Pycharm支持关联gitlab仓库,关联后对远端项目的克隆和提交都很方便.当初笔者在关联时遇到了很多坑,网上也没找到相关解决办法,所以在这里分享下完整的关联过程. 一.安装git 下载地址http ...