今天來說說NSQD.TCPServer中的核心函數IOLoop的具體實現,IOLoop主要的工作是接收和響應客戶的命令。同時開啓messagePump goroutine 進行心跳檢查,給訂閱者發生消息等操作。
詳細流程參考 https://blog.csdn.net/H_L_S/article/details/104709619 中的邏輯流程圖。
主要代碼文件:
1.nsqd/protocol_v2.go
IOLoop函數
func (p *protocolV2) IOLoop(conn net.Conn) error {
....
//客戶端實例化
clientID := atomic.AddInt64(&p.ctx.nsqd.clientIDSequence, 1)
client := newClientV2(clientID, conn, p.ctx)
p.ctx.nsqd.AddClient(client.ID, client)
...
//messagePump啓動信號,當 messagePump初始化完成,才能開始進行命令處理
messagePumpStartedChan := make(chan bool)
go p.messagePump(client, messagePumpStartedChan)
<-messagePumpStartedChan
for {
//net.Conn 讀超時設置
if client.HeartbeatInterval > 0 {
client.SetReadDeadline(time.Now().Add(client.HeartbeatInterval * 2))
} else {
client.SetReadDeadline(zeroTime)
}
//以\n 爲分隔符作爲一條命令的結束(協議)
line, err = client.Reader.ReadSlice('\n')
...
//每條命令 以separatorBytes(這裏定義的是空格) 作爲命令參數分解符(協議)
params := bytes.Split(line, separatorBytes)
var response []byte
//執行命令
response, err = p.Exec(client, params)
if err != nil {
//命令無法執行,或執行錯誤,返回對應信息給客戶端
...
continue
}
//發送響應給客戶端
if response != nil {
err = p.Send(client, frameTypeResponse, response)
...
}
}
....
}
messagePump函數
func (p *protocolV2) messagePump(client *clientV2, startedChan chan bool) {
var err error
var memoryMsgChan chan *Message //內存消費消息
var backendMsgChan chan []byte //磁盤消費消息(內存無法存儲時,將消息存儲到磁盤)
var subChannel *Channel //訂閱者消息channel
//發送給客戶的消息並不是馬上發給客戶端,而是先緩存到client.Writer中,然後在發送給客戶端
//而flusherChan 是緩存刷出的信號。
var flusherChan <-chan time.Time
var sampleRate int32 //暫時不知用處
subEventChan := client.SubEventChan //客戶端的訂閱信號
identifyEventChan := client.IdentifyEventChan //身份認證信號
outputBufferTicker := time.NewTicker(client.OutputBufferTimeout) //刷出緩存的定時器
heartbeatTicker := time.NewTicker(client.HeartbeatInterval) //心跳檢查定時器
heartbeatChan := heartbeatTicker.C
//消息超時時間,這裏的意思是,如果消費者消費消息,超過這個時間,沒有消費確認,會將消息重新消費。
msgTimeout := client.MsgTimeout
...
flushed := true
...
close(startedChan) //初始化完成,開始接受請求命令
for {
if subChannel == nil || !client.IsReadyForMessages() { //沒有訂閱請求
...
} else if flushed { //正在刷出緩存至客戶端
....
} else {
memoryMsgChan = subChannel.memoryMsgChan // 訂閱者的內存channel
backendMsgChan = subChannel.backend.ReadChan() //訂閱者的磁盤channel
flusherChan = outputBufferTicker.C //定時刷出緩存的定時器。
}
select {
case <-flusherChan: //刷出緩存
...
case <-client.ReadyStateChan:
case subChannel = <-subEventChan: //訂閱
// 不允許再被其他人訂閱
subEventChan = nil
case identifyData := <-identifyEventChan:
...
case <-heartbeatChan: //心跳檢查
err = p.Send(client, frameTypeResponse, heartbeatBytes)
if err != nil {
goto exit
}
case b := <-backendMsgChan: //消費磁盤channel的消息,
....
case msg := <-memoryMsgChan: //消費內存channel的消息
if sampleRate > 0 && rand.Int31n(100) > sampleRate {
continue
}
msg.Attempts++ //嘗試重新消費的次數,如果超過這個次數,不應該再重新消費
//加入等待消費確認的隊列,如果超過msgTimeout,就把被重新被消費,加入這個隊裏。
subChannel.StartInFlightTimeout(msg, client.ID, msgTimeout)
//增加總共發送的消息總數 和 待消費確認的消息總數
client.SendingMessage()
//發送消息到客戶緩存
err = p.SendMessage(client, msg)
if err != nil {
goto exit
}
flushed = false
case <-client.ExitChan:
goto exit
}
}
...
}
Exec函數
func (p *protocolV2) Exec(client *clientV2, params [][]byte) ([]byte, error) {
if bytes.Equal(params[0], []byte("IDENTIFY")) { //身份認證
return p.IDENTIFY(client, params)
}
err := enforceTLSPolicy(client, p, params[0]) //TLS 加密
if err != nil {
return nil, err
}
switch {
case bytes.Equal(params[0], []byte("FIN")): //消費確認
return p.FIN(client, params)
case bytes.Equal(params[0], []byte("RDY")): //Ready 確認
return p.RDY(client, params)
case bytes.Equal(params[0], []byte("REQ")): //消息重新消費
return p.REQ(client, params)
case bytes.Equal(params[0], []byte("PUB")): //單消息發佈
return p.PUB(client, params)
case bytes.Equal(params[0], []byte("MPUB")): //多消息發佈
return p.MPUB(client, params)
case bytes.Equal(params[0], []byte("DPUB")): //帶延時的發佈
return p.DPUB(client, params)
case bytes.Equal(params[0], []byte("NOP")): //心跳檢測
return p.NOP(client, params)
case bytes.Equal(params[0], []byte("TOUCH")): //更新消息的msgTimeout 超時時間
return p.TOUCH(client, params)
case bytes.Equal(params[0], []byte("SUB")): //訂閱
return p.SUB(client, params)
case bytes.Equal(params[0], []byte("CLS")): //關閉
return p.CLS(client, params)
case bytes.Equal(params[0], []byte("AUTH")): //認證
return p.AUTH(client, params)
}
return nil, protocol.NewFatalClientErr(nil, "E_INVALID", fmt.Sprintf("invalid command %s", params[0]))
}
總結
1.IOLoop 主要實現兩個循環處理,一個是messagePump,主要是對心跳檢測,訂閱的消費的消息輸出等。另一個是對客戶端請求命令的處理Exec。
下次分享:IOLoop 中的 Exec相關函數