springboot与rabbitMQ实现延迟加载

参考:

http://blog.csdn.net/u014308482/article/details/53036770

http://blog.csdn.net/i_vic/article/details/72742277

里面的例子参考自这两篇博客,记录下使用过程。

为什么要延迟加载:

制定一项任务,在某个时间之后去执行,这种场景比较适合使用延迟加载的模式。

    延迟队列存储的对象肯定是对应的延时消息,所谓”延时消息”是指当消息被发送以后,并不想让消费者立即拿到消息,而是等待指定时间后,消费者才拿到这个消息进行消费。

原理:


Time To Live(TTL)
  RabbitMQ可以针对Queue和Message设置 x-message-tt,来控制消息的生存时间,如果超时,则消息变为dead letter
  RabbitMQ针对队列中的消息过期时间有两种方法可以设置。
A: 通过队列属性设置,队列中所有消息都有相同的过期时间。
B: 对消息进行单独设置,每条消息TTL可以不同。
如果同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的TTL值,就成为dead letter


Dead Letter Exchanges(DLX)
  RabbitMQ的Queue可以配置x-dead-letter-exchange 和x-dead-letter-routing-key(可选)两个参数,如果队列内出现了dead letter,则按照这两个参数重新路由。
x-dead-letter-exchange:出现dead letter之后将dead letter重新发送到指定exchange
x-dead-letter-routing-key:指定routing-key发送


队列出现dead letter的情况有:

  消息或者队列的TTL过期
  队列达到最大长度
  消息被消费端拒绝(basic.reject or basic.nack)并且requeue=false


  利用DLX,当消息在一个队列中变成死信后,它能被重新publish到另一个Exchange。这时候消息就可以重新被消费。

利用这两个特性,设置消息的过期时间,当生产的消息没有消费者去接收(这样的队列称为死信队列),消息

rabbitServer的到达设置的过期时间时,就会将死信队列中的过期消息发送到DLX中设置的Exchange中,这样

就实现了延迟加载。


上图:



集成过程如下:

生产者端配置

1.引入依赖:

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.41</version>
		</dependency>
	</dependencies>

2.配置application.properties

server.port=10001
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.virtual-host=/

3.配置AMQP

@Configuration
public class AmqpConfig {

	@Bean
	RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
		return new RabbitAdmin(connectionFactory);
	}

	@Bean
	@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
	public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
		RabbitTemplate template = new RabbitTemplate(connectionFactory);
		return template;
	}
}

4.声明队列、交换机

@Configuration
public class ExchangeConfig {


	/******************************************死信队列***************************************************/
 //exchange name  
    public static final String DEFAULT_EXCHANGE = "KSHOP";       
    //DLX QUEUE  
    public static final String DEFAULT_DEAD_LETTER_QUEUE_NAME = "kshop.dead.letter.queue";       
    //DLX repeat QUEUE 死信转发队列  
    public static final String DEFAULT_REPEAT_TRADE_QUEUE_NAME = "kshop.repeat.trade.queue";     
    
  //信道配置  
    @Bean  
    public DirectExchange defaultExchange() {  
        return new DirectExchange(DEFAULT_EXCHANGE, true, false);  
    }  
	
    @Bean 
    public Queue repeatTradeQueue() {  
        Queue queue = new Queue(DEFAULT_REPEAT_TRADE_QUEUE_NAME,true,false,false);  
        return queue;   
    }  
      
    @Bean  
    public Binding  drepeatTradeBinding() {  
        return BindingBuilder.bind(repeatTradeQueue()).to(defaultExchange()).with(DEFAULT_REPEAT_TRADE_QUEUE_NAME);  
    }  
  
    @Bean 
    public Queue deadLetterQueue() {  
        Map<String, Object> arguments = new HashMap<>();  
        arguments.put("x-dead-letter-exchange", DEFAULT_EXCHANGE);  
        arguments.put("x-dead-letter-routing-key", DEFAULT_REPEAT_TRADE_QUEUE_NAME);  
        Queue queue = new Queue(DEFAULT_DEAD_LETTER_QUEUE_NAME,true,false,false,arguments);  
        System.out.println("arguments :" + queue.getArguments());  
        return queue;   
    }  
  
    @Bean  
    public Binding  deadLetterBinding() {  
        return BindingBuilder.bind(deadLetterQueue()).to(defaultExchange()).with(DEFAULT_DEAD_LETTER_QUEUE_NAME);  
    }  
		
}

这里指定了声明了死信队列失效之后的发送的交换机和routing-key,其实这里可以指定两个交换机,一个是死信队列的交换机1绑定死信队列,一个是失效之后到达的交换机2绑定延迟队列,死信队列的交换机没有消费者去监听,而交换机2绑定的队列就是真正的延迟队列了,消费者去监听这个队列。


5.定义业务service
@Service
public class DeadLetterService {

	@Autowired
	private RabbitTemplate rabbitTemplate;

	
	public void send(LogCarrier logCarrier) {
		MessagePostProcessor processor = new MessagePostProcessor() {
			@Override
			public Message postProcessMessage(Message message) throws AmqpException {
				message.getMessageProperties().setExpiration(30000 + "");
				return message;
			}
		};
		rabbitTemplate.convertAndSend(ExchangeConfig.DEFAULT_EXCHANGE, ExchangeConfig.DEFAULT_DEAD_LETTER_QUEUE_NAME,
				JSON.toJSONString(logCarrier), processor);
	}

}
这里指定了超时时间为30秒

6.创建controller去调用

@RestController
public class DeadLetterController {

	@Autowired
	private DeadLetterService deadLetterService;

	@GetMapping("deadLetter")
	public void direct() throws InterruptedException {
		long i = 0;
		while(i<10) {
			LogCarrier contract = new LogCarrier();
			contract.setId(i++);
			contract.setType("direct");
			deadLetterService.send(contract);			
		}
		System.out.println("消息发送时间:"+new Date());
	}

}
这样,生产者端的基本都配置完成。

消费者端配置,消费者端的配置很简单:

1.依赖

<dependencies>
		<!-- <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> 
			</dependency> -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-amqp</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.41</version>
		</dependency>
	</dependencies>

2.application.properties

server.port=0
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.publisher-confirms=true
spring.rabbitmq.virtual-host=/


3.AMQP配置
@Configuration
@EnableRabbit
public class ConsumerConfig implements RabbitListenerConfigurer {
	@Bean
	public DefaultMessageHandlerMethodFactory myHandlerMethodFactory() {
		DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
		factory.setMessageConverter(new MappingJackson2MessageConverter());
		return factory;
	}
	@Bean
	public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
		SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
		factory.setConnectionFactory(connectionFactory);
		factory.setPrefetchCount(1);//设置预读取数,可以进行有效的负载均衡。
		factory.setAcknowledgeMode(AcknowledgeMode.AUTO);//自动ask
		return factory;
	}
	@Override
	public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
		registrar.setMessageHandlerMethodFactory(myHandlerMethodFactory());
	}
}


4.监听service

/**
 * 死信队列
 * 
 * @author cfyj 2017年11月24日 下午3:11:05
 *
 */
@Service
public class CustomService4 {
	private static int num = 0;

	@RabbitListener(queues = "kshop.repeat.trade.queue")  
	@RabbitHandler
	public void process(String obj) {
		LogCarrier logCarrier= JSON.parseObject(obj, LogCarrier.class);	
		System.out.println(num+":------消息接收时间"+new Date()+logCarrier);
	}
}

传输实体:

package com.cfyj.demo.domain;

public class LogCarrier {

	private Long id;

	private String type;

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	@Override
	public String toString() {
		return "LogCarrier [id=" + id + ", type=" + type + "]";
	}
}

这样生产者和消费者都配置完成了。注意 生产者和消费者端传输的对象实体类信息必须一致。


这样就开始测试吧,测试之前我们带着几个问题去测试:

只启动生产者,然后向死信队列发送信息,消息失效后会怎么样?

如果指定交换机的类型为fanout,没有消费者监听是否会将信息直接丢弃呢?


1.测试,启动生产者和消费者(先启动生产者来声明交换机和队列)

发送消息的时间


死信队列中的消息数


延迟队列的消息,这时因为过期时间还没到,所以死信队列中的信息还没有到达延迟队列中



消费者收到延迟队列的时间



接收-发送的时间正好为过期时间30s,这样就实现了消息的延迟消费,在到达过期时间后,死信队列的消息会发送到指定x-dead-letter-exchange的交换机中,由交换机发送到设置的延迟队列。


2.当我们只启动生产者时,发送消息,消息会怎么样?(topic类型和direct类型的测试结果一致


发送请求时间:


死信队列中的消息:


当达到过期时间后,延迟队列的消息(注意两个队列收到消息的时间):



当只启动生产者服务然后发送消息到死信队列时,消息会先堆积到死信队列,然后到达过期时间后重发到延迟队列中。


3.如果指定交换机的类型为fanout,没有消费者监听是否会将信息直接丢弃呢?

发送信息后,消息会先进入死信队列中,并没有直接丢弃消息

死信队列,注意接收消息的时间:


死信队列中的消息到达过期时间后:



延迟队列:



测试结果与direct类型相同。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值