遊戲陪玩app開發,訊息可靠性的實現

雲豹科技程式設計師 發表於 2021-12-07

在遊戲陪玩app開發中,隨著業務的不斷複雜和呼叫鏈路的不斷增長,我們可能會慢慢引入越來越多的中介軟體來更好的服務於我們的系統,但是每樣技術都是一把雙刃劍,在提高遊戲陪玩app開發系統效能的同時,也要想辦法來減少它對系統帶來穩定性的影響,今天要帶來的是如何讓RabbitMQ的可靠性達到保證。

要想了解如何保證RabbitMQ的可靠性,首先要從它的執行流程開始瞭解。

執行流程

在這裡插入圖片描述

  • 生產者傳送訊息或者消費者進行消費訊息都會先與遊戲陪玩app開發的主機建立起一條長連線,由長連線裡的channal來傳送訊息。
  •  長連線優點:消費者如果出現當機或者下線,mq會感知到,沒法繼續派發後會把這條訊息再次儲存起來,避免造成訊息大面積丟失
  • 訊息由訊息頭+訊息體+路由鍵組成。
  • 訊息傳送出去後,首先進入遊戲陪玩app開發的mq伺服器指定的一個虛擬主機中,由虛擬主機中的exchange交換機收到後,通過訊息的路由鍵和繫結關係,最終決定發往那個佇列。
  • 消費者通過監聽指定佇列拿到訊息。

由執行流程就可以看到,訊息不管是在生產者傳送到MQ伺服器的過程中或者是在消費的過程中都存在著丟失的風險,那怎麼辦呢?

訊息確認機制-可靠抵達

事務

提到保證可靠性的問題,小夥伴們肯定首先可以想到的是事務機制,RabbitMQ也提供了事務訊息,不過官方文件也寫到事務訊息讓MQ的效能下降250倍,所以說在遊戲陪玩app開發對效能要求很高的情況下,顯然不適合去使用事務訊息。

那隻能從其他幾個方面來保證可靠性了。

在這裡插入圖片描述

這是一張訊息傳送到消費的簡圖,要保證可靠性的話,要從三個方面來來考慮。

publisher → Broker,confirmCallback機制

  • 在建立connectionFactory的時候設定publisherConfirm(true)選項,開始confirmCallback。
  • 訊息只要被broker收到就會執行confirmCallback,如果是cluster模式,需要所有broker都接收到才會呼叫confirmCallback。
  • 被broker接收到只能表示message已經抵達伺服器,並不能保證訊息一定被投遞到目標queue裡。所以需要用到接下來的returnCallback。
#配置yml檔案
spring:
	rabbitmq:
    	#開啟傳送單確認
    	publisher-confirms: true
//定製[email protected]  //MyRabbitConfig初始化完成後,執行這個方法public void initRabbitTemplate(){
    // 伺服器收到訊息確認回撥
    /*
      correlationData 訊息的唯一id
      ack 訊息是否成功收到
      cause  失敗原因
     */
    rabbitTemplate.setConfirmCallback(((correlationData, ack, cause) -> {
        log.info("confirm---->correlationData{},-------->ack{},-------->cause{}",correlationData,ack,cause);
    }));}

Exchange → Queue,returnCallback 機制

  • confirm模式只能保證訊息抵達broker,不能保證訊息準確投遞到目標queue裡。在遊戲陪玩app開發的一些業務場景下,需要保證訊息一定要投遞到目標queue裡,此時就需要用到return退回模式。
  • 這樣如果未能投遞到目標queue裡將會呼叫returnCallback
    ,可以記錄下詳細到投遞資料,定期的巡檢或者自動糾錯都將需要這些資料。
#配置yml檔案
spring:
	rabbitmq:
    	 #開啟傳送端訊息抵達佇列確認
    	 publisher-returns: true
    	 #只要抵達佇列,以非同步方式優先回撥這個returnconfirm   		 template:
      		mandatory: true
//定製[email protected]  //MyRabbitConfig初始化完成後,執行這個方法public void initRabbitTemplate(){
    //設定訊息抵達queue的確認回撥 (訊息沒有投遞給指定佇列,才會觸發這個失敗回撥)
    /*
     message   投遞失敗的詳細資訊
    replyCode   回覆的狀態碼
    replyText   回覆的文字內容
    exchange     這個訊息傳送給哪個交換機
    routingKey    這個訊息用的哪個路由鍵
     */
    rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
        log.info("return---->message{},-->replyCode{},-->replyText{},-->exchange{},-->routingKey{}",
                message,replyCode,replyText,exchange,routingKey);
    });}

Queue → Consumer,ack訊息確認機制(消費端)

  • 預設自動ack,訊息被消費者收到,就會從broker的queue中移除
  •  問題:收到很多訊息,自動回覆給伺服器ack,只處理一個訊息,遊戲陪玩app開發伺服器就當機了。這時訊息就會全部丟失,所以要關閉預設的自動ack機制。
  • 消費者獲取到訊息,成功處理,可以回覆ack給broker
  •  basic.ack用於肯定確認;broker將移除此訊息
  •  basic.nack用於否定確認;可以指出broker是否丟棄此訊息,可以批量
  •  basic.reject用於否定確認;同上,但不能批量
  • queue無消費者,訊息依然會被儲存,直到消費者消費
  • 消費者收到訊息,預設會自動ack。但是如果無法確認此訊息是否被處理完成,或者成功處理。我們可以開啟手動ack模式
  •  訊息處理成功,ack(),接受下一個訊息,此訊息broker就會移除
  •  訊息處理失敗,nack()/reject(),重新傳送給其他人進行處理,或者容錯處理後ack
  •  訊息一直都沒有呼叫ack/nack方法,broker認為此訊息正在被處理,不會投遞給別人。此時客戶端斷開。訊息不會被broker移除,會投遞給別人
  •  手動ack機制下,只要沒有明確告訴mq訊息被消費,沒有ack,遊戲陪玩app開發訊息就一直是unacked狀態。即使consumer當機,訊息不會丟失,會變為ready狀態,下次一有新的consumer連線進來就發給他
#配置yml檔案
spring:
	rabbitmq:
    	#切換為手動ack
        listener:
          direct:
            acknowledge-mode: manual

完成以上三個配置後,我們用於消費訊息的程式碼就會變成這樣

/**
 * @author lp
 * @date 2020/8/9 15:05
 */@Service
@Slf4j
@RabbitListener(queues = "demo.queue")public class DemoListener {
    @Autowired    private DemoService service;
    @RabbitHandler    public void listener(DemoEntity entity, Channel channel, Message message) throws IOException {
        log.info("-----------開始消費訊息----------");
        try {
        	//具體業務
            service.doSomething(entity);
            //成功處理回覆acl
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (Exception e) {
        	//失敗處理重新返回queue
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
        }
    }}

當然在遊戲陪玩app開發中僅僅是這三步配置還是不夠的,因為上述步驟並沒有做到訊息持久化,在做好持久化方案後,我們的訊息將無敵。(誇張的修辭手法)

小結:

  • 訊息傳送出去,由於網路問題沒有抵達遊戲陪玩app開發伺服器
  •  做好容錯方法(try-catch),傳送訊息可能會網路失敗,失敗後要有重試機制,可記錄到資料庫,採用定期掃描重發的方式。
  •  做好日誌記錄(給資料庫儲存每一個訊息的詳細資訊),每個訊息狀態是否都被伺服器收到,應記錄。
  •  做好定期重發,如果訊息沒有傳送成功,定期去資料庫掃描未成功的訊息進行重發。
  • 訊息抵達Broker,Broker要將訊息寫入磁碟(持久化)才算成功。此時Broker尚未持久化完成,當機
  •  publisher也必須加入確認回撥機制,確認成功的訊息,修改資料庫訊息狀態。
  • 自動ack的狀態下。消費者收到訊息,但還沒來得及處理訊息,當機
  •  一定開啟手動ack,訊息消費成功後才移除,失敗或者沒來得及處理就noAck並重新入隊。

做好這些後,遊戲陪玩app開發中訊息丟失的可能性已經很小很小了,但是又會有新的問題出現,比如說重複消費,訊息過多導致消費者當機等。

防止訊息重複

  • 訊息消費成功,事務已經提交,ack時,機器當機。導致沒有ack成功,Broker的訊息重新由unack變為ready,併傳送給其他消費者
  • 消費失敗時,由於重試機制,自動又將訊息傳送出去
  • 成功消費,ack時當機,訊息由unack變為ready,Broker又重新傳送
  •  消費者的業務消費介面應該設計為冪等性的。比如扣庫存有工作單的狀態標誌
  •  使用防重表(redis/mysql),傳送訊息每一個都有業務的唯一標識,處理過就不用處理
  •  rabbitMq每一次訊息都有redelivered欄位,可以獲取是否被重新投遞過來的,而不是第一次被投遞過來的

訊息積壓

  • 產生原因:
  •  消費者當機
  •  消費者能力不足積壓
  •  傳送者傳送流量太大
  • 如何解決:
  •  上線更多消費者,進行正常消費
  •  上線專門的訊息佇列服務,將訊息先批量取出來,記錄到資料庫,離線慢慢處理

本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理
原文連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2846306/,如需轉載,請註明出處,否則將追究法律責任。