今天来说说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相关函数