RabbitMQ
消息中间件
名称 | 图标 | 简介 | 适用场景 | 优点 | 缺点 |
---|---|---|---|---|---|
RabbitMQ | 是AMQP协议领先的一个实现,他实现了代理架构,意味着在发送到客户端之前可以在中央节点上排队,此特性使得RabbitMQ易于使用和部署 | 路由、负载均衡、消息持久化 | 几行代码就可以搞定 | 扩展性差,速度较慢--中央节点增加了延迟,消息封装后也比较大 | |
ZeroMQ | 是一个非常轻量级的消息系统,专门为高吞吐量/低延迟的场景开发 | 金融界 | 与RabbitMQ相比,ZeroMQ支持许多高级的场景但是必须实现ZeroMQ的框架中的各个快(比如Socket和Device等);非常灵活 | 手册有80多页,如果手写一个分布式系统,一定得阅读它 | |
ActiveMQ | 居于两者之间 | 类似于RabbitMQ,可以部署于代理模式和p2p模式;类似于ZeroMQ,易于实现高级场景,而且只需付出低消耗 | 被视为消息中间件的“瑞士军刀” | 因为吸收两者的优点,暂时略缺点 | |
Apollo | 下一代的ActiveMQ | 越来越多领域喽 | 越来越好喽 | 这个暂时略 |
RabbitMQ简单学习--从京淘(电商网)项目入手
- 引入
- 当前京淘的架构性能提升点
- NGINX高并发
- Redis内存缓存数据库(非结构数据)
- amoeba提升数据最后关卡的性能
- 超负荷的请求,以上三个技术无法处理
- 当请求来到时,如果并发量太大,就让请求排成队列
- 基于erlang语言
- 当前京淘的架构性能提升点
- 消息队列分类
- simple简单队列【先后顺序】
- work工作模式【资源竞争】--红包
- publish/subscribe发布订阅【共享资源】:引入交换机--邮件的群发、群聊天、广播
- 路由模式:消息的生产者发送给交换机,通过路由判断key值发送到相应的队列--error通知
- topic主题模式(路由模式的一种):通过表达式进行判断--*代表多个单词,#号代表一个单词
- 注意:别名
- publish:fanout
- routing:direct
- topic:topic
使用
- 依赖
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>3.5.1</version> </dependency>
- 流程
- 创建连接工厂
- 从连接工厂获取connection
- 从连接获取channel
- 从channel获取绑定的queue
- 生产者生产消息放入队列
- 释放资源
RabbitMQ的工作原理
单发送,单接收
- 使用场景:简单的发送与接收,没有设么特别的处理
示例【生产者】
public class Send { private final static String QUEUE_NAME = "hello";//队列的名称 public static void main(String[] argv) throws Exception { // 获取连接工厂 ConnectionFactory factory = new ConnectionFactory(); // 设置主机IP 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()); 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(); // 设置主机IP 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 + "'"); } } }
单发送多接收
- 使用场景:一个发送端,多个接收端,如分布式的任务派发。为了保证消息发送的可靠性,不丢失消息,使消息持久化了。同时为了防止接收端在处理消息时down掉,只有在消息处理完成后才发送ack消息。
示例【生产者】
public class NewTask { // 队列的名称 private static final String TASK_QUEUE_NAME = "task_queue"; public static void main(String[] argv) throws Exception { // 连接工厂 ConnectionFactory factory = new ConnectionFactory(); // 设置主机IP factory.setHost("localhost"); // 获取连接 Connection connection = factory.newConnection(); // 创建通道 Channel channel = connection.createChannel(); channel.queueDeclare(TASK_QUEUE_NAME, true, false, false, null); String message = getMessage(argv); // PERSISTENT_TEXT_PLAIN:消息的持久化 channel.basicPublish("", TASK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); channel.close(); connection.close(); } private static String getMessage(String[] strings){ if (strings.length < 1) return "Hello World!"; 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"); Connection connection = factory.newConnection(); 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)保证在接收端一个消息没有处理完时不会接收另一个消息,即接收端发送了ack后才会接收下一个消息。在这种情况下发送端会尝试把消息发送给下一个not busy的接收端 channel.basicQos(1); QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(TASK_QUEUE_NAME, false, 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"); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); } } private static void doWork(String task) throws InterruptedException { for (char ch: task.toCharArray()) { if (ch == '.') Thread.sleep(1000); } } }
Publish/Subscribe
- 使用场景:发布、订阅模式,发送端发送广播消息,多个接收端接收
示例【生产者】
public class EmitLog { 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 message = getMessage(argv); channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes()); System.out.println(" [x] Sent '" + message + "'"); channel.close(); connection.close(); } private static String getMessage(String[] strings){ if (strings.length < 1) return "info: Hello World!"; 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 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"); QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(queueName, true, consumer); while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String message = new String(delivery.getBody()); System.out.println(" [x] Received '" + message + "'"); } } }
Routing (按路线发送接收)
- 使用场景:发送端按routing key发送消息,不同的接收端按不同的routing key接收消息
示例【生产者】
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, "direct"); String severity = getSeverity(argv); String message = getMessage(argv); channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes()); 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, "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"); QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(queueName, true, consumer); while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String message = new String(delivery.getBody()); String routingKey = delivery.getEnvelope().getRoutingKey(); System.out.println(" [x] Received '" + routingKey + "':'" + message + "'"); } } }
Topics (按topic发送接收)
- 使用场景:发送端不只按固定的routing key发送消息,而是按字符串“匹配”发送,接收端同样如此。
示例【生产者】
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, "topic"); String routingKey = getRouting(argv); String message = getMessage(argv); channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes()); 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(); } }
示例【消费者】
public class ReceiveLogsTopic { 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, "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"); QueueingConsumer consumer = new QueueingConsumer(channel); channel.basicConsume(queueName, true, consumer); while (true) { QueueingConsumer.Delivery delivery = consumer.nextDelivery(); String message = new String(delivery.getBody()); String routingKey = delivery.getEnvelope().getRoutingKey(); System.out.println(" [x] Received '" + routingKey + "':'" + message + "'"); } } catch (Exception e) { e.printStackTrace(); } finally { if (connection != null) { try { connection.close(); } catch (Exception ignore) {} } } } }
秒杀
业务场景分析
- 并发量很高的时间段--抢商品
- 队列中的消息可以是什么
- 电话号码
- username
- ticket
- ......
- 做法
- 调用SSO查询用户信息,把前n个消息获取到,后面的放入rabbitmq的垃圾桶
- 更高的并发可以考虑分布式的队列
- 文件位置
- 生产者:后台
- 消费者:前台
- 秒杀设计
- 未完待续。。。
注:参考文章
- RabbitMQ的几种典型使用场景:https://www.rabbitmq.com/getstarted.html