RabbitMQ與CMQ的使用與實戰

RabbitMQ

①Rabbitmq的啓動和關閉
  rabbitmq-server前臺啓動服務 rabbitmq-server-detached後臺啓動服務(常用) rabbitmqctl stop停止服務 端口號是5672 可視化端口15672
  Linux中查看正在運行的端口號:netstat -tulpn

②終止與啓動應用
  Rabbitmqctl start_app啓動引用 對消息隊列的暫停的恢復
  Rabbitmqctl stop_app 終止應用 只是對消息隊列進行一個暫停
  這兩個命令都不會對MQ進程產生影響,只是短暫的對消息隊列的操作。

③用戶管理
在這裏插入圖片描述
  Rabbitmqctl add_user zhongbing 123456
  RabbitMQ中有四種用戶角色:超級管理員(可查看所有信息,可登錄遠程訪問控制)、監控者(管理rabbitmq節點運行狀態的,可登錄遠程訪問控制)、策略制定者(規定節點如何運行的)、普通管理者(僅限登陸管理控制檯,無法看到節點信息)
  RabbitMQ可以有個可視化控制檯,安裝好後就可以使用,就像Navicat。
  對於角色來說,是對MQ基礎組建進行管理,而用戶能訪問哪些虛擬主機呢,就是根據rabbitmqctl set_permissions –p / user_admin ‘.’’.’’.’,.表示可以進行所有權限,這裏是user_admin這個用戶可以對默認的/這個虛擬主機進行可讀、可配置、可寫的操作。虛擬主機就是對應的mysql的數據庫,通過虛擬主機可以區別開是什麼系統的MQ。
  Rabbitmqctl set_permission –p / zhongbing ‘.
’’.
’’.*’ .
  Rabbitmqctl默認的用戶名是guest,權限特別大,不能遠程訪問。如果想要遠程操作,就必須創建一個超級管理員纔可以用這個超級管理員進行登陸。要進行遠程訪問,還需要防火牆放行哈。

④AMQP是一個消息隊列傳遞的協議,也就是所有的MQ在實現信息之間傳遞的時候底層遵從的共同的標準就是AMQP。我們的那些MQ其實都是AMQP的實現者。
在這裏插入圖片描述
  虛擬主機:相當於MQ的數據庫,例如我有十個消息隊列MQ,其中五個服務於這個系統,另外五個服務於這一個系統,那就要建立 2個虛擬主機,存放這兩種消息隊列。
  在使用MQ的時候,要先創建一個虛擬主機,在可視化控制界面建立好Vhost就可以了。例如/test,之後我們把創建的消息隊列就放入這個虛擬主機就可以了。

⑤第一次MQ通信
  創建好Vhost之後,創建一個maven項目,maven是項目對象模型,類似於docker,可以通過pom.xml配置信息來從倉庫中獲取以來的文件(jar包),這個倉庫就是網上的,也可以是第三方倉庫(私人公司的)。然後從官網上找到rabbitmq的客戶端配置包信息引入。

創建一個生產者:

  第一步:通過配置連接工廠(是哪一臺主機進行消息生產)來創建TCP物理連接,得到一個connection對象。
在這裏插入圖片描述
  執行到這裏後,本地客戶端的java程序就和服務器端MQ建立了物理的TCP連接。

  第二步:創建通信通道,相當於TCP的虛擬連接。之所以有這一步,是因爲物理連接的開啓成本開銷很大,當有多個任務同時進行的時候就非常慢。所以這裏就有了通道的概念,相當於在當前物理連接中開闢多個虛擬的通信管道,通過通道進行傳輸。

 Channel channel =  conn.createChannel();
 channel.queueDeclare(“helloworld”, false, false, false, null);

  創建隊列,聲明並創建一個隊列,如果隊列已存在,則使用這個隊列

    //第一個參數:隊列名稱ID
    //第二個參數:是否持久化,false對應不持久化數據,MQ停掉數據就會丟失
    //第三個參數:是否隊列私有化,false則代表所有消費者都可以訪問,true代表只有第一次擁有它的消費者才能一直使用,其他消費者不讓訪問
    //第四個:是否自動刪除,false代表連接停掉後不自動刪除掉這個隊列
    //其他額外的參數, null

  第三步:發送數據 BasecPublish是進行數據的發送,他也有四個參數。

String message = “nihaoa”;  //要發送的消息
channel.basicPublish("" ,” helloworld” ,null  , message.getBytes());

    //四個參數
    //exchange 交換機,暫時用不到,在後面進行發佈訂閱時纔會用到
    //隊列名稱 必須和上面一樣
    //額外的設置屬性
    //最後一個參數是要傳遞的消息字節數組

創建一個消費者:

第一步:創建鏈接工廠獲得連接,進行TCP物理連接。
在這裏插入圖片描述

第二步:創建通道進行虛擬TCP連接。
在這裏插入圖片描述

第三步:創建一個消息消費者,並簽收。

	channel.basicConsume(“helloworld”, false, new Reciver(channel));

    //第一個參數是消費的是MQ中的哪個通道。
    //第二個參數代表是否自動確認收到消息,false代表手動編程來確認消息,這是MQ的推薦做法。
    //第三個參數要傳入DefaultConsumer的實現類,這裏就要創建一個Reciver實現類來對消息進行處理。創建這個Reciver實現類要集成DefaultConsumer,即默認的消費者,他有兩個要求:
    1.重寫傳入通道channel的構造方法。
    2.重寫handleDelivery方法表示消息處理。

 class Reciver extends DefaultConsumer{
     private Channel channel;
     //重寫構造函數,Channel通道對象需要從外層傳入,在handleDelivery中要用到
     public Reciver(Channel channel) {
         super(channel);
         this.channel = channel;
     }

     @Override
     public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws    IOException {
         String messageBody = new String(body);  //傳入的Body就是從通道中收到的字節數組
         System.out.println("消費者接收到:" + messageBody);
         //簽收消息,確認消息
         //envelope.getDeliveryTag() 獲取這個消息的TagId,也就是這個通道的ID
         //false只確認簽收當前的消息,設置爲true的時候則代表簽收該消費者所有未簽收的消息
        channel.basicAck(envelope.getDeliveryTag() , false);
     }
 }

  在消費者這端我們不要close通道,因爲消費者會一直循環監聽消費。

⑥消息的三種狀態
  Ready:消息已被送入隊列,等待被消費
  Unacked:消息已經被消費者認領,但是還未被確認已被消費。
  Finished:調用basicAck()方法後,表示消息已被消費,從隊列中移除。

⑦RabbitMQ六種工作模式

  1. Hello world
    也就是點對點模式,一個生產者對應一個消費者,生產一個消費一個。
    在這裏插入圖片描述
  2. 工作隊列
    一個生產者生產消息放入工作隊列,由多個消費者進行處理。期間可能會按照權重、工作時間等把消息給不同的消費者消費。一般適合於集羣。
    在這裏插入圖片描述
  3. 發佈訂閱模式
    中間有有一個交換器,他的作用是將我們的數據按照一定的規則分發給多個消費者,他和工作隊列的區別是他會把數據產生多個副本發給不同的消費者。例如下面:c1和c2是完全相同的數據。適合於視頻網站,當一個發佈者發佈消息後,所有的消費者接受到的都是相同的消息進行消費。
    在這裏插入圖片描述
  4. 路由器模式
    和發佈訂閱模式相比,消費者不再是收到相同的數據,而是數據通過交換機有選擇的把數據消息分給不同的消費者。他有個缺點,就是需要對數據進行精準匹配,面對一些模糊查詢就沒轍了。
    在這裏插入圖片描述
  5. 主題模式
    解決了路由器模式的缺點,定義一個表達式規則,讓消息根據表達式規則發送給不同的消費者。
    在這裏插入圖片描述
  6. RPC模式
    遠程過程調用。不是MQ主要負責的東西。

⑧工作隊列模式
  一個生產者,把信息放到隊列裏後,被多個消費者消費,每一個消息只能分配給一個消費者。他可以根據消費者處理數據的能力把消息數量適量的分配給不同消費者。
在這裏插入圖片描述
  傳統的你下單買好火車票後,發送成功的短信是同步的,12306發送購買成功的短信後他會一直等消息返回,期間什麼都做不了,造成浪費。對於12306一個短信服務肯定不夠,肯定有多個短信服務,例如賬號註冊、購買成功,而且這些短信其實並不是主要業務,這個短信是否發送成功對業務沒有起重要作用(沒有發送短信,但是我票還是取到了得不影響乘車),所以針對這些情況,就把要發送的短信包放入RabbitMQ,讓服務器從RabbitMQ異步拿消息,12306系統只需要把數據放入MQ就可以執行其他程序操作了。

  生產者:發送200個對象給MQ,把對象轉成json字符串發送給MQ。

	public class OrderSystem {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(RabbitConstant.QUEUE_SMS, false, false, false, null);
        for(int i = 100 ; i <= 200 ; i++) {
            SMS sms = new SMS("乘客" + i, "13900000" + i, "您的車票已預訂成功");
            String jsonSMS = new Gson().toJson(sms);
            channel.basicPublish("" , RabbitConstant.QUEUE_SMS , null , jsonSMS.getBytes());
        }
        System.out.println("發送數據成功");
        channel.close();
        connection.close();
    }
}

在這裏插入圖片描述
  對象轉字符串和字符串轉對象,就要用到谷歌的gson這個工具包。我們把生產者生產的對象通過gson轉成json格式的字符串發佈出去。

  消費者(3個,這裏就寫一個):以前我們是實現一個繼承了DefaultConsumer類的類作爲參數傳入,現在我們直接用DefaultConsumer當內部類來使用。

public class SMSSender1 {
public static void main(String[] args) throws IOException {
    Connection connection = RabbitUtils.getConnection();
    final Channel channel = connection.createChannel();

    channel.queueDeclare(RabbitConstant.QUEUE_SMS, false, false, false, null);
   channel.basicQos(1);//處理完一個取一個
   channel.basicConsume(RabbitConstant.QUEUE_SMS , false , new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
AMQP.BasicProperties properties, byte[] body) throws IOException {
                String jsonSMS = new String(body);
                System.out.println("SMSSender1-短信發送成功:" + jsonSMS);
                Thread.sleep(10);  //本來有try\catch 用這個模擬不同消費者的快慢
                channel.basicAck(envelope.getDeliveryTag() , false);
            }
        });
    }
}

  如果不寫basicQos,則自動MQ會將所有請求平均發送給所有消費者,我們消費者也會有處理速度快慢的說法,爲了不讓處理快的消費者一直等待,就要加這個。basicQos,MQ不再對消費者一次發送多個請求,而是消費者處理完一個消息後(確認後),在從隊列中獲取一個新的,如果是basicQos(10)則就是處理完10個再從消息隊列裏面拿。

⑨發佈訂閱模式
  就像B站訂閱,當up主發佈一個視頻後,所有訂閱的人看到的消息都是一樣的,都能看到這個視頻。在發佈訂閱模式中新增加一個交換機,讓生產者不再和隊列綁定,交換機的作用就是將數據按照某種規矩送入與之綁定的隊列,在 發佈訂閱模式中交換機的作用就是把數據進行復制無差別的發送到所有的消息隊列中。
  該模式適合於數據提供商與應用商。常用的就是中國氣象局提供的天氣預報送入交換機,網易、新浪、百度、搜狐等門戶就可以通過隊列綁定到交換機,自動獲取氣象局推送的氣象數據。

  第一步:在RabbitMQ的可視化管理控制中新建一個Exchanges。選定交換機綁定的虛擬主機,設置名字weather,類型爲fanout(針對於發佈訂閱的)等等,包括持久化選項等等。

  第二步:創建生產者。這裏有個區別了,就是在發佈訂閱模式中,我們basicPublish方法第一個參數之前是空,那是因爲沒有交換機,現在有交換機了這裏就要寫交換機的名字。其次第二個參數本來是隊列的名稱,因爲這裏我們生產者不是直接和隊列進行交互,是和交換機進行交互的,所以這裏爲空即可。

public class WeatherBureau {
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = RabbitUtils.getConnection();
        String input = new Scanner(System.in).next(); //手動輸入天氣情況
        Channel channel = connection.createChannel();
        channel.basicPublish(RabbitConstant.EXCHANGE_WEATHER,"" , null , input.getBytes());
        channel.close();
        connection.close();
    }
}

  RabbitConstant.EXCHANGE_WEATHER是之前定義好的常量,即交換機名字weather。所以此時,在生產者這邊,就沒有創建隊列了,直接把生產者和交換機進行綁定。

  第三步:創建消費者(百度和新浪,這裏只給了百度的例子,新浪是一樣的),在消費者中讓隊列和交換機綁定。

public class Baidu {
   public static void main(String[] args) throws IOException {
        Connection connection = RabbitUtils.getConnection();
        final Channel channel = connection.createChannel();
        channel.queueDeclare(RabbitConstant.QUEUE_BAIDU, false, false, false, null);
        //queueBind用於將隊列與交換機綁定
        //參數1:隊列名 參數2:交互機名  參數三:路由key(暫時用不到)
channel.queueBind(RabbitConstant.QUEUE_BAIDU, 
RabbitConstant.EXCHANGE_WEATHER, "");
        channel.basicQos(1);
        channel.basicConsume(RabbitConstant.QUEUE_BAIDU , false , 
new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, 
				AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("百度收到氣象信息:" + new String(body));
                channel.basicAck(envelope.getDeliveryTag() , false);
            }
        });
    }
}

  所以發佈訂閱模式和工作隊列的區別如下:1.生產者不再生產隊列,也不再和隊列進行直接綁定,而是與交換機進行直接綁定。2.數據交給交換機後,交換機會把數據進行復制分發給嚇交換機訂閱了得隊列,也就是每個隊列收到的數據都是一樣的。3.隊列由消費者進行創建,而且由消費者把隊列和交換機進行綁定。
而工作隊列模式則是生產者創建隊列把消息發給隊列,生產者拿到隊列並從隊列裏拿消息。

⑩路由模式
  是發佈訂閱模式的變種,發佈訂閱是無條件的把所有消息發給所有消費者隊列,而路由模式則是根據路由key有條件的篩選數據然後發給消費者隊列。路由模式下的交換機被叫做direct。

  生產者:
  這裏代碼我就寫有區別的地方,這裏路由模式下,生產者方也不用創建隊列,只需要和交換機綁定,但是綁定代碼basicPublish有區別:

channel.basicPublish(“weather“,me.getKey() , null , me.getValue().getBytes());

  這裏的第二個參數不再是null了,而是me.getKey(),其中me是之前定義的一個迭代器,相當於一個map,這裏的第二個參數就是數據篩選的條件。

  消費者:
  區別點在綁定的queueBing裏的第三個參數,是路由的key。這裏強調一下,就是在生產者這邊創建的隊列,可以綁定到多個交換機上,同理一個交換機也可以綁定多個隊列。

channel.queueBind(RabbitConstant.QUEUE_BAIDU, RabbitConstant.EXCHANGE_WEATHER_ROUTING, "china.shandong.qingdao.20991011");

  當然,我們可以在一個消費者中定義多個queueBind,隊列和交換機綁定都相同,只是key不同,從而讓一個消費者接收不同key的消息。
  也就是說,在生產者這邊,我們的消息在交換機上都加一個Key,消費者在創建隊列的時候讓隊列和交換機綁定,也必須與與之對應的key相綁定纔會受到對應的key的那些數據。

⑪主題Topic模式
  因爲 路由模式的key必須是精準的,則主題topic模式則是在路由模式的基礎上提供了模糊匹配的功能。模糊匹配的規則:
  * 匹配單個關鍵字
  #匹配所有關鍵字
  主題模式下交換機的類型爲topic。

  第一步:還是在可視化控制檯中創建topic模式下的交換機。交換機名字設爲topic

  第二步:生產者。這裏只給出區別代碼。把生產者和交換機進行綁定。
  channel.basicPublish(“topic”,me.getKey() , null , me.getValue().getBytes());

  第三步:消費者。進行模糊匹配綁定。
  channel.queueBind(RabbitConstant.QUEUE_BAIDU, “topic”, “..*.20991011”);
  channel.queueBind(RabbitConstant.QUEUE_SINA, “topic”, “us.#”);

  對於解綁,我們可以在可視化控制檯中進行解綁,也可以調用queueUnbind()方法。

⑫RabbitMQ消息確認機制
  生產者通過消息確認機制得知自己的消息被正確投入到了MQ中。RabbitMQ提供了監聽器來接收消息投遞的狀態,消息確認涉及兩種狀態:confirm和return。
  Confirm:代表生產者將消息送到MQ時產生的狀態,後續會出現兩種情況(即生產者投遞消息給MQ後的結果有兩種情況ack和nack)。Ack代表MQ已經將數據接收,nack代表MQ拒絕接收消息,可能是因爲隊列已滿、限流、異常等。
  Return:代表消息被MQ正常接收並且ack後,但是MQ沒有對應的隊列進行投遞,消息被退回給生產者。
  所以其實消息確認一共會產生三種狀況。
  Confirm和return兩種狀態只代表生產者和MQ之間消息投遞的情況,與消費者是否接收確認消息無關。
  可以在生產者中通過addConfirmListener來實現監聽過程:
在這裏插入圖片描述
  當然,在消費者這邊也需要一個Listener。

⑬RabbitMQ集羣架構模式
  用RabbitMQ集羣架構模式來解決消息隊列高可用。RabbitMQ集羣包含四種架構模式:(1)主備模式 (2)鏡像模式 (3)遠程模式 (4)多活模式。
  主備模式:實現RabbitMQ的高可用集羣,即一主一備,一般用在併發和數據量不高的情況下。即兩臺MQ服務器,一臺在工作,一臺在閒置,當一臺服務器宕機了另一臺服務器接上。他有個缺點就是不均衡,資源浪費嚴重。
  鏡像模式:能保證100%數據不丟失,但開銷很大,每一個RabbitMQ實例都會擁有一個QUEUE隊列,一個RabbitMQ實例對QUEUE隊列數據的修改都會同步到所有的RabbitMQ實例的隊列中。RabbitMQ實例包括隊列和交換機等信息。
  遠程模式:遠程模式就是可以把消息進行不同數據中心的複製工作,跨地域的讓兩個mq集羣互聯。北京、上海、成都三個集羣同步,任何一個壞了都可以頂上。但是配置很複雜。
  多活模式:解決了遠程模式的配置複雜,實現異地數據複製的主流模式,依賴插件,可以實現持續的可靠的AMQP數據通信。

⑭搭建鏡像集羣
(1)準備工作-修改host配置
在這裏插入圖片描述
(2)開放防火牆端口
在這裏插入圖片描述
  4369:erlang服務的主機發現端口。
  5672:RabbitMQ的端口
  15672:可視化控制rabbitMQ的端口
  25672:是erlang底層進行消息分配發送的端口

(3)複製erlang.cookie
  Erlang.cookie是一種通信證,集羣內所有設備要持有相同的erlang.cookie文件纔會被允許彼此通信。讓所有主機都持有這個通信證文件。

(4)配置鏡像集羣 在m2服務器上執行命令將與m1服務器進行復制。(也就是把m2加入到m1集羣中)

(5)接下來解決鏡像模式遇到的問題。現在我們有兩臺服務器,java客戶端發出請求後是交給哪個RabbitMQ服務器去解決呢,就需要用到負載均衡代理服務器haproxy。讓客戶端不直接與MQ服務器接觸,而是通過代理服務器haproxy進行轉發。
在這裏插入圖片描述

  Haproxy是輪詢負載均衡算法,還可以檢查哪些MQ服務器出現問題,出現問題就把該節點從集羣中剔除出去。Haproxy是四層負載均衡,是基於TCP傳輸層的。他之所以可以檢查出哪些MQ服務器出問題,是因爲他每五秒發送一次心跳包,如果連續兩次有響應則代表狀態良好,如果連續3次沒有相應,則視爲服務器故障,刪除節點。
  要使用haproxy負載均衡器,就需要先下載安裝。安裝好後在haproxy的核心配置文件haproxy.cfg進行編輯配置。包括設置連接的MQ服務器。

CMQ

①CMQ隊列消費模式代碼

# 從騰訊雲官網查看雲api的密鑰信息
    secretId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    secretKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
    # 使用地域的消息服務
    endpoint = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'    
    # Account類對象不是線程安全的,如果多線程使用,需要每個線程單獨初始化Account類對象
    my_account = Account(endpoint, secretId, secretKey, debug=True)
    my_account.set_log_level(logging.DEBUG)
    queue_name = "GZ-QualPlat-ThresholdsEvent-Queue"
    my_queue = my_account.get_queue(queue_name)
    recv_msg = my_queue.receive_message()
    print "Receive Message Succeed! MessageBody:%s" % (recv_msg.msgBody)
    #register(recv_msg.msgBody)  註冊到數據庫消費
    my_queue.delete_message(recv_msg.receiptHandle)

②CMQ生產模式
  代碼都千篇一律,無外乎都是先設置CMQ隊列的各種屬性,包括隊列名、消息等待時間、消息最長長度等等,然後創建CMQ隊列,然後send_message。
  具體的可以參見騰訊雲官網API

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章