Rabbitmq基本API使用

 

 

一、生产者
  1. 创建ConnectionFactory工厂(地址、用户名、密码、vhost)
  2. 创建Connection
  3. 创建信道(Channel)
  4. 创建 exchange(指定 名称、类型-DIRECT("direct"), FANOUT("fanout"), TOPIC("topic"), HEADERS("headers");、是否持久化)
  5. 发送消息(指定:exchange、发送的routingKey , 发送到的消息 )
       
基础的生产者: 

 

public class TestProducer {


    public final static String EXCHANGE_NAME = "direct_logs";
    public static void main(String[] args)
            throws IOException, TimeoutException {
        /* 创建连接,连接到RabbitMQ*/
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.112.131");
        connectionFactory.setVirtualHost("my_vhost");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = connectionFactory.newConnection();


        /*创建信道*/
        Channel channel = connection.createChannel();
        /*创建交换器*/
        channel.exchangeDeclare(EXCHANGE_NAME,"direct");
        //channel.exchangeDeclare(EXCHANGE_NAME,BuiltinExchangeType.DIRECT);


        /*日志消息级别,作为路由键使用*/
        String[] routekeys = {"king","queue","prince"};
        for(int i=0;i<3;i++){
            String routekey = routekeys[i%3];
            String msg = "Hellol,RabbitMq"+(i+1);
            /*发布消息,需要参数:交换器,路由键,其中以日志消息级别为路由键*/
            channel.basicPublish(EXCHANGE_NAME,routekey,null,
                    msg.getBytes());
            System.out.println("Sent "+routekey+":"+msg);
        }
        channel.close();
        connection.close();
    }
}
View Code

 

二、消费者
  1. 创建ConnectionFactory工厂(地址、用户名、密码、vhost)
  2. 创建Connection
  3. 创建信道(Channel)
  4. 声明一个 exchange(指定 名称、类型、是否持久化)
  5. 创建一个队列(指定:名称,是否持久化,是否独占,是否自动删除,其他参数)
  6. 队列、exchange通过routeKey进行绑定
  7. 消费者接收消息(队列名称,是否自动ACK)
    基本的消费者:

    

public class TestConsumer {
    public static void main(String[] argv)
            throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.112.131");
        factory.setVirtualHost("my_vhost");
        factory.setUsername("admin");
        factory.setPassword("admin");
        // 打开连接和创建频道,与发送端一样
        Connection connection = factory.newConnection();
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(TestProducer.EXCHANGE_NAME,
                "direct");
        /*声明一个队列*/
        String queueName = "focuserror";
        channel.queueDeclare(queueName,false,false,
                false,null);

        /*绑定,将队列和交换器通过路由键进行绑定*/
        String routekey = "king";/*表示只关注error级别的日志消息*/
        channel.queueBind(queueName,TestProducer.EXCHANGE_NAME,routekey);
        System.out.println("waiting for message........");
        /*声明了一个消费者*/
        final Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                System.out.println("Received["+envelope.getRoutingKey()
                        +"]"+message);
            }
        };
        /*消费者正式开始在指定队列上消费消息*/
        channel.basicConsume(queueName,true,consumer);
    }
}
View Code

 

三、消息持久化
  1.  exchange 需要持久化
  2. 发送消息设置参数为 MessageProperties.PERSISTENT_TEXT_PLAIN
  3. 队列需要设置参数为持久化
1、//TODO 创建持久化交换器 durable=true
channel.exchangeDeclare(EXCHANGE_NAME,"direct",true);

2、//TODO 发布持久化的消息(delivery-mode=2)
channel.basicPublish(EXCHANGE_NAME,routekey,
        MessageProperties.PERSISTENT_TEXT_PLAIN,
        msg.getBytes());

3、//TODO 声明一个持久化队列(durable=true)
// autoDelete=true 消费者停止了,则队列会自动删除
//exclusive=true独占队列,只能有一个消费者消费
String queueName = "msgdurable";
channel.queueDeclare(queueName,true,false,
        false,null);
四、如何支持事务(防止投递消息的时候消息丢失-效率特别低,不建议使用,可以使用生产者ACK机制)
  1.    启动事务
  2. 成功提交
  3. 失败则回滚
//TODO
//加入事务
channel.txSelect();
try {
    for(int i=0;i<3;i++){
        String routekey = routekeys[i%3];
        // 发送的消息
        String message = "Hello World_"+(i+1)
                +("_"+System.currentTimeMillis());
        channel.basicPublish(EXCHANGE_NAME, routekey, true,
                null, message.getBytes());
        System.out.println("----------------------------------");
        System.out.println(" Sent Message: [" + routekey +"]:'"
                + message + "'");
        Thread.sleep(200);
    }
    //TODO
    //事务提交
    channel.txCommit();
} catch (IOException e) {
    e.printStackTrace();
    //TODO
    //事务回滚
    channel.txRollback();
} catch (InterruptedException e) {
    e.printStackTrace();
}
View Code
五、消费消息手动ACK,如果异常则使用拒绝的方式,然后异常消息推送到-死信队列
       批量ack的时候如果其中有一个消息出现异常,则会导致消息丢失(日志处理的时候可以使用批量)
 
1 /*消费者正式开始在指定队列上消费消息,第二个参数false为手动应答*/
channel.basicConsume(queueName,false,consumer);

2 收到消息以后,手动应答数据接收成功
channel.basicAck(
        envelope.getDeliveryTag(),false);
3 收到消息,如果处理失败则拒绝消息:DeliveryTag是消息在队列中的标识
channel.basicReject(
        envelope.getDeliveryTag(),false);
4 决绝的参数说明
//TODO Reject方式拒绝(这里第2个参数决定是否重新投递),不要重复投递,因为消息重复投递后处理可能依然异常
//channel.basicReject(envelope.getDeliveryTag(),false);

//TODO Nack方式的拒绝(第2个参数决定是否批量,第3个参数是否重新投递)
channel.basicNack(envelope.getDeliveryTag(), false, true);
View Code

 六、创建队列的参数解析:场景,延迟队列,保存带有时效性的订单,一旦订单过期,则信息会转移到死信队列

//TODO /*自动过期队列--参数需要Map传递*/
String queueName = "setQueue";
Map<String, Object> arguments = new HashMap<String, Object>();
arguments.put("x-expires",10*1000);//消息在队列中保存10秒后被删除
//TODO 队列的各种参数
/*加入队列的各种参数*/
// autoDelete=true 消费者停止了,则队列会自动删除
//exclusive=true独占队列,只能有一个消费者消费
channel.queueDeclare(queueName,true,true, false,arguments);
七、发送消息以后带有应答的队列
  1. 声明一个回应队列
  2. 声明一个回应消息的消费者
  3. 声明一个属性对象(指定队列,会唯一的id)
  4. 生产者发送消息给消费者(带着回应队列)
  5. 消费者接收到消息以后根据对应的信息,给予回应
  生产者端:
 
  
1//TODO 响应QueueName ,消费者将会把要返回的信息发送到该Queue
String responseQueue = channel.queueDeclare().getQueue();
//TODO 消息的唯一id
String msgId = UUID.randomUUID().toString();

2/*声明了一个消费者*/
final Consumer consumer = new DefaultConsumer(channel){
    @Override
    public void handleDelivery(String consumerTag,
                               Envelope envelope,
                               AMQP.BasicProperties properties,
                               byte[] body) throws IOException {
        String message = new String(body, "UTF-8");
        System.out.println("Received["+envelope.getRoutingKey()
                +"]"+message);
    }
};
//TODO 消费者应答队列上的消息
channel.basicConsume(responseQueue,true,consumer);
3//TODO 设置消息中的应答属性
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder()
        .replyTo(responseQueue)
        .messageId(msgId)
        .build();
4、
String msg = "Hello,RabbitMq";
//TODO 发送消息时,把响应相关属性设置进去
channel.basicPublish(EXCHANGE_NAME,"error",
        properties,
        msg.getBytes());
View Code

 消费者端:

  

String message = new String(body, "UTF-8");
System.out.println("Received["+envelope.getRoutingKey()
        +"]"+message);
//TODO 从消息中拿到相关属性(确定要应答的消息ID,)
AMQP.BasicProperties respProp
        = new AMQP.BasicProperties.Builder()
        .replyTo(properties.getReplyTo())
        .correlationId(properties.getMessageId())
        .build();
//TODO 消息消费时,同时需要生作为生产者生产消息(以OK为标识)
channel.basicPublish("", respProp.getReplyTo() ,
        respProp ,
        ("OK,"+message).getBytes("UTF-8"));

 

 

 

八、死信队列 - 下列消息会放到死信队列
  1. 消息被否定确认,使用 channel.basicNack 或 channel.basicReject ,并且此时requeue 属性被设置为false。
  2. 消息在队列的存活时间超过设置的TTL时间。
  3. 消息队列的消息数量已经超过最大队列长度

   

 测试,消费者被拒绝的时候消息会进到死信队列中:
 
        final Channel channel = connection.createChannel();
        channel.exchangeDeclare(WillMakeDlxConsumer.BUS_EXCHANGE_NAME,
                BuiltinExchangeType.TOPIC);
        //TODO 绑定死信交换器
        /*声明一个队列,并绑定死信交换器*/


        Map<String,Object> args = new HashMap<>();
        args.put("x-dead-letter-exchange", WillMakeDlxConsumer.DLX_EXCHANGE_NAME);
//        //TODO 死信路由键,会替换消息原来的路由键
//        args.put("x-dead-letter-routing-key", "deal");


        channel.queueDeclare(WillMakeDlxConsumer.BUS_QUEUE_NAME,false,false,
                false,
                args);


        /*绑定,将队列和交换器通过路由键进行绑定*/
        channel.queueBind(WillMakeDlxConsumer.BUS_QUEUE_NAME,
                WillMakeDlxConsumer.BUS_EXCHANGE_NAME,"#");


        System.out.println("waiting for message........");


        //声明死信队列
        channel.exchangeDeclare(WillMakeDlxConsumer.DLX_EXCHANGE_NAME, "topic",true);
        channel.queueDeclare(WillMakeDlxConsumer.DLX_QUEUE_NAME, true, false, false, null);
        //路由键为 # 代表可以路由到所有消息
        channel.queueBind(WillMakeDlxConsumer.DLX_QUEUE_NAME ,WillMakeDlxConsumer.DLX_EXCHANGE_NAME,  "#");


        /*声明了一个消费者*/
        final Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag,
                                       Envelope envelope,
                                       AMQP.BasicProperties properties,
                                       byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                //TODO
                //TODO 如果是king的消息确认
                if(envelope.getRoutingKey().equals("king")){
                    System.out.println("Received["
                            +envelope.getRoutingKey()
                            +"]"+message);
                    channel.basicAck(envelope.getDeliveryTag(),
                            false);
                }else{
                    //TODO 如果是其他的消息拒绝(rqueue=false),成为死信消息
                    System.out.println("Will reject["
                            +envelope.getRoutingKey()
                            +"]"+message);
                    channel.basicReject(envelope.getDeliveryTag(),
                            false);
                }
            }
        };
        /*消费者正式开始在指定队列上消费消息*/
        channel.basicConsume(WillMakeDlxConsumer.BUS_QUEUE_NAME ,false,consumer);
View Code

过期消息进入到死信队列:

public static void main(String[] argsv) throws IOException, TimeoutException {


    Connection connection = getConnection();

    // 创建一个信道
    Channel channel = connection.createChannel();
    // 指定转发
    channel.exchangeDeclare(WillMakeDlxConsumer.BUS_EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

    Map<String,Object> args = new HashMap<>();
    args.put("x-dead-letter-exchange", WillMakeDlxConsumer.DLX_EXCHANGE_NAME);
    //设置队列中消息过期时间,过期则进入死信队列
    args.put("x-message-ttl", 10*1000);


    channel.queueDeclare(WillMakeDlxConsumer.BUS_QUEUE_NAME,true,false,
            false,
            args);


    /*绑定,将队列和交换器通过路由键进行绑定*/
    channel.queueBind(WillMakeDlxConsumer.BUS_QUEUE_NAME,
            WillMakeDlxConsumer.BUS_EXCHANGE_NAME,"#");


    System.out.println("waiting for message........");


    //声明死信队列
    channel.exchangeDeclare(WillMakeDlxConsumer.DLX_EXCHANGE_NAME, "topic",true);
    channel.queueDeclare(WillMakeDlxConsumer.DLX_QUEUE_NAME, true, false, false, null);
    //路由键为 # 代表可以路由到所有消息
    channel.queueBind(WillMakeDlxConsumer.DLX_QUEUE_NAME ,WillMakeDlxConsumer.DLX_EXCHANGE_NAME,  "#");


    /*日志消息级别,作为路由键使用*/
    String[] routekeys = {"king","mark","james"};
    for(int i=0;i<10;i++){
        String routekey = routekeys[i%3];
        String msg = "Hellol,RabbitMq"+(i+1);
        /*发布消息,需要参数:交换器,路由键,其中以日志消息级别为路由键*/
        channel.basicPublish(WillMakeDlxConsumer.BUS_EXCHANGE_NAME,routekey,null,
                msg.getBytes());
        System.out.println("Sent "+routekey+":"+msg);
    }
    // 关闭频道和连接
    channel.close();
    connection.close();
}
View Code

 

 

九、消费者批量预取消费-每次服务器给消费者推送多少数据进行处理

//TODO 如果是两个消费者(QOS ,批量)则轮询获取数据

//TODO 150条预取(150都取出来 150, 210-150  60  )
channel.basicQos(150,true);
/*消费者正式开始在指定队列上消费消息*/
channel.basicConsume(queueName,false,consumer);
//TODO 自定义消费者批量确认
//BatchAckConsumer batchAckConsumer = new BatchAckConsumer(channel);
//channel.basicConsume(queueName,false,batchAckConsumer);
View Code

 

 

十、生产者投递消息确认模式,如果失败了则可以重新投递
 
   同步确认:
  
// 启用发送者确认模式
channel.confirmSelect();


//所有日志严重性级别
for(int i=0;i<2;i++){
    // 发送的消息
    String message = "Hello World_"+(i+1);
    //参数1:exchange name
    //参数2:routing key
    channel.basicPublish(EXCHANGE_NAME, ROUTE_KEY, true,null, message.getBytes());
    System.out.println(" Sent Message: [" + ROUTE_KEY +"]:'"+ message + "'");
    //TODO
    //确认是否成功(true成功)
    if(channel.waitForConfirms()){
        System.out.println("send success");
    }else{
        //如果失败则,可以重新投递减小消息丢失的几率
        System.out.println("send failure");
    }
}
View Code

 异步确认-添加监听器:

  

//TODO
// 启用发送者确认模式
channel.confirmSelect();
//TODO
// 添加发送者确认监听器
channel.addConfirmListener(new ConfirmListener() {
    //TODO 成功
    public void handleAck(long deliveryTag, boolean multiple)
            throws IOException {
        System.out.println("send_ACK:"+deliveryTag+",multiple:"+multiple);
    }
    //TODO 失败
    public void handleNack(long deliveryTag, boolean multiple)
            throws IOException {
        System.out.println("Erro----send_NACK:"+deliveryTag+",multiple:"+multiple);
    }
});


//TODO
// 添加失败者通知
channel.addReturnListener(new ReturnListener() {
    public void handleReturn(int replyCode, String replyText,
                             String exchange, String routingKey,
                             AMQP.BasicProperties properties,
                             byte[] body)
            throws IOException {
        String message = new String(body);
        System.out.println("RabbitMq路由失败:  "+routingKey+"."+message);
    }
});




String[] routekeys={"king","mark"};
//TODO 6条
for(int i=0;i<20;i++){
    String routekey = routekeys[i%2];
    //String routekey = "king";
    // 发送的消息
    String message = "Hello World_"+(i+1)+("_"+System.currentTimeMillis());
    channel.basicPublish(EXCHANGE_NAME, routekey, true,
            MessageProperties.PERSISTENT_BASIC, message.getBytes());
}
View Code

 

十一、基本信息-创建链接

  

public class BasicMq {

    public static final String  MQ_IP = "192.168.112.131";
    public static final String  USER = "admin";
    public static final String  PWD = "admin";
    public static final String  VHOST = "my_vhost";

    /**
     *
     * @return
     */
    public static final Connection getConnection()   {
        /* 创建连接,连接到RabbitMQ*/
        ConnectionFactory connectionFactory = null;
        try {
            connectionFactory = new ConnectionFactory();
            connectionFactory.setHost(BasicMq.MQ_IP);
            connectionFactory.setUsername( BasicMq.USER);
            connectionFactory.setPassword( BasicMq.PWD );
            connectionFactory.setVirtualHost( BasicMq.VHOST );

            return connectionFactory.newConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null ;
    }


    public static final Channel getChannel() throws IOException {
        return getConnection().createChannel();
    }
}
View Code

 

十二、异常整理
  1.  如果生产者声明exchange为 durable=true,那么消费者对应的exchange也必须为durable=true
  2. 消费者原来是durable=false,修改后变为durable=true,那么因为服务器上已经有这个队列,但是参数不一致会异常,需要删除服务器上的对应队列

 

posted @ 2020-12-17 17:38  码来  阅读(1600)  评论(0编辑  收藏  举报