SSM框架下配置rabbitMQ

一、准备工作:

第一步:下载erlang  
http://www.erlang.org/downloads/19.3


第二步:下载rabbitMQ 
http://www.rabbitmq.com/download.html

rabbitMQ安装完成后,打开rabbitMQ控制台 输入:rabbitmq-plugins enable rabbitmq_management 
打开网址 http://127.0.0.1:15672/  默认账号 guest/guest


项目配置:
一、pom文件的配置:
<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>1.3.5.RELEASE</version>
</dependency>

或者

<dependency>
  <groupId>com.rabbitmq</groupId>
  <artifactId>amqp-client</artifactId>
  <version>3.6.0</version>
</dependency>
<dependency>
  <groupId>org.springframework.amqp</groupId>
  <artifactId>spring-rabbit</artifactId>
  <version>1.6.5.RELEASE</version>
</dependency>

二、rabitmq.properties文件配置:

mq.host=127.0.0.1
mq.username=guest
mq.password=guest
mq.port=5672

三、spring-mybatis.xml文件配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/rabbit
    http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd" >

    <rabbit:connection-factory id="connectionFactory"
                               username="${mq.username}" password="${mq.password}" host="${mq.host}" port="${mq.port}"
                               virtual-host="/" />

    <!-- 定义rabbit template 用于数据的接收和发送 -->
    <rabbit:template id="amqTemplate" connection-factory="connectionFactory"
                     exchange="Q_PAY_PPMS_RECON"></rabbit:template>

    <!-- 通过指定下面的admin信息,当前productor中的exchange和queue会在rabbitmq服务器上自动生成 -->
    <rabbit:admin connection-factory="connectionFactory" />

    <!--定义queue  说明:durable:是否持久化 exclusive: 仅创建者可以使用的私有队列,断开后自动删除 auto_delete: 当所有消费客户端连接断开后,是否自动删除队列-->
    <rabbit:queue name="chase1" durable="true" auto-delete="false" exclusive="false" />
    <rabbit:queue name="chase2" durable="true" auto-delete="false" exclusive="false" />
    <rabbit:queue name="chase3" durable="true" auto-delete="false" exclusive="false" />
    <!--topic 模式:发送端不是按固定的routing key发送消息,而是按字符串“匹配”发送,接收端同样如此。 -->
    <rabbit:topic-exchange name="mq.asdfExChange"
                           durable="true" auto-delete="false">
        <rabbit:bindings>
            <!-- 配置多个消费者 根据不同业务类型选择对应消费者  pattern="*.*.test1" -->
            <rabbit:binding queue="chase1" pattern="mq.*.send"></rabbit:binding>
            <rabbit:binding queue="chase2" pattern="mq.*.send"></rabbit:binding>
            <rabbit:binding queue="chase3" pattern="mq.*.send"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <!-- fanout 模式:客户端中只要是与该路由绑定在一起的队列都会收到相关消息,这类似广播,发送端不管队列是谁,都由客户端自己去绑定,谁需要数据谁去绑定自己的相应队列 -->
    <rabbit:fanout-exchange name="delayed_message_exchange" durable="true" auto-delete="false" id="delayed_message_exchange">
        <rabbit:bindings>
            <rabbit:binding queue="chase1"/>
            <rabbit:binding queue="chase2"/>
            <rabbit:binding queue="chase3"/>
        </rabbit:bindings>
    </rabbit:fanout-exchange>

    <!--定义direct-exchange direct 消息转换队列 绑定key,意思就是消息与一个特定的路由键匹配,会转发。rabbit:binding:设置消息queue匹配的key -->
    <rabbit:direct-exchange name="mq.qwerExChange" durable="true" auto-delete="false">
        <rabbit:bindings>
            <rabbit:binding queue="chase1" key="mq.qwer.send" ></rabbit:binding>
            <rabbit:binding queue="chase2" key="mq.qwer.send2" ></rabbit:binding>
            <rabbit:binding queue="chase3" key="mq.qwer.send3" ></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>


    <!-- 消息接收者 -->
    <bean id="asdfConsumer" class="com.rabbitmq.Consumor"></bean>
    <bean id="asdfConsumer2" class="com.rabbitmq.Consumor2"></bean>
    <bean id="qwerConsumer3" class="com.rabbitmq.Consumor3"></bean>
    
    <!-- queue litener 观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象
    acknowledeg = "manual",意为表示该消费者的ack方式为手动-->
    <rabbit:listener-container connection-factory="connectionFactory"  acknowledge="manual">
        <rabbit:listener  queues="chase1"  ref="asdfConsumer"/>
    </rabbit:listener-container>

    <rabbit:listener-container connection-factory="connectionFactory" >
        <rabbit:listener  queues="chase2"  ref="asdfConsumer2"/>
    </rabbit:listener-container>
    <rabbit:listener-container connection-factory="connectionFactory" >
        <rabbit:listener  queues="chase3"  ref="qwerConsumer3"/>
    </rabbit:listener-container>
</beans>


四、spring主配置文件中引入刚刚添加的配置文件

    <!-- 加载配置文件 -->
    <context:property-placeholder location="classpath:rabbitMQ.properties" />
    
    <import resource="spring-rabbitmq.xml"></import>


五、生产者生产消息

在代码中注入:
    @Autowired
    private AmqpTemplate amqpTemplate;
    
如下可以将消息发送到交换器(mq.asdfExChange)中,交换器的工作模式为topic模式,根据工作模式将消息发送到队列chase1,chase2,chase3:

amqpTemplate.convertAndSend("mq.asdfExChange", "mq.asdfExChange.send", msg);


六、消费者消费消息
定义在spring-mybatis.xml中配置的消费者:com.rabbitmq.Consumor和com.rabbitmq.Consumor2和com.rabbitmq.Consumor3
这里只定义一个Consumor供参考:

public class Consumor implements ChannelAwareMessageListener {

    /**
     * message 消息实体
     * Channel 当前通道
     */
    public void onMessage(Message message, Channel channel) throws Exception {
        System.out.println("消费者接收到信息");
        String msg = new String(message.getBody(), "utf-8");
        //消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息
         channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        //ack返回false,并重新回到队列,api里面解释得很清楚
        //channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        //true拒绝消息 false确认接受到消息
        //channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
        System.out.println("消费者消费掉了消息:" + msg);
    }
    
}


七、消息确认机制
首先介绍一下消息确认的几种类型:
AcknowledgeMode.NONE:自动确认
AcknowledgeMode.AUTO:根据情况确认
AcknowledgeMode.MANUAL:手动确认
消息通过 ACK 确认是否被正确接收,每个 Message 都要被确认(acknowledged),可以自动去ACK或手动ACK(在spring-mybatis.xml中配置acknowledge="manual")
1.自动确认会在消息发送给消费者后立即确认,但存在丢失消息的可能,如果消费端消费逻辑抛出异常,也就是消费端没有处理成功这条消息,那么就相当于丢失了消息
2.根据情况确认会在消费者挂掉,待ack的消息回归到队列中。消费者抛出异常,消息会不断的被重发,直到处理成功。不会丢失消息,即便服务挂掉,没有处理完成的消息会重回队列,但是异常会让消息不断重试
3.如果消息已经被处理,但后续代码抛出异常,使用 Spring 进行管理的话消费端业务逻辑会进行回滚,这也同样造成了实际意义的消息丢失
4.如果手动确认则当消费者调用 ack、nack、reject 几种方法进行确认,手动确认可以在业务失败后进行一些操作,如果消息未被 ACK 则会发送到下一个消费者(下一个消费者指的是什么,没有下一个消费者会怎样)
5.如果某个服务忘记 ACK 了,则 RabbitMQ 不会再发送数据给它,因为 RabbitMQ 认为该服务的处理能力有限,标记为uncheck状态
6.手动确认方法 channel.basicAck(deliveryTag, multiple)
deliveryTag(唯一标识 ID):当一个消费者向 RabbitMQ 注册后,会建立起一个 Channel ,RabbitMQ 会用 basic.deliver 方法向消费者推送消息,这个方法携带了一个 deliveryTag, 
它代表了 RabbitMQ 向该 Channel 投递的这条消息的唯一标识 ID,是一个单调递增的正整数,deliveryTag 的范围仅限于 Channel
multiple:为了减少网络流量,手动确认可以被批处理,当该参数为 true 时,则可以一次性确认 deliveryTag 小于等于传入值的所有消息
7.否认消息方法  channel.basicNack(deliveryTag, multiple, requeue)
multiple:是否批量.true:将一次性拒绝所有小于deliveryTag的消息。
requeue:被拒绝的是否重新入队列
8.拒绝消息方法 channel.basicReject(deliveryTag, requeue)
requeue:被拒绝的是否重新入队列


问题1:刚开始运行一切OK,后来我把<rabbit:topic-exchange>下的chase2和chase3删除只留下chase1,结果发现发给chase1的信息,还是同时发给了chase2和chase3
1.确认配置文件是否正常    
2.浏览器打开 http://127.0.0.1:15672 找到设置的交换器,比如我设置的交换器 mq.asdfExChange ,发现下面绑定channel列表里依然是3个分别是chase1,chase2,chase3
原来虽然在配置文件更新了,但是在rabbitMQ里没有进行同步更新

问题2:rabbitMQ后台管理中的ready、unchecked、total分别是什么意思
ready是准备发送的消息  unchecked是消费了未确认  total=ready+unchecked

问题3:消费者消费消息时候出错如何处理
经过测试发现,如果异常出现在消息确认之后,不影响后面的消息推送。
如果异常出现在确认消息之前,会导致消息没有被确认,打开rabbitMQ控制台,发现uncheck状态的消息数增加。
可以通过在消费者逻辑里try..catch..处理,确保消息确认可以正常返回给rabbitMQ


文章参考:
https://www.jianshu.com/p/2c5eebfd0e95    
https://www.cnblogs.com/piaolingzxh/p/5448927.html

猜你喜欢

转载自my.oschina.net/u/3387320/blog/2876559
今日推荐