由于分布式系统的广泛应用,越来越多地涉及到系统间通信。系统间通信一般有两种方式,一种是基于远程过程调用的方式,另一种是基于消息队列的方式。基于消息队列的方式是指由应用中的某个系统负责发送消息,由关心这条消息的系统负责接收消息,并在接收到消息后进行各自的业务处理。
目前主流的消息中间件有RabbitMQ、RocketMQ、ActiveMQ、Kafka等
一、消息队列的作用
(1)解耦
消息队列的各种实现产品又叫,既然是中间件,就是用消息队列实现两个模块的远程调用,模块只关心自己的核心流程,而不依赖调用的执行结果。
(2)流量削峰
利用消息队列可以将短时间高并发请求持久化,然后逐步处理,从而削平高峰期的并发流量,改善系统性能
(3)日志收集
利用消息队列产品在接收和持久化消息方面的高性能,引入消息队列快速收集日志信息,避免为写入日志时的某些故障导致业务系统访问阻塞、请求延迟等。
(4)事务最终一致性
二、消息队列的功能特点
消息队列这个属于包含消息和队列两个关键词,消息是指应用间传递的数据,可以使简单的字符串,也可以是复杂的结构化对象定义格式;队列指消息的进和出,它包含一个容器,至少需实现消息的发送、接收和暂存功能。在生产环境中,消息队列还需解决诸如消息堆积、消息持久化、可靠投递、消息重复、严格有序、集群等各种问题。
消息队列的简单模型:
Broker:消息处理中心,负责消息的接收、存储、转发
Producer:消息生产者,负责产生和发送消息到消息处理中心
Consumer:消息消费者,负责从消息中心获取消息,并进行相应的处理
三、用java实现一个简单的消息队列
结构图:
Broker类:
package com.youzi.MQ;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 消息处理中心
*/
public class Broker {
//设置存储消息的最大数量
private final static int MAX_SIZE = 5;
//保存消息的容器
private static ArrayBlockingQueue<String> MassageQueue = new ArrayBlockingQueue<String>(MAX_SIZE);
//生产消息
public static void produce(String msg){
if (MassageQueue.offer(msg)){
System.out.println("成功向消息中心投递消息:"+msg+",当前暂存消息数目为"+MassageQueue.size());
}else{
System.out.println("消息中心已满,不能继续放入消息!");
}
System.out.println("==================================");
}
//消费消息
public static String consume(){
String msg = MassageQueue.poll();
if(msg!=null){
System.out.println("已经消费消息:"+msg+",当前暂存消息数目为"+MassageQueue.size());
}else{
System.out.println("消息处理中心已经没有消息可供消费!");
}
System.out.println("==================================");
return msg;
}
}
用BrokerServer类对外提供Broker类的服务:
package com.youzi.MQ;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 消息队列服务
*/
public class BrokerServer implements Runnable {
public static int SERVICE_PORT = 9999;
private final Socket socket ;
public BrokerServer(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try (
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream());
){
while (true){
String str = in.readLine();
if (str==null){
continue;
}
System.out.println("接收到的原始数据为:"+str);
if (str.equals("CONSUME")){//CONSUME表示要消费一条消息
String msg = Broker.consume();
out.println(msg);
out.flush();
}else{//其他情况都表示要生产消息到消息队列中
Broker.produce(str);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(BrokerServer.SERVICE_PORT);
while(true){
BrokerServer bs = new BrokerServer(server.accept());
new Thread(bs).start();
}
}
}
客户端访问:
package com.youzi.MQ;
import org.omg.CORBA.portable.UnknownException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
public class MQClient {
//生产消息
public static void produce(String msg) throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost(),BrokerServer.SERVICE_PORT);
try (
PrintWriter out = new PrintWriter(socket.getOutputStream());
){
out.println(msg);
out.flush();
}
}
//消费消息
public static String consume() throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost(),BrokerServer.SERVICE_PORT);
try(BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream())){
//先向消息队列发送CONSUME表示消费消息
out.println("CONSUME");
out.flush();
//再从队列获取一条消息
String message = in.readLine();
return message;
}
}
}
生产者客户端测试类:
package com.youzi.MQ;
public class ProduceClient {
public static void main(String[] args) throws Exception {
MQClient client = new MQClient();
client.produce("Hello World4!!");
}
}
消费者客户端测试类:
package com.youzi.MQ;
public class ConsumeClient {
public static void main(String[] args) throws Exception {
MQClient client = new MQClient();
String message = client.consume();
System.out.println("获取的消息为:"+message);
}
}
先启动服务端BrokerServer,因为消息处理中心最大容量设置了5,这里生产者启动6次,服务端输出:
然后启动6次消息消费端,服务端输出: