RabbitMq-tutorials
rabbitmq例子
http://www.rabbitmq.com/tutorials/tutorial-one-java.html
简单例子
http://www.cnblogs.com/frankyou/p/5283539.html
producing       发送消息
queue           邮箱,相当于无限大的buffer
consuming       接收消息
基本的发送消息
public class Send {
  private final static String QUEUE_NAME = "hello";
  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(QUEUE_NAME, false, false, false, null);
    String message = "Hello World!";
    channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
    System.out.println(" [x] Sent '" + message + "'");
    channel.close();
    connection.close();
  }
}
基本接收,异步接收
public class Recv {
  private final static String QUEUE_NAME = "hello";
  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(QUEUE_NAME, false, false, false, null);
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
    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(" [x] Received '" + message + "'");
      }
    };
    channel.basicConsume(QUEUE_NAME, true, consumer);
  }
}
工作队列
我们要创建一个工作队列,用来向多个worker分发耗时的任务;
工作队列的主要想法是,不立刻执行资源密集任务,而是调度它一会再执行;
把任务封装成消息,放到队列中,一个后台的worker进程,会执行任务,如果有很多workers,它们会共享这些task;
robin分配
如果积压了很多任务,可以增加worker的数量,这时候发现消息是在不同的worker间轮询分配的
消息确认
目前的配置,一旦消息发送,会从内存清除;
消息确认需要worker执行完,返回一个确认消息,这时候rabbitmq可以删除它
如果consumer死了,RabbitMQ会重新分配它,这样即使worker偶尔死掉,也能保证消息不会丢失
开启消息确认需要设置autoAck=false,并且需要设置一个返回的ack消息
channel.basicQos(1); // accept only one unack-ed message at a time (see below)
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(" [x] Received '" + message + "'");
    try {
      doWork(message);
    } finally {
      System.out.println(" [x] Done");
      channel.basicAck(envelope.getDeliveryTag(), false);
    }
  }
};
boolean autoAck = false;
channel.basicConsume(TASK_QUEUE_NAME, autoAck, consumer);
如果ack信号丢失了,消息会重新分配执行,这样可能会有点问题..
消息持久化
如果worker死了,可以保证消息不丢失,但是如果RabbitMQ server死了,如何保证不丢失呢?
需要设置queue和message为durable
boolean durable = true;
channel.queueDeclare("hello", durable, false, false, null);
只能在创建queue的时候使用,如果已经存在的queue会报错
设置MessageProperties为PERSISTENT_TEXT_PLAIN会持久化消息
import com.rabbitmq.client.MessageProperties;
channel.basicPublish("", "task_queue",
            MessageProperties.PERSISTENT_TEXT_PLAIN,
            message.getBytes());
把消息设置为持久化,并不能保证消息不丢失,就收到消息,但是还未持久化这个时间,仍然可能丢失;
因为需要缓存一下,再写入disk,如果需要更健壮,可以使用publisher confirms
公平分配
比如消息偶数都比较重,基数都比较轻,按照robbin方式,会有问题,因为没有考虑到ack
设置prefetchCount=1会,就不会向正在忙碌的work分配任务,而是选择下一个不忙碌的worker
int prefetchCount = 1;
channel.basicQos(prefetchCount);
总结起来
发送
public class NewTask {
  private static final String TASK_QUEUE_NAME = "task_queue";
  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(TASK_QUEUE_NAME, true, false, false, null);
    String message = getMessage(argv);
    channel.basicPublish("", TASK_QUEUE_NAME,
        MessageProperties.PERSISTENT_TEXT_PLAIN,
        message.getBytes("UTF-8"));
    System.out.println(" [x] Sent '" + message + "'");
    channel.close();
    connection.close();
  }
  private static String getMessage(String[] strings) {
    if (strings.length < 1)
      return "Hello..... ";
    return joinStrings(strings, " ");
  }
  private static String joinStrings(String[] strings, String delimiter) {
    int length = strings.length;
    if (length == 0) return "";
    StringBuilder words = new StringBuilder(strings[0]);
    for (int i = 1; i < length; i++) {
      words.append(delimiter).append(strings[i]);
    }
    return words.toString();
  }
}
接收
public class Worker {
  private static final String TASK_QUEUE_NAME = "task_queue";
  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    final Connection connection = factory.newConnection();
    final Channel channel = connection.createChannel();
    channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null);
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
    channel.basicQos(1);
    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(" [x] Received '" + message + "'");
        try {
          doWork(message);
        } finally {
          System.out.println(" [x] Done");
          channel.basicAck(envelope.getDeliveryTag(), false);
        }
      }
    };
    channel.basicConsume(TASK_QUEUE_NAME, false, consumer);
  }
  private static void doWork(String task) {
    for (char ch : task.toCharArray()) {
      if (ch == '.') {
        try {
          Thread.sleep(1000);
        } catch (InterruptedException _ignored) {
          Thread.currentThread().interrupt();
        }
      }
    }
  }
}
发布订阅
之前假定一个消息发送给一个consumer,发布订阅可以把一个消息发送给多个consumer
Exchanges
完整的rabbitmq模型

producer不会把信息直接发送给queue,而是发送给excnage
exchange方式:direct, topic, headers and fanout
现在只讨论fanout方式,创建一个exchange,叫logs
channel.exchangeDeclare("logs", "fanout");
它会广播接受到的信息到所有的queue
然后我们可以发布到exchange中
channel.basicPublish( "logs", "", null, message.getBytes());
使用rabbitmqctl能查看exchange信息
$ sudo rabbitmqctl list_exchanges
Listing exchanges ...
        direct
amq.direct      direct
amq.fanout      fanout
amq.headers     headers
amq.match       headers
amq.rabbitmq.log        topic
amq.rabbitmq.trace      topic
amq.topic       topic
logs    fanout
...done.
里面有很多自动创建的,都以amq.*开头
临时队列
String queueName = channel.queueDeclare().getQueue();
自动生成唯一队列,使用后删除,不持久化,比如amq.gen-JzTY20BRgKO-HjmUJj0wLg
绑定
channel.queueBind(queueName, "logs", "");
这样logs exchange会绑定信息到刚创建的queueName上
可以使用命令列出绑定
rabbitmqctl list_bindings.
组合到一起

发送的时候需要提供一个routingKey,但是会被fanout忽略
import java.io.IOException;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
public class EmitLog {
    private static final String EXCHANGE_NAME = "logs";
    public static void main(String[] argv)
                  throws java.io.IOException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        String message = getMessage(argv);
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");
        channel.close();
        connection.close();
    }
    //...
}
consumer
import com.rabbitmq.client.*;
import java.io.IOException;
public class ReceiveLogs {
  private static final String EXCHANGE_NAME = "logs";
  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
    String queueName = channel.queueDeclare().getQueue();
    channel.queueBind(queueName, EXCHANGE_NAME, "");
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
    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(" [x] Received '" + message + "'");
      }
    };
    channel.basicConsume(queueName, true, consumer);
  }
}
路由
binding是exchange和queue之间的联系
binding可以设置一个routingKey参数
比如:
channel.queueBind(queueName, EXCHANGE_NAME, "black");
key的意义,依赖于exchange的类型
Direct

分别设置不同的,信息到不同的queue
多绑定

可以多个queue绑定同一个routing key
发送log
创建一个exchange
channel.exchangeDeclare(EXCHANGE_NAME, "direct");
channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
订阅
我们为每一种严重程度的log设置一个绑定
String queueName = channel.queueDeclare().getQueue();
for(String severity : argv){    
  channel.queueBind(queueName, EXCHANGE_NAME, severity);
}
发送
public class EmitLogDirect {
  private static final String EXCHANGE_NAME = "direct_logs";
  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
    String severity = getSeverity(argv);
    String message = getMessage(argv);
    channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8"));
    System.out.println(" [x] Sent '" + severity + "':'" + message + "'");
    channel.close();
    connection.close();
  }
  private static String getSeverity(String[] strings){
    if (strings.length < 1)
    	    return "info";
    return strings[0];
  }
  private static String getMessage(String[] strings){
    if (strings.length < 2)
    	    return "Hello World!";
    return joinStrings(strings, " ", 1);
  }
  private static String joinStrings(String[] strings, String delimiter, int startIndex) {
    int length = strings.length;
    if (length == 0 ) return "";
    if (length < startIndex ) return "";
    StringBuilder words = new StringBuilder(strings[startIndex]);
    for (int i = startIndex + 1; i < length; i++) {
        words.append(delimiter).append(strings[i]);
    }
    return words.toString();
  }
}
接收
public class ReceiveLogsDirect {
  private static final String EXCHANGE_NAME = "direct_logs";
  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
    String queueName = channel.queueDeclare().getQueue();
    if (argv.length < 1){
      System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]");
      System.exit(1);
    }
    for(String severity : argv){
      channel.queueBind(queueName, EXCHANGE_NAME, severity);
    }
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
    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(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
      }
    };
    channel.basicConsume(queueName, true, consumer);
  }
}
topic
topic exchange需要有一个任意的routing_key,比如 "stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit"
255字节以内
绑定的key也是这种形式topic
* 匹配一个字
# 匹配0或更多字

要发送:速度.颜色.类别
做三个绑定:
"*.orange.*"
"*.*.rabbit" 
"lazy.#"
Q1对所有orange的animal感兴趣
Q2对所有的rabbit和lazy动物感兴趣
"quick.orange.rabbit"会发送给两个队列
"lazy.orange.elephant"也会发送给两个
"quick.orange.fox" 第一个
"lazy.brown.fox" 第二个
"lazy.pink.rabbit" 也会匹配第二个,但是不会匹配两次
"quick.brown.fox" 不匹配任何,会被丢弃
"quick.orange.male.rabbit" 丢弃
"lazy.orange.male.rabbit" 匹配第二个
发送
public class EmitLogTopic {
  private static final String EXCHANGE_NAME = "topic_logs";
  public static void main(String[] argv) {
    Connection connection = null;
    Channel channel = null;
    try {
      ConnectionFactory factory = new ConnectionFactory();
      factory.setHost("localhost");
      connection = factory.newConnection();
      channel = connection.createChannel();
      channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
      String routingKey = getRouting(argv);
      String message = getMessage(argv);
      channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes("UTF-8"));
      System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
    }
    catch  (Exception e) {
      e.printStackTrace();
    }
    finally {
      if (connection != null) {
        try {
          connection.close();
        }
        catch (Exception ignore) {}
      }
    }
  }
  private static String getRouting(String[] strings){
    if (strings.length < 1)
    	    return "anonymous.info";
    return strings[0];
  }
  private static String getMessage(String[] strings){
    if (strings.length < 2)
    	    return "Hello World!";
    return joinStrings(strings, " ", 1);
  }
  private static String joinStrings(String[] strings, String delimiter, int startIndex) {
    int length = strings.length;
    if (length == 0 ) return "";
    if (length < startIndex ) return "";
    StringBuilder words = new StringBuilder(strings[startIndex]);
    for (int i = startIndex + 1; i < length; i++) {
        words.append(delimiter).append(strings[i]);
    }
    return words.toString();
  }
}
接收
import com.rabbitmq.client.*;
import java.io.IOException;
public class ReceiveLogsTopic {
  private static final String EXCHANGE_NAME = "topic_logs";
  public static void main(String[] argv) throws Exception {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = factory.newConnection();
    Channel channel = connection.createChannel();
    channel.exchangeDeclare(EXCHANGE_NAME, "topic");
    String queueName = channel.queueDeclare().getQueue();
    if (argv.length < 1) {
      System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
      System.exit(1);
    }
    for (String bindingKey : argv) {
      channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
    }
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
    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(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
      }
    };
    channel.basicConsume(queueName, true, consumer);
  }
}
RPC
客户端接口
FibonacciRpcClient fibonacciRpc = new FibonacciRpcClient();   
String result = fibonacciRpc.call("4");
System.out.println( "fib(4) is " + result);
rpc滥用会造成代码的不可维护性
- 知道哪个是rpc哪个是local
- 用文档把组件依赖明确
- 处理异常,如果rpc server超时了
尽量用异步pipline,异步地把信息传递给下一个计算步骤
回调队列
调用rpc后,server需要回复信息,需要设置一个callback queue
callbackQueueName = channel.queueDeclare().getQueue();
BasicProperties props = new BasicProperties
                            .Builder()
                            .replyTo(callbackQueueName)
                            .build();
channel.basicPublish("", "rpc_queue", props, message.getBytes());
// ... then code to read a response message from the callback_queue ...
AMQP协议预定义了14个属性,大部分都使用过了,除了下面的
deliveryMode
contentType
replyTo     命名一个callbackqueue
correlationId
Correlation Id
之前,我们为每一个rpc设置一个callback队列,这是很低效的;
现在我们为每一个client设置一个queue,这样每个request对应的response
correlationId就是指定唯一的request

rpc会这样工作:
- client创建一个异步的callback queue
- 对于rpc request,client发送消息,并提供两个属性:replyTo,correlationId
- request发送到rpc_queue
- rpc worker等待request,消息发送给replyTo
- client检查correlationId
public class RPCServer {
  private static final String RPC_QUEUE_NAME = "rpc_queue";
  private static int fib(int n) {
    if (n ==0) return 0;
    if (n == 1) return 1;
    return fib(n-1) + fib(n-2);
  }
  public static void main(String[] argv) {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    Connection connection = null;
    try {
      connection      = factory.newConnection();
      Channel channel = connection.createChannel();
      channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
      channel.basicQos(1);
      System.out.println(" [x] Awaiting RPC requests");
      Consumer consumer = new DefaultConsumer(channel) {
        @Override
        public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
          AMQP.BasicProperties replyProps = new AMQP.BasicProperties
                  .Builder()
                  .correlationId(properties.getCorrelationId())
                  .build();
          String response = "";
          try {
            String message = new String(body,"UTF-8");
            int n = Integer.parseInt(message);
            System.out.println(" [.] fib(" + message + ")");
            response += fib(n);
          }
          catch (RuntimeException e){
            System.out.println(" [.] " + e.toString());
          }
          finally {
            channel.basicPublish( "", properties.getReplyTo(), replyProps, response.getBytes("UTF-8"));
            channel.basicAck(envelope.getDeliveryTag(), false);
          }
        }
      };
      channel.basicConsume(RPC_QUEUE_NAME, false, consumer);
      //loop to prevent reaching finally block
      while(true) {
        try {
          Thread.sleep(100);
        } catch (InterruptedException _ignore) {}
      }
    } catch (IOException | TimeoutException e) {
      e.printStackTrace();
    }
    finally {
      if (connection != null)
        try {
          connection.close();
        } catch (IOException _ignore) {}
    }
  }
}
public class RPCClient {
  private Connection connection;
  private Channel channel;
  private String requestQueueName = "rpc_queue";
  private String replyQueueName;
  public RPCClient() throws IOException, TimeoutException {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    connection = factory.newConnection();
    channel = connection.createChannel();
    replyQueueName = channel.queueDeclare().getQueue();
  }
  public String call(String message) throws IOException, InterruptedException {
    String corrId = UUID.randomUUID().toString();
    AMQP.BasicProperties props = new AMQP.BasicProperties
            .Builder()
            .correlationId(corrId)
            .replyTo(replyQueueName)
            .build();
    channel.basicPublish("", requestQueueName, props, message.getBytes("UTF-8"));
    final BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);
    channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
      @Override
      public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        if (properties.getCorrelationId().equals(corrId)) {
          response.offer(new String(body, "UTF-8"));
        }
      }
    });
    return response.take();
  }
  public void close() throws IOException {
    connection.close();
  }
  public static void main(String[] argv) {
    RPCClient fibonacciRpc = null;
    String response = null;
    try {
      fibonacciRpc = new RPCClient();
      System.out.println(" [x] Requesting fib(30)");
      response = fibonacciRpc.call("30");
      System.out.println(" [.] Got '" + response + "'");
    }
    catch  (IOException | TimeoutException | InterruptedException e) {
      e.printStackTrace();
    }
    finally {
      if (fibonacciRpc!= null) {
        try {
          fibonacciRpc.close();
        }
        catch (IOException _ignore) {}
      }
    }
  }
}
更多参考:
http://www.rabbitmq.com/tutorials/tutorial-six-java.html
 
                    
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号