SocketServer
说明,socketserver主要用于处理kafka server对外提交网络请求的操作,用于检查连接数,把请求添加到请求的队列中,对KafkaApis提供操作支持.
实例创建与启动
socketServer = new SocketServer(config, metrics, kafkaMetricsTime)
socketServer.startup()
实例初始化生成:
得到listeners配置的值,默认是PLAINTEXT://:port,前面部分是协议,
可配置为PLAINTEXT,SSL,SASL_PLAINTEXT,SASL_SSL.
private val endpoints = config.listeners
得到num.network.threads配置的处理网络请求的线程个数配置,默认是3.
private val numProcessorThreads = config.numNetworkThreads
得到queued.max.requests配置的请求队列的最大个数,默认500.
private val maxQueuedRequests = config.queuedMaxRequests
这里生成总的处理线程的个数,用于网络请求,根据listener的个数与网络线程个数的乘积.默认只有一个listener.
private val totalProcessorThreads = numProcessorThreads * endpoints.size
得到max.connections.per.ip配置的单机IP的最大连接个数的配置,默认不限制.
private val maxConnectionsPerIp = config.maxConnectionsPerIp
得到max.connections.per.ip.overrides配置的针对某个特别的IP的连接个数限制的重新设置值.
多个IP配置间使用逗号分开,如:host1:500,host2:600
private val maxConnectionsPerIpOverrides = config.maxConnectionsPerIpOverrides
this.logIdent = "[Socket Server on Broker " + config.brokerId + "], "
生成用于网络请求的管道,管道的处理线程数就是配置的线程数*listeners的个数,请求队列的大小就是配置的队列大小.
val requestChannel = new RequestChannel(totalProcessorThreads, maxQueuedRequests)
实例启动:
def startup() {
this.synchronized {
这里根据每个ip对应的连接数限制,生成一个用于对连接数进行记数的quotas实例.
connectionQuotas = new ConnectionQuotas(maxConnectionsPerIp,
maxConnectionsPerIpOverrides)
读取如下配置项,用于生成socket中SO_SNDBUF,SO_RCVBUF等的buffer的大小.
读取socket.send.buffer.bytes配置项,默认值100kb,这个用于SOCKET发送数据的缓冲区大小.
读取socket.receive.buffer.bytes配置项默认值100kb,这个用于SOCKET的接收数据的缓冲区大小.
读取socket.request.max.bytes配置的值,这个用于设置每次请求的数据大小.默认值,100MB.
读取connections.max.idle.ms配置的值,默认为10分钟,用于设置每个连接最大的空闲回收时间.
val sendBufferSize = config.socketSendBufferBytes
val recvBufferSize = config.socketReceiveBufferBytes
val maxRequestSize = config.socketRequestMaxBytes
val connectionsMaxIdleMs = config.connectionsMaxIdleMs
得到配置项broker.id的值.
val brokerId = config.brokerId
这里根据每一个endpoint(也就是配置的listener的协议与端口),生成处理的网络线程Processor与Acceptor实例.并启动endpoint对应的Acceptor实例.在生成Acceptor的实例时,会同时启动此实例中对应的线程处理实例数组Processor.
var processorBeginIndex = 0
endpoints.values.foreach { endpoint =>
val protocol = endpoint.protocolType
val processorEndIndex = processorBeginIndex + numProcessorThreads
for (i <- processorBeginIndex until processorEndIndex) {
processors(i) = new Processor(i,
time,
maxRequestSize,
requestChannel,
connectionQuotas,
connectionsMaxIdleMs,
protocol,
config.values,
metrics
)
}
val acceptor = new Acceptor(endpoint, sendBufferSize, recvBufferSize, brokerId,
processors.slice(processorBeginIndex, processorEndIndex), connectionQuotas)
acceptors.put(endpoint, acceptor)
Utils.newThread("kafka-socket-acceptor-%s-%d".format(protocol.toString,
endpoint.port), acceptor, false).start()
acceptor.awaitStartup()
processorBeginIndex = processorEndIndex
}
}
newGauge("NetworkProcessorAvgIdlePercent",
new Gauge[Double] {
def value = allMetricNames.map( metricName =>
metrics.metrics().get(metricName).value()).sum / totalProcessorThreads
}
)
info("Started " + acceptors.size + " acceptor threads")
}
接收网络请求
当SocketServer实例生成并启动后,每一个endpoint会生成对应的Acceptor的实例,这个实例中根据网络请求配置的线程个数,生成对应个数的Processor实例.
首先先看看Acceptor实例中的run函数,这个用于对网络请求的接收:
def run() {
serverChannel.register(nioSelector, SelectionKey.OP_ACCEPT)
startupComplete()
try {
var currentProcessor = 0
while (isRunning) {
try {
这里进行堵塞接收,最多等500ms,如果ready返回的值是0表示还没有准备好,否则表示准备就绪.表示有通道已经被注册
val ready = nioSelector.select(500)
if (ready > 0) {
这里得到已经准备好的网络通道的key的集合.
val keys = nioSelector.selectedKeys()
val iter = keys.iterator()
while (iter.hasNext && isRunning) {
try {
val key = iter.next
iter.remove()
如果selectkey已经注册到accept事件,通过accept函数与对应的线程Processor进行处理.
这里表示这个socket的通道包含有一个client端的连接请求.
if (key.isAcceptable)
accept(key, processors(currentProcessor))
else
throw new IllegalStateException("Unrecognized key state for acceptor
thread.")
每次接收一个socket请求后,用于处理的线程进行轮询到一个线程中处理.
// round robin to the next processor thread
currentProcessor = (currentProcessor + 1) % processors.length
} catch {
case e: Throwable => error("Error while accepting connection", e)
}
}
}
}
catch {
case e: ControlThrowable => throw e
case e: Throwable => error("Error occurred", e)
}
}
} finally {
debug("Closing server socket and selector.")
swallowError(serverChannel.close())
swallowError(nioSelector.close())
shutdownComplete()
}
}
接下来看看accept函数的处理流程:
def accept(key: SelectionKey, processor: Processor) {
val serverSocketChannel = key.channel().asInstanceOf[ServerSocketChannel]
得到请求的socket通道.
val socketChannel = serverSocketChannel.accept()
try {
这里检查当前的IP的连接数是否已经达到了最大的连接数,如果是,直接throw too many connect.
connectionQuotas.inc(socketChannel.socket().getInetAddress)
socketChannel.configureBlocking(false)
socketChannel.socket().setTcpNoDelay(true)
socketChannel.socket().setKeepAlive(true)
socketChannel.socket().setSendBufferSize(sendBufferSize)
.....................................
这里通过对应线程的Processor实例(Endpoint对应的Processor)来处理这个socket通道.
processor.accept(socketChannel)
} catch {
case e: TooManyConnectionsException =>
info("Rejected connection from %s, address already has the configured maximum
of %d connections.".format(e.ip, e.count))
close(socketChannel)
}
}
然后再看看这个Processor实例中,如何去处理socket通道的请求流程:
/**
* Queue up a new connection for reading
*/
def accept(socketChannel: SocketChannel) {
把socket通道添加到队列中.等待Processor线程的处理.
newConnections.add(socketChannel)
wakeup()
}
接下来,当Processor的run函数不停的迭代时,会执行如下的处理流程:
override def run() {
startupComplete()
while(isRunning) {
try {
这里取出一个Socket的通道,并注册到selector中,
// setup any new connections that have been queued up
configureNewConnections()
// register any new responses for writing
processNewResponses()
try {
这里开始处理socket通道中的请求,根据如下几个流程进行处理:
1,如果请求中包含有一个isConnectable操作,把这个连接缓存起来.
2,如果请求中包含有isReadable操作.表示这个client的管道中包含有数据,需要读取,接收数据.
3,如果包含有isWriteable的操作,表示需要向client端进行写操作.
4,最后检查是否有connect被关闭的请求或connect连接空闲过期.
selector.poll(300)
} catch {
case e @ (_: IllegalStateException | _: IOException) =>
error("Closing processor %s due to illegal state or IO exception".format(id))
swallow(closeAll())
shutdownComplete()
throw e
}
得到对应的请求的Request的实例,并把这个Request通过SocketServer中的RequestChannel的sendRequest的函数,把请求添加到请求的队列中.等待KafkaApis来进行处理.
selector.completedReceives.asScala.foreach { receive =>
try {
val channel = selector.channel(receive.source)
val session = RequestChannel.Session(
new KafkaPrincipal(KafkaPrincipal.USER_TYPE,
channel.principal.getName),
channel.socketAddress)
val req = RequestChannel.Request(processor = id,
connectionId = receive.source, session = session,
buffer = receive.payload, startTimeMs = time.milliseconds,
securityProtocol = protocol)
这里请参见KafkaApis中对requestChannel的队列请求进行处理的具体流程.
requestChannel.sendRequest(req)
} catch {
case e @ (_: InvalidRequestException | _: SchemaException) =>
error("Closing socket for " + receive.source + " because of error", e)
close(selector, receive.source)
}
selector.mute(receive.source)
}
这里的send完成表示有向client端进行响应的写操作处理完成.
selector.completedSends.asScala.foreach { send =>
val resp = inflightResponses.remove(send.destination).getOrElse {
throw new IllegalStateException(s"Send for ${send.destination} completed,
but not in `inflightResponses`")
}
resp.request.updateRequestMetrics()
selector.unmute(send.destination)
}
如果socket server中包含有已经关闭的连接,减少这个quotas中对此ip的连接数的值.
这个情况包含connect处理超时或者说有connect的消息处理错误被发起了close的请求后的处理成功的消息.
selector.disconnected.asScala.foreach { connectionId =>
val remoteHost = ConnectionId.fromString(connectionId).getOrElse {
throw new IllegalStateException(s"connectionId has unexpected format:
$connectionId")
}.remoteHost
connectionQuotas.dec(InetAddress.getByName(remoteHost))
}
} catch {
case e : ControlThrowable => throw e
case e : Throwable =>
error("Processor got uncaught exception.", e)
}
}
debug("Closing selector - processor " + id)
swallowError(closeAll())
shutdownComplete()
}