RabbitMQ消息队列+spring监听mq服务器多个ip,接收消费mq消息(三)

背景:上个月写过一个客户端监听mq。用的是配置方式,这个配置用了ChannelAwareMessageListener监听器,所以项目启动自动开启监听。但需求现在改了。mq服务器改成用Erlang的分布式特性进行Rabbitmq集群,各Rabbitmq服务为对等节点—即每个节点都提供服务给客户端连接,进行消息的发送和接受。需求变成了搭建3个节点,它们之间保持着mq信息同步,因此需要监听多个节点ip以防止其中一个down掉导致mq没有监听消费。由于配置方式只能监听一个ip,无法满足需求,改成今天这种纯java代码灵活的方式来满足需求。

由于不用配置方式,普通java方式又不能主动在项目启动时就开启监听,就需要用到注解@PostConstruct或者继承ServletContextListener监听器。今天先说注解这种方式。贴出来的代码部分我也精简只说和rabbitmq相关的部分,像dao层,mapper,ssm框架配置都忽略了。就是正常的ssm框架,我之前的代码里也有不少整合过得。这次用的还是原来的框架

开发环境和框架:ssm+maven3.3.9+eclips+tomcat7+jdk1.7

先看下工程整体结构:
RabbitMQ消息队列+spring监听mq服务器多个ip,接收消费mq消息(三)_第1张图片

1.pom.xml中引入rabbitmq依赖


        <dependency>
            <groupId>org.springframework.amqpgroupId>
            <artifactId>spring-rabbitartifactId>
            <version>1.3.5.RELEASEversion>
        dependency>

2.在com.zlf.rabbitmq下自定义消费端监听类

package com.zlf.rabbitmq;

import java.io.IOException;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Address;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import com.rabbitmq.client.ShutdownListener;
import com.rabbitmq.client.ShutdownSignalException;
import com.zlf.bo.StaffBo;
import com.zlf.service.IStaffService;
@Service("RabbitMqListenerIpsByPostConstruct")
public class RabbitMqListenerIpsByPostConstruct {

    @Resource  
    private IStaffService staffService;

    private Address[] addre;
    private String vhost;
    private String user;
    private String pwd;
    private String queueName;

    public RabbitMqListenerIpsByPostConstruct() {
        this.addre = new Address[] { new Address("10.100.82.121", 5672),
                new Address("10.100.82.122", 5672),
                new Address("10.100.82.123", 5672) };
        this.vhost = "gf-iih";
        this.user = "admin";
        this.pwd = "admin";
        this.queueName = "tkq.queue";

    }

    @PostConstruct
    public void handleMq() {
        RabbitMqListenerIpsByPostConstruct rm = new RabbitMqListenerIpsByPostConstruct();
        rm.StartQueueListener(new ShutdownListener() {
            @Override
            public void shutdownCompleted(ShutdownSignalException arg0) {
                System.out.println("Connection Shutdown!");
            }
        });
    }

    private Connection getConnection(ShutdownListener listener) {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUsername(user);
        factory.setPassword(pwd);
        factory.setVirtualHost(vhost);
        factory.setAutomaticRecoveryEnabled(true);

        Connection conn = null;
        try {
            conn = factory.newConnection(addre);
            conn.addShutdownListener(listener);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return conn;
    }

    public void StartQueueListener(ShutdownListener listener) {
        Connection conn = getConnection(listener);
        if (conn == null) {
            System.out.println("Failed to Create Connection!");
            return;
        }
        try {
            final Channel channel = conn.createChannel();
            // 设置ACK为手动模式,不在自动ACK
            boolean autoAck = false;
            /**channel.basicConsume各个参数的解释
             * String queueName:队列名
             * boolean autoAck: 服务器是否要手动应答/确认,true-不需要。false-需要。所以这里我们要在处理完业务逻辑后,消费掉mq后发送ack。
             * String consumerTag:用于建立上下文的客户端标签。每个标签都代表一个独立的订阅。同一个channel的不同consumer使用不同的标签。
             * Consumer callback: 消费端接口,实现Consumer的最方便方法是继承DefualtConsumer,并将其作为参数传给basicConsumer方法。
             */
            channel.basicConsume(queueName, autoAck, "zhanglfConsumerTag",
                    new DefaultConsumer(channel) {
                        @Override
                        public void handleDelivery(
                                String consumerTag,
                                Envelope envelope, 
                                BasicProperties properties,
                                byte[] body)
                        throws IOException {
                            long deliveryTag = envelope.getDeliveryTag();
                            String out = new String(body, "UTF-8");
                            StaffBo staffBo = staffService.selectByPrimaryKey("s01");
                            System.out.println("------------------"+staffBo.getName()+"------------");
                            if ("业务处理结果".equals("业务处理结果")) {
                                // 通知mq服务器移除此条mq。设置ack为每条mq都ack,不是批量ack。
                                channel.basicAck(deliveryTag, false);
                            } else {
                                // 如果业务处理异常,通知服务器回收此条mq。
                                channel.basicNack(deliveryTag, false, true);
                            }
                        }
                    });

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

所有引入有关rabbitmq的包均来至:com.rabbitmq.client.*

3.在配置文件中扩大扫包范围到监听类所在的包

   
    <context:component-scan base-package="com.zlf" /> 

发现范围包含住了自定义监听类。就不用改动了。
这样就可以实现项目启动时开启监听了。用@PostConstruct的注意点总结如下:

1.使用@PostConstruct的类上面必须有注解,一般用@Service注解就没问题。至于@Service(…)括号里面的值直接是本类名就可以。并且注解要能够被扫包扫描到才行。

2.@PostConstruct注解的方法必须是void类型,不能有入参。

3.实现监听的主要通过ShutdownListener这个参数。一定不能换成别的Listener。

4.一定要应答服务器的方式可以是通知移除一条mq:channel.basicAck(deliveryTag, false); 也可以是通知回收一条mq:channel.basicNack(deliveryTag, false, true);

5.监听多个ip也不是同时监听,而是地址数组中的第一个ip地址如果连接断开,监听器可以自动找到位于数组第二位置的ip。即自动切换ip.这样就可以实现ip多个监听了。

你可能感兴趣的:(rabbitMq专栏)