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中可看),廣播模式則保存在本地,所以後續再啓動時,接着上一次的消費進度繼續消費。所以得從應用層解決。
解決辦法
- 每次啓動時,groupname增加時間戳,這樣每次都是新的訂閱組,不過後期維護很麻煩,不推薦
- 手動設置offset,不過這種方式go裏面我沒找到函數。kafka可以採用這種方式
- 應用層根據時間戳自己處理
我採用第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中保存了消費進度,所以繼續上一次進程退出前的位置繼續消費。