簡単な紹介
我々はネットワーク層でサーバーを生産工程の最後の主な情報源KafkaProducerを分析し、今日で理解し、最後の時間は何をすべきかを学習している、全体的なアーキテクチャ図KafkaServerを見て
これらの大規模なモジュールのネットワーク層、API層、ロギングサブシステム、サブシステムのコピーは、図カフカのサーバーから含まれて見ました。クライアントが要求を開始すると、ネットワーク層は、要求を受信し、共有のリクエストキューへの要求は、キューからの要求スレッドハンドラAPI層、及び実行要求から削除します。例えば、要求がディスクに書き込まれたメッセージをログに記録しますKafkaProducer生産メッセージ経由で送信され、最終的に応答がクライアントに返されます
ネットワーク層
上記のチャートから、あなたは良いデザインがたくさんあり、カフカのサーバーは、まだやるべきことがたくさんある見ることができ、その後、ゆっくりと私たちの後ろ導入、今日のメイン学習ネットワーク層
主にここでそれらを繰り返すしないように参照することができます関連記事(リンク)の前に書かれたイベント駆動型モードに基づいて原子炉のモデルを使用して、他のブローカーへのネットワーク層とクライアントのネットワーク接続を完了するために
コア層は、新しい接続、受付部プロセッサスレッドの対応する複数の受信のための受容を含む、ネットワークのSocketServer型であり、各スレッドは、バック接続応答からの要求を読み書きするために使用される、独自のセレクタプロセッサを有しています
同時にアクセプタスレッドハンドラに対応する複数のスレッド、これは真の処理要求スレッドで、スレッドがハンドラは、要求プロセッサスレッドに応答を返し、前記スレッドハンドラプロセッサデータ転送スレッドRequestChannel介して、ReqeustChannelプロセッサと共有REQUESTQUEUE含む処理しました絵の下に取り付けたプライベートRESPONSEQUEUE、
それはカフカサーバは、要求と応答がクライアントに返される受信方法を確認するために私たちの深いソースコードに、いくつかの抽象化を語ります
ソースコード解析
kafkaservです
カフカKafkaServerは、サーバのメインクラスであり、KafkaServer及びネットワーク層に関連するコンポーネントがSockerServer、KafkaApisとKafkaRequestHandlerPoolが挙げられます。KafkaApis KafkaRequestHandlerPoolネットワーク要求が設けRequestChannel SocketServerによって処理され、前記次のように、ネットワーク層は、このコードの提示に関連します
class KafkaServer(...) {
...
var dataPlaneRequestProcessor: KafkaApis = null
...
def startup(): Unit = {
...
// SocketServer主要关注网络层相关事宜
socketServer = new SocketServer(...)
// Processor涉及到权限相关,所以调用SocketServer.startup时,只先启动Acceptor,初始化完权限相关的凭证后,再启动Processor,所以startupProcessors为false
socketServer.startup(startupProcessors = false)
...
// KafkaApis进行真正的业务逻辑处理
dataPlaneRequestProcessor = new KafkaApis(socketServer.dataPlaneRequestChannel ,...)
// 创建KafkaRequestHandler线程池,其中KafkaRequestHandler主要用来从请求通道中取请求
dataPlaneRequestHandlerPool = new KafkaRequestHandlerPool(config.brokerId, socketServer.dataPlaneRequestChannel, dataPlaneRequestProcessor, config.numIoThreadsx,...)
...
// 初始化结束
...
// Processor涉及到权限相关的操作,所以要等初始化完成,再启动Processor
socketServer.startDataPlaneProcessors()
...
}
}复制代码
私たちは、その後、特定のクラスとKafkaServerネットワーク層は、図を示しています。
具体的な手順
1.客户端(NetworkClient)发送请求被接收器(Acceptor)转发给处理器(Processor)处理
2.处理器把请求放到请求通道(RequestChannel)的共享请求队列中
3.请求处理器线程(KafkaRequestHandler)从请求通道的共享请求队列中取出请求
4.业务逻辑处理器(KafkaApis)进行业务逻辑处理
5.业务逻辑处理器把响应发到请求通道中与各个处理器对应的响应队列中
6.处理器从对应的响应队列中取出响应
7.处理器将响应的结果返回给客户端复制代码
SocketServer.startup方法
KafkaServer関連するソースとは、ネットワーク接続に関連した学習の主な情報源は、のはSocketServer.startup何が行われたかを見てみましょう今日から、全体的な処理の流れについて知っています
// SocketServer.startup
def startup(startupProcessors: Boolean = true): Unit = {
this.synchronized {
// 初始化控制每个IP上的最大连接数的对象,底层是Map
connectionQuotas = new ConnectionQuotas(config, time)
...
// 创建接收器和处理器
createDataPlaneAcceptorsAndProcessors(config.numNetworkThreads, config.dataPlaneListeners)
// 判断是否需要启动处理器,在KafkaServer初始化代用SocketServer.startup方法时,startipProcessors传
// 的是false,原因请看上面介绍KafkaServer的源码
if (startupProcessors) {
// 启动处理器
startControlPlaneProcessor()
...
}
}
...
} 复制代码
// 创建接收器和处理器,同时并启动接收器
private def createDataPlaneAcceptorsAndProcessors(dataProcessorsPerListener: Int,endpoints: Seq[EndPoint]): Unit = synchronized {
// 遍历broker节点,为每个broker节点都创建接收器
endpoints.foreach { endpoint =>
connectionQuotas.addListener(config, endpoint.listenerName)
// 创建接收器
val dataPlaneAcceptor = createAcceptor(endpoint, DataPlaneMetricPrefix)
// 创建处理器,并把处理器添加到RequestChannel和Acceptor的处理器列表中
addDataPlaneProcessors(dataPlaneAcceptor, endpoint, dataProcessorsPerListener)
// 启动接收器线程
KafkaThread.nonDaemon(s"data-plane-kafka-socket-acceptor-${endpoint.listenerName}-${endpoint.securityProtocol}-${endpoint.port}", dataPlaneAcceptor).start()
// 等待启动完成
dataPlaneAcceptor.awaitStartup()
// 把启动好的处理器添加到map中,并和broker做好了映射
dataPlaneAcceptors.put(endpoint, dataPlaneAcceptor)
}
}复制代码
受信機が開始されますが、プロセッサを起動しなかった以上のことから、それはSocketServer.startup結論することができる主な方法は、プロセッサがKafkaServerの初期化が完了するまで待機するアクセス許可を使用しますので、それは別のプロセッサを開始します、受信機およびプロセッサを作成します。作成したチャンネルリストプロセッサと受信機の要求を追加するためのプロセッサ
Acceptor.run
今、あなたは以前に作成され、受信機、私たちは何が行われたかを見て、受信を開始したことを?
def run(): Unit = {
// 将ServerSocketChannel注册到Selector的OP_ACCEPT事件上
serverChannel.register(nioSelector, SelectionKey.OP_ACCEPT)
// 标志启动完成
startupComplete()
try {
var currentProcessorIndex = 0
while (isRunning) {
try {
// 选择器轮训请求
val ready = nioSelector.select(500)
if (ready > 0) {
// 获取所有的选择键
val keys = nioSelector.selectedKeys()
// 遍历选择键
val iter = keys.iterator()
while (iter.hasNext && isRunning) {
try {
val key = iter.next
iter.remove()
if (key.isAcceptable) {
//OP_ACCEPT事件发生,获取注册到选择键上的ServerSocketChannel,然后调用其accept方法,建立一个客户端和服务端的连接通道
accept(key).foreach { socketChannel =>
// 重试的次数=该接收器对应的处理器的个数
var retriesLeft = synchronized(processors.length)
var processor: Processor = null
do {
// 重试次数减一
retriesLeft -= 1
processor = synchronized {
// 采用轮训的方式把客户端的连接通道分配给处理器即每个处理器都会有多个 SocketChannel,对应多个客户端的连接
currentProcessorIndex = currentProcessorIndex % processors.length
// 根据索引获取对应的处理器
processors(currentProcessorIndex)
}
currentProcessorIndex += 1
// 如果新连接的队列满了,一直阻塞直到最后一个处理器可以能够接收连接
} while (!assignNewConnection(socketChannel, processor, retriesLeft == 0))
}
...复制代码
あなたはOPが登録されたときに受信クライアントを接続するための主要な責任を負うのレシーバーサービス側は、上記のソースから見ることができ、受信スレッドが開始され、クライアントが接続を開始する際に、受信スレッドがOPに耳を傾けることができるようになります、イベントをACCEPTそして、イベントをACCEPT選択キーを取得し、接続チャネルクライアントとサーバーを確立するのServerSocketChannel方法を受け入れる呼び出すには、次のチャートを見てバインドのServerSocketChannel
1.服务端ServerSocketChannel在选择器上注册OP_ACCEPT事件
2.客户端在选择器上注册OP_CONNECT事件
3.服务端的选择器轮训到OP_ACCEPT事件,接收客户端的连接
4.服务端的ServerSocketChannel调用accept方法创建和客户端的通道SocketChannel复制代码
プロセッサ
これまでの説明によって、我々は、受信機プロセッサが複数のクライアントに対応し、それによって、また、のSocketChannelの複数のそれぞれに対応するようにプロセッサに割り当てられたクライアントのSocketChannelを介して回転されることを知っています。プロセッサが割り当てられたSocketChannelがキューを遮断する独自の受信機を入れて、その対応するセレクタ仕事を覚ますでしょう、次は、対応するソースです
do {
processor = synchronized {
// 采用轮训的方式把客户端的连接通道分配给处理器即每个处理器都会有多个SocketChannel,对应多个客户端的连接
currentProcessorIndex = currentProcessorIndex % processors.length
// 根据索引获取对应的处理器
processors(currentProcessorIndex)
}
currentProcessorIndex += 1
} while (!assignNewConnection(socketChannel, processor, retriesLeft == 0)) //把SocketChannel分配给处理器复制代码
private def assignNewConnection(socketChannel: SocketChannel, processor: Processor, mayBlock: Boolean): Boolean = {
if (processor.accept(socketChannel, mayBlock, blockedPercentMeter)) {
...
}复制代码
def accept(socketChannel: SocketChannel, mayBlock: Boolean,
acceptorIdlePercentMeter: com.yammer.metrics.core.Meter): Boolean = {
val accepted = {
// 往处理器的阻塞队列中添加SocketChannel
if (newConnections.offer(socketChannel))
true
...
if (accepted)
// 唤醒选择器线程开始轮询,原来的轮询因为没有事件到来被阻塞
wakeup()
accepted
}复制代码
ここでは、runメソッドプロセッサを見て
override def run(): Unit = {
startupComplete()
try {
while (isRunning) {
// 把新的SocketChannel注册OP_READ事件到选择器上
configureNewConnections()
// 注册OP_WRITE事件,用来给客户端写回响应
processNewResponses()
// 选择器轮训事件,用来读取请求、发送响应,默认超时时间为300ms
poll()
// 处理已经接收完成的客户端请求
processCompletedReceives()
// 处理已经完成的响应
processCompletedSends()
...![file](https://user-gold-cdn.xitu.io/2019/9/15/16d34913299554be?w=600&h=600&f=jpeg&s=84948)复制代码
Processor.configureNewConnections
private def configureNewConnections(): Unit = {var connectionsProcessed = 0
// 从队列中取出SocketChannel
val channel = newConnections.poll()
// 注册OP_READ事件
// 根据本地的地址和端口、远程客户端的地址和端口构建唯一的connectionId
selector.register(connectionId(channel.socket), channel)
connectionsProcessed += 1
...复制代码
Facieレジスタ方法は、最も内側とされています
public void register(String id, SocketChannel socketChannel) throws IOException {
registerChannel(id, socketChannel, SelectionKey.OP_READ);
}复制代码
protected SelectionKey registerChannel(String id, SocketChannel socketChannel, int interestedOps) throws IOException {
// 把SocketChannel注册OP_READ到选择器上,同时绑定SelectionKey
SelectionKey key = socketChannel.register(nioSelector, interestedOps);
// 构建Kafka通道KafkaChannel并将其绑定到选择键上
KafkaChannel channel = buildAndAttachKafkaChannel(socketChannel, id, key);
...
return key;
}复制代码
private KafkaChannel buildAndAttachKafkaChannel(SocketChannel socketChannel, String id, SelectionKey key) throws IOException {
// 构建Kafka通道
KafkaChannel channel = channelBuilder.buildChannel(id, key, maxReceiveSize, memoryPool);
// 将Kafka通道绑定到选择键上,这样就可以根据选择键获取到对应的通道
key.attach(channel);
return channel;
...
}复制代码
ここでconfigureNewConnections()タスクが完了した(新しいのSocketChannelは、セレクタにOP_READイベントを登録しました)
その結果に応じてプロセッサの下に見てどのように
Processor.processNewResponses
private def processNewResponses(): Unit = {var currentResponse: RequestChannel.Response = null
// 处理器还有响应要发送
while ({currentResponse = dequeueResponse(); currentResponse != null}) {
// KafkaChannel唯一标识
val channelId = currentResponse.request.context.connectionId
try {
currentResponse match {
case response: NoOpResponse =>
...
// 没有响应发送给客户端,需要读取更多请求,注册读事件
tryUnmuteChannel(channelId)
case response: SendResponse =>
// 有响应要发送给客户端,注册写事件
sendResponse(response, response.responseSend)
...复制代码
protected[network] def sendResponse(response: RequestChannel.Response, responseSend: Send): Unit = {
val connectionId = response.request.context.connectionId
...
if (openOrClosingChannel(connectionId).isDefined) {
// 将响应通过Selector标记为Send,实际发送通过poll轮询完成
selector.send(responseSend)
// 添加到 inflightResponses 底层是可变的Map key:connectionId value:response
inflightResponses += (connectionId -> response)
}
}复制代码
configureNewConnections()とprocessNewResponses()メソッドの実行ポーリングの開始後()メソッド、ポーリングイベントセレクタ、読み出し要求を、バック応答を書き込み、その後、受信した応答が完了している処理
Processor.processCompletedReceives()
private def processCompletedReceives(): Unit = {
selector.completedReceives.asScala.foreach { receive =>
try {
openOrClosingChannel(receive.source) match {
case Some(channel) =>
// 解析ByteBuffer 到ReuestHeader
val header = RequestHeader.parse(receive.payload)
val connectionId = receive.source
// 组装RequestContext对象
val context = new RequestContext(header, connectionId, channel.socketAddress,...)
// 构建 RequestChannel.Request对象 包含了处理器的编号,响应对象中可以获取到,这样就能保证请求和响应的处理都是在同一个处理器中完成
val req = new RequestChannel.Request(processor = id, context = context,...)
// 发送给 RequestChannel 处理
requestChannel.sendRequest(req)
// 移除OP_READ读事件,已经接收到请求了,所以就不用再读了
selector.mute(connectionId)
...复制代码
Processor.processCompletedSends()
private def processCompletedSends(): Unit = {
selector.completedSends.asScala.foreach { send =>
try {
// 结束的写请求要从inflightResponses移出
val response = inflightResponses.remove(send.destination).getOrElse {...}
...
// 添加 OP_READ 读事件,这样就可以继续读取客户端发来的请求
tryUnmuteChannel(send.destination)
... 复制代码
ここでは、サーバとネットワーク接続のソースコードに関連するオーバー導入された、私たちが知っている要求処理要求キューに、応答キューから戻って、クライアントへの応答を取得するには、その後、誰がキュー要求、追加要求に対処するために起こっていますか?そして、誰がそれで応答キュープロセッサに応答を入れますか?
Kafaka処理要求スレッドKafkaRequestHandler
前回の発表では、我々はKafkaServerは、初期化要求処理スレッドプール(KafkaRequestHandlerPool)中に作成さ知っている各要求処理スレッドが共有要求にアクセスすることができ、要求処理スレッド・プールが作成され、(KafkaRequestHandlerを)要求処理スレッドを開始しますキュー、あなたがリクエストキューから、その後、KafkaApisプロセスへの要求処理スレッドの要求を取得することができますので。
// KafkaServer 会创建 KafkaRequestHandlerPool,同时把请求队列传过去
dataPlaneRequestHandlerPool = new KafkaRequestHandlerPool(config.brokerId, socketServer.dataPlaneRequestChannel, dataPlaneRequestProcessor, ...)
复制代码
// 请求处理线程的数量取决于配置 num.io.threads,默认是8
val runnables = new mutable.ArrayBuffer[KafkaRequestHandler](numThreads)
for (i <- 0 until numThreads) {
// 创建 KafkaRequestHandler Kafka请求处理线程
createHandler(i)
}
def createHandler(id: Int): Unit = synchronized {
// 每个请求处理线程都是共享同一个 RequestChannel
runnables += new KafkaRequestHandler(id, brokerId, aggregateIdleMeter, threadPoolSize, requestChannel, apis, time)
// 启动请求处理线程
KafkaThread.daemon(logAndThreadNamePrefix + "-kafka-request-handler-" + id, runnables(id)).start()
}复制代码
def run(): Unit = {
while (!stopped) {
val startSelectTime = time.nanoseconds
// 从请求队列里获取下一个请求或者阻塞到超时
val req = requestChannel.receiveRequest(300)
...
req match {
...
case request: RequestChannel.Request =>
try {
// 交给KafkaApis处理
apis.handle(request)
...复制代码
、要求処理スレッドのタスクは、ソースコード解析によって共有要求キューから要求を除去するだけで見られる要求を処理し、次いでKafakaApisを呼び出すことができるように
サービス要求処理KafkaApisの入口端
KafkaServerは、初期化された実サーバのリクエスト処理
// 全局的服务端入口
dataPlaneRequestProcessor = new KafkaApis(socketServer.dataPlaneRequestChannel,...)复制代码
ハンドル要求処理スレッドは、ビジネス・ロジックを処理するKafkaApisメソッドを呼び出します
def request.header.apiKey match {
// 处理客户端生产消息的请求
case ApiKeys.PRODUCE => handleProduceRequest(request)
case ApiKeys.FETCH => handleFetchRequest(request)
...复制代码
カフカ視認サーバ側の要求処理入り口KafkaApisこれらの要求のためのサーバは、我々は次の時間を共有して何をしたとして、プロセッサの選択要求の種類に応じて、
参考資料
1.「カフカインサイド」
2.「Apacheのカフカソース解析」
3.Kafka最新のトランク支店コード