メッセージキューの分析NSQ -カタログ
の実用的なアプリケーションでは、クラスタのサービスの一部同時に同じに加入することができるtopic
、と同じでchannel
ケース。ときにnsqd
メッセージが必要な時間を考慮するために、クライアントのニーズに送られ、に対処するためのクライアントをサブスクライブに送信されるように、私はそれがメッセージの負荷であると言うでしょう。
負荷が考慮されていない場合は、顧客端末を処理するためのメッセージを送信するために、ランダムなメッセージは、マシンの異なる性能の場合、状況は速度を処理する1つまたは複数のクライアントを発生することがありますが、新しいメッセージの数があります対処する必要性は、他のクライアントがアイドル状態です。理想的な状況は、メッセージを処理するために、現在の比較的自由なクライアントを見つけることです。
nsq
アプローチは、クライアントがイニシアチブを取るですnsqd
(ニュースレポートは、ある数扱うことができ、独自のRDY
コマンド)。nsqd
ランダムメッセージ処理が実行されるクライアント・メッセージに利用可能である状況メッセージは、各クライアント接続のために処理することができる送信します
下図のように:
クライアントアップデートRDY
設定の最初の記事の例から、我々は消費者を設定してい
config := nsq.NewConfig()
config.MaxInFlight = 1000
config.MaxBackoffDuration = 5 * time.Second
config.DialTimeout = 10 * time.Second
MaxInFlight
プロセスへのメッセージの最大数を設定し、更新するかどうかに応じて変数を算出しRDY
、接続処理の最大数を設定するnsqd updateRDYサーバに送信するクライアントの初期化
func (r *Consumer) maybeUpdateRDY(conn *Conn) {
inBackoff := r.inBackoff()
inBackoffTimeout := r.inBackoffTimeout()
if inBackoff || inBackoffTimeout {
r.log(LogLevelDebug, "(%s) skip sending RDY inBackoff:%v || inBackoffTimeout:%v",
conn, inBackoff, inBackoffTimeout)
return
}
remain := conn.RDY()
lastRdyCount := conn.LastRDY()
count := r.perConnMaxInFlight()
// refill when at 1, or at 25%, or if connections have changed and we're imbalanced
if remain <= 1 || remain < (lastRdyCount/4) || (count > 0 && count < remain) {
r.log(LogLevelDebug, "(%s) sending RDY %d (%d remain from last RDY %d)",
conn, count, remain, lastRdyCount)
r.updateRDY(conn, count)
} else {
r.log(LogLevelDebug, "(%s) skip sending RDY %d (%d remain out of last RDY %d)",
conn, count, remain, lastRdyCount)
}
}
残りの利用可能な処理の数が場合remain
未満又は1に等しく、以下最後のセットの利用可能な数以上であるlastRdyCount
1/4、又は平均の利用可能な接続がmaxInFlight 0より大きく、小さいremain
、更新RDY
ステータス
複数存在する場合nsqd
、メッセージは、最大平均計算になります。
// perConnMaxInFlight calculates the per-connection max-in-flight count.
//
// This may change dynamically based on the number of connections to nsqd the Consumer
// is responsible for.
func (r *Consumer) perConnMaxInFlight() int64 {
b := float64(r.getMaxInFlight())
s := b / float64(len(r.conns()))
return int64(math.Min(math.Max(1, s), b))
}
以下からのメッセージがある場合nsqd
によって送ら呼び出しますmaybeUpdateRDY
計算が送信する必要がある場合は、この方法RDY
のコマンドは、
func (r *Consumer) onConnMessage(c *Conn, msg *Message) {
atomic.AddInt64(&r.totalRdyCount, -1)
atomic.AddUint64(&r.messagesReceived, 1)
r.incomingMessages <- msg
r.maybeUpdateRDY(c)
}
上面就是主要的处理逻辑,但还有一些逻辑,就是当消息处理发生错误时,nsq
有自己的退避算法backoff
也会更新RDY
简单来说就是当发现有处理错误时,来进行重试和指数退避,在退避期间RDY
会为0,重试时会先放尝试RDY
为1看有没有错误,如果没有错误则全部放开,这个算法这篇帖子我就不详细说了。
服务端nsqd选择客户端进行发送消息
同时订阅同一topic
的客户端(comsumer)有很多个,每个客户端根据自己的配置或状态发送RDY
命令到nsqd
表明自己能处理多少消息量
nsqd服务端会检查每个客户端的的状态是否可以发送消息。也就是IsReadyForMessages
方法,判断inFlightCount是否大于readyCount,如果大于或者等于就不再给客户端发送数据,等待Ready
后才会再给客户端发送数据
func (c *clientV2) IsReadyForMessages() bool {
if c.Channel.IsPaused() {
return false
}
readyCount := atomic.LoadInt64(&c.ReadyCount)
inFlightCount := atomic.LoadInt64(&c.InFlightCount)
c.ctx.nsqd.logf(LOG_DEBUG, "[%s] state rdy: %4d inflt: %4d", c, readyCount, inFlightCount)
if inFlightCount >= readyCount || readyCount <= 0 {
return false
}
return true
每一次发送消息inFlightCount
会+1并保存到发送中的队列中,当客户端发送FIN会-1在之前的帖子中有说过。
func (p *protocolV2) messagePump(client *clientV2, startedChan chan bool) {
// ...
for {
// 检查订阅状态和消息是否可处理状态
if subChannel == nil || !client.IsReadyForMessages() {
// the client is not ready to receive messages...
memoryMsgChan = nil
backendMsgChan = nil
flusherChan = nil
// ...
flushed = true
} else if flushed {
memoryMsgChan = subChannel.memoryMsgChan
backendMsgChan = subChannel.backend.ReadChan()
flusherChan = nil
} else {
memoryMsgChan = subChannel.memoryMsgChan
backendMsgChan = subChannel.backend.ReadChan()
flusherChan = outputBufferTicker.C
}
select {
case <-flusherChan:
// ...
// 消息处理
case b := <-backendMsgChan:
client.SendingMessage()
// ...
case msg := <-memoryMsgChan:
client.SendingMessage()
//...
}
}
// ...
}