go rocketmq——ConsumeFromTimestamp的注意事項

消費者

go中使用rocketmq

初始化消費者的時候,官方例子是這樣的:

c, _ := rocketmq.NewPushConsumer(
   consumer.WithGroupName("testGroup"),
   consumer.WithNsResovler(primitive.NewPassthroughResolver([]string{"127.0.0.1:9876"})),
)

從RocketMQ源碼或者一些書籍我們知道還可以指定消費的offset。

查看官方源碼或者golang中包的源碼,支持3種模式:

const (
    /**
     * 一個新的訂閱組第一次啓動從隊列的最後位置開始消費<br>
     * 後續再啓動接着上次消費的進度開始消費
     */
   ConsumeFromLastOffset ConsumeFromWhere = iota
   /**
     * 一個新的訂閱組第一次啓動從隊列的最前位置開始消費<br>
     * 後續再啓動接着上次消費的進度開始消費
     */
   ConsumeFromFirstOffset
   /** 
    * 一個新的訂閱組第一次啓動從指定時間點開始消費<br> 
    * 後續再啓動接着上次消費的進度開始消費<br> 
    * 時間點設置參見DefaultMQPushConsumer.consumeTimestamp參數 */
   ConsumeFromTimestamp
)

簡單總結一下:

  • ConsumeFromLastOffset:從上一次消費的位置開始
  • ConsumeFromFirstOffset:從頭開始消費,如果消費過會出現重複消費的問題
  • ConsumeFromTimestamp:第一次啓動從給定時間戳消費,後續啓動接着上一次消費的位置繼續

go裏面可以使用consumer.WithConsumeFromWhere來指定。

c, err := rocketmq.NewPushConsumer(
   consumer.WithGroupName(groupName),
   consumer.WithConsumeFromWhere(consumer.ConsumeFromTimestamp),// default fromLastOffset
   consumer.WithNsResovler(primitive.NewPassthroughResolver(nameServers)))

那IM裏面如果使用MQ進行消息的廣播、推送等,應該使用哪種模式呢?
我個人感覺應該是第三種:ConsumeFromTimestamp。爲什麼要使用這種模式,看下面。

爲什麼ConsumeFromTimestamp模式下,還能消費到之前生產的消息?

寫測試代碼的時候,先寫了生產者,啓動後生產了幾條數據。然後中間有其他事情,過了幾天消費者寫好了啓動,我發現把之前的消息打印出來了。

因爲我是用MQ來解決不同網關之間通信廣播的問題(爲什麼不用route_server來中轉?因爲存在單點問題,需要使用haproxy做主備),如下app用戶給web用戶發消息,但是這2個用戶登錄在不同的網關上,勢必需要進行路由廣播。
在這裏插入圖片描述

所以我希望在啓動時,只消費當前時間戳往後的消息,不然可能就會產生很奇怪的問題,1個小時前發的消息,怎麼1個小時後纔到?

調查ConsumeFromTimestamp

嘗試使用這個模式,我發現還是能收到生產者在之前生產的消息。所以又回過頭去查詢文檔,找到了問題所在:

一個新的訂閱組第一次啓動從指定時間點開始消費
後續再啓動接着上次消費的進度開始消費

所以只有 第一次啓動時 纔有效果,因爲我使用的集羣模式,消費進度會保存在broker上(rocketmq-console中可看),廣播模式則保存在本地,所以後續再啓動時,接着上一次的消費進度繼續消費。所以得從應用層解決。

解決辦法

  1. 每次啓動時,groupname增加時間戳,這樣每次都是新的訂閱組,不過後期維護很麻煩,不推薦
  2. 手動設置offset,不過這種方式go裏面我沒找到函數。kafka可以採用這種方式
  3. 應用層根據時間戳自己處理

我採用第3種解決,先看一下時間戳:

[root@localhost bin]# ./mqadmin queryMsgById -i 0A006BDA00002A9F0000000000000B71 -n 10.0.107.218:9876
RocketMQLog:WARN No appenders could be found for logger (io.netty.util.internal.PlatformDependent0).
RocketMQLog:WARN Please initialize the logger system properly.
OffsetID:            0A006BDA00002A9F0000000000000B71
Topic:               cim_msg_push
Tags:                [null]
Keys:                [null]
Queue ID:            0
Queue Offset:        11
CommitLog Offset:    2929
Reconsume Times:     0
Born Timestamp:      2020-07-01 10:47:01,169
Store Timestamp:     2020-06-17 05:09:02,072
Born Host:           10.0.106.117:60041
Store Host:          10.0.107.218:10911
System Flag:         0
Properties:          {UNIQ_KEY=0A006A7508CD0000000002505c880001}
Message Body Path:   /tmp/rocketmq/msgbodys/0A006A7508CD0000000002505c880001

MessageTrack [consumerGroup=group1, trackType=NOT_ONLINE, exceptionDesc=CODE:206 DESC:the consumer group[group1] not online]

爲什麼born timestamp和store timestamp不一樣?

啓動時,記錄startTimeStamp,然後訂閱後,判斷時間戳大於該時間戳才處理。

func NewMsgConsumer() *MsgConsumer {
   return &MsgConsumer{
      pushMsgChan:    make(chan *cim.CIMPushMsg),
      startTimeStamp: time.Now().Unix() * 1000, // ms
   }
}


func (m *MsgConsumer) onMsgPush(ctx context.Context, msgs ...*primitive.MessageExt) (result consumer.ConsumeResult, err error) {
   for i := range msgs {
      logger.Sugar.Infof("subscribe callback: %v", msgs[i])

      // if msg too old,drop all
      if msgs[i].BornTimestamp < m.startTimeStamp {
         logger.Sugar.Warnf("expired msg,dorp it: %v", msgs[i])
         continue
      }

      msg := &cim.CIMPushMsg{}
      err = proto.Unmarshal(msgs[i].Body, msg)
      if err != nil {
         logger.Sugar.Info(err)
      } else {
         m.pushMsgChan <- msg
      }
   }
   return consumer.ConsumeSuccess, nil
}

輸出:

2020-07-01 14:01:20.971	[INFO]	[mq/consumer.go:140]	subscribe callback: [Message=[topic=cim_msg_push, body�10.0.107.117:8000 (2�wx_2020�"$16757bf1-c9e5-4705-ba04-3b2bca925f27(����08hello mq, Flag=0, properties=map[CONSUME_START_TIME:1593583280971 MAX_OFFSET:16 MIN_OFFSET:0 UNIQ_KEY:0A006A753A940000000003022ff80001], TransactionId=], MsgId=0A006A753A940000000003022ff80001, OffsetMsgId=0A006BDA00002A9F0000000000000F49,QueueId=0, StoreSize=246, QueueOffset=15, SysFlag=0, BornTimestamp=1593583275496, BornHost=10.0.106.117:59062, StoreTimestamp=1592352409555, StoreHost=10.0.107.218:10911, CommitLogOffset=3913, BodyCRC=2070446043, ReconsumeTimes=0, PreparedTransactionOffset=0]
2020-07-01 14:01:20.971	[WARN]	[mq/consumer.go:144]	expired msg,dorp it: [Message=[topic=cim_msg_push, body�10.0.107.117:8000 (2�wx_2020�"$16757bf1-c9e5-4705-ba04-3b2bca925f27(����08hello mq, Flag=0, properties=map[CONSUME_START_TIME:1593583280971 MAX_OFFSET:16 MIN_OFFSET:0 UNIQ_KEY:0A006A753A940000000003022ff80001], TransactionId=], MsgId=0A006A753A940000000003022ff80001, OffsetMsgId=0A006BDA00002A9F0000000000000F49,QueueId=0, StoreSize=246, QueueOffset=15, SysFlag=0, BornTimestamp=1593583275496, BornHost=10.0.106.117:59062, StoreTimestamp=1592352409555, StoreHost=10.0.107.218:10911, CommitLogOffset=3913, BodyCRC=2070446043, ReconsumeTimes=0, PreparedTransactionOffset=0]

總結

ConsumeFromTimestamp模式下

  • 只會在訂閱組第一次啓動的時候,過濾掉小於當前系統時間戳的消息,後續如果進程停掉或者崩潰,但是又生產了新消息。下次啓動消費者時,會繼續消費停掉期間新生產的消息。
  • 後續行爲和ConsumeFromLastOffset類似,再啓動時,因爲是在broker中保存了消費進度,所以繼續上一次進程退出前的位置繼續消費。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章