1.訂閱模式
作用類似與微信公衆號,你訂閱了就可以接收到消息
解讀:
1.一個生產者,多個消費者。
2.每一個消費者 都有自己的隊列
3.生產者沒有直摟把洧息發送到隊列而是發到了交換機 轉發器exchange
4.每個隊列都要綁定到交換機上。
5.生產者發送的消息經過交換機到達臥列 就能實現一個消息到多個消費者消費
1.2分發模式fanout
該模式下,生產者發送消息,多個消費者通過他們對應的隊列獲得消息,但是所獲得的消息是一樣的,不能指定將哪個消息給那個隊列
創建一個生產者
public class send {
private static final String EXCHANGE_NAME="MXH_Exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
----聲明一個交換機
----fanout:分發模式
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
String msg = "hello exchange";
發送消息到交換機
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
channel.close();
connection.close();
}
}
創建第一個消費者
public class receive1 {
private static final String EXCHANGE_NAME="MXH_Exchange";
private static final String QUEUE_NAME="MXH_Exchange_note";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
聲明一個對列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
將隊列綁定到交換機
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
一次應答前只發送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手動應答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
創建第二個消費者
public class receiver2
{
private static final String EXCHANGE_NAME="MXH_Exchange";
private static final String QUEUE_NAME="MXH_Exchange_emain";
public static void main(String[] args) throws IOException, TimeoutException
{
------創建一個連接
Connection connection = ConnectionUtil.getConnection();
------創建一個通道
Channel channel = connection.createChannel();
------創建一個隊列,用戶名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
------將隊列綁定到交換機
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"");
------一次應答前只發送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
------手動應答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
------自動應答 true爲開啓 false爲關閉
boolean autoAck = false;
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
2.Exchange交換機
作用:一方面是接收生產者的消息,另一方面是向隊列推送消息。
2.1Fanout(不處理路由健)
匿名轉發。
Fanout(不處理路由健):對消息不作處理,消息不能指定發給特定的消費者
2.2Direct路由模式
交換機給隊列發送消息時會在後面加一個Key,只有隊列的Key和交換機的Key對應上,交換機纔會把消息發給隊列,如下圖,交換機有error,info,warng三個Key,error的key對應c1和c2,所以帶有error的key的消息就會發送給c1和c2
創建一個生產者
綁定key到交換機
public class send {
private static final String EXCHANGE_NAME="MXH_Exchange";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
-----聲明一個交換機
-----fanout:分發模式 channel.exchangeDeclare(EXCHANGE_NAME,"direct");
String msg = "hello direct";
-----發送消息到交換機,綁定key到交換機
String key ="love";
channel.basicPublish(EXCHANGE_NAME,key,null,msg.getBytes());
channel.close();
connection.close();
}
}
創建第一個消費者
綁定key到交換機
public class receive1 {
private static final String EXCHANGE_NAME="MXH_Exchange";
private static final String QUEUE_NAME="MXH_Exchange_note";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//聲明一個對列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//將隊列綁定到交換機,綁定key到交換機
String key ="error";
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
//一次應答前只發送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手動應答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
創建第二個消費者
public class receiver2
{
private static final String EXCHANGE_NAME="MXH_Exchange";
private static final String QUEUE_NAME="MXH_Exchange_emain";
public static void main(String[] args) throws IOException, TimeoutException
{
//創建一個連接
Connection connection = ConnectionUtil.getConnection();
//創建一個通道
Channel channel = connection.createChannel();
//創建一個隊列,用戶名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//將隊列綁定到交換機,綁定key到交換機
String key1 = "error";
String key2 = "info";
String key3 = "love";
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key1);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key2);
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key3);
//一次應答前只發送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手動應答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
2.3Toplc exchanger主題模式
Toplc exchanger:將路由鍵和某模式匹配,
#匹配一個或者多個。
‘*’匹配一個。
第二個消費者:
注意我的key值: String key =“love.#”;
#代表多個,只要生產者發給交換機的key是love開頭,這個消費者就能接收到
創建一個生產者:
注意我的key值:String key =“love.del”;
public class send {
private static final String EXCHANGE_NAME="MXH_Exchange_topic";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//聲明一個交換機
channel.exchangeDeclare(EXCHANGE_NAME,"topic");//fanout:分發模式
String msg = "hello direct";
//發送消息到交換機
String key ="love.del";
channel.basicPublish(EXCHANGE_NAME,key,null,msg.getBytes());
channel.close();
connection.close();
}
}
創建第一個消費者
注意我的key值: String key =“love.add”;
public class receive1 {
private static final String EXCHANGE_NAME="MXH_Exchange_topic";
private static final String QUEUE_NAME="MXH_Exchange_note";
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//聲明一個對列
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//將隊列綁定到交換機
String key ="love.add";
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
//一次應答前只發送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手動應答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
創建第二個消費者:
注意我的key值: String key =“love.#”;
#代表多個,只要生產者發給交換機的key是love開頭,這個消費者就能接收到
public class receiver2
{
private static final String EXCHANGE_NAME="MXH_Exchange_topic";
private static final String QUEUE_NAME="MXH_Exchange_emain";
public static void main(String[] args) throws IOException, TimeoutException
{
//創建一個連接
Connection connection = ConnectionUtil.getConnection();
//創建一個通道
Channel channel = connection.createChannel();
//創建一個隊列,用戶名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//將隊列綁定到交換機
String key = "love.#";
channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,key);
//一次應答前只發送一次
channel.basicQos(1);
DefaultConsumer consumer = new DefaultConsumer(channel)
{
@Override
public void handleDelivery(String consumerTag, Envelope envelope,
AMQP.BasicProperties properties,
byte[] body) throws IOException
{
String msg = new String(body);
System.out.println("RE:"+msg);
try {
Thread.sleep(500);
} catch (InterruptedException e)
{
e.printStackTrace();
}finally
{
//手動應答
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
boolean autoAck = false;//自動應答 true爲開啓 false爲關閉
channel.basicConsume(QUEUE_NAME,autoAck, consumer);
}
}
消息確認機制
在rabbitmq中我們可以通過持久化數據解決rabbitmq服務器異常的數據丟失問題。
問題:生產者將消息發送出去之後,消息到底有沒有到達rabbitmq 服務器默認的情況是不知道的;
兩種方式可以知道:
1.AMQP實現的事務機制
2.Confirm橫式
AMQP實現的事務機制
AMQP提供了三個方法
channel.txSelect();-----開啓事物:在消息發送前開啓
channel.txCommit();-----提交事物:在消息發送後提交
channel.txRollback();-----事物回滾:如果有異常就回滾,處理異常
這種模式增加了服務器的請求量,使服務器處理的效率降低
public class send {
private static final String QUEUE_NAME="MXH";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//創建一個連接
Connection connection = ConnectionUtil.getConnection();
//從連接中獲取一個通道
Channel channel = connection.createChannel();
//創建一個消息隊列 QUEUE_NAME:隊列名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
try
{
String msg = "hello,rqbbitmq你好";
channel.txSelect();-----開啓事物
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
int i =1/0;
channel.txCommit();-----提交事物
}catch (Exception e)
{
channel.txRollback();-----事物回滾
System.out.println("發送異常,消息未送達");
}
channel.close();
connection.close();
}
}
Confirm橫式
原理
生產者將信道設置成confirm橫式,一旦信道進入confirm橫式,所有在該信道上面發佈的洧息都會被指很一個唯一的ID(從1開始)。一旦洧息被投遞到所有匹配的隊列之後,broker就會發送-一個確認給生產者(包含消息的唯一ID) .這就使得生產者知道消息已經正確到達目的隊列了。如果消息和隊列是可持久化的,那麼確認消息會將消息寫入磁盤之後發出,broker 固傳給生產者的確認消息中deliver-tag 域包含了確認消息的序列號,此外broker也可以設置basic.ack 的multiple城,表示到這個序列號之前的所有消息都已經得到了處理。。
Confirm摸式最大的好處在於他是異步。
串行模式
channel.confirmSelect();//開啓confirm模式
channel.waitForConfirms():該方法在生產者發送消息到隊列後會返回true
if(channel.waitForConfirms())
{
System.out.println(“發送成功”);
}else
{
System.out.println(“發送失敗”);
}
普通模式:該模式一次發送一條消息到隊列,一發一回
public class send {
private static final String QUEUE_NAME="MXH_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//創建一個連接
Connection connection = ConnectionUtil.getConnection();
//從連接中獲取一個通道
Channel channel = connection.createChannel();
//創建一個消息隊列 QUEUE_NAME:隊列名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
try {
channel.confirmSelect();//開啓confirm模式
String msg = "hello,rqbbitmq你好";
int i =1/0;
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
if(channel.waitForConfirms())
{
System.out.println("發送成功");
}else
{
System.out.println("發送失敗");
}
}catch (Exception e)
{
System.out.println("發送失敗");
}
channel.close();
connection.close();
}
}
批量模式:該模式用for循環發送多條數據,多發一回,發完所有的消息服務器回一條是否收到消息
public class send {
private static final String QUEUE_NAME="MXH_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
//創建一個連接
Connection connection = ConnectionUtil.getConnection();
//從連接中獲取一個通道
Channel channel = connection.createChannel();
//創建一個消息隊列 QUEUE_NAME:隊列名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
try {
channel.confirmSelect();//開啓confirm模式
String msg = "hello,rqbbitmq你好";
int i =1/0;
for(int p =0;p<10;p++)
{
channel.basicPublish("",QUEUE_NAME,null,msg.getBytes());
}
if(channel.waitForConfirms())
{
System.out.println("發送成功");
}else
{
System.out.println("發送失敗");
}
}catch (Exception e)
{
System.out.println("發送失敗");
}
channel.close();
connection.close();
}
}
異步模式
原理
Channel對象提供的Confirmlistener回調方法只包含deliveryTag (當前Chanel發出的消息序號)。我們需要自己爲每一個Channel維護一個unconfirm的消息序號集合,每publish 一條數據,集合中元素加1.每回調一次handleAck方法,unconfirm 集合刪掉相應的一條(multiple=false)或多條(multiple=true)記錄。從程序運行效率上看,這個unconfirm集合最好採用有序集合SortedSet存儲結構。
創建一個生產者:
public class send {
private static final String QUEUE_NAME="MXH_confirm";
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException
{
//創建一個連接
Connection connection = ConnectionUtil.getConnection();
//從連接中獲取一個通道
Channel channel = connection.createChannel();
//創建一個消息隊列 QUEUE_NAME:隊列名
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
channel.confirmSelect();//開啓confirm模式
----未確認的消息標識
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
----添加通道監聽
channel.addConfirmListener(new ConfirmListener()
{
@Override
-----消息發送成功
public void handleAck(long deliveryTag , boolean mulpitle) throws IOException
{
if (mulpitle) {
confirmSet.headSet(deliveryTag + 1).clear();
} else {
confirmSet.remove(deliveryTag );
}
}
@Override
-----消息發送失敗
public void handleNack(long deliveryTag , boolean mulpitle) throws IOException {
if (mulpitle) {
confirmSet.headSet(deliveryTag + 1).clear();
} else {
confirmSet.remove(deliveryTag );
}
}
});
String msg = "hello,rqbbitmq你好";
while(true)
{
long setNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
confirmSet.add(setNo);
}
// channel.close();
// connection.close();
}
}