RabbitMQ项目中的使用场景
1.系统间解耦
2.异步处理,提高性能
3.流量削峰
4.最终一致性问题
在我们的支付系统中,主要是起系统解耦的作用。
在通道计算平台中,起流量削峰作用。
RabbitMQ采用的理由
1.Exchange、Queue、Message都可以持久化,高可用性
2.RabbitMQ支持消息确认机制,能报保证消息可靠传输和可靠消费。
3.路由灵活
4.集群部署,保证可靠性
5.UI管理界面
6.erlang语言开发,高并发处理性能比较好
RabbitMQ简单实用
//连接方法
public void connect() {
//1.实例化一个连接工厂(ConnectFactory)
connectionFactory = new ConnectionFactory();
//2.指定连接主机地址
connectionFactory.setHost(RABBITMQ_URL);
//3.指定端口
connectionFactory.setPort(RABBITMQ_PORT);
//4.用户名
connectionFactory.setUsername(RABBITMQ_USERNAME);
//5.密码
connectionFactory.setPassword(RABBITMQ_PASSWORD);
//6.指定虚拟主机(相当于命名空间,指定Exchange和Queue在哪个Virtual host中)
connectionFactory.setVirtualHost(RABBITMQ_VirtualHost);
//7.设置连接超时时间
connectionFactory.setConnectionTimeout(30000);
try {
//8.通过connectionFactory新建一个连接
connection = connectionFactory.newConnection();
//9.为当前连接指定一个通道
channel = connection.createChannel();
/**
*
* String exchange, Exchange名称
* BuiltinExchangeType type, exchange类型[DIRECT("direct"), FANOUT("fanout"), TOPIC("topic"), HEADERS("headers")]
* boolean durable, 是否持久化(高可用的保证,保证交换机持久化)
* boolean autoDelete, 是否自动删除,当它不被使用的Exchange服务器会自动删除
* boolean internal, 是否内部存在,如果为true那么这个Exchange不能直接给客户端使用
* Map<String, Object> arguments 额外参数
*/
//10.声明一个Exchange,如果MQ服务器中没有则新建。
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true, false, false, null);
/**
*
* String queue, Queue名称
* boolean durable, 是否持久化(高可用的保证,保证队列持久化)
* boolean exclusive, 是否时独占的,true为独占即为该连接中才可使用
* boolean autoDelete, 是否自动删除,当它不被使用的Queue服务器会自动删除
* Map<String, Object> arguments 额外参数
*/
Map<String, Object> arguments = new HashedMap();
//设置过期时间
arguments.put("x-message-ttl", 60000);
//设置队列长度
arguments.put("x-max-length", 2);
//设置死信队列
arguments.put("x-dead-letter-exchange", "letter_dead_queue");
//11.声明一个Queue,如果MQ服务器中没有则新建。
channel.queueDeclare(QUEUE_NAME, true, false, false, arguments);
/**
* String queue, Queue名称
* String exchange, Exchange名称
* String routingKey, Queue与Exchange绑定Key
* Map<String, Object> arguments 额外参数,在spring与rabbitMQ整合的代码中,该参数为空
*
*/
//12.建立绑定关系
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTINGKEY, null);
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭方法
public void close() {
try {
channel.close();
connection.close();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
//producter发送消息
public void send(Object object) throws Exception {
//序列化对象
byte[] bytes = SerializeUtil.serialize(object);
/***
*
* Return机制:
* 确保消息发送到Queue中,当发送失败,当前方法回调
* Confirm机制:
* 需要在channel中开启 channel.confirmSelect()
* 确保消息发送到Exchange中,当数据发送到Exchange,第一个参数会回调,发送失败第二个参数回调
*
*/
//13.开启Confirm机制
channel.confirmSelect();
try {
channel.addReturnListener(returnMessage -> {
System.out.println("Exchange:" + returnMessage.getExchange());
System.out.println("RoutingKey:" + returnMessage.getRoutingKey());
System.out.println("ReplyCode:" + returnMessage.getReplyCode());
System.out.println("ReplyText:" + returnMessage.getReplyText());
System.out.println("Body:" + SerializeUtil.unSerialize(returnMessage.getBody()).toString());
});
channel.addConfirmListener((deliveryTag, multiple) -> {
System.out.println("deliveryTag1:" + deliveryTag);
System.out.println("multiple1:" + multiple);
}, (deliveryTag, multiple) -> {
System.out.println("deliveryTag2:" + deliveryTag);
System.out.println("multiple2:" + multiple);
});
//14.发送数据,第三个参数保证了Message的持久化
channel.basicPublish(EXCHANGE_NAME, ROUTINGKEY, true, MessageProperties.PERSISTENT_TEXT_PLAIN, bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
//consumer接收消息
public void receive() throws IOException {
/**
*
* 消费者接收消息后需通过该方法回调进行消息处理
*
*/
DeliverCallback deliverCallback = (consumer, deliver) -> {
System.out.println("consumer:" + consumer);
//对象反序列化
Object object = SerializeUtil.unSerialize(deliver.getBody());
System.out.println("接收到的数据:" + object.toString());
/**
*
* long deliveryTag, 消息投递唯一标识
* boolean multiple
* true:确认所有的消息包括已经接受的投递标识,
* false:确认已经接受的投递标识
*/
//消息确认被确认
channel.basicAck(deliver.getEnvelope().getDeliveryTag(), true);
/**
* long deliveryTag, 消息投递唯一标识
* boolean multiple,
* true:拒绝所有的消息包括已经接受的投递标识,
* * false:拒绝已经接受的投递标识
* boolean requeue 是否重新放入队列
*
*/
//消息没有被确认
channel.basicNack(deliver.getEnvelope().getDeliveryTag(), true, true);
};
/**
* 设置通道的请求数,其实这里指的是在消费端消息最大的存储数(也就是没有被消费者确认消费的消息)
*/
channel.basicQos(3);
/**
* String queue, 队列名称
* boolean autoAck, 是否主动确认(不太建议,可能会造成消息丢失情况,当)
* DeliverCallback deliverCallback, 消息被投递时的回调函数
* CancelCallback cancelCallback 消费者被取消是的回调函数
*/
//接受消息
channel.basicConsume(QUEUE_NAME, false, deliverCallback, (CancelCallback) null);
}
死信队列(DLX dead-letter-exchange)
(1)消息被拒绝并且没有重新放到队列中
(2)消息超时(通过Arguments对象设置x-message-ttl Time To Live简称TTL)
(3)超过队列长度限制的消息
通过这种特性可以做延时队列。
集群方式
主从集群
一般一台主要的,两台次要服务器。主服务器上,存储元数据以及消息,从服务器上存储元数据。当客户端连接到从服务器上时,从服务器会跟主服务器获取消息,然后再给消费者。确定时,一旦主服务器宕机,消息存在丢失情况。
镜像集群
我们公司采用HaProxy作为集群负载,使用了KeepAlived工具将两台HaProxy服务器做热备组,通过镜像队列模式设置集群,每个节点上都存在队列服务,一个节点宕机故障失效转移,从而避免了单点问题。