之所以写原生态,是为了我们将来去使用整合入SpringBoot的RabbitMQ时不至于一脸懵逼,会用而不知其原理。
①以往的业务服务是在订单系统中中直接写好需要使用RPC框架调用库存、支付、物流等系统的操作,然后当订单系统与库存系统交互后等待返回结果,等到获取结果后再去与支付系统交互:
这样的设计会出现两个问题:
①当订单系统调用库存系统交互的过程中,出现异常,则会导致后续与其他系统交互出现问题。
②所有的交互过程代码都写在订单系统中,当需要在原有操作的情况下进行增删改,则会需要回去修改代码。
总结:系统的耦合性越高,容错性也就越低,可维护性也就越低。
②所以后续加入了RabbitMQ,用于保存系统A提交的信息,然后剩余的B、C、D等系统就根据信息来判断是否与自己有关,然后去获取这个消息队列中的该条信息进行操作:
解决了(1)中出现的问题。
③所以带来的优势就是:客户去访问订单系统的所需要等待的时间就从原来的订单系统与库存、支付、物流等系统交互完的时间 转变成了 订单系统与MQ交互的时间,这也是我们平常所说的异步提速:
加入MQ前:
加入MQ后:
假设在某个时间有5000个请求访问我们的系统,然而我们的系统最高只能一次处理一千个,那么我们就可以加入RabbitMQ消息队列来存放用户的请求,将所有的用户请求加入到MQ中,然后由我们的系统每秒1000个慢慢处理完。
①解耦:在项目启动之初是很难预测未来会遇到什么困难的,消息中间件在处理过程中插入了一个隐含的,基于数据的接口层,两边都实现这个接口,这样就允许独立的修改或者扩展两边的处理过程,只要两边遵守相同的接口约束即可。
②冗余(存储):在某些情况下处理数据的过程中会失败,消息中间件允许把数据持久化知道他们完全被处理
③扩展性:消息中间件解耦了应用的过程,所以提供消息入队和处理的效率是很容易的,只需要增加处理流程就可以了。
④削峰:在访问量剧增的情况下,但是应用仍然需要发挥作用,但是这样的突发流量并不常见。而使用消息中间件采用队列的形式可以减少突发访问压力,不会因为突发的超时负荷要求而崩溃
⑤可恢复性:当系统一部分组件失效时,不会影响到整个系统。消息中间件降低了进程间的耦合性,当一个处理消息的进程挂掉后,加入消息中间件的消息仍然可以在系统恢复后重新处理
⑥顺序保证:在大多数场景下,处理数据的顺序也很重要,大部分消息中间件支持一定的顺序性
⑦缓冲:消息中间件通过一个缓冲层来帮助任务最高效率的执行
⑧异步通信:通过把把消息发送给消息中间件,消息中间件并不立即处。
com.rabbitmq
amqp-client
4.0.3
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消息生产者
*/
public class ProducerDemo {
//创建一个常量队列
private static final String QUEUE = "helloworld";
public static void main(String[] args) throws IOException, TimeoutException {
//通过连接工厂创建新的连接和mq建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);//端口
factory.setUsername("guest");
factory.setPassword("guest");
//设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
factory.setVirtualHost("/");
//建立新连接
Connection connection = null;
//建立回话通道
Channel channel = null;
try {
connection = factory.newConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
channel = connection.createChannel();
//声明队列,如果队列在mq中没有则要创建
//参数: String queue,boolean durable,boolean exclusive,boolean autoDelete,Map arguments
//参数明细:
//1.queue:队列名称
//2.durable:是否持久化,如果持久化,mq重启后队列还在
//3.exclusive:是否独占连接,队列只允许在该连接中访问,
// 如果连接关闭队列自动删除,如果设置此参数为true可用于临时队列的创建
//4.autoDelete:自动删除,队列不再使用时是否自动删除,
// 如果设置为true则可以实现临时队列(队列不用了自动删除)
//5.arguments:参数,可以设置一个队列的拓展参数,比如:可以设置存活时间
channel.queueDeclare(QUEUE,true,false,false,null);
/**
* 发送消息
* 参数:String exchange, String routingKey, BasicProperties props, byte[] body
* 1.exchange:交换机,如果不指定将使用mq的默认交换机,设置为""
* 2.routingKey:路由Key,交换机根据路由Key来将消息转发到指定的队列,
* 使用默认交换机则设置为队列的名称
* 3.props:消息的属性
* 4.body:消息的内容
*/
String message = "消费者你好,你撒币吗?";
channel.basicPublish("",QUEUE,null,message.getBytes());
System.out.println("已发送至mq,消息为[" + message + "]");
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}finally {
//关闭连接
//先关闭通道
channel.close();
//再关闭连接
connection.close();
}
}
}
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消息消费者
*/
public class ConsumerDemo {
private static final String QUEUE = "helloworld";
public static void main(String[] args) {
//通过连接工厂创建新的连接和mq建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);//端口
factory.setUsername("guest");
factory.setPassword("guest");
//设置虚拟机,一个MQ服务可以设置多个虚拟机,每个虚拟机就相当于一个独立的MQ
factory.setVirtualHost("/");
//建立新连接
Connection connection = null;
try {
connection = factory.newConnection();
//创建会话通道,生产者和mq服务所有通信都在channel通道中完成
Channel channel = connection.createChannel();
//声明队列,如果队列在mq中没有则要创建
//参数: String queue,boolean durable,boolean exclusive,boolean autoDelete,Map arguments
//参数明细:
//1.queue:队列名称
//2.durable:是否持久化,如果持久化,mq重启后队列还在
//3.exclusive:是否独占连接,队列只允许在该连接中访问,如果连接关闭队列自动删除,如果设置此参数为true可用于临时队列的创建
//4.autoDelete:自动删除,队列不再使用时是否自动删除,如果设置为true则可以实现临时队列(队列不用了自动删除)
//5.arguments:参数,可以设置一个队列的拓展参数,比如:可以设置存活时间
channel.queueDeclare(QUEUE,true,false,false,null);
//实现消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
/**
* 当接受到消息之后,此方法将会被调用
* @param consumerTag 消费者标签,用于标识消费者,可以不设置然后在监听队列时设置channel.basicConsume
* @param envelope 信封,通过envelope可以获取
* @param properties
* @param body
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//获取交换机
String exchange = envelope.getExchange();
//消息id,mq在channel中用来标识消息的id,可用于确定消息已接收
//也就是用于下面监听队列中autoAck设置为false之后进行手动设置确认接收
long deliveryTag = envelope.getDeliveryTag();
//消息内容
String message = new String(body,"utf-8");
System.out.println("交换机是:"+exchange);
System.out.println("标识的消息id是:" + deliveryTag);
System.out.println("已接收到消息,内容为[" + message +"]");
}
};
/**
* 监听队列
* 参数:String queue, boolean autoAck, Consumer callback
* 1.queue:队列名称
* 2.autoAck:自动回复,当消费者接收到了消息之后会告诉mq消息已经接收
* 如果设置为true,则会在消费者接受到消息之后自动回复给mq,
* 如果设置为false,则需要通过编程手动回复
* 3.callback:消费方法,当消费者接受到消息需要执行的方法。
*/
channel.basicConsume(QUEUE,true,defaultConsumer);
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
注意一个地方:我们的消息生产者在运行完后可以进行关闭(先关闭通道再关闭连接),而我们的消费者不能关闭连接,因为生产者可以在发送完消息后就不用监听MQ消息队列中的变化,但是消费者需要持续监听MQ消息队列中的变化。
如果有没有安装好RabbitMQ然后打开RabbitMQ可视化管理平台的可以看我的另一篇文章:
RabbitMQ安装教程及可视化平台组件使用
然后我们运行消息生产者ProducerDemo的main方法:
接着我们去看我们的RabbitMQ消息平台:
进入界面后:
然后点击Get Message:
接着我们可以运行我们的消费者ConsumerDemo,然后我们就能看到我们获取的消息:
接着,我们可以看到RabbitMQ的消息平台的helloworld队列的准备好的未处理的消息已经为啦:
OK,到这里,RabbitMQ的原生态基础使用就讲解结束啦!可能有没有讲好的地方还请多多包涵!
致谢