主流MQ产品
- RabbitMQ
- 优点
- 缺点
- 吞吐量较低
- 消息积压会影响性能
- erlang语言比较小众
- 适用环境
- kafka
- RocketMQ
RabbitMQ
基本概念

交换机
队列
- 类型
- Classic:经典队列是默认类型,灵活性强,大部分功能可以通过参数进行设置,在特定情况下消息会丢失。
- Quorum:适合用于集群使用,一条消息会同步到集群节点上,有消息备份,且主节点故障需要重新选举。增加了毒消息的处理,可通过x-delivery-limit来判断是否为毒消息后进行删除。队列也有一定策略,没有消息是会自动删除。
- Stream:将消息数据存储到了日志文件中,解决了传统消息挤压导致性能下降的问题,但他的刷盘持久化机制需要靠操作系统来进行刷盘,安全性很差。
- 一系列参数
- duration:是否持久化,即rabbitmq重启系统可以到磁盘获取之前未消费的数据
- auto Delete:当前队列没有消费者连接会自动删除
- dead letter exchange:设置死信交换机,即数据没有应答时会将消息转到死信队列中,可用于做延迟队列。
- auto expire:在一定时间内,队列未被使用,就会自动删除
交换机
- 类型
- direct:绑定队列,可通过routingKey完全匹配来给指定的队列传递数据。
- fanout:当交换机接收到消息,他会给绑定的所有队列都传递一条消息。
- topic:支持routingKey通配符匹配,即模糊匹配,*一个单词,#0-n个单词。
- headers:与routingkey不同,他可以通过object对象来进行路由匹配,any是匹配一个键值对即可发送消息,all需要所以键值对都匹配才可以发送消息。
- 一系列参数
- alternate exchange:备用交换机,当消息不能被路由时,那么该不可用消息会转发到备用交换机上。
基础开发模型
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
原生API代码写法
消费者
public class Customer {
private static final String HOST="127.0.0.1";
private static final Integer PORT=5672;
private static final String USERNAME="guest";
private static final String PASSWORD="guest";
private static final String VIRTUAL_HOST="/mirror";
private static final String QUEUE_NAME = "test2";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost(VIRTUAL_HOST);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
Channel channelB = connection.createChannel(2);
Map<String,Object> params = new HashMap<>();
channel.queueDeclare(QUEUE_NAME,true,false,false,params);
channel.basicQos(1);
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("============");
String routingKey = envelope.getRoutingKey();
System.out.println("routingKey->"+routingKey);
String contentType = properties.getContentType();
System.out.println("contentType->"+contentType);
long deliveryTag = envelope.getDeliveryTag();
System.out.println("deliveryTag->"+deliveryTag);
System.out.println("消息:"+ new String(body,"UTF-8"));
channel.basicAck(deliveryTag,true);
}
};
channel.basicConsume(QUEUE_NAME,false,consumer);
}
void pull() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost(VIRTUAL_HOST);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
GetResponse response = channel.basicGet(QUEUE_NAME,false);
byte[] body = response.getBody();
Envelope envelope = response.getEnvelope();
int messageCount = response.getMessageCount();
AMQP.BasicProperties props = response.getProps();
System.out.println("==========");
String routingKey = envelope.getRoutingKey();
System.out.println("routingKey->"+routingKey);
String contentType = props.getContentType();
System.out.println("contentType->"+contentType);
long deliveryTag = envelope.getDeliveryTag();
System.out.println("messageCount"+messageCount);
System.out.println("消息内容->"+new String(body,"UTF-8"));
channel.close();
connection.close();
}
生产者
public class Producer {
private static final String HOST="127.0.0.1";
private static final Integer PORT=5672;
private static final String USERNAME="guest";
private static final String PASSWORD="guest";
private static final String VIRTUAL_HOST="/mirror";
private static final String QUEUE_NAME = "test2";
private static final String EXCHANGE_NAME = "producerExchange";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost(VIRTUAL_HOST);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
Map<String,Object> params = new HashMap<>();
params.put("x-queue-type","classic");
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT,true,false,params);
channel.queueDeclare(QUEUE_NAME,true,false,false,new HashMap<>());
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"key1");
String message = "直接发送到队列";
channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
String message2 = "直接发送到交换机";
channel.basicPublish(EXCHANGE_NAME,"key1", MessageProperties.PERSISTENT_TEXT_PLAIN,message2.getBytes());
channel.close();
connection.close();
}
}
整合Springboot实现mq的使用
连接配置
spring:
application:
name: mqProject
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /mirror
publisher-confirm-type: correlated
publisher-returns: true
listener:
simple:
prefetch: 1
concurrency: 5
max-concurrency: 10
acknowledge-mode: manual
队列和交换机的配置
@Configuration
public class FanoutConfig {
@Bean
public Queue fanoutA(){
return new Queue("testA",true);
}
@Bean
public Queue topicB(){
return new Queue("testB",true);
}
@Bean
public FanoutExchange fanoutExchangeA(){
Map<String,Object> params = new HashMap();
return new FanoutExchange("springExchangeA",true,false);
}
@Bean
public TopicExchange topicExchangeA(){
return new TopicExchange("topicExchange",true,false);
}
@Bean
public Binding bindA(){
return BindingBuilder.bind(fanoutA()).to(fanoutExchangeA());
}
@Bean
public Binding bindB(){
return BindingBuilder.bind(topicB()).to(topicExchangeA()).with("*.host");
}
}
消费者配置
@Component
public class Consumer {
@RabbitListener(queues = "testA",containerFactory = "qos_2")
public void testA(Message message, Channel channel,String messageStr) throws UnsupportedEncodingException {
System.out.println("接收到的消息"+new String(message.getBody(),"UTF-8"));
}
}
消息发送
@RestController
public class producer {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/testTopic")
public Object testTopic(){
MessageProperties messageProperties = new MessageProperties();
messageProperties.setContentType(MessageProperties.CONTENT_TYPE_TEXT_PLAIN);
messageProperties.setPriority(2);
String message = "测试";
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplate.send("directqueue",new Message(message.getBytes()));
return message;
}
}
监听与回溯
原生api写法
- 监听类
- DeliverCallback:消费者消息传递到的时候触发
- CancelCallback:队列删除的时候触发
- ConsumerShutdownSignalCallback:connection连接中断的时候触发
- deliveryType : 消息传递的编号
- consumerTag: 消费者的标签
public class CallbackConsumer {
private static final String HOST="127.0.0.1";
private static final Integer PORT=5672;
private static final String USERNAME="guest";
private static final String PASSWORD="guest";
private static final String VIRTUAL_HOST="/mirror";
private static final String QUEUE_NAME = "test2";
private static final String EXCHANGE_NAME = "producerExchange";
private static final String EXCHANGE_NAME_B = "producerExchangeB";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST);
factory.setPort(PORT);
factory.setUsername(USERNAME);
factory.setPassword(PASSWORD);
factory.setVirtualHost(VIRTUAL_HOST);
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
Map<String,Object> params = new HashMap<>();
params.put("alternate-exchange",EXCHANGE_NAME_B);
channel.exchangeDeclare(EXCHANGE_NAME_B,BuiltinExchangeType.DIRECT,true,false,params);
channel.basicConsume(QUEUE_NAME, new DeliverCallback() {
@Override
public void handle(String consumerTag, Delivery delivery) throws IOException {
long deliveryTag = delivery.getEnvelope().getDeliveryTag();
String correlationId = delivery.getProperties().getCorrelationId();
System.out.println("接收到消息" + new String(delivery.getBody(), "UTF-8"));
channel.basicAck(deliveryTag, false);
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("删除队列触发");
}
}, new ConsumerShutdownSignalCallback() {
@Override
public void handleShutdownSignal(String s, ShutdownSignalException e) {
System.out.println("连接关闭了");
}
});
}
}
springboot写法
@Component
public class RabbitmqConfirmCallback implements RabbitTemplate.ConfirmCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
System.out.println("实例化了");
rabbitTemplate.setConfirmCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
System.out.println("监听到ack");
}
}
@Component
public class RabbitReturnCallback implements RabbitTemplate.ReturnsCallback {
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
System.out.println("监听到服务端返回的消息");
}
}
死信队列
- 参数
- Dead letter exchange:配置死信交换机(普通交换机)
- Dead letter routing key:配置死信的routing key 他会覆盖原本的routingkey
- 机制
- 主要就是创建一个死信交换机并绑定一个队列,在其他队列配置上死信队列,当他的接收到消息,但没有接到ack确认信息,那么他就会把该数据发送到死信队列中。
- 其他作用
- 延迟队列:只要做法就是,在一个队列中设置一个ttl时间,等这个时间超时就进入到死信队列中,即达到延迟效果。
Kafka