1.定时任务线程池(定时执行某一个任务的线程池ScheduledThreadPoolExecutor)
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(
scheduledThreadPoolExecutor.schedule(()->{
System.out.println("延迟队列执行了");
},10, TimeUnit.SECONDS);
2.延迟队列(DelayQueue)DelayQueue 是一个支持延时获取元素的无界阻塞队列,由JDK提供,队列中的元素必须实现 Delayed 接口,并重写 getDelay(TimeUnit) 和 compareTo(Delayed) 方法,DelayQueue 实现延迟队列
getDelay :获取延迟任务的延迟时间,当一个任务延迟时间到了,那么此时从延迟队列中获取延迟任务的时候就可以得到延迟任务
compareTo:当我们调用put,add方法向延迟队列中添加延迟任务的时候,此时就会调用该方法,比较延迟日内瓦的延迟时间,按照时间进行排序
@Configuration
public class DelayQueueConfiguration {
@Bean
publicDelayQueue orderDelayTask() {
return new DelayQueue() ;
}
}
public class OrderDelayTask implements Delayed {
private String orderId ;
private long delayTime ;
publicStringgetOrderId() {
returnorderId;
}
public OrderDelayTask(StringorderId , longdelayTime ) {
this.orderId=orderId ;
this.delayTime=System.currentTimeMillis() +delayTime ; // 计算完整的延迟时间
}
// DelayQueue会调用该方法,每隔一秒执行一次,来获取延迟任务的剩余时间
@Override
public long getDelay(TimeUnitunit) {
System.out.println("OrderDelayTask...getDelay...."+newDate());
returndelayTime-System.currentTimeMillis();
}
/**
* 用于比较延时任务,通过这个时间可以对队列中的元素进行排序。
* 当生产者线程调用 put 之类的方法加入元素时,会触发 Delayed 接口中的 compareTo 方法进行排序,
* 也就是说队列中元素的顺序是按到期时间排序的,而非它们进入队列的顺序。
* 排在队列头部的元素是最早到期的,越往后到期时间赿晚。
* @param o
* @return
*/
@Override
public int compareTo(Delayedo) {
return (int) (getDelay(TimeUnit.MILLISECONDS) -o.getDelay(TimeUnit.MILLISECONDS));
}
}
// 消费者的定时任务
@Component
public class ScheduledTask {
@Autowired
private DelayQueueorderDelayTasks ;
@Scheduled(cron="*/1 * * * * ?")
public void consumerDelayQueue() {
OrderDelayTaskdelayTask=orderDelayTasks.poll(); // 拉取一个延迟任务,如果没有没有延迟任务,那么此时返回的就是null
if(delayTask!=null) {
System.out.println("获取到了延迟任务"+delayTask.getOrderId());
}
}
}
@EnableScheduling
@SpringBootApplication
publicclassDelayApplication {
public static void main(String[] args) {
ConfigurableApplication ContextapplicationContext=SpringApplication.run(DelayApplication.class, args);
DelayQueue delayQueue=applicationContext.getBean(DelayQueue.class);
delayQueue.add(newOrderDelayTask("123" , 30*1000)) ;
}
}
Rabbitmq延迟队列
rabbitmq消息传输方式:
简单队列模式
工作队列模式:一个队列可以绑定多个消费者,多个消费者会共同消费队列中的消息
订阅模式:需要我们指定交换机
fanout:吧消息转发到与之绑定的所有队列
direct:根据消息的routingKey和队列的bindingkey进行比对,然后把消息发送给指定的队列
topic:根据消息的routingKey和队列的bindingKey进行比对,规则匹配,然后把消息发送给指定的队列!在进行bindingKey指定的时候可以使用通配符*匹配一个单词,#匹配多个单词.
rabbitmq死信队列:
作用:存储死信
什么是死信?
信息的(ttl) 信息在队列中的存活时间到了
消费者拒收消息,并且不会把这个消息重写加入到队列中
队列中的消息数量达到了队列的容器上限
默认情况下rabbitmq会直接将死信丢弃
rabbitmq延迟队列:
在rabbitmq中没有直接提供延迟队列的技术,想要实现延迟队列就需要使用死信队列+TTL时间
生产者----交换机------队列ttl(时间)----给队列绑定死信交换机----死信队列-----消费者
1.整合Rabbitmq
1、在pom.xml文件中添加如下依赖
org.springframework.boot
spring-boot-starter-amqp
2、在application.yml文件添加如下配置
# rabbitmq的配置
spring:
rabbitmq:
host: 192.168.6.103
port: 5672
username: admin
password: admin
virtual-host: /
使用RabbitTemplate发送消息测试。
publisher-confirm-type: correlated #确认机制:可以感知消息是否投递到了交换机,如果投递成功,那么此时rabbitmq会给生产者返回ack,如果没有投递成功那么rabbitmq会返回nack
publisher-returns: true #回退机制: 生产者可以感知到消息是否正常投递给了队列,如果没有正常投递,那么此时rabbitmq就会返回一个nack
在application.yml文件中添加如下依赖:
# rabbitmq的配置
spring:
rabbitmq:
host: 192.168.6.103
port: 5672
username: admin
password: admin
virtual-host: /
publisher-confirm-type: correlated #生产者到交换机确认机制
publisher-returns: true #回退机制
定义配置类
@Configuration
public class RabbitMqConfiguration {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
// 创建rabbitTemplate对象
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory) ;
// 设置confirmCallBack,给rabbitTemplate绑定confirmCallBack机制
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, booleanack, Stringcause) {
if(ack) {
System.out.println("消息成功发送给了交换机....");
}else {
System.out.println("消息没有成功发送给交换机....");
}
}
});
// 设置returnCallBack
rabbitTemplate.setMandatory(true); // 将发送失败的消息再次发给生产者
rabbitTemplate.setReturnCallback(newRabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Messagemessage, intreplyCode, String replyText, String exchange, String routingKey) {
System.out.println("消息正常发送给交换机"+exchange+",但是没有发送给队列 ---->routingKey:"+routingKey);
}
});
// 返回
return rabbitTemplate ;
}
}
扩展:提取为公共模块后,自定义注解,哪里需要在使用
@Target(value = ElementType.TYPE) // 当前这个自定义注解的使用位置为启动类
@Retention(value = RetentionPolicy.RUNTIME) // 生效时期
@Import(value = RabbitMQConfiguration.class)
public @interface EnableRabbit {
}
设计1:两个交换机两个队列
设计2:一个交换机(死信交换机和消息交换机使用同一个),两个队列
通过java代码声明对应的队列和交换机,以及队列和交换机的绑定信息
配置类定义如下所示:
/**
* 当使用RabbitTempalte发送消息的时候此时配置才会生效
* 关于如下信息的声明可以使用@RabbitListener注解完成,并且使用@RabbitListener声明完毕以后,声明信息会立即生效
*/
@Configuration
public class MqConfiguration {
// 声明交换机
@Bean
public Exchange Exchange() {
return ExchangeBuilder.directExchange(RabbitConstant.ORDER_EXCHANGE).durable(true).build() ;
}
// 声明订单队列
@Bean
public Queue Queue() {
return QueueBuilder.durable(RabbitConstant.ORDER_QUEUE).deadLetterExchange(RabbitConstant.ORDER_EXCHANGE)
.deadLetterRoutingKey(RabbitConstant.ORDER_DELAY_ROUTING_KEY)
.ttl(RabbitConstant.ORDER_MSG_TTL).build() ;
}
// 完成订单队列和订单交换机的绑定
@Bean
public Binding QueueBinding() {
return BindingBuilder.bind(orderQueue()).to(orderExchange()).with(RabbitConstant.ORDER_ROUTING_KEY).noargs() ;
}
// 声明关闭订单队列
@Bean
public Queue closeQueue() {
return QueueBuilder.durable(RabbitConstant.CLOSE_QUEUE).build() ;
}
// 完成关单队列和交换机的绑定
@Bean
public Binding CloseQueueBinding() {
return BindingBuilder.bind(closeQueue()).to(orderExchange()).with(RabbitConstant.ORDER_DELAY_ROUTING_KEY).noargs() ;
}
常量:
public interface MqConstant {
/**
* 定义订单超时未支付的交换机
*/
public static final String ORDER_TIMEOUT_EXCHANGE = "order.exchange" ;
/**
* 定义订单超时未支付的队列
*/
public static final String ORDER_TIMEOUT_QUEUE = "order.queue" ;
/**
* 定义死信队列的routingKey
*/
public static final String ORDER_TIMEOUT_CLOSE_ROUTINGKEY = "close" ;
/**
* 定时的超时时间
*/
public static final Integer ORDER_TIMEOUT = 1000 * 30 ;
/**
* 定义订单队列的bindKey
*/
public static final String ORDER_ROUTINGKEY = "order" ;
/**
* 定义关闭订单的队列名称
*/
public static final String ORDER_CLOSE_QUEUE = "close.queue" ;
/**
* 定义消息重试次数
*/
public static final String REDIS_MSG_COUNT = "msg:count:" ;
}
为了保证消费消息的时候消息不丢失,那么此时就需要开启消费者手动确认机制:
# rabbitmq的配置
spring:
rabbitmq:
host: 192.168.6.103
port: 5672
username: admin
password: admin
virtual-host: /
publisher-confirm-type: correlated #交换机确认机制
publisher-returns: true #回退机制
listener:
simple:
acknowledge-mode: manual # 手动确认
prefetch: 1 #进行消费者限流
消费者监听器代码实现:
@Component
public class OrderDelayListener {
@Autowired
private OrderBizService orderBizService ;
@RabbitListener(queues = RabbitConstant.CLOSE_QUEUE)
public void closeQueueListener(Message message, Channel channel) {
// 获取消费者标签
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
// 对业务数据进行处理,如果处理成功返回ack标记,如果处理失败返回nack标记
String closeOrderJsonInfo = new String(message.getBody()) ;
Map map = JSON.parseObject(closeOrderJsonInfo, Map.class);
// 获取订单的id和用户的id
Long orderId = Long.parseLong(map.get("orderId").toString());
Long userId = Long.parseLong(map.get("userId").toString());
// 调用关单方法
orderBizService.closeOrder(orderId , userId);
// 返回ack
channel.basicAck(deliveryTag , true);
}catch (Exception e) {
e.printStackTrace();
// 产生异常,处理失败返回nack标记
try {
channel.basicNack(deliveryTag , true , true);
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}