rabbitmq学习

一:简介 

rabbitmq做为接收和发送数据的一个黑盒子

   队列用于保存发送的信息,消费者可以从队列中取出数据。下图是简单的一个图了。

生产者基本代码如下

public static void main(String[] argv) throws Exception{
        
        // 创建一个连接到服务器的通道
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        // 声明一个队列
        channel.queueDeclare("hello", false, false, false, null);
        //String message = "Hello World!";
        String[] abc = {"a",".","b","c",".","d"};
        String message = getMessage(abc);
        // 发送数据到队列中
        // 第一个参数是交换机,第二个数据是队列名,第三个是 AMQP.BasicProperties属性值,第四个是发送的消息

       // 如果第一个参数为“”,那代表的是默认或是无名字的交换机,这时消息是发送的队列就直接由第二个参数来决定了。
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        
        // 最后关闭
        channel.close();
        connection.close();
    }

消费者的基本代码如下

public static void main(String[] args) throws Exception{
        // TODO Auto-generated method stub
    
        // 创建一个连接到rabbitmq
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        // 绑定到一个队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        // 创建一个消费者,把这个消费者绑定到队列中
        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(QUEUE_NAME, true, consumer);

        while (true) {
          QueueingConsumer.Delivery delivery = consumer.nextDelivery();
          String message = new String(delivery.getBody());
          System.out.println(" [x] Received '" + message + "'");
          doWork(message);
          System.out.println(" [x] Done");
        }

    }

(二)数据丢失问题

1:消费者的运行情况

对于工作队列,默认的情况下是按顺序接收生产者发送过来的数据,比如说,服务端(消费者)起了2个同样的服务C1,C2,然后当生产者发送一个消息时,这时只有C1或是C2接收,假如说是C1接收,那么生产者第二次发送消息时,这时是C2接收;生产者第三次发送消息时,这时是C1接收;生产者第四次发送消息时,这时是C2接收,以此类推。

2:消费者运行崩溃,数据丢失问题

在消费者端,在下面的语句:

     channel.basicConsume(QUEUE_NAME, true, consumer);//第一个参数是队列名,第二个参数是是否自动应答,第三个参数是一个消息者对像的接口。

此代表了消费者对生产者进行了自动应答,这种情况下,生产者发送数据到队列后,就把队列中的数据删除了。假如说消费者在实行过程中出问题时,那么发送的数据就会丢失。

    如果声明成

channel.basicConsume(QUEUE_NAME, false, consumer);

此代表了消费者需对生产者进行人为应答,这种情况下,生产者发送数据到队列后,就不会把队列中的数据删除了。假如说消费者在实行过程中出问题时,那么发送的数据就不会丢失。

3:RabbitMQ服务停止,数据丢失问题

上面说明了在消费者运行出错后,数据不会丢失,不过如果RabbitMQ服务停止,上面的运行还是会丢失数据。为了在RabbitMQ服务停止时,数据不会丢失,那么需要运用到持久性。

 要解决上面提出的问题,那么我们就需要声明RabbitMQ的持久性。

首先在生产者的生命队列时,

// 第一个参数是队列名,第二个参数是是否持久性,第三个参数是是否唯一性,第四个参数是否自动删除,第五个参数是队列的其它属性。

   channel.queueDeclare(QUEUE_NAME, false, false, false, null);

把第二个参数改成true,这代表着其是持久性,如下   channel.queueDeclare(QUEUE_NAME, true, false, false, null);

然后在生产者发送数据时,

        // 第一个参数是交换机,第二个数据是队列名,第三个是 AMQP.BasicProperties属性值,第四个是发送的消息
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

对第三个参数进行设置值 channel.basicPublish("", QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());

这样完以后,即使rabbitMQ服务重启,数据都不会丢失

4:公平分发

   我们可能已经注意到,rabbitMQ在分发队列时,是按顺序进行分发的。假如如说,奇数的消息都需要很长时间,而耦数的消息都很简单。假如起了两个消费者,那么就会有其中的一个消费者一直处于忙的状态,并且无法急时处理消息,而另外一个一直处于空闲状态。

   为了解决这个问题,那么可以在消费者端使用

channel.basicQos(1);
此行代表告诉消费者,当消费者正忙时,消息不往此消费者发送。

(三)分布/订阅

    前面的几节都讲了一个生产者的消息只能由一个消费者进行接收,本节主要讲的是一个生产者的消息可以由多个消费者同时进行接收处理。在这里提到了一个交换机的概念。我们已经知道之前的模式如下图

上图的模式是生产者直接把数据发送到队列中,这时就只能发送到一个队列中。

而对于分布/订阅而言,其模式如下

交换机(exchange 这里是X)从生产者中接收消息,另外它又发送数据到队列。交换机必须要确切知道它接收到的是做什么。它(指的是交换机)应该把消息发送到某个具体的队列中?还是它应该把消息发送到多个队列 中?或者说它应该放弃此消失?这些问题都应该被交换机的类型来定义。

交换机的类型有direct, topic, headersand fanout。分布/订阅使用的是fanout。创建一个交换机如下,

channel.exchangeDeclare("logs", "fanout");
第一个参数是交换机名称,第二个是交换机类型
然后发送消息时,如下的代码:
channel.basicPublish( "logs", "", null, message.getBytes());
比如说我们要作个日志,接收所有的消息,那么要做两个事情
第一:声明一个随机名字的队列,并且在Rabbit服务启动后,需要一个空的消息了。
String queueName = channel.queueDeclare().getQueue();
第二:把队列绑定到交换机上
channel.queueBind(queueName, "logs", "");
其图如下




(四)路由(选择性接收数据)
上一节我们讲解了所有的消费者都能接收一个生产者的消息。其思想是在生产者端做如下事情:
1):声明一个随机名字的队列
2):然后把此队列绑定到一个类型为fanout,名字为log(可随便取)的交换机,
3):发送数据时,队列名为""
而在消费者端
  1):声明一个随机名字的队列
2):声明一个类型为fanout,名字为log(可随便取)的交换机
3):把队列 与此交换机绑定,绑定表示此队列对此交换机感兴趣了。

本节要实现的,一些消费者只接收指定的消息,查看一下之前的绑定代码
channel.queueBind(queueName, EXCHANGE_NAME, "");
此代码段中的第三个参数代表的就是路由,这个参数对于交换机类型为fanout的无效。

假如现在要实现一个功能,生产者可能会发送日志消息,每个日志消息的程度都不一样,可能有error,warning,debug,消费者根据消息的程度,可能对于error进行保存到硬盘,
而对于warning和debug则显示在控制台,不进行保存到硬盘.

生产者端
// 第一个参数交换机名,第二个参数是类别
channel.exchangeDeclare("log", "direct");
然后发送时(假如这时发送的是error的日志)
// 第一个参数是交换机名,第二个是routingKey,第三个是BasicProperties属性,第四个是发送的消息。
channel.basicPublish("log", "error", null, message.getBytes());
注意在这两行代码中,注意第一个参数值是一样的,而第二值是routdingkey,这里就不能理解成是队列了,这个要注意。

在消费者端
声明的交换机与生产者端是一样的。
channel.exchangeDeclare("log", "direct");
而在交换机与队列绑定时要注意,其代码如下
channel.queueBind(queueName, EXCHANGE_NAME, "error");
在这里第三个参数值要与生产者发送代码中的第二个参数保持一样的。在这种direct的方式下,队列名反而没有多大意义了。如果从代码上理解,那就是说在有routingkey的情
况下,队列名变成不是很重要的。从下图中或许可以看出。

生产者是发送消息到交换机上去的。而交换机的发送数据这时是根据routingkey了。也就是说,如果有多个消费者,其绑定的交换机一样,并且其routingkey值也一样,则消费
者同时接收
生产者的数据。实际上的例子也说明确是如此了。
对于多绑定,其实现的代码如下
String[] aa={"error","warning","debug"};
        for (String severity : aa) {
            channel.queueBind(queueName, EXCHANGE_NAME, severity);
        }
这段代码意味着把三个key都绑定到同一个队列中,那么在生产者的routingkey值为这三者之一的时候,那么这个消费者可以同时接收这些生产者了。
(五):topic(基于模式来接收数据)
前面已讲的routing 类型还无法达到即基于程度的订阅消息,而且还想基于来源的忽略消息。要想实现此功能,那么则要使用topic交换机类型的进行开发。
在topic交换机类型中,生产者端的routing_key是由字母和点“.”构成的。在格式匹配中“*”号代表的是一个字母或是单词,而“#”则代表的是多个单词,
(六)RPC(远程过程调用)

选程调用时,要让消费者把通过生产者定义的队列把数据发送回来,要实现此功能,那么在发送时,要对第三个参数进行设置值,代码如下

callbackQueueName = channel.queueDeclare().getQueue();

BasicProperties props = new BasicProperties
                            .Builder()
                            .replyTo(callbackQueueName)
                            .build();
channel.basicPublish("", "rpc_queue", props, message.getBytes())
BasicProperties 这个属性很少被用到,除了下面的四个属性
1):deliveryMode:代表持久化
2):contentType:代表传输的字节码:比如说如果是用josn,那么其值就为application/json
3):replyTo:回调的队列名称
4):correlationId:RPC响应请求的RPC ID。

Correlation Id用途

如果代码向我们上面那样写,那么其是非常无效的。因为这样就会为每个请求创建一个回调队列。最好的方式是对每个客户端创建一个单一的请求队列。如果这样做,会导致一

个问题,我们不清楚在队列中的响应是属于哪个请求的响应。Correlation id就是用于此的。对每个请求我们都设置一个独一无二的Correlation id值。响应时我们通过查找这

个Correlation id 值,就可以映射到对应的请求。

rpc的工作流程看起来如下:

  • When the Client starts up, it creates an anonymous exclusive callback queue.
  • For an RPC request, the Client sends a message with two properties: replyTo, which is set to the callback queue and correlationId, which is set to a unique value for every request.
  • The request is sent to an rpc_queue queue.
  • The RPC worker (aka: server) is waiting for requests on that queue. When a request appears, it does the job and sends a message with the result back to the Client, using the queue from the replyTo field.
  • The client waits for data on the callback queue. When a message appears, it checks the correlationId property. If it matches the value from the request it returns the response to the application.

总结:routingkey的概念是在生产者,bindingkey的概念是在消费者,两个的概念应该是一样的,只不过在生产者是使用routing_key,代表的是发送的消息查找的路由,
      binding_key在消费者中,代表的是交换机和队列绑定的绑定关键字。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值