1.RabbitMq 介绍
1.1消息队列协议AMQP(RabbitMQ就是基于AMQP标准开发的)
- Broker:
- 接收和分发消息的应用,我们在介绍消息中间件的时候所说的消息系统就是Message Broker。
- Virtual host:
- 出于多租户和安全因素设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中 的namespace概念。当多个不同的用户使用同一个RabbitMQ server提供的服务时,可以划分出多个vhost, 每个用户在自己的vhost创建exchange/queue等。
- 用于进行逻辑隔离,最上层的消息路由。一个Virtual Host里面可以有若干个Exchange和Queue,同一个Virtual 不能有相同名称的Exchange或Queue。
- Connection:
- publisher/consumer和broker之间的TCP连接。断开连接的操作只会在client端进行,Broker 不会断开连接,除非出现网络故障或broker服务出现问题。
- Channel:
- Channel是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯。Channel是轻量级的Connection。
- Exchange:
- 交换机负责接收消息,根据路由键转发消息到绑定的队列。
- message根据分发规则,匹配查询表中的routing key,分发消息到queue 中去。
- direct :point-to-point,交换机通过一个routingKey与队列绑定。
- topic :publish-subscribe,交换机路由规则也是和routingKey有关 只不过 topic他可以根据:*,#进行匹配( *号代表过滤一单词,#代表过滤后面所有单词用.隔开)。
- fanout :multicast,与routingKey没关系直接与队列绑定。
- Queue:
- 消息最终被送到这里等待consumer取走。一个message可以被同时拷贝到多个queue中。
- Binding:
- exchange和queue之间的虚拟连接,binding中可以包含routing key。Binding信息被保存到 exchange中的查询表中,用于message的分发依据。
1.2RabbitMq快速开始
- 生产者:将消息通过exchange在进行routkey匹配发送到对应的queue
-
public class Producer { public static void sendByExchange(String message) throws Exception { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); //这里, 我们演示绑定fanout的类型的交换机, 所以不需要routingKey 就可以路由,只需要绑定即可。 //(默认的交换机类型是direct 路由键就是队列的名字) //声明队列 channel.queueDeclare(ConnectionUtil.QUEUE_NAME,true,false,false,null); // 声明exchange channel.exchangeDeclare(ConnectionUtil.EXCHANGE_NAME, "fanout"); //交换机和队列绑定,routingKey为空字符串 channel.queueBind(ConnectionUtil.QUEUE_NAME, ConnectionUtil.EXCHANGE_NAME, ""); channel.basicPublish(ConnectionUtil.EXCHANGE_NAME, "", null, message.getBytes()); System.out.println("发送的信息为:" + message); channel.close(); connection.close(); } }
- 消费者:将消费者和对应的一个队列进行绑定
-
public class Customer { public static void getMessage() throws Exception { Connection connection = ConnectionUtil.getConnection(); Channel channel = connection.createChannel(); DefaultConsumer deliverCallback = new DefaultConsumer(channel) { @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body, "UTF-8")); } }; //直接自动确认,一般会在回调方法里进行确认,来确保消息确实被消费了! channel.basicConsume(ConnectionUtil.QUEUE_NAME, true, deliverCallback); } }
1.3主流MQ对比
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
单机吞吐量 | 万级,比 RocketMQ、Kafka 低一个数量级 | 同 ActiveMQ | 10 万级,支撑高吞吐 | 10 万级,高吞吐,一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
topic 数量对吞吐量的影响 | topic 可以达到几百/几千的级别,吞吐量会有较小幅度的下降,这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个时候,吞吐量会大幅度下降,在同等机器下,Kafka 尽量保证 topic 数量不要过多,如果要支撑大规模的 topic,需要增加更多的机器资源 | ||
时效性 | 毫秒级 | 微秒级,这是 RabbitMQ 的一大特点,延迟最低 | 毫秒级 | 毫秒级 |
可用性 | 高,基于主从架构实现高可用 | 同 ActiveMQ | 非常高,分布式架构 | 非常高,分布式,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 经过参数优化配置,可以做到 0 丢失 | 同 RocketMQ | |
功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,并发能力很强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用 |
2.RabbitMq 特性
2.1spring boot 整合rabbitmq
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
- javaConfig配置
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost",5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("testhost");
//是否开启消息确认机制,发送端在发送完成后会进行回调
//connectionFactory.setPublisherConfirms(true);
return connectionFactory;
}
@Bean//默认交换机
public DirectExchange defaultExchange() {
return new DirectExchange("directExchange");
}
@Bean
public Queue queue() {
//名字 是否持久化
return new Queue("testQueue", true);
}
@Bean
public Binding binding() {
//绑定一个队列 to: 绑定到哪个交换机上面 with:绑定的路由键(routingKey)
return BindingBuilder.bind(queue()).to(defaultExchange()).with("direct.key");
}
- 发送消息
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
//注意 这个ConnectionFactory 是使用javaconfig方式配置连接的时候才需要传入的
RabbitTemplate template = new RabbitTemplate(connectionFactory);
//设置message的属性
//还可以设置message的type转换器,objectEntity-->json/map
return template;
}
@Component
public class TestSend {
@Autowired
RabbitTemplate rabbitTemplate;
public void testSend() {
//参数介绍: 交换机名字,路由建, 消息内容
rabbitTemplate.convertAndSend("directExchange", "direct.key", "hello");
}
}
- 接收消息(监听队列)
@Component
public class TestListener {
@RabbitListener(queues = "testQueue")
public void get(String message) throws Exception{
System.out.println(message);
}
}
2.2确保消息一定发送到Rabbitmq
失败回调
- 顾名思义 就是消息发送失败的时候会调用我们事先准备好的回调函数,并且把失败的消息 和失败原因等 返回过来。
需要 开启失败回调 以及 实现回调方法
@Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate template = new RabbitTemplate(connectionFactory); //开启mandatory模式(开启失败回调) template.setMandatory(true); //指定失败回调接口的实现类 template.setReturnCallback(new MyReturnCallback()); return template; } //回调接口的实现类: //实现RabbitTemplate.ReturnCallback里面的returnedMessage方法 public class MyReturnCallback implements RabbitTemplate.ReturnCallback { @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { System.out.println(message); System.out.println(replyCode); System.out.println(replyText); System.out.println(exchange); System.out.println(routingKey); } }
开启事务
- 可以保证100%投递成功,但是相对比效率下降严重;
发送方确认模式
- 对性能影响小一般也会和失败回调一起使用
- 开启发送确认 以及 实现回调接口
@Bean public ConnectionFactory connectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory("localhost",5672); connectionFactory.setUsername("admin"); connectionFactory.setPassword("admin"); connectionFactory.setVirtualHost("testhost"); //开启消息确认机制,发送端在发送完成后会进行回调 connectionFactory.setPublisherConfirms(true); return connectionFactory; } public class MyConfirmCallback implements RabbitTemplate.ConfirmCallback{ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println(correlationData); System.out.println(ack); System.out.println(cause); } }
2.3消费端特性
消费者确认消费
- 可以在队列设置自动提交,不会进行消费者的确认,但是可能会有问题;
- 设置手动提交,在消费端处理完业务逻辑后进行确认,也可以批量确认增加性能;
- 在容器里指定消息为手动确认
@Bean public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory (ConnectionFactory connectionFactory){ SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory = new SimpleRabbitListenerContainerFactory(); //这个connectionFactory就是我们自己配置的连接工厂直接注入进来 simpleRabbitListenerContainerFactory.setConnectionFactory(connectionFactory); //这边设置消息确认方式由自动确认变为手动确认 //3个状态 不确认 手动确认 自动确认 simpleRabbitListenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL); return simpleRabbitListenerContainerFactory; } @Component public class TestListener { //containerFactory:指定我们刚刚配置的容器 @RabbitListener(queues = "testQueue",containerFactory="simpleRabbitListenerContainerFactory") public void getMessage(Message message, Channel channel) throws Exception{ System.out.println(new String(message.getBody(),"UTF-8")); System.out.println(message.getBody()); //这里我们调用了一个下单方法 如果下单成功了 那么这条消息就可以确认被消费了 boolean f =placeAnOrder(); if (f){ //传入这条消息的标识 //第二个boolean参数指定是不是批量处理 channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); }else { //无论这条消息成功与否 一定要通知 就算失败了 如果不通知的话 //rabbitmq端会显示这条消息一直处于未确认状态, //那么这条消息就会一直堆积在rabbitmq端 除非与rabbitmq断开连接 //前两个参数 和上面的意义一样, 最后一个参数 就是这条消息是返回到原队列 //还是这条消息作废 就是不退回了--如果有死信队列和交换机默认会放入此。 channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,true); //只能处理单条消息 //channel.basicReject(message.getMessageProperties().getDeliveryTag(),true); } } }
消息预取
- rabbitmq 默认 他会最快 以轮询的机制吧队列所有的消息发送给所有客户端,这样队列性能不同会出现队列等待问题;
- 消息预取是一次取多少消息,消费完了再去取,而不是一股脑全部消息平均分配,一定程度解决了队列等待问题;
- 设置预取消息数量
@Bean public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory (ConnectionFactory connectionFactory){ SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory = new SimpleRabbitListenerContainerFactory(); simpleRabbitListenerContainerFactory.setConnectionFactory(connectionFactory); //手动确认消息 simpleRabbitListenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置消息预取的数量 simpleRabbitListenerContainerFactory.setPrefetchCount(1); return simpleRabbitListenerContainerFactory; }
- 消息预取数量与性能成正比,与消息可靠性成反比------当正在处理数据时,如果突然宕机那么预取量还没有消费完毕只要还没有确认那么消息就会重新放入队列,会导致重复消费问题;
死信交换机
- 当消费失败放回原队列,可能会一直被消费失败,可以选择不放入原队列,只要配置了私信队列和交换机默认就会放入此;
- 配置队列、死信交换机、交换机和队列绑定、消费端的编写
@Bean public Queue queue() { Map<String,Object> map = new HashMap<>(); //设置消息的过期时间 单位毫秒 map.put("x-message-ttl",10000); //设置附带的死信交换机 map.put("x-dead-letter-exchange","exchange.dlx"); //指定重定向的路由建 消息作废之后可以决定需不需要更改他的路由建 如果需要 就在这里指定 map.put("x-dead-letter-routing-key","dead.order"); return new Queue("testQueue", true,false,false,map); }