基礎請求模式簡圖:
服務A 發送消息給rabbit的隊列,服務B監聽這個隊列,發現隊列有消息了就獲取消息。
代碼:
1、maven依賴
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>4.0.2</version>
</dependency>
2、開發代碼
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 工具類
* Created by py
* 2020/4/19
*/
public class RabbitUtils {
public static Connection getInstance() throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin_py");
connectionFactory.setPassword("123456");
connectionFactory.setVirtualHost("/adminPy");
Connection connection = connectionFactory.newConnection();
return connection;
}
}
import com.example.springcloud.eurekaclinet1demo.uitl.RabbitUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消息生產者
* Created by py
* 2020/4/19
*/
public class SendRabbit {
public static void main(String[] args) throws IOException, TimeoutException {
/** 獲取一個連接 */
Connection instance = RabbitUtils.getInstance();
/** 從連接中創建通道 */
Channel channel = instance.createChannel();
/** 在通道中聲明一個隊列
* 因爲我們要往隊列裏面發送消息,這是後就得知道往哪個隊列中發送,就好比在哪個管子裏面放水
* */
boolean durable=false;//數據是否持久化:即rabbit服務關閉了,rabbit中的信息是否會保存
boolean exclusive=false;//信息只允許一個交換機處理,還是允許多個交換機處理
boolean autoDelete=false;//隊列中信息處理完是否自動刪除該隊列
//simple_queue:隊列名稱
channel.queueDeclare("simple_queue",durable,exclusive,autoDelete,null);
String msg = "This is simple queque!";
//將消息發送到隊列simple_queue中
channel.basicPublish("","simple_queue",null,msg.getBytes());
channel.close();
instance.close();
System.out.println("信息發送成功");
}
}
import com.example.springcloud.eurekaclinet1demo.uitl.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消息消費者
* Created by py
* 2020/4/19
*/
public class RecRabbit {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection instance = RabbitUtils.getInstance();
Channel channel = instance.createChannel();
//聲明隊列 如果能確定是哪一個隊列 這邊可以刪掉,不去掉 這裏會忽略創建
channel.queueDeclare("simple_queue",false,false,false,null);
//定義消費者用來處理收到的消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
//將消息轉成字符串
String message = new String(body, "UTF-8");
System.err.println("new==Received '" + message + "'");
}
};
//simple_queue:隊列名稱
//true:自動確認;消費者直接告訴rabbit消息以處理,此時rabbit會將該條信息從simple_queue隊列中刪除
//defaultConsumer:用於具體處理接受消息的方法
channel.basicConsume("simple_queue",true,defaultConsumer);
}
}
QA:當客戶端請求量增大之後一臺服務器難以支持大量的數據請求,那這個時候我們需要對服務進行集羣操作,那rabbit如何將消息發給集羣的服務呢?
/**
* 生產者
* 輪詢策略 Round-robin 將消息平均的分發給每一個消費者
* Created by py
* 2020/4/19
*/
public class SendRabbit {
public static void main(String[] args) throws IOException, TimeoutException {
Connection instance = RabbitUtils.getInstance();
Channel channel = instance.createChannel();
channel.queueDeclare("work_queue",false,false,false,null);
for (int i = 0; i < 50; i++) {
String msg = "This is work queue!==="+i;
channel.basicPublish("","work_queue",null,msg.getBytes());
}
channel.close();
instance.close();
}
}
/**
* 消費者1
* Created by py
* 2020/4/19
*/
public class FristConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
Connection instance = RabbitUtils.getInstance();
Channel channel = instance.createChannel();
channel.queueDeclare("work_queue",false,false,false,null);
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String str = new String(body,"utf-8");
System.out.println("FristConsumer==="+str);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
boolean autoAck = true; //消息的確認模式自動應答
channel.basicConsume("work_queue",autoAck,consumer);
}
}
import com.example.springcloud.eurekaclinet1demo.uitl.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消費者2
* Created by py
* 2020/4/19
*/
public class SecondConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
Connection instance = RabbitUtils.getInstance();
Channel channel = instance.createChannel();
channel.queueDeclare("work_queue",false,false,false,null);
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String str = new String(body,"utf-8");
System.out.println("SecondConsumer==="+str);
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
boolean autoAck = true; //消息的確認模式自動應答
channel.basicConsume("work_queue",autoAck,consumer);
}
}
這樣可以實現消息輪詢的發送給兩個服務器,但是有一個問題,SecondConsumer 執行一個請求sleep2.5S,FristConsumer 執行一個請求sleep0.5S,將請求平均的發送給兩臺服務器的話會導致FristConsumer 有很長的一段空閒時間,SecondConsumer的接收的那部分請求要很久才能完成。
QA:如何讓生產端的數據儘可能快的執行完呢?
放棄輪詢發送,根據消費端的消費情況來推送消息,即哪個服務器執行完成了就給他下一條消息,讓FristConsumer 會多執行部分請求,SecondConsumer少執行部分請求,這樣就能儘可能快的消費完生產者創建的消息了。
/**
* 生產者
* Fair dispatch(公平分發) 處理完當前信息在分發下一個信息
* Created by py
* 2020/4/19
*/
public class SendRabbit {
final static String queue = "work_queue_durable";
public static void main(String[] args) throws IOException, TimeoutException {
Connection instance = RabbitUtils.getInstance();
Channel channel = instance.createChannel();
//設置rabbit信息持久化,爲true時rabbit會將消息寫入磁盤中
boolean durable = true;
//PS:rabbit不支持修改已聲明的隊列,
//若一個隊列聲明的時候爲設置持久化,聲明後在設置持久化就會報錯,
//想要操作要麼把隊列刪除重新聲明該隊列,要命聲明一個新隊列設置持久化
channel.queueDeclare(queue,durable,false,false,null);
int prefetchCount = 1;
//每個消費者發送確認信號之前,消息隊列不發送下一個消息過來,一次只處理一個消息
//限制發給同一個消費者不得超過1條消息
channel.basicQos(prefetchCount);
// 發送的消息
for (int i = 0; i < 50; i++) {
String msg = "This is work queue!==="+i;
channel.basicPublish("","work_queue",null,msg.getBytes());
}
channel.close();
instance.close();
}
}
/**
* 消費者1
* Created by py
* 2020/4/19
*/
public class FristConsumer {
final static String queue = "work_queue_durable";
public static void main(String[] args) throws IOException, TimeoutException {
Connection instance = RabbitUtils.getInstance();
Channel channel = instance.createChannel();
//設置rabbit信息持久化,爲true時rabbit會將消息寫入磁盤中
boolean durable = true;
//PS:rabbit不支持修改已聲明的隊列,
//若一個隊列聲明的時候爲設置持久化,聲明後在設置持久化就會報錯,
//想要操作要麼把隊列刪除重新聲明該隊列,要命聲明一個新隊列設置持久化
channel.queueDeclare(queue,durable,false,false,null);
//每個消費者發送確認信號之前,消息隊列不發送下一個消息過來,一次只處理一個消息
//限制發給同一個消費者不得超過1條消息
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String str = new String(body,"utf-8");
System.out.println("FristConsumer==="+str);
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//手動回覆rabbit消息已處理
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//消息的確認模式:禁止自動應答
boolean autoAck = false;
channel.basicConsume("work_queue",autoAck,consumer);
}
}
import com.example.springcloud.eurekaclinet1demo.uitl.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 消費者2
* Created by py
* 2020/4/19
*/
public class SecondConsumer {
final static String queue = "work_queue_durable";
public static void main(String[] args) throws IOException, TimeoutException {
Connection instance = RabbitUtils.getInstance();
Channel channel = instance.createChannel();
//設置rabbit信息持久化,爲true時rabbit會將消息寫入磁盤中
boolean durable = true;
//PS:rabbit不支持修改已聲明的隊列,
//若一個隊列聲明的時候爲設置持久化,聲明後在設置持久化就會報錯,
//想要操作要麼把隊列刪除重新聲明該隊列,要命聲明一個新隊列設置持久化
channel.queueDeclare(queue,durable,false,false,null);
//每個消費者發送確認信號之前,消息隊列不發送下一個消息過來,一次只處理一個消息
//限制發給同一個消費者不得超過1條消息
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String str = new String(body,"utf-8");
System.out.println("SecondConsumer==="+str);
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//手動回覆rabbit消息已處理
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
//消息的確認模式:禁止自動應答
boolean autoAck = false;
channel.basicConsume("work_queue",autoAck,consumer);
}
}
PS:此處添加了一些新的參數,以實現我們的需求
每次只發送一條數據,等對方告知數據完成消費在發送下一條數據
channel.basicQos(1);
//prefetchSize:數據的大小;不寫是不限制數據大小
//prefetchCount:設置一次發送幾條數據
//global:默認false
public void basicQos(int prefetchSize, int prefetchCount, boolean global)
//手動回覆rabbit消息已處理
channel.basicAck(envelope.getDeliveryTag(),false);
//deliveryTag:當前這條消息的標識,
//multiple:true:多條消息,false:1條消息;正常設置爲false
public void basicAck(long deliveryTag, boolean multiple);
//消息的確認模式:禁止自動回覆rabbit,這裏設置爲false配合basicAck()一起使用
boolean autoAck = false;
channel.basicConsume("work_queue",autoAck,consumer);