Kafka 客户端实现逻辑分析
这里主要分析kafka 客户端实现 (代码分析以perl kafka实现为准)
kafka客户端分为生产者和消费者,生产者发送消息,消费者获取消息.
在kafka协议里客户端通信中用到的最多的四个协议命令是fetch,fetchoffset,send,metadata.这四个分别是获取消息,获取offset,发送消息,获取metadata.剩下的其他协议命令大多都是kafka server内部通信用到的.offsetcommit这个命令在有些语言的client api的实现里给出了接口可以自己提交offset.但是在perl的实现里并没有.
先看看直接producer和consumer的代码
my $request = {
ApiKey => $APIKEY_PRODUCE,
CorrelationId => $self->{CorrelationId},
ClientId => $self->{ClientId},
RequiredAcks => $self->{RequiredAcks},
Timeout => $self->{Timeout} * 1000,
topics => [
{
TopicName => $topic,
partitions => [
{
Partition => $partition,
MessageSet => $MessageSet,
},
],
},
],
}; foreach my $message ( @$messages ) {
push @$MessageSet, {
Offset => $PRODUCER_ANY_OFFSET,
Key => $key,
Value => $message,
};
} return $self->{Connection}->receive_response_to_request( $request, $compression_codec );
代码并未完全贴上.核心代码就这一部分.最后一行代码可以看见最终调用connection::receive_response_to_request函数.再上面的部分是设置消息格式.和消息内容的数据结构.
my $request = {
ApiKey => $APIKEY_FETCH,
CorrelationId => $self->{CorrelationId},
ClientId => $self->{ClientId},
MaxWaitTime => $self->{MaxWaitTime},
MinBytes => $self->{MinBytes},
topics => [
{
TopicName => $topic,
partitions => [
{
Partition => $partition,
FetchOffset => $start_offset,
MaxBytes => $max_size // $self->{MaxBytes},
},
],
},
],
}; my $response = $self->{Connection}->receive_response_to_request( $request );
这是consumer的获取消息的核心部分代码.最后同producer一样.代码结构也相似.同样是设置消息数据结构然后发送.只是最后多了代码返回处理的部分.消息返回处理的部分就不再贴上详细说明了.有兴趣自行去cpan上看源代码.
下面看看最核心的函数代码.
sub receive_response_to_request {
my ( $self, $request, $compression_codec ) = @_; local $Data::Dumper::Sortkeys = if $self->debug_level; my $api_key = $request->{ApiKey}; //这里获取请求类型,是发送消息,还是获取消息和offset的. # WARNING: The current version of the module limited to the following:
# supports queries with only one combination of topic + partition (first and only). my $topic_data = $request->{topics}->[]; //这些消息具体处理就略过不提了.
my $topic_name = $topic_data->{TopicName};
my $partition = $topic_data->{partitions}->[]->{Partition}; if ( //这里是比较关键的.判断是否有完整的metadata信息.没有metadata信息就通过fetch meta命令获取.
!%{ $self->{_metadata} } # the first request
|| ( !$self->{AutoCreateTopicsEnable} && defined( $topic_name ) && !exists( $self->{_metadata}->{ $topic_name } ) )
) {
//updata_metadata函数就是封装了fetch metadata请求命令发送给kafka 来获取metadata信息.在这个地方处理不同语言里处理逻辑多少有些差别.php-kafka中有两种方式,一种通过这里的这个方法.另一种是通过zookeeper获取meta信息.在使用的时候需要指定zookeeper地址.
$self->_update_metadata( $topic_name ) # hash metadata could be updated
# FATAL error
or $self->_error( $ERROR_CANNOT_GET_METADATA, format_message( "topic = '%s'", $topic_name ) );
}
my $encoded_request = $protocol{ $api_key }->{encode}->( $request, $compression_codec ); //这里将消息格式化成网络字节序. my $CorrelationId = $request->{CorrelationId} // _get_CorrelationId; say STDERR sprintf( '[%s] compression_codec = %d, request: %s',
scalar( localtime ),
$compression_codec // '<undef>',
Data::Dumper->Dump( [ $request ], [ 'request' ] )
) if $self->debug_level; my $attempts = $self->{SEND_MAX_ATTEMPTS};
my ( $ErrorCode, $partition_data, $server );
ATTEMPTS:
while ( $attempts-- ) { //在while里进行发送尝试.java版客户端的三次尝试即是这里同样的逻辑
REQUEST:
{
$ErrorCode = $ERROR_NO_ERROR;
//这里差早topic分区对应的leader,成功则进行leader连接发送请求
if ( defined( my $leader = $self->{_metadata}->{ $topic_name }->{ $partition }->{Leader} ) ) { # hash metadata could be updated
unless ( $server = $self->{_leaders}->{ $leader } ) { //没有找到对应leader的server就跳过此次请求尝试,更新metadata并进行下一次尝试
$ErrorCode = $ERROR_LEADER_NOT_FOUND;
$self->_remember_nonfatal_error( $ErrorCode, $ERROR{ $ErrorCode }, $server, $topic_name, $partition );
last REQUEST; # go to the next attempt //在这里跳出主逻辑块.进行块之后的动作.
} # Send a request to the leader
if ( !$self->_connectIO( $server ) ) { //这里连接此topic分区的leader
$ErrorCode = $ERROR_CANNOT_BIND;
} elsif ( !$self->_sendIO( $server, $encoded_request ) ) { //这里向这个leader发送请求
$ErrorCode = $ERROR_CANNOT_SEND;
}
if ( $ErrorCode != $ERROR_NO_ERROR ) { //判断动作是否成功
$self->_remember_nonfatal_error( $ErrorCode, $self->{_IO_cache}->{ $server }->{error}, $server, $topic_name, $partition );
last REQUEST; # go to the next attempt
} my $response; //这里处理返回情况.如果发送的produce请求并且没有任何response返回.则构建一个空的response返回.
if ( $api_key == $APIKEY_PRODUCE && $request->{RequiredAcks} == $NOT_SEND_ANY_RESPONSE ) { # Do not receive a response, self-forming own response
$response = {
CorrelationId => $CorrelationId,
topics => [
{
TopicName => $topic_name,
partitions => [
{
Partition => $partition,
ErrorCode => ,
Offset => $BAD_OFFSET,
},
],
},
],
};
} else { //这里获取response.并从网络字节序转换成字符格式.
my $encoded_response_ref;
unless ( $encoded_response_ref = $self->_receiveIO( $server ) ) {
if ( $api_key == $APIKEY_PRODUCE ) {
# WARNING: Unfortunately, the sent package (one or more messages) does not have a unique identifier
# and there is no way to verify the delivery of data
$ErrorCode = $ERROR_SEND_NO_ACK; # Should not be allowed to re-send data on the next attempt
# FATAL error
$self->_error( $ErrorCode, $self->{_IO_cache}->{ $server }->{error} );
} else {
$ErrorCode = $ERROR_CANNOT_RECV;
$self->_remember_nonfatal_error( $ErrorCode, $self->{_IO_cache}->{ $server }->{error}, $server, $topic_name, $partition );
last REQUEST; # go to the next attempt
}
}
if ( length( $$encoded_response_ref ) > ) { # MessageSize => int32
$response = $protocol{ $api_key }->{decode}->( $encoded_response_ref );
say STDERR sprintf( '[%s] response: %s',
scalar( localtime ),
Data::Dumper->Dump( [ $response ], [ 'response' ] )
) if $self->debug_level;
} else {
$self->_error( $ERROR_RESPONSEMESSAGE_NOT_RECEIVED );
}
} $response->{CorrelationId} == $CorrelationId
# FATAL error
or $self->_error( $ERROR_MISMATCH_CORRELATIONID );
$topic_data = $response->{topics}->[];
$partition_data = $topic_data->{ $api_key == $APIKEY_OFFSET ? 'PartitionOffsets' : 'partitions' }->[]; if ( ( $ErrorCode = $partition_data->{ErrorCode} ) == $ERROR_NO_ERROR ) {
return $response;
} elsif ( exists $RETRY_ON_ERRORS{ $ErrorCode } ) {
$self->_remember_nonfatal_error( $ErrorCode, $ERROR{ $ErrorCode }, $server, $topic_name, $partition );
last REQUEST; # go to the next attempt
} else {
# FATAL error
$self->_error( $ErrorCode, format_message( "topic = '%s', partition = %s", $topic_name, $partition ) );
}
}
} # Expect to possible changes in the situation, such as restoration of connection
say STDERR sprintf( '[%s] sleeping for %d ms before making request attempt #%d (%s)',
scalar( localtime ),
$self->{RETRY_BACKOFF},
$self->{SEND_MAX_ATTEMPTS} - $attempts + ,
$ErrorCode == $ERROR_NO_ERROR ? 'refreshing metadata' : "ErrorCode ${ErrorCode}",
) if $self->debug_level;
sleep $self->{RETRY_BACKOFF} / ; $self->_update_metadata( $topic_name ) //最重要的逻辑在这里.可以看见上面失败则跳出REQUEST块,直接到这里执行更新动作.更新完之后再进行下一次尝试.这个逻辑应对着topic 分区的leader动态切换的.现有leader死了,切换到其他的leader上来.客户端能对此作出应对.
# FATAL error
or $self->_error( $ErrorCode || $ERROR_CANNOT_GET_METADATA, format_message( "topic = '%s', partition = %s", $topic_name, $partition ) );
} # FATAL error
if ( $ErrorCode ) {
$self->_error( $ErrorCode, format_message( "topic = '%s'%s", $topic_data->{TopicName}, $partition_data ? ", partition = ".$partition_data->{Partition} : q{} ) );
} else {
$self->_error( $ERROR_UNKNOWN_TOPIC_OR_PARTITION, format_message( "topic = '%s', partition = %s", $topic_name, $partition ) );
} return;
}
上面主要分析核心逻辑实现.可以发现:
consumer在消费的时候并没有手动提交过offset.也未设置groupId相关的配置,所以在消费的时候server其实并不是强制按group消费的,也不自动记录对应offset.只是按提交的offset返回对应的消息和下一个offset值而已.所以在kafka按组消费的功能其实是有各个客户端api实现的.在新版java的api中可以看见有autoCommitOffset的方法.在老版java api实现里也有autocommit的线程在替用户提交groupId与offset的记录.
producer和consumer的request里均需要指定topic分区.所以实际上在真正的api底层是没有对topic分区做负载的.一些具有负载功能的其他语言的api均由客户端内部实现.并非kafka server提供的功能.
Kafka 客户端实现逻辑分析的更多相关文章
- 转:Kafka 客户端TimeoutException问题之坑
原文出自:http://www.jianshu.com/p/2db7abddb9e6 各种TimeoutException问题 会抛出org.apache.kafka.common.errors.Ti ...
- Erlang 编写 Kafka 客户端之最简单入门
Erlang 编写 Kafka 客户端之最简单入门 费劲周折,终于测通了 erlang 向kafka 发送消息,使用了ekaf 库,参考: An advanced but simple to use, ...
- 【原创】大叔问题定位分享(5)Kafka客户端报错SocketException: Too many open files 打开的文件过多
kafka0.8.1 一 问题 10月22号应用系统忽然报错: [2014/12/22 11:52:32.738]java.net.SocketException: 打开的文件过多 [2014/12/ ...
- kafka客户端发布record(消息)
kafka客户端发布record(消息)到kafka集群. 新的生产者是线程安全的,在线程之间共享单个生产者实例,通常单例比多个实例要快. 一个简单的例子,使用producer发送一个有序的key/v ...
- c#实例化继承类,必须对被继承类的程序集做引用 .net core Redis分布式缓存客户端实现逻辑分析及示例demo 数据库笔记之索引和事务 centos 7下安装python 3.6笔记 你大波哥~ C#开源框架(转载) JSON C# Class Generator ---由json字符串生成C#实体类的工具
c#实例化继承类,必须对被继承类的程序集做引用 0x00 问题 类型“Model.NewModel”在未被引用的程序集中定义.必须添加对程序集“Model, Version=1.0.0.0, Cu ...
- python confluent kafka客户端配置kerberos认证
kafka的认证方式一般有如下3种: 1. SASL/GSSAPI 从版本0.9.0.0开始支持 2. SASL/PLAIN 从版本0.10.0.0开始支持 3. SASL/SCRAM-SHA- ...
- 如何创建Kafka客户端:Avro Producer和Consumer Client
1.目标 - Kafka客户端 在本文的Kafka客户端中,我们将学习如何使用Kafka API 创建Apache Kafka客户端.有几种方法可以创建Kafka客户端,例如最多一次,至少一次,以及一 ...
- 从0开始搭建kafka客户端
上一节,我们实现了搭建kafka集群.本节我们将从0开始,使用Java,搭建kafka客户端生产消费模型. 1.创建maven项目2.kafka producer3.kafka consumer4.结 ...
- Kafka客户端Producer与Consumer
Kafka客户端Producer与Consumer 一.pom.xml 二.相关配置文件 producer.properties log4j.properties base.properties 三. ...
随机推荐
- list和map集合
List特点:元素有放入顺序,元素可重复Set特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)Map特点: ...
- 纯CSS图片缩放后显示详细信息
哎~!突然好久没更新博客了,最近总在下雨,晚上也经常没有时间来管理博客(目前在敲自己的一个平台,晢时还在写逻辑层的代码),好吧!废话不多说了,言归正传. 现在很多图片缩放的特效大多数都是用javasc ...
- 一个使用openGL渲染的炫丽Android动画库二(碎片化曲面动画)
续一个使用openGL渲染的炫丽Android动画库 MagicSurfaceView v1.1.0发布, 新增碎片化曲面动画 地址:https://github.com/gplibs/android ...
- javaScript 设计模式系列之一:观察者模式
介绍 观察者模式又叫发布订阅模式(Publish/Subscribe),一个目标对象管理所有相依于它的观察者对象.该模式中存在两个角色:观察者和被观察者.目标对象与观察者之间的抽象耦合关系能够单独扩展 ...
- WPF 杂谈——自定义控件
如果只是使用现有的WPF控件的话,是很难满足当前社会多复杂的业务.所以用户自己订制一系列控件也是一种不可避免的情势.WPF在控制方面分为俩种:用户控件和自定义控件.相信看过前面章节的就明白他们俩者之间 ...
- 4、Swing在JPanel中添加背景图片方法
4.Jpanel面板中加载背景图片 在实际应用Java做界面的过程中,常常会涉及到加载背景图片以使页面美化.下面整理了一个小模块以便于调用. 1 package com.tntxia.commonsw ...
- Android studio 3.0 引起的 outputFile sync failed:not vaild
我们大多使用 android studio 改变生成安装包命名会用以下方式: applicationVariants.all { variant -> variant.outputs.each ...
- 备忘录《一》基于cookie使用拦截器实现客户每次访问自登陆一次
原创声明:本文为本人原创作品,绝非他处摘取,转载请联系博主 相信大家在各大网站都会遇到,登录时,在登录框出现下次免登陆/一个月免登陆的类似选项,本次博文就是讲解如何实现,在这记录一下,也算是做个备忘录 ...
- 11.Java 加解密技术系列之 总结
Java 加解密技术系列之 总结 序 背景 分类 常用算法 原理 关于代码 结束语 序 上一篇文章中简单的介绍了第二种非对称加密算法 — — DH,这种算法也经常被叫做密钥交换协议,它主要是针对密钥的 ...
- 在eclipse-jee-juno中配置Aptana对jQuery代码自动提示
主要问题 在Aptana的Web Project中打开js文件有JavaScript的自动提示,但是在JavaEE项目中却没有. 版本 eclipse-jee-juno,即Eclipse 4.2的Ja ...