rabbitmq


简介

rabbitmq由三部分组成,生产者、消费者、broker。
broker的作用是存储与转发消息。
mq的用途:异步通信、解耦、流量削峰。

rabbitmq工作模型:

生产者、消费者与broker维持了tcp长连接,长连接中可以创建许多channel,消息通过channel传输,broker中可以有很多虚拟主机(VHOST)。


Exchange交换机

Direct 直连模式:指定路由键通过交换机和队列的绑定关系发送到指定队列。

public class MyProducer {
    private final static String EXCHANGE_NAME = "SIMPLE_EXCHANGE";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        // 连接IP
        factory.setHost("192.168.47.128");
        // 连接端口
        factory.setPort(5672);
        // 虚拟机
        factory.setVirtualHost("/");
        // 用户
        factory.setUsername("hx");
        factory.setPassword("123456");

        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 发送消息
        String msg = "Hello world, Rabbit MQ";

        // String exchange, String routingKey, BasicProperties props, byte[] body
        channel.basicPublish(EXCHANGE_NAME,"test", null, msg.getBytes());

        channel.close();
        conn.close();
    }
}

public class MyConsumer {
    private final static String EXCHANGE_NAME = "SIMPLE_EXCHANGE";
    private final static String QUEUE_NAME = "SIMPLE_QUEUE";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        // 连接IP
        factory.setHost("192.168.47.128");
        // 连接端口
        factory.setPort(5672);
        // 虚拟机
        factory.setVirtualHost("/");
        // 用户
        factory.setUsername("hx");
        factory.setPassword("123456");
        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 声明交换机
        // String exchange, String type, boolean durable, boolean autoDelete, Map<String, Object> arguments
        channel.exchangeDeclare(EXCHANGE_NAME,"direct",false, false, null);

        // 声明队列
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" Waiting for message....");

        // 绑定队列和交换机
        channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"test");

        // 创建消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println("Received message : '" + msg + "'");
                System.out.println("consumerTag : " + consumerTag );
                System.out.println("deliveryTag : " + envelope.getDeliveryTag() );
            }
        };

        // 开始获取消息
        // String queue, boolean autoAck, Consumer callback
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

Topoc主题模式:比对由通配符组成的路由键发送到相应队列。* 代表一个单词,#代表0个或多个单词,单词长度不限定。案例见下方

Fanout广播模式:发送到所有队列。案例见下方

header模式略过。

交换机声明参数说明:exchangeDeclare(String exchange, String type, boolean durable, boolean autoDelete,Map<String, Object> arguments)
durable:交换机持久化,为true时rabbitmq服务器重启后交换机依然存在。
autoDelete:交换机自动删除,为true且当调用 channel.queueUnbind解除交换机与所有队列的绑定时或者删除所有队列时触发自动删除。交换机删除后,所有队列的交换机指向默认交换机,当生产者调用channel.basicPublish("",“queue_name”, null, msg.getBytes())时发向(AMQP default)默认交换机的指定队列queue_name。

队列声明参数说明:queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,Map<String, Object> arguments)
durable:队列持久化,为true时rabbitmq服务器重启后队列依然存在。
autoDelete:队列自动删除,为true且当调用channel.basicConsume(QUEUE_NAME, true, null)解除队列与所有消费者的订阅关系以及队列不指向死信交换机时触发自动删除。
exclusive:队列排他性,只对首次声明它的连接(Connection)可见,会在其连接断开的时候自动删除。


死信队列

ttl:time to live 消息存活时间,分为队列控制和消息本身控制两种形式,两种都存在时取最小ttl。
超过ttl未被消费的消息就是死信,配置死信交换机后,死信将发到死信交换机而后路由到死信队列。

死信包括三种情况:
1.超过ttl的消息。
2.调用channel.basicReject(long deliveryTag, boolean requeue),重新入队为false时。
3.消息长度大于max length或大于max length bytes时,队列最前的消息进入死信交换机。

public class DlxConsumer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));
        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        final Channel channel = conn.createChannel();

        // 指定队列的死信交换机
        Map<String,Object> arguments = new HashMap<String,Object>();
        arguments.put("x-dead-letter-exchange","DLX_EXCHANGE");
        arguments.put("x-dead-letter-routing-key","test.dead.letter");
        //arguments.put("x-message-ttl",4000); // 设置队列的TTL
         arguments.put("x-max-length", 2); // 如果设置了队列的最大长度,超过长度时,先入队的消息会被发送到DLX

        // 声明队列(默认交换机AMQP default,Direct)
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare("TEST_DLX_QUEUE", false, false, true, arguments);

        // 声明死信交换机
        //exchangeDeclare(String exchange,String type,boolean durable,boolean autoDelete,boolean internal,Map<String, Object> arguments)
        //internal : 为true时客户端不能直接发送消息到该交换机
        channel.exchangeDeclare("DLX_EXCHANGE","topic", false, true, true, null);
        // 声明死信队列
        channel.queueDeclare("DLX_QUEUE", false, false, true, null);
        //绑定,此处 Dead letter routing key 设置为 #.dead.letter
        channel.queueBind("DLX_QUEUE","DLX_EXCHANGE","#.dead.letter");
        System.out.println(" Waiting for message....");

        // 创建消费者
        /*Consumer consumer2 = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                channel.basicReject(envelope.getDeliveryTag(),false);
            }
        };*/

        // 原队列消费者
       // channel.basicConsume("TEST_DLX_QUEUE", false, consumer2);

        // 创建消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                System.out.println(DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss SSS") + "  Dead Queue received message : '" + msg + "'");
            }
        };

        // 死信队列消费者
         channel.basicConsume("DLX_QUEUE", true, consumer);
    }
}

public class DlxProducer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));

        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        String msg = "Hello world4, Rabbit MQ, DLX MSG";

        // 设置属性,消息10秒钟过期
        /*AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .deliveryMode(2) // 持久化消息
                .contentEncoding("UTF-8")
                .expiration("5000") // TTL
                .build();*/

        // 发送消息
        System.out.println(DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss SSS"));
        //channel.basicPublish("", "TEST_DLX_QUEUE", properties, msg.getBytes());
        channel.basicPublish("", "TEST_DLX_QUEUE", null, msg.getBytes());


        channel.close();

        conn.close();
    }
}

管理界面新增队列时指定死信交换机、死信路由键、队列ttl。

延迟队列

消息延迟投递。有两种实现方式,第一种是通过死信队列实现,第二种是通过插件实现。

死信队列实现消息延迟投递

主要是通过控制ttl的方式。

ttl控制ttl时间统一ttl时间不统一
队列一个原队列转发到死信交换机多个原队列转发到死信交换机
消息本身×

ttl时间不统一时,通过消息本身控制ttl是存在问题的。当队列前一个的ttl时间比后一个大时,后一个消息到了ttl却无法出队,只有前一个消息到了ttl并且出队后一个消息才能出队。
如图所示,第一个消息ttl为5秒,第二个为3秒,消费者经过5秒才接收到两条消息。

插件实现消息延迟投递

首先找到rabbitmq安装目录下的plugins文件夹,下载插件到该文件夹,再启用该插件。
插件页面:https://www.rabbitmq.com/community-plugins.html
delay插件:https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/tag/3.8.9

//下载资源
$ wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/3.8.9/rabbitmq_delayed_message_exchange-3.8.9-0199d11c.ez
//移动到插件目录
$ mv rabbitmq_delayed_message_exchange-3.8.9-0199d11c.ez /usr/local/rabbitmq/plugins
//启用插件
$ rabbitmq-plugins enable rabbitmq_delayed_message_exchange

消费者声明特殊类型交换机,生产者设置延迟时间。生产者将消息发送到broker,broker延迟投递消息。

public class DelayPluginConsumer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));
        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 声明x-delayed-message类型的exchange
        Map<String, Object> argss = new HashMap<String, Object>();
        argss.put("x-delayed-type", "direct");
        channel.exchangeDeclare("DELAY_EXCHANGE", "x-delayed-message", false,
                true, argss);

        // 声明队列
        channel.queueDeclare("DELAY_QUEUE", false,false,true,null);

        // 绑定交换机与队列
        channel.queueBind("DELAY_QUEUE", "DELAY_EXCHANGE", "DELAY_KEY");

        // 创建消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");
                SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                System.out.println("收到消息:[" + msg + "]\n接收时间:" +sf.format(new Date()));
            }
        };

        // 开始获取消息
        // String queue, boolean autoAck, Consumer callback
        channel.basicConsume("DELAY_QUEUE", true, consumer);
    }
}

public class DelayPluginProducer {

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));

        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        // 延时投递,比如延时5秒钟
        Date now = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.SECOND, +5);// 5秒钟后投递
        Date delayTime = calendar.getTime();

        // 定时投递,把这个值替换delayTime即可
        // Date exactDealyTime = new Date("2019/01/14,22:30:00");

        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        String msg = "发送时间:" + sf.format(now) + ",需要投递时间:" + sf.format(delayTime);

        // 延迟的间隔时间,目标时刻减去当前时刻
        Map<String, Object> headers = new HashMap<String, Object>();
        headers.put("x-delay", delayTime.getTime() - now.getTime());

        AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().headers(headers).build();
        channel.basicPublish("DELAY_EXCHANGE", "DELAY_KEY", props, msg.getBytes());
        System.out.println("结束时间:" + DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss.SSS"));

        channel.close();
        conn.close();
    }
}

流量控制

服务端限流

①超过内存多少停止接收生产者消息
②磁盘剩余空间不足多少停止接收生产者消息
很明显当前rabbitmq并没有配置文件。需要从官网下载一个配置文件标准样例,随便保存在一个位置。

可以通过修改以下两个参数实现流控。

接下来就是将配置文件放在合适的位置然后重启rabbitmq服务器。
首先通过find / -name rabbitmq-defaults找到默认文件位置,再从默认文件中找到配置文件位置,我的配置文件应该是/usr/local/rabbitmq/etc/rabbitmq/rabbitmq.conf,将修改后的文件移动到该地址,重启rabbitmq。

为了方便测试,改动了内存参数,当占用内存超过70M时,broker不再接收生产者消息,vm_memory_high_watermark.absolute = 70M。
声明交换机和队列,但是消费者不订阅,生产者发送消息,可以明显看到当i等于1528的时候不发送消息了,此处实际上broker只接收了1000条,剩下的528条可能被broker缓存到某个地方了。

③另外一种限流方式就是设置队列的max-length或者max-length-bytes,超过设置的长度或大小时先入队的消息会被丢弃或转发到死信交换机。

消费者限流

消费者手动ack,通过basicQos设置消费者消费的消息数目上限。

Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String msg = new String(body, "UTF-8");

                System.out.println("Consumer1 Received message : '" + msg + "'" );

                channel.basicAck(envelope.getDeliveryTag(), true);

            }
        };

        // 非自动确认消息的前提下,如果一定数目的消息(通过基于consume或者channel设置Qos的值)未被确认前,不进行消费新的消息。
        channel.basicQos(2); // prefetch count
        channel.basicConsume(QUEUE_NAME, false, consumer);

Spring AMQP

rabbit:connection-factory 配置rabbitmq服务器连接
rabbit:admin 声明交换机和队列需要 。xsd中描述 Creates a rabbit admin (org.springframework.amqp.rabbit.core.RabbitAdmin),used to manage exchanges, queues and bindings.
rabbit:queue 声明队列
rabbit:direct-exchange rabbit:topic-exchange rabbit:fanout-exchange 声明交换机
rabbit:bindings 交换机绑定队列
rabbit:template 发送和接收消息的模板类
rabbit:listener-container 配置监听队列的消费者

<!--配置connection-factory,指定连接rabbit server参数 -->
    <rabbit:connection-factory id="connectionFactory" virtual-host="/" username="hx" password="123456" host="192.168.47.128" port="5672" />

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

    <!--######分隔线######-->
    <!--定义queue -->
    <rabbit:queue name="MY_FIRST_QUEUE" durable="false" auto-delete="true" exclusive="false" declared-by="connectAdmin" />

    <!--定义direct exchange,绑定MY_FIRST_QUEUE -->
    <rabbit:direct-exchange name="MY_DIRECT_EXCHANGE" durable="false" auto-delete="true" declared-by="connectAdmin">
        <rabbit:bindings>
            <rabbit:binding queue="MY_FIRST_QUEUE" key="FirstKey">
            </rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

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

    <!--消息接收者 -->
    <bean id="messageReceiver" class="com.gupaoedu.consumer.FirstConsumer"></bean>

    <!--queue listener 观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象 -->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener queues="MY_FIRST_QUEUE" ref="messageReceiver" />
    </rabbit:listener-container>

    <!--定义queue -->
    <rabbit:queue name="MY_SECOND_QUEUE" durable="false" auto-delete="true" exclusive="false" declared-by="connectAdmin" />

    <!-- 将已经定义的Exchange绑定到MY_SECOND_QUEUE,注意关键词是key -->
    <rabbit:direct-exchange name="MY_DIRECT_EXCHANGE" durable="false" auto-delete="true" declared-by="connectAdmin">
        <rabbit:bindings>
            <rabbit:binding queue="MY_SECOND_QUEUE" key="SecondKey"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!-- 消息接收者 -->
    <bean id="receiverSecond" class="com.gupaoedu.consumer.SecondConsumer"></bean>

    <!-- queue litener 观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象 -->
    <rabbit:listener-container connection-factory="connectionFactory">
    <rabbit:listener queues="MY_SECOND_QUEUE" ref="receiverSecond" />
</rabbit:listener-container>

SpringBoot AMQP

消费者配置

配置文件

#rabbitmq服务器配置
spring.rabbitmq.host=192.168.47.128
spring.rabbitmq.port=5672
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=hx
spring.rabbitmq.password=123456

#手动ack
spring.rabbitmq.listener.direct.acknowledge-mode=manual
spring.rabbitmq.listener.simple.acknowledge-mode=manual

交换机、队列声明、交换机绑定队列、设置消息转换器

@Configuration
@PropertySource("classpath:gupaomq.properties")
public class RabbitConfig {
    @Value("${com.gupaoedu.firstqueue}")
    private String firstQueue;

    @Value("${com.gupaoedu.secondqueue}")
    private String secondQueue;

    @Value("${com.gupaoedu.thirdqueue}")
    private String thirdQueue;

    @Value("${com.gupaoedu.fourthqueue}")
    private String fourthQueue;

    @Value("${com.gupaoedu.directexchange}")
    private String directExchange;

    @Value("${com.gupaoedu.topicexchange}")
    private String topicExchange;

    @Value("${com.gupaoedu.fanoutexchange}")
    private String fanoutExchange;

    // 创建四个队列
    @Bean("vipFirstQueue")
    public Queue getFirstQueue(){
        return new Queue(firstQueue);
    }

    @Bean("vipSecondQueue")
    public Queue getSecondQueue(){
        return new Queue(secondQueue);
    }

    @Bean("vipThirdQueue")
    public  Queue getThirdQueue(){
        return  new Queue(thirdQueue);
    }

    @Bean("vipFourthQueue")
    public  Queue getFourthQueue(){
        return  new Queue(fourthQueue);
    }

    // 创建三个交换机
    @Bean("vipDirectExchange")
    public DirectExchange getDirectExchange(){
        return new DirectExchange(directExchange);
    }

    @Bean("vipTopicExchange")
    public TopicExchange getTopicExchange(){
        return new TopicExchange(topicExchange);
    }

    @Bean("vipFanoutExchange")
    public FanoutExchange getFanoutExchange(){
        return new FanoutExchange(fanoutExchange);
    }

    // 定义四个绑定关系
    @Bean
    public Binding bindFirst(@Qualifier("vipFirstQueue") Queue queue, @Qualifier("vipDirectExchange") DirectExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("gupao.best");
    }

    @Bean
    public Binding bindSecond(@Qualifier("vipSecondQueue") Queue queue, @Qualifier("vipTopicExchange") TopicExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("*.gupao.*");
    }

    @Bean
    public Binding bindThird(@Qualifier("vipThirdQueue") Queue queue, @Qualifier("vipFanoutExchange") FanoutExchange exchange){
        return BindingBuilder.bind(queue).to(exchange);
    }

    @Bean
    public Binding bindFourth(@Qualifier("vipFourthQueue") Queue queue, @Qualifier("vipFanoutExchange") FanoutExchange exchange){
        return BindingBuilder.bind(queue).to(exchange);
    }

    /**
     * 在消费端转换JSON消息
     * 监听类都要加上containerFactory属性
     * @param connectionFactory
     * @return
     */
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        factory.setAutoStartup(true);
        return factory;
    }
}

消费者监听队列 @RabbitListener queues 指定需要监听的队列,containerFactory指定连接工厂。@RabbitHandler注解接收消息的方法,第一个消费者未ack消息存在服务器。

@Component
@PropertySource("classpath:gupaomq.properties")
@RabbitListener(queues = "${com.gupaoedu.firstqueue}", containerFactory="rabbitListenerContainerFactory")
public class FirstConsumer {

    @RabbitHandler
    public void process(@Payload Merchant merchant){

        System.out.println("First Queue received msg : " + merchant.getName());
    }

}

@Component
@PropertySource("classpath:gupaomq.properties")
@RabbitListener(queues = "${com.gupaoedu.secondqueue}", containerFactory="rabbitListenerContainerFactory")
public class SecondConsumer {
    @RabbitHandler
    public void process(String msgContent,Channel channel, Message message) throws IOException {
        System.out.println("Second Queue received msg : " + msgContent );

        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

生产者配置

为amqpTemplate设置消息转换器

@Configuration
public class RabbitConfig {
    /**
     * 所有的消息发送都会转换成JSON格式发到交换机
     * @param connectionFactory
     * @return
     */
    @Bean
    public RabbitTemplate gupaoTemplate(final ConnectionFactory connectionFactory) {
        final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        return rabbitTemplate;
    }
}

发送消息。默认为amqpTemplate

@Autowired
    AmqpTemplate gupaoTemplate;
    
    gupaoTemplate.convertAndSend(topicExchange,topicRoutingKey1, "a topic msg : shanghai.gupao.teacher");

可靠性投递

四个容易出现不可靠投递的阶段

  • 生产者往broker发送消息,生产者不知道消息有没有抵达broker。
  • 交换机往队列发送消息,路由键不存在的话消息无法发到队列。
  • 队列接收到消息却因为重启服务器等原因丢失消息。
  • 队列给消费者推送消息,队列不知道消费者有没有接收到消息。

服务端确认生产者

Transacation模式需要等待服务器响应,消耗性能,发送消息效率低。
Confirm模式显然是异步ack消息性能最高。

Transacation模式

生产者伪代码

try {
            channel.txSelect();
            // 发送消息
            // String exchange, String routingKey, BasicProperties props, byte[] body
            channel.basicPublish("", QUEUE_NAME, null, (msg).getBytes());
           // int i =1/0;
            channel.txCommit();
            System.out.println("消息发送成功");
        } catch (Exception e) {
            channel.txRollback();
            System.out.println("消息已经回滚");
        }

Confirm模式

消息逐个确定

// 开启发送方确认模式
        channel.confirmSelect();

        channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
        // 普通Confirm,发送一条,确认一条
        if (channel.waitForConfirms()) {
            System.out.println("消息发送成功" );
        }

消息批量确定

try {
            channel.confirmSelect();
            for (int i = 0; i < 50; i++) {
                // 发送消息
                // String exchange, String routingKey, BasicProperties props, byte[] body
                channel.basicPublish("", QUEUE_NAME, null, (msg +"-"+ i).getBytes());
            }
            // 直到所有信息都发布,只要有一个未被Broker确认就会IOException
            channel.waitForConfirmsOrDie();
            System.out.println("消息发送完毕,批量确认成功");
        } catch (Exception e) {
            // 发生异常,可能需要对所有消息进行重发
            e.printStackTrace();
        }

消息异步确定

public class AsyncConfirmProducer {
    private final static String QUEUE_NAME = "ORIGIN_QUEUE";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));

        // 建立连接
        Connection conn = factory.newConnection();
        // 创建消息通道
        Channel channel = conn.createChannel();

        String msg = "Hello world, Rabbit MQ, Async Confirm";
        // 声明队列(默认交换机AMQP default,Direct)
        // String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 用来维护未确认消息的deliveryTag
        final SortedMap<Long,String> confirmMap = Collections.synchronizedSortedMap(new TreeMap<Long, String>());
        
        //失败消息,定时重发
        final List<String> failMsg = new LinkedList<String>();

        // 这里不会打印所有响应的ACK;ACK可能有多个,有可能一次确认多条,也有可能一次确认一条
        // 异步监听确认和未确认的消息
        channel.addConfirmListener(new ConfirmListener() {
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("Broker未确认消息,标识:" + deliveryTag);
                if (multiple) {
                    failMsg.addAll(confirmMap.subMap(confirmMap.firstKey(),deliveryTag + 1L).values());
                    confirmMap.subMap(confirmMap.firstKey(),deliveryTag + 1L).clear();
                } else {
                    failMsg.add(confirmMap.remove(deliveryTag));
                }
                // 这里添加重发的方法
            }
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                // 如果true表示批量执行了deliveryTag这个值以前(小于deliveryTag的)的所有消息,如果为false的话表示单条确认
                System.out.println(String.format("Broker已确认消息,标识:%d,多个消息:%b", deliveryTag, multiple));
                if (multiple) {
                    // 删除deliveryTag + 1L之前的元素
                    confirmMap.headMap(deliveryTag + 1L).clear();
                } else {
                    // 只移除一个元素
                    confirmMap.remove(deliveryTag);
                }
                System.out.println("未确认的消息:"+confirmMap);
            }
        });

        // 开启发送方确认模式
        channel.confirmSelect();
        for (int i = 0; i < 10; i++) {
            long nextSeqNo = channel.getNextPublishSeqNo();
            // 发送消息
            // String exchange, String routingKey, BasicProperties props, byte[] body
            confirmMap.put(nextSeqNo,nextSeqNo + "msg");
            channel.basicPublish("", QUEUE_NAME, null, (msg +"-"+ i).getBytes());
        }
        System.out.println("所有消息:"+confirmMap);

        // 这里注释掉的原因是如果先关闭了,可能收不到后面的ACK
        //channel.close();
        //conn.close();
    }
}

路由

交换机无法根据路由键找到队列时的解决办法。

broker回发给生产者

mandatory为true + returnListener监听

channel.addReturnListener(new ReturnListener() {
            public void handleReturn(int replyCode,
                                     String replyText,
                                     String exchange,
                                     String routingKey,
                                     AMQP.BasicProperties properties,
                                     byte[] body)
                    throws IOException {
                System.out.println("=========监听器收到了无法路由,被返回的消息============");
                System.out.println("replyText:"+replyText);
                System.out.println("exchange:"+exchange);
                System.out.println("routingKey:"+routingKey);
                System.out.println("message:"+new String(body));
            }
        });

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(2).
                contentEncoding("UTF-8").build();
                // 发送到了默认的交换机上,由于没有任何队列使用这个关键字跟交换机绑定,所以会被退回
        // 第三个参数是设置的mandatory,如果mandatory是false,消息也会被直接丢弃
        channel.basicPublish("","gupaodirect",true, properties,"只为更好的你".getBytes());
        //等待rabbitmq服务器回发未路由消息
        TimeUnit.SECONDS.sleep(10);

备份交换机

public class BackExchange {
    public static void main(String[] args) throws Exception{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));

        Connection connection = factory.newConnection();

        Channel channel = connection.createChannel();

        AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().deliveryMode(2).
                contentEncoding("UTF-8").build();

        // 在声明交换机的时候指定备份交换机
        Map<String,Object> arguments = new HashMap<String,Object>();
        arguments.put("alternate-exchange","ALTERNATE_EXCHANGE");
        channel.exchangeDeclare("TEST_EXCHANGE","direct", false, false, arguments);
        channel.exchangeDeclare("ALTERNATE_EXCHANGE","topic", false, false, null);
        channel.queueDeclare("alter_queue",false,false,false,null);

        channel.queueBind("alter_queue","ALTERNATE_EXCHANGE","#.gupaodirect");
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println(new String(body));
            }
        };
        channel.basicConsume("alter_queue",true,consumer);

        channel.basicPublish("TEST_EXCHANGE","gupaodirect", properties,"只为更好的你".getBytes());

        TimeUnit.SECONDS.sleep(3);
        channel.close();
        connection.close();
    }
}

消息存储

交换机持久化、队列持久化、消息持久化

消息持久化

AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
                .deliveryMode(2)   // 2代表持久化
                .contentEncoding("UTF-8")  // 编码
                .expiration("10000")  // TTL,过期时间
                .headers(headers) // 自定义属性
                .priority(5) // 优先级,默认为5,配合队列的 x-max-priority 属性使用
                .messageId(String.valueOf(UUID.randomUUID()))
                .build();

        String msg = "Hello world, Rabbit MQ";

        // 发送消息
        // String exchange, String routingKey, BasicProperties props, byte[] body
        channel.basicPublish("", QUEUE_NAME, properties, msg.getBytes());

投递消费者

消费者ack

开启手动ack,消费者接收到消息后ack给broker,broker再从队列删除这条消息。

channel.basicAck(); // 手动应答
channel.basicReject(); // 单条拒绝
channel.basicNack(); // 批量拒绝

消费者回调生产者

如下代码每次只能消费一条消息,思考了很久怎么发多条消息,都会出现问题,以至于在代码中还用了同步锁保证线程安全。

发布消息时设置应答队列属性

public class RPCClient {
    private final static String REQUEST_QUEUE_NAME = "RPC_REQUEST";
    private final static String RESPONSE_QUEUE_NAME = "RPC_RESPONSE";
    private Channel channel;

    // 从匿名内部类中获取返回值
    final BlockingQueue<String> response = new LinkedBlockingQueue<String>(1);
    final BlockingQueue<Long> ackTag = new LinkedBlockingQueue<Long>(1);

    //构造函数 初始化连接
    public RPCClient() throws IOException, TimeoutException, NoSuchAlgorithmException, KeyManagementException, URISyntaxException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));

        //创建一个新的连接 即TCP连接
        Connection connection = factory.newConnection();
        //创建一个通道
        channel = connection.createChannel();
        //创建一个请求队列
        channel.queueDeclare(REQUEST_QUEUE_NAME, true, false, false, null);
        //创建一个回调队列
        channel.queueDeclare(RESPONSE_QUEUE_NAME, true, false, false, null);
        channel.basicQos(1);

        // 创建消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                response.offer(new String(body, "UTF-8"));
                ackTag.offer(envelope.getDeliveryTag());
            }
        };

        // 开始获取消息
        // String queue, boolean autoAck, Consumer callback
        channel.basicConsume(RESPONSE_QUEUE_NAME, false, consumer);
    }

    /**
     * PRC 远程调用计算平方
     *
     * @param message
     * @return
     * @throws Exception
     */
    public String getSquare(String message) throws Exception {
        //定义消息属性中的correlationId
        String correlationId = java.util.UUID.randomUUID().toString();

        //设置消息属性的replyTo和correlationId
        BasicProperties properties = new BasicProperties.Builder()
                .correlationId(correlationId)
                .replyTo(RESPONSE_QUEUE_NAME)
                .build();

        // 发送消息到请求队列rpc_request队列
        // 消息发送到与routingKey参数相同的队列中
        synchronized (RPCClient.class){
            channel.basicPublish("", REQUEST_QUEUE_NAME, properties, message.getBytes());
            String take = response.take();
            channel.basicAck(ackTag.take(), false);
            return take;
        }
    }

    public static void main(String[] args) throws Exception {
        final RPCClient rpcClient = new RPCClient();
        final AtomicInteger atomicInteger = new AtomicInteger(0);

        for (int i = 0; i < 1000; i++) {
            final int j = i + 1;
            Thread t = new Thread(new Runnable() {
                public void run() {
                    String result = null;
                    try {
                        result = rpcClient.getSquare("" + j);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if(result.contains("" + j)){
                    //    System.out.println(j + "的response is : " + result);
                        atomicInteger.addAndGet(1);
                    }
                }
            });
            t.join();
            t.start();
        }

        TimeUnit.SECONDS.sleep(3);
        System.out.println(atomicInteger.get());
    }
}
public class RPCServer {
    private final static String REQUEST_QUEUE_NAME = "RPC_REQUEST";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setUri(ResourceUtil.getKey("rabbitmq.uri"));

        //创建一个新的连接 即TCP连接
        Connection connection = factory.newConnection();
        //创建一个通道
        final Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(REQUEST_QUEUE_NAME, true, false, false, null);
        //设置prefetch值 一次处理1条数据
        channel.basicQos(1);

        // 创建消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                BasicProperties replyProperties = new BasicProperties.Builder()
                        .correlationId(properties.getCorrelationId())
                        .build();

                //获取客户端指定的回调队列名
                String replyQueue = properties.getReplyTo();
                //返回获取消息的平方
                String message = new String(body, "UTF-8");
                System.out.println(message);
                // 计算平方
                // Double mSquare =  Math.pow(Integer.parseInt(message),2);
                String repMsg = String.valueOf(message + "sss");
                channel.basicAck(envelope.getDeliveryTag(), false);

                // 把结果发送到回复队列
                channel.basicPublish("", replyQueue, replyProperties, repMsg.getBytes());

            }
        };

        channel.basicConsume(REQUEST_QUEUE_NAME, false, consumer);
    }
}

排错工具

firehose消息追踪。默认关闭,每次重启rabbitmq服务器重置为关闭状态。
rabbitmqctl trace_on [-p vhost] 开启firehose
rabbitmqctl trace_off [-p vhost] 关闭firehose

Firehose的原理是将生产者投递给RabbitMQ的消息,或者RabbitMQ投递给消费者的消息按照指定的格式发送到默认的交换器上。这个默认交换器的名称是amq.rabbitmq.trace,它是一个topic类型的交换器。发送到这个交换器上的消息路由键是:

  • publish.{exchangename},其中exchangename为交换器名称,对应生产者投递到交换器的消息
  • deliver.{queuename},其中queuename为队列名称,对应消费者从队列获取的消息

集群、高可用

集群的目的:高可用、负载均衡
rabbitmq集群节点类型:disc(存储在硬盘)、ram(存储在内存和硬盘)

参考文档

rabbitmq安装
firehose
搭建rabbitmq高可用集群

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值