1、在 Docker 下安装 RabbitMq:
(1)拉取镜像:
docker pull rabbitmq:3.7.7-management
选择带有“mangement”的版本(包含web管理页面)。
(2)启动容器:
docker run -d -p 5672:5672 -p 15672:15672 --name myrabbitmq rabbitmq:3.7.7-management
说明:
-p:端口映射,选项格式:-p 主机端口:容器内端口
--name: 自定义容器实例名称
-d: 以后台守护进程运行
5672:客户端与rabbitmq通信端口
15672:rabbitmq web管理端口,访问http://ip:15672 默认用户:guest 默认密码:guest
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
(2)application.yml 配置:
rabbitmq:
host: 192.168.229.142
# 默认就是 5672 端口,可以不用写
port: 5672
username: guest
password: guest
# 支持发布确认回调
publisher-confirms: true
# 支持发布返回回调
publisher-returns: true
# 虚拟主机,默认就是 / ,可以不用写
virtual-host: /
listener:
simple:
# 采用ACK 手动应答
acknowledge-mode: manual
# 指定最小消费者数量
concurrency: 1
# 指定最大消费者数量
max-concurrency: 1
# 开启消费者重试
retry:
enabled: true
(3)AmqpConfig 类:
package spcommon.config; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 该配置类用于定义交换器、队列,绑定等组件,在系统初始化时会自动创建,即动态创建 */ @Configuration public class AmqpConfig { private static final String QUEUE_name = "dynamic.thread"; private static final String TOPIC_EXCHANGE_NAME = "dynamic.exchange"; /** * 定义 topic 交换器 * @return */ @Bean public TopicExchange topicExchange() { return new TopicExchange(TOPIC_EXCHANGE_NAME); } /** * 定义 queue * @return */ @Bean public Queue queue() { return new Queue(QUEUE_name); } /** * 将 queue 绑定到 exchange * @param queue * @param topicExchange * @return */ @Bean public Binding bindingTopicExchange(Queue queue, TopicExchange topicExchange) { // 三个参数分别为 队列,交换器,绑定的路由键 return BindingBuilder.bind(queue).to(topicExchange).with("*.thread"); } }
(4)MyRabbitConfig 类:
package spcommon.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MyRabbitConfig { private static Logger log = LoggerFactory.getLogger(MyRabbitConfig.class); @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); // 配置 MessageConverter 为 Jackson2JsonMessageConverter, 将消息序列化为 json 串, 发送给 rabbitmq rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter()); /** * confirmFallback: 当消息成功发送到 Broker 后触发回调。 * returnFallback: 启动消息失败返回,比如路由不到队列时触发回调。 * 通过以上两个回调方法可确认消息是否发送成功或失败 */ // 设置发送确认回调 rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() { @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { if (ack) { log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause); } else { log.info("消息发送失败:correlationData({}),ack({}),cause({})", correlationData, ack, cause); } } }); // 设置发送返回回调 rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() { @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { log.info("消息丢失:交换器({}),路由键({}),应答码({}),原因({}),消息:{}", exchange, routingKey, replyCode, replyText, message); } }); return rabbitTemplate; } }
(5)MqReceiverService 类:
package spapi.mq; import com.rabbitmq.client.Channel; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; import java.io.IOException; /** * 监听队列消息 */ @Component public class MqReceiverService { // 1、@RabbitListener注解指定目标方法来作为消费消息的方法 // 2、监听多个队列: // @RabbitListener(queues = {"java.socket", "python.socket"}) // 3、监听单个队列 @RabbitListener(queues = "java.socket") public void receiveMessage(Channel channel, Message message) { try { System.out.println("========= 消费消息 ========="); // 接收消息,并将消息转换为 json 字符串 System.out.println(new String(message.getBody())); // 1、进行消息手动确认, 用于通知服务器已收到这条消息,可以在队列删掉了,否则消息会一直存在服务器中 // 2、channel.basicAck方法: // (1)deliveryTag 为该消息的 index; // (2)multiple:是否批量.true:将一次性应答所有小于deliveryTag的消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); System.out.println("Receive success"); } catch (Exception e) { e.printStackTrace(); // 丢弃这条消息(需注意的是:如果是线上环境,这里需要将失败的队列消息作为日志记录到数据库中,方便后续排查失败原因) // 2、channel.basicNack方法: // (1)deliveryTag:该消息的index // (2)是否批量.true:将一次性拒绝所有小于deliveryTag的消息 // (3)被拒绝的是否重新入队列(会放入对尾) try { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);//消息丢弃 } catch (IOException e1) { e1.printStackTrace(); } System.out.println("Receive fail"); } /** * 补充说明: * 1、如果消费者设置了手动应答模式,并且设置了重试,出现异常时无论是否捕获了异常,都是不会重试的 * 2、如果消费者没有设置手动应答模式,并且设置了重试,那么在出现异常时没有捕获异常会进行重试,如果捕获了异常不会重试。 */ } }
(6)在启动类加上对应注解:
@EnableRabbit // 开启基于注解的 RabbitMq
(7)测试:
package spapi; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.HashMap; import java.util.Map; @RunWith(SpringRunner.class) @SpringBootTestpublic class TestRabbitMq { @Autowired RabbitTemplate rabbitTemplate; /** * exchange是交换机交换机的主要作用是接收相应的消息并且绑定到指定的队列.交换机有四种类型,分别为Direct,topic,headers,Fanout. * 1、fanout是路由广播的形式,将会把消息发给绑定它的全部队列,即便设置了key,也会被忽略. * 2、topic转发信息主要是依据通配符,队列和交换机的绑定主要是依据一种模式(通配符+字符串),而当发送消息的时候, * 只有指定的Key和该模式相匹配的时候,消息才会被发送到该消息队列中. * 3、direct是RabbitMQ默认的交换机模式,也是最简单的模式.即创建消息队列的时候,指定一个BindingKey.当发送者发送消息的时候,指定对应的Key. * 当Key和消息队列的BindingKey一致的时候,消息将会被发送到该消息队列中. */
/** * 测试单播(点对点) */ @Test public void testDirectExchange() { // 构造一个消息实体 Map<String, Object> messageObj = new HashMap<>(); messageObj.put("name", "Java"); messageObj.put("data", "direct"); /** * 使用格式: * rabbitTemplate.convertAndSend(exchange, routeKey, object); * object 为消息体,会自动序列化后发送给 rabbitmq */ rabbitTemplate.convertAndSend("exchange.direct", "java.socket", messageObj); } /** * 测试广播 */ @Test public void testFanoutExchange() { // 构造一个消息实体 Map<String, Object> messageObj = new HashMap<>(); messageObj.put("name", "Java"); messageObj.put("data", "fanout"); // 如果以广播方式发送,则可以忽略路由键 rabbitTemplate.convertAndSend("exchange.fanout", "", messageObj); } /** * 测试模式匹配 */ @Test public void testTopicExchange() { // 构造一个消息实体 Map<String, Object> messageObj = new HashMap<>(); messageObj.put("name", "Java"); messageObj.put("data", "topic"); // 路由键:# 匹配单个或多个单词,* 匹配一个单词 rabbitTemplate.convertAndSend("exchange.topic", "*.socket", messageObj); } }