RabbitMQ使用例子

02、数据同步:思路分析及常量准备

我们已经完成了对MQ的基本学习和认识。接下来,我们就改造项目,实现搜索服务、商品静态页的数据同步。

1)思路分析

发送方:商品微服务

  • 什么时候发?

    当商品服务对商品进行新增和上下架的时候,需要发送一条消息,通知其它服务。

  • 发送什么内容?

    对商品的增删改时其它服务可能需要新的商品数据,但是如果消息内容中包含全部商品信息,数据量太大,而且并不是每个服务都需要全部的信息。因此我们只发送商品id,其它服务可以根据id查询自己需要的信息。

接收方:搜索微服务、静态页微服务

  • 接收消息后如何处理?

    • 搜索微服务:

      • 上架:添加新的数据到索引库

      • 下架:删除索引库数据

    • 静态页微服务:

      • 上架:创建新的静态页

      • 下架:删除原来的静态页

商品上下架数据同步

 

2)常量准备

ly-common中编写一个常量类,记录将来会用到的Exchange名称、Queue名称、routing_key名称

package com.leyou.common.constants;

/**
* @author 黑马程序员
*/
public abstract class MQConstants {

   public static final class Exchange {
       /**
        * 商品服务交换机名称
        */
       public static final String ITEM_EXCHANGE_NAME = "ly.item.exchange";
  }

   public static final class RoutingKey {
       /**
        * 商品上架的routing-key
        */
       public static final String ITEM_UP_KEY = "item.up";
       /**
        * 商品下架的routing-key
        */
       public static final String ITEM_DOWN_KEY = "item.down";
  }

   public static final class Queue{
       /**
        * 搜索服务,商品上架的队列
        */
       public static final String SEARCH_ITEM_UP = "search.item.up.queue";
       /**
        * 搜索服务,商品下架的队列
        */
       public static final String SEARCH_ITEM_DOWN = "search.item.down.queue";

       /**
        * 商品详情服务,商品上架的队列
        */
       public static final String PAGE_ITEM_UP = "page.item.up.queue";
       /**
        * 商品详情服务,商品下架的队列
        */
       public static final String PAGE_ITEM_DOWN = "page.item.down.queue";
  }
}

这些常量我们用一个图来展示用在什么地方:

image-20200323142920428

 

 

 

03、数据同步:商品微服务发送消息

我们先在商品微服务ly-item中实现发送消息。

1)引入依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2)配置文件

我们在application.yml中添加一些有关RabbitMQ的配置:

spring:
rabbitmq:
  host: 127.0.0.1
  username: leyou
  password: leyou
  virtual-host: /leyou
  publisher-confirms: true
  template:
    retry:
      enabled: true
      initial-interval: 10000ms
      max-interval: 80000ms
      multiplier: 2
  • template:有关AmqpTemplate的配置

    • retry:失败重试

      • enabled:开启失败重试

      • initial-interval:第一次重试的间隔时长

      • max-interval:最长重试间隔,超过这个间隔将不再重试

      • multiplier:下次重试间隔的倍数,此处是2即下次重试间隔是上次的2倍

    • exchange:缺省的交换机名称,此处配置后,发送消息如果不指定交换机就会使用这个

  • publisher-confirms:生产者确认机制,确保消息会正确发送,如果发送失败会有错误回执,从而触发重试

3)Json消息序列化方式(选做)

需要注意的是,默认情况下,AMQP会使用JDK的序列化方式进行处理,传输数据比较大,效率太低。我们可以自定义消息转换器,使用JSON来处理:

package com.leyou.item.config;

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* 配置json消息转换
*/
@Configuration
public class RabbitConfig {

   @Bean
   public Jackson2JsonMessageConverter jsonMessageConverter(){
       return new Jackson2JsonMessageConverter();
  }

}

4)改造GoodsService

改造GoodsService中的商品上下架功能,发送消息,注意用静态导入方式,导入在ly-common中定义的常量:

public void updateSaleable(Long id, Boolean saleable) {
       try {
           Spu spu = new Spu();
           spu.setId(id);
           spu.setSaleable(saleable);

           spuMapper.updateById(spu);//注意:updateById可以自动判断NULL不更新

           //发送消息给MQ
           String routingKey = saleable? MQConstants.RoutingKey.ITEM_UP_KEY:MQConstants.RoutingKey.ITEM_DOWN_KEY;
           amqpTemplate.convertAndSend(MQConstants.Exchange.ITEM_EXCHANGE_NAME,routingKey,id);
      } catch (Exception e) {
           e.printStackTrace();
           throw new LyException(ExceptionEnum.UPDATE_OPERATION_FAIL);
      }
  }

注意:此刻不能启动项目测试,因为rabbitMQ中还没有交换机和队列。

 

 

04、数据同步:搜索微服务接收消息

搜索服务接收到消息后要做的事情:

  • 上架:添加新的数据到索引库

  • 下架:删除索引库数据

我们需要两个不同队列,监听不同类型消息。

1)引入依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

 

2)添加配置

spring:
rabbitmq:
  host: 127.0.0.1
  username: leyou
  password: leyou
  virtual-host: /leyou

这里只是接收消息而不发送,所以不用配置template相关内容。

3)Json消息序列化配置

不过,不要忘了消息转换器(如果生产者没有加消息转换器,那么消费者也不要加):

1553262961034

/**
* @author 黑马程序员
*/
@Configuration
public class RabbitConfig {

   @Bean
   public Jackson2JsonMessageConverter messageConverter(){
       return new Jackson2JsonMessageConverter();
  }
}

 

4)编写监听器

1553263050354

代码:

package com.leyou.search.mq;

import com.leyou.common.constants.MQConstants;
import com.leyou.search.service.SearchService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* 商品上下架监听
*/
@Component
@Slf4j
public class ItemListener {

   @Autowired
   private SearchService searchService;

   /**
    * 商品上架->添加索引库到ES
    * 注意:消费者接受的内容和生产者发送的内容要一致
    */
   @RabbitListener(bindings = @QueueBinding(
           value = @Queue(name = MQConstants.Queue.SEARCH_ITEM_UP),
           exchange = @Exchange(name = MQConstants.Exchange.ITEM_EXCHANGE_NAME,type = ExchangeTypes.TOPIC),
           key = MQConstants.RoutingKey.ITEM_UP_KEY
  ))
   public void createIndex(Long spuId){
       try {
           searchService.createIndex(spuId);
           log.info("【索引库操作】建立索引成功,商品ID:"+spuId);
      } catch (Exception e) {
           e.printStackTrace();
           log.error("【索引库操作】建立索引失败,商品ID:"+spuId);
      }
  }

   /**
    * 商品下架->删除索引库
    * 注意:消费者接受的内容和生产者发送的内容要一致
    */
   @RabbitListener(bindings = @QueueBinding(
           value = @Queue(name = MQConstants.Queue.SEARCH_ITEM_DOWN),
           exchange = @Exchange(name = MQConstants.Exchange.ITEM_EXCHANGE_NAME,type = ExchangeTypes.TOPIC),
           key = MQConstants.RoutingKey.ITEM_DOWN_KEY
  ))
   public void deleteIndex(Long spuId){
       try {
           searchService.deleteIndex(spuId);
           log.info("【索引库操作】删除索引成功,商品ID:"+spuId);
      } catch (Exception e) {
           e.printStackTrace();
           log.error("【索引库操作】删除索引失败,商品ID:"+spuId);
      }
  }
   
}

 

5)编写创建和删除索引方法

这里因为要创建和删除索引,我们需要在SearchService中拓展两个方法,创建和删除索引:

 public void createIndex(Long spuId) {
       //1.根据spuId查询SpuDTO
       SpuDTO spuDTO = itemClient.findSpuDTOById(spuId);
       //2.把SpuDTO转换为Goods
       Goods goods = buildGoods(spuDTO);
       //3.把商品存入ES索引库
       searchRepository.save(goods);
  }

   public void deleteIndex(Long spuId) {
       searchRepository.deleteById(spuId);
  }

 

05、数据同步:静态页服务接收消息

商品静态页服务接收到消息后的处理:

  • 上架:创建新的静态页

  • 下架:删除原来的静态页

与前面搜索服务类似,也需要两个队列来处理。

1)引入依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2)添加配置

spring:
rabbitmq:
  host: 127.0.0.1
  username: leyou
  password: leyou
  virtual-host: /leyou

这里只是接收消息而不发送,所以不用配置template相关内容。

3)Json消息序列化配置

不过,不要忘了消息转换器:

1577093458724

 

/**
* @author 黑马程序员
*/
@Configuration
public class RabbitConfig {

   @Bean
   public Jackson2JsonMessageConverter messageConverter(){
       return new Jackson2JsonMessageConverter();
  }
}

 

4)编写监听器

1577093412587

代码:

package com.leyou.page.mq;

import com.leyou.common.constants.MQConstants;
import com.leyou.page.service.PageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
* 商品上下架监听
*/
@Component
@Slf4j
public class ItemListener {

   @Autowired
   private PageService pageService;

   /**
    * 商品上架->添加静态页
    * 注意:消费者接受的内容和生产者发送的内容要一致
    */
   @RabbitListener(bindings = @QueueBinding(
           value = @Queue(name = MQConstants.Queue.PAGE_ITEM_UP),
           exchange = @Exchange(name = MQConstants.Exchange.ITEM_EXCHANGE_NAME,type = ExchangeTypes.TOPIC),
           key = MQConstants.RoutingKey.ITEM_UP_KEY
  ))
   public void createStaticPage(Long spuId){
       try {
           pageService.createStaticPage(spuId);
           log.info("【静态页操作】建立静态页成功,商品ID:"+spuId);
      } catch (Exception e) {
           e.printStackTrace();
           log.error("【静态页操作】建立静态页失败,商品ID:"+spuId);
      }
  }

   /**
    * 商品下架->删除静态页
    * 注意:消费者接受的内容和生产者发送的内容要一致
    */
   @RabbitListener(bindings = @QueueBinding(
           value = @Queue(name = MQConstants.Queue.PAGE_ITEM_DOWN),
           exchange = @Exchange(name = MQConstants.Exchange.ITEM_EXCHANGE_NAME,type = ExchangeTypes.TOPIC),
           key = MQConstants.RoutingKey.ITEM_DOWN_KEY
  ))
   public void deleteStaticPage(Long spuId){
       try {
           pageService.deleteStaticPage(spuId);
           log.info("【静态页操作】删除静态页成功,商品ID:"+spuId);
      } catch (Exception e) {
           e.printStackTrace();
           log.error("【静态页操作】删除静态页失败,商品ID:"+spuId);
      }
  }

}

5)添加删除页面方法

创建商品详情静态页面的方法我们之前已经添加过了,只需要添加删除静态页的方法即可:

/**
    * 用于为每个商品生产静态页面
    */
   public void createStaticPage(Long spuId){
       //1)创建Context上下文对象,设置动态数据(Map集合)
       Context context = new Context();
       //设置动态数据
       context.setVariables(getDetailData(spuId));

       //2)设计一个模板页面(item.html) 注意:模板引擎对象默认会到classpath:templates目录下读取该文件
       String tempName = itemTemplate+".html";

       //3)使用模板引擎对象写出静态页面,定义输出流(输出到nginx服务器的目录中)
       /**
        * 参数一:模板页面名称(封装静态数据)
        * 参数二:Context上下文对象(封装动态数据)
        * 参数三:输出位置
        */
       //特别注意:一旦使用输出流,记住必须关闭流,否则文件被锁定,无法被删除或修改。
       PrintWriter writer = null;
       try {
           writer = new PrintWriter(new File(itemDir,spuId+".html"));
           templateEngine.process(tempName,context,writer);
      } catch (FileNotFoundException e) {
           e.printStackTrace();
      } finally {
           if(writer!=null)writer.close();
      }
  }

   public void deleteStaticPage(Long spuId) {
       //1.读取文件
       File file = new File(itemDir,spuId+".html");
       //2.删除文件
       if(file.exists()){
           file.delete();
      }
  }

 

 

posted @ 2021-03-08 23:50  childrenByCode  阅读(179)  评论(0)    收藏  举报