kafka04-消费者

1、python中KafkaConsumer类

1、KafkaConsume类

class kafka.KafkaConsumer(*topics, **configs)

  • 使用Kafka集群的记录。
  • 消费者将透明地处理Kafka集群中服务器的故障,并在topic分区创建或在broker之间迁移时进行调整。它还与已分配的kafka Group Coordinator节点交互,以允许多个消费者对topic的消费进行负载均衡(要求 kafka >= 0.9.0.0)。
  • 消费者不是线程安全的,不应跨线程共享。

下面介绍KafkaConsumer类的位置参数:

topics(str)

  • 要订阅的主题。如果没有设置,在消费消息之前调用subscribe()或assign()。

下面介绍KafkaConsumer类的关键字参数:

1、bootstrap_servers

  • 'host[:port]'字符串(或'host[:port]'字符串列表),例如bootstrap_servers='host[:port]'(或['host1[:port]','host2[:port]','host3[:port]'])
  • 消费者应该联系它来获取集群元数据。这不必是完整的节点列表(即只需要kefka集群中部分kafka服务器的即可)。它只需要至少有一个代理来响应元数据 API 请求。默认端口为 9092。
  • 如果未指定服务器,则默认为localhost:9092。
  • 设置两个以上的broker地址信息,当其中任意一个宕机时,消费者仍然可以连接到Kafka集群上。

2、client_id(str)

  • 此客户端的名称。该字符串在每个请求中传递给服务器,可用于识别与此客户机对应的特定服务器端日志条目。也提交给GroupCoordinator,用于记录有关消费者组管理的日志。
  • 默认值:'kafka-python-{version}'

3、group_id(str or None)

  • 要加入动态分区分配(如果启用)并用于获取和提交偏移量的消费者组的名称。如果为None,则禁用自动分区分配(通过组协调器)和偏移提交。
  • 默认值:None

4、key_deserializer (callable)

  • 任何采用原始消息键并返回反序列化键的可调用对象。
  • 消费者从broker端获取的消息格式都是字节数组(byte[])类型,所以需要执行相应的反序列化操作才能还原成原有的对象格式。这个参数分别用来指定消息中key所需反序列化操作的反序列化器,无默认值。

5、value_deserializer(callable)

  • 任何采用原始消息值并返回反序列化值的可调用对象。
  • 消费者从broker端获取的消息格式都是字节数组(byte[])类型,所以需要执行相应的反序列化操作才能还原成原有的对象格式。这个参数分别用来指定消息中value所需反序列化操作的反序列化器,无默认值。

6、fetch_min_bytes(int)

  • 该参数用来配置Consumer在一次拉取请求(调用poll()方法)中能从Kafka中拉取的最小数据量,默认值为1(B)。
  • Kafka在收到Consumer的拉取请求时,如果返回给Consumer的数据量小于这个参数所配置的值,那么它就需要进行等待(最多等待fetch_max_wait_ms),直到数据量满足这个参数的配置大小。可以适当调大这个参数的值以提高一定的吞吐量,不过也会造成额外的延迟(latency),对于延迟敏感的应用可能就不可取了。

7、fetch_max_wait_ms(int)

  • 如果没有足够的数据来立即满足fetch_min_bytes给出的要求,则kafka在响应请求之前将阻塞的最长时间)。这个参数的设定和Consumer与Kafka之间的延迟也有关系,如果业务应用对延迟敏感,那么可以适当调小这个参数。
  • 默认值:500(毫秒)。

8、fetch_max_bytes(int)

  • 该参数与fetch_min_bytes参数对应,它用来配置Consumer在一次拉取请求中从Kafka中拉取的最大数据量,默认值为52428800(B),也就是50MB。
  • 如果这个参数设置的值比任何一条写入Kafka中的消息要小,那么会不会造成无法消费呢?很多资料对此参数的解读认为是无法消费的,比如一条消息的大小为10B,而这个参数的值是1(B),既然此参数设定的值是一次拉取请求中所能拉取的最大数据量,那么显然1B<10B,所以无法拉取。这个观点是错误的,该参数设定的不是绝对的最大值,如果在第一个非空分区中拉取的第一条消息大于该值,那么该消息将仍然返回,以确保消费者继续工作。也就是说,上面问题的答案是可以正常消费。与此相关的,Kafka中所能接收的最大消息的大小通过服务端参数message.max.bytes (对应于主题端参数max_message_bytes)来设置。
  • 注意:消费者对多个代理并行执行取回操作,因此内存使用将取决于包含主题分区的代理的数量。支持的 Kafka 版本 >= 0.10.1.0。

9、max_partition_fetch_bytes(int)

  • 这个参数用来配置从每个分区里返回给Consumer的最大数据量,默认值为1048576(B),即1MB。
  • 这个参数与fetch_max_bytes参数相似,只不过前者用来限制一次拉取中每个分区的消息大小,而后者用来限制一次拉取中整体消息的大小。
  • 同样,如果这个参数设定的值比消息的大小要小,那么也不会造成无法消费,Kafka为了保持消费逻辑的正常运转不会对此做强硬的限制。

10、request_timeout_ms(int)

  • 这个参数用来配置Consumer等待请求响应的最长时间,默认值为30000(ms)。

11、retry_backoff_ms(int)

  • 这个参数用来配置尝试重新发送失败的请求到指定的主题分区之前的等待(退避)时间,避免在某些故障情况下频繁地重复发送,默认值为100(ms)。

12、reconnect_backoff_ms(int)

  • 这个参数用来配置尝试重新连接指定主机之前的等待时间(也称为退避时间),避免频繁地连接主机,默认值为50(ms)。
  • 这种机制适用于消费者向broker发送的所有请求。

13、reconnect_backoff_max_ms(int)

  • 当重新连接到重复连接失败的代理时,后退/等待的最大时间(以毫秒为单位)。
    • 如果提供,每个主机的回退将在每次连续连接失败时呈指数级增加,直到这个最大值。一旦达到最大值,重新连接尝试将以这个固定速率周期性地继续。
    • 为了避免连接风暴,将对回退应用0.2的随机因素,从而产生一个介于计算值以下20%和以上20%之间的随机范围。
  • 默认值:1000。

14、max_in_flight_requests_per_connection(int)

  • 请求通过管道传输到kafka代理,最多达到每个代理连接的最大请求数。
  • 默认值:5。

15、auto_offset_reset(str)

  • 用于在OffsetOutOfRange错误上重置偏移量的策略:“earliest”将移至最旧的可用消息,“latest”将移至最新的消息。任何其他值都会引发异常。
  • 默认值:'latest'。

16、enable_auto_commit(bool)

  • 是否开启自动提交消费位移的功能,默认为True,即开启。

17、auto_commit_interval_ms(int)

  • 当enable_auto_commit参数设置为true时才生效,表示开启自动提交消费位移功能时自动提交消费位移的时间间隔。
  • 默认值:5000。

18、default_offset_commit_callback(callable)

  • 作为callback(offsets, response)响应将是一个Exception或一个OffsetCommitResponse结构体。这个回调可以用于在提交请求完成时触发自定义操作。

19、check_crcs(bool)

  • 自动检查消耗记录的CRC32。这确保不会发生消息的在线或磁盘损坏。这种检查会增加一些开销,因此在寻求极端性能的情况下可能会禁用它。
  • 默认值:True

20、metadata_max_age_ms(int)

  • 这个参数用来配置元数据的过期时间,默认值为300000(ms),即5分钟。
  • 如果元数据在此参数所限定的时间范围内没有进行更新,则会被强制更新,即使没有任何分区变化或有新的broker加入。

21、partition_assignment_strategy(list)

  • 使用组管理时用于在消费者实例之间分配分区所有权的对象列表。
  • 默认值:[RangePartitionAssignor, RoundRobinPartitionAssignor]

22、max_poll_records(int)

  • 这个参数用来配置Consumer在一次拉取(poll()方法)请求中拉取的最大消息数,默认值为500(条)。
  • 如果消息的大小都比较小,则可以适当调大这个参数值来提升一定的消费速度。

23、max_poll_interval_ms(int)

  • 当通过消费组管理消费者时,该配置指定拉取消息线程最大空闲时间,若超过这个时间间隔还没有发起poll操作,则消费组认为该消费者己离开了消费组, 将进行再均衡操作。
  • 默认:300000。

24、session_timeout_ms(int)

  • 当使用Kafka的组管理工具时,用于检测失败的超时时间。消费者定期向broker发送心跳以表明其活跃状态。如果在此会话超时到期之前代理没有接收到心跳,则代理将从组中删除该消费者并发起重新平衡。
  • 注意,该值必须在group.min.session.timeout.ms和group.max.session.timeout.ms在代理配置中配置的允许范围内。
  • 默认值:10000

25、heartbeat_interval_ms(int)

  • 使用Kafka的组管理功能时,心跳到消费者协调器之间的预计时间(以毫秒为单位)。心跳用于确保消费者的会话保持活跃,并在新消费者加入或离开群组时促进重新平衡。该值必须小于session_timeout_ms,但一般不大于该值的1/3。它还可以调整得更低,以控制正常重新平衡的预期时间。
  • 默认值:3000

26、receive_buffer_bytes(int)

这个参数用来设置Socket接收消息缓冲区(SO_RCVBUF)的大小,默认值:None(依赖于系统默认值)。(java客户机默认为32768)

  • 如果设置为-1,则使用操作系统的默认值。如果Consumer与Katka处于不同的机房,则可以适当调大这个参数值。

27、send_buffer_bytes(int)

  • 这个参数用来设置Socket发送消息缓冲区(SO_SNDBUF)的大小,默认值:None(依赖于系统默认值)。(java客户机默认为131072)
  • 如果设置为-1,则使用操作系统的默认值。

28、socket_options(list)

  • socket.setsockopt的元组参数列表,适用于代理连接套接字。
  • 默认值:[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]

29、consumer_timeout_ms (int)

  • 在引发StopIteration(即结束迭代器)之前,消息迭代期间要阻塞的毫秒数。
  • 默认块永远 [float('inf')]。

30、security_protocol(str)

  • 用于与代理通信的协议。有效值为:PLAINTEXT、SSL、SASL_PLAINTEXT、SASL_SSL。
  • 默认值:PLAINTEXT。

31、ssl_context(ssl.SSLContext)

  • 用于包装套接字连接的预配置SSLContext。如果提供,所有其他ssl_*配置将被忽略。
  • 默认值:None。

32、ssl_check_hostname(bool)

  • 配置ssl握手是否应验证证书与代理主机名匹配的标志。
  • 默认值:True。

33、ssl_cafile(str)

  • 可选文件名,在证书验证中使用的ca文件。
  • 默认值:None。

34、ssl_certfile(str)

  • 可选文件名,包含客户端证书的pem格式文件,以及建立证书真实性所需的任何ca证书。
  • 默认值:None。

35、ssl_keyfile(str)

  • 可选文件名,包含客户端私钥的文件。
  • 默认值:None。

36、ssl_password(str)

  • 可选密码,加载证书链时使用。
  • 默认值:None。

37、ssl_crlfile(str)

  • 可选文件名,包含用于检查证书过期的CRL。缺省情况下,不进行CRL检查。在提供文件时,仅根据这个CRL检查叶证书。只能使用Python 3.4+或2.7.9+检查CRL。
  • 默认值:None。

38、ssl_ciphers(str)

  • 可以选择为SSL连接设置可用的密码。它应该是OpenSSL密码列表格式的字符串。如果不能选择密码(因为编译时选项或其他配置禁止使用所有指定的密码),则会引发ssl.SSLError。见ssl.SSLContext.set_ciphers

39、api_version(tuple)

  • 指定要使用的Kafka API版本。如果设置为None,客户端将尝试通过探测各种api来推断代理版本。不同的版本支持不同的功能。
  • 默认值:None。
(0, 9)启用具有自动功能的完整组协调功能              #分区分配和重新平衡
(0, 8, 2)使用手动启用kafka-storage偏移提交        #仅分区分配,
(0, 8, 1) 手动启用 zookeeper-storage 偏移提交    #仅分区分配
(0, 8, 0)启用基本功能但需要手动                   #分区分配和偏移管理。

40、api_version_auto_timeout_ms(int)

  • 在检查代理API版本时从构造函数抛出超时异常的毫秒数。仅当api_version设置为None时应用。

41、connections_max_idle_ms

  • 这个参数用来指定在多久之后关闭限制(空闲)的连接,默认值是540000(ms),即9分钟。
  • 这样可以避免在客户端碰到意想不到的套接字断开连接错误。

42、metric_reporters(list)

  • 用作指标报告者的类列表。实现AbstractMetricsReporter接口允许插入将被通知新指标创建的类。
  • 默认:[]

43、metrics_num_samples(int)

  • 为计算指标而维护的样本数。
  • 默认值:2

44、metrics_sample_window_ms(int)

  • 用于计算指标的样本的最大时间(以毫秒为单位)。
  • 默认值:30000

45、selector(selectors.BaseSelector)

  • 提供用于I/O多路复用的特定选择器实现。
  • 默认值: selectors.DefaultSelector

46、exclude_internal_topics(bool)

  • Katka中有两个内部的主题:__consumer_offsets和__transaction_state。
  • exclude_internal_topics用来指定Kafka中的内部主题是否可以向消费者公开,默认值为true。
  • 如果设置为true,那么只能使用subscribe(Collection)的方式而不能使用subscribe(Pattern)的方式来订阅内部主题,设置为false则没有这个限制。

47、sasl_mechanism(str)

  • 为SASL_PLAINTEXT或SASL_SSL配置security_protocol时的身份验证机制。有效值为:PLAIN、GSSAPI、OAUTHBEARER、SCRAM-SHA-256、SCRAM-SHA-512。

48、sasl_plain_username(str)

  • sasl PLAIN和SCRAM身份验证的用户名。如果sasl_mechanism是PLAIN或SCRAM机制之一,则为必需。

49、sasl_plain_password(str)

  • sasl PLAIN和SCRAM身份验证的密码。如果sasl_mechanism是PLAIN或SCRAM机制之一,则为必需。

50、sasl_kerberos_service_name(str)

  • 要包含在GSSAPI sasl机制握手中的服务名称。
  • 默认值:‘kafka’

51、sasl_kerberos_domain_name(str)

  • 在GSSAPI sasl机制握手中使用的kerberos域名。
  • 默认值:引导服务器之一

52、sasl_oauth_token_provider(AbstractTokenProvider)

  • OAuthBearer令牌提供程序实例。(参见 kafka.oauth.abstract)。
  • 默认值:None

2、KafkaConsume类的方法

1、assign(partitions)

  • 为消费者手动分配分区
  • 此接口不支持增量分配,将替换以前的分配(如果有的话)。
  • 通过此方法手动分配主题不使用消费者的组管理功能。因此,当组成员或集群和主题元数据发生变化时,不会触发重新平衡操作。
  • 注意,不可能同时使用assign()的手动分区分配和subscribe()的组分配。
  • partitions(list of TopicPartition)为这个实例赋值。

2、assignment()

  • 获取当前分配给该消费者的TopicPartitions(主题分区)集合。
  • 如果使用assign()直接分配分区,那么这将简单地返回先前分配的相同分区。如果使用subscribe()订阅主题,那么这将给出当前分配给使用者的主题分区集(如果还没有分配,或者分区正在重新分配,则可能为None)。

3、beginning_offsets(partitions)

  • 获取给定分区的第一个偏移量。
  • 此方法不会改变分区的当前使用者位置。
  • 如果分区不存在,此方法可能会无限期阻塞。
  • partitions(list):要获取偏移量的TopicPartition实例列表。

4、bootstrap_connected()

  • 如果引导程序已连接,则返回True。

5、close(autocommit=True)

  • 关闭消费者,无限期地等待任何需要的清理。
  • autocommit(bool):如果为该消费者配置了自动提交,则此可选标志将导致该消费者在关闭之前尝试提交任何挂起的已使用偏移量。
  • 默认值:True

6、commit(offsets=None)

  • 提交偏移量到kafka,阻塞直到成功或错误。
  • 它仅向Kafka提交偏移量。使用此API提交的偏移量将在每次重新平衡后的第一次获取和启动时使用。因此,如果你需要在Kafka以外的任何地方存储偏移量,则不应使用此API。为了避免在消费者重启时重新处理最后读取的消息,提交的偏移量应该是你的应用程序应该消费的下一个消息,即:last_offset + 1。
  • 阻塞,直到提交成功或遇到不可恢复的错误(在这种情况下,它被抛出给调用者)。
  • 目前只支持kafka-topic偏移存储(不支持zookeeper)。
  • offsets(dict, optional):使用配置的group_id提交的字典({TopicPartition: OffsetAndMetadata} )。默认为所有订阅分区当前使用的偏移量。

7、commit_async(offsets=None, callback=None)

  • 异步提交偏移量给kafka,可以选择触发回调。
  • 它仅向Kafka提交偏移量。使用此API提交的偏移量将在每次重新平衡后的第一次获取和启动时使用。因此,如果你需要在Kafka以外的任何地方存储偏移量,这个API不应该被使用。为了避免在消费者重启时重新处理最后读取的消息,提交的偏移量应该是你的应用程序应该消费的下一个消息,即:last_offset + 1。
  • 这是一个异步调用,不会阻塞。遇到的任何错误要么被传递给回调函数(如果提供的话),要么被丢弃。
  • offsets(dict, optional):使用配置的group_id提交的字典({TopicPartition: OffsetAndMetadata} )。默认为所有订阅分区当前使用的偏移量。
  • callback(callable, optional):作为callback(offsets, response)调用,响应为Exception或OffsetCommitResponse结构。此回调可用于在提交请求完成时触发自定义操作。

8、committed(partition, metadata=False)

  • 获取给定分区的最后提交偏移量。
  • 在发生故障时,这个偏移量将被用作消费者的位置。
  • 如果有问题的分区没有分配给这个消费者,或者消费者还没有初始化其提交偏移量的缓存,这个调用可能会阻塞执行远程调用。
  • partition(TopicPartition):分区。
  • metadata(bool, optional):如果为True,则返回OffsetAndMetadata结构,而不是offset int。默认值:False。
  • 返回值:最后提交的偏移量(int或OffsetAndMetadata),如果之前没有提交,则为None。

9、end_offsets(partitions)

  • 获取给定分区的最后偏移量。一个分区的最后偏移量就是即将到来的消息的偏移量,即最后一个可用消息的偏移量 + 1。
  • 此方法不会改变分区的当前使用者位置。
  • 如果分区不存在,此方法可能会无限期阻塞。
  • partitions(list):要获取偏移量的TopicPartition实例列表。

10、highwater(partition)

  • 最后一次已知的highwater偏移量。
  • highwater偏移量是将分配给产生的下一条消息的偏移量。通过与报告的位置进行比较,可能对计算滞后是有用的。注意,position和highwater都指向下一个偏移量,即highwater偏移量比最新的可用消息大1。
  • highwater偏移量会在FetchResponse消息中返回,所以如果这个分区还没有发送fetchrequest,那么highwater偏移量将不可用。
  • partition(TopicPartition):要检查的分区
  • 返回值:偏移(如果可用)

11、metrics(raw=False)

  • 获取消费者性能指标。
  • 注意:这是一个不稳定的方法。在未来的版本中,它可能会在没有任何警告的情况下发生变化。
  • 这是从Java消费者中移植的,详细信息请参见:https://kafka.apache.org/documentation/#consumer_monitoring

12、offsets_for_times(timestamps)

  • 根据时间戳查找给定分区的偏移量。每个分区返回的偏移量是时间戳大于或等于对应分区中给定时间戳的最早的偏移量。
  • 这是一个阻塞调用。不必为消费者分配分区。
  • 如果某个分区中的消息格式版本在0.10.0之前,即消息没有时间戳,则该分区将返回None。如果分区中没有消息,也将返回None。
  • 如果分区不存在,此方法可能会无限期阻塞。
  • timestamps(dict):从分区映射到要查找的时间戳。单位应该是自纪元开始的毫秒数(午夜1970年1月1日(UTC))。字典{TopicPartition: int}

12、partitions_for_topic(topic)

  • 获取主题的分区,此方法首先检查本地元数据缓存。
  • 如果没有找到主题(因为主题不存在、用户没有权限查看主题、或者没有元数据缓存),那么它将向集群发出元数据更新调用。
  • topic(str):要检查的主题。

13、pause(*partitions)

  • 暂停从某些分区中获取数据。
  • 以后调用poll()将不会从这些分区返回任何记录,直到使用resume()恢复它们 。
  • 注意:此方法不会影响分区订阅。特别是,当使用自动分配时,它不会导致组重新平衡。
  • *partitions(TopicPartition):要暂停的分区

14、paused()

  • 使用paused()获取被暂停的分区集合。

15、poll(timeout_ms=0, max_records=None, update_offsets=True)

  • 从指定的主题/分区获取数据。
  • 通过主题分区批量获取和返回记录。在每次轮询中,消费者将尝试使用上次消费的偏移量作为起始偏移量并依次获取。最后消耗的偏移量可以通过seek()手动设置,也可以自动设置为订阅的分区列表的最后提交偏移量。
  • 与迭代器接口不兼容--只能使用其中一个,不能同时使用两个。
  • timeout_ms(int, optional):如果缓冲区中没有数据,则轮询中等待的毫秒数。如果为0,则立即返回缓冲区中当前可用的任何记录,否则返回空。不能是负的。默认值:0
  • max_records(int, optional):一次调用poll()返回的最大记录数。默认值:从max_poll_records继承值。

16、position(partition)

  • 获取将要使用的下一条消息的偏移量
  • partition(TopicPartition):要检查的分区

17、resume(*partitions)

  • 从指定的(暂停的)分区恢复获取。
  • *partitions(TopicPartition):要恢复的分区。

18、seek(partition, offset)

  • 重置偏移量,并从重置的偏移量开始消费。
  • 覆盖消费者将在下一次poll()中使用的获取偏移量。如果对同一个分区多次调用此API,则将在下一次poll()中使用最新的偏移量。
  • 注意:如果在消费过程中任意使用这个API来重置获取偏移量,您可能会丢失数据。
  • partition(TopicPartition):用于查找操作的分区
  • offset(int):分区中的消息偏移量

19、seek_to_beginning(*partitions)

  • 为分区寻找最旧的可用偏移量。
  • *partitions:可以选择提供特定的TopicPartitions,否则默认为所有分配的分区。

20、seek_to_end(*partitions)

  • 查找分区的最新可用偏移量。
  • *partitions:可以选择提供特定的TopicPartitions,否则默认为所有分配的分区。

21、subscribe(topics=(), pattern=None, listener=None)

  • 以列表或正则表达式模式的方式订阅主题。
  • 分区将通过组协调器动态分配。主题订阅不是增量的:此列表将替换当前的赋值(如果有)。
  • 此方法与assign()不兼容
  • topics(list):订阅的主题列表。
  • pattern(str):以正则表达式的形式订阅特定模式的主题。必须提供topics或pattern参数,但只能提供其中一个。
  • listener(ConsumerRebalanceListener):可选地包括监听器回调,它将在每次重新平衡操作之前和之后被调用。
    • 作为组管理的一部分,消费者将跟踪属于特定组的消费者列表,并在以下事件之一触发时触发重新平衡操作:
      • 任何订阅主题的分区数发生变化
      • 创建或删除主题
      • 消费者组的现有成员死亡
      • 一个新成员被添加到消费者组
    • 当这些事件中的任何一个被触发时,提供的侦听器将首先被调用以指示消费者的分配已被撤销,然后在收到新分配时再次调用。请注意,此侦听器将立即覆盖在先前的subscribe调用中设置的任何侦听器。但是,可以保证通过此接口撤销/分配的分区来自在此调用中订阅的主题。

22、subscription()

  • 获取当前主题订阅。

23、topics()

  • 获取用户有权查看所有主题。这将始终向集群发出远程调用以获取最新的信息。

24、unsubscribe()

  • 取消订阅所有主题并清除所有分配的分区。

2、消费者与消费组

  • 消费者(Consumer)负责订阅Kaflca中的主题(Topic),并且从订阅的主题上拉取消息。
  • 与其他一些消息中间件不同的是:在Kaflca的消费理念中还有一层消费组(Consumer Group)的概念,每个消费者都有一个对应的消费组。当消息发布到主题后,只会被投递给订阅它的每个消费组中的一个消费者(主题的分区按照既有的规则分配给消费组中的消费者),即一个分区只能被消费组中的一个消费者所消费

1、消费者、消费组与分区

  • 某个主题中共有4个分区(Partition):P0、Pl、P2、P3。有两个消费组A和B都订阅了这个主题,消费组A中有4个消费者(C0、Cl、C2和C3),消费组B中有2个消费者(C4和C5)。
  • 按照Kafka默认的规则,分配结果是消费组A中的每一个消费者分配到1个分区,消费组B中的每一个消费者分配到2个分区,两个消费组之间互不影响。
  • 每个消费者只能消费所分配到的分区中的消息

2、分区的分配策略

  • 消费组内的消费者个数变化时所对应的分区分配的演变(默认分区分配策略)
    • 假设目前某消费组内只有一个消费者C0,订阅了一个主题,这个主题包含7个分区:P0、Pl、P2、P3、P4、P5、P6。也就是说,这个消费者C0订阅了7个分区,具体分配情形参考图1。
    • 此时消费组内又加入了一个新的消费者C1,按照既定的逻辑,需要将原来消费者C0的部分分区分配给消费者C1消费,如图2所示。消费者C0和C1各自负责消费所分配到的分区,彼此之间并无逻辑上的干扰。
    • 紧接着消费组内又加入了一个新的消费者C2,消费者C0、C1和C2按照图3中的方式各自负责消费所分配到的分区。
    • 消费者与消费组这种模型可以让整体的消费能力具备横向伸缩性,可以增加(或减少)消费者的个数来提高(或降低)整体的消费能力。
      对于分区数固定的清况,一味地增加消费者并不会让消费能力一直得到提升,如果消费者过多,出现了消费者的个数大于分区个数的清况,就会有消费者分配不到任何分区。如图4,一共有8个消费者,7个分区,那么最后的消费
      者C7由于分配不到任何分区而无法消费任何消息。

  • 可以通过消费者客户端参数partition_assignment_strategy来设置消费者与订阅主题之间的分区分配策略。

3、消息投递模式

  • 对于消息中间件而言,一般有两种消息投递模式:点对点(P2P,Point-to-Point)模式和发布/订阅(Pub/Sub)模式。
    • 点对点模式是基于队列的,消息生产者发送消息到队列,消息消费者从队列中接收消息。
    • 发布订阅模式定义了如何向一个内容节点发布和订阅消息,这个内容节点称为主题(Topic),主题可以认为是消息传递的中介,消息发布者将消息发布到某个主题,而消息订阅者从主题中订阅消息。主题使得消息的订阅者和发布者互相保持独立,不需要进行接触即可保证消息的传递,发布/订阅模式在消息的一对多广播时采用。
  • Kafka同时支待两种消息投递模式,而这正是得益于消费者与消费组模型的契合:
    • 如果所有的消费者都隶属于同一个消费组,那么所有的消息都会被均衡地投递给每一个消费者,即每条消息只会被一个消费者处理,这就相当于点对点模式的应用。
    • 如果所有的消费者都隶属于不同的消费组,那么所有的消息都会被广播给所有的消费者,即每条消息会被所有的消费者处理,这就相当于发布/订阅模式的应用。
  • 消费组是一个逻辑上的概念,它将多个消费者归为一类:
    • 每一个消费者只隶属于一个消费组。
    • 每一个消费组都会有一个固定的名称。
    • 消费者在进行消费前需要指定其所属消费组的名称,可以通过消费者客户端参数group_id来配置,默认值为空字符串。
  • 消费者并非逻辑上的概念,它是实际的应用实例,它可以是一个线程,也可以是一个进程。同一个消费组内的消费者既可以部署在同一台机器上,也可以部署在不同的机器上。

3、消费者客户端开发

  • 一个正常的消费逻辑需要具备以下几个步骤:
    • (1)配置消费者客户端参数及创建相应的消费者实例。
    • (2)订阅主题。
    • (3)拉取消息并消费。
    • (4)提交消费位移。
    • (5)关闭消费者实例。

1、KafkaProducer的简单使用

  • bootstrap_servers参数只需要配置部分broker节点的地址即可,不需要配置所有broker节点的地址,因为客户端可以自己发现其他broker节点的地址,具体的参看元数据更新。

示例1:消费者

from kafka import KafkaConsumer, TopicPartition
# consumer = KafkaConsumer('topic-name1', bootstrap_servers=['192.168.248.128:9092', '192.168.248.131:9092'])    #创建消费者,并订阅一个主题
consumer = KafkaConsumer(bootstrap_servers=['192.168.248.128:9092', '192.168.248.131:9092'])    #创建消费者,没有订阅主题,可以使用assign()或subscribe()订阅
consumer.subscribe(topics=['topic-name1', 'topic-name2'])    #订阅主题
# consumer.assign([TopicPartition(topic='topic-name1', partition=0), TopicPartition(topic='topic-name1', partition=1)])    #订阅分区
print(consumer.topics())                                     #获取用户有权查看的所有主题
print(consumer.partitions_for_topic('topic-name1'))          #获取主题的分区信息
print(consumer.subscription())                               #获取当前主题订阅
print(consumer.assignment())                                 #获取当前分配给该消费者的TopicPartitions(主题分区)集合
print(consumer.beginning_offsets(
    [TopicPartition(topic='topic-name1', partition=0),
     TopicPartition(topic='topic-name1', partition=1)]))                           #获取给定分区的第一个偏移量
print(consumer.end_offsets([TopicPartition(topic='topic-name1', partition=0),
                            TopicPartition(topic='topic-name1', partition=1)]))    #获取给定分区的最后偏移量
print(consumer.position((TopicPartition(topic='topic-name1', partition=0))))       #获取给定分区将要使用的下一条消息的偏移量
consumer.seek(TopicPartition(topic='topic-name1', partition=0), 25)                #重置偏移量,并从重置的偏移量开始消费。
# print(consumer.metrics())                                                        #获取消费者性能指标
for message in consumer:
    print("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition, message.offset, message.key, message.value))

示例2:生产者

from kafka import KafkaProducer
producer = KafkaProducer(bootstrap_servers=['192.168.248.128:9092', '192.168.248.131:9092'])
producer.send('topic-name1', b'topic-name1-value1')
producer.send('topic-name1', value=b'topic-name1-value2', key=b'topic-name1-key2')
producer.close()

2、订阅主题和分区

  • subscribe()订阅一个或多个主题。
  • assign()直接订阅某些主题的特定分区。
  • assign()和subscribe()不能同时使用
  • 通过subscribe()方法订阅主题具有消费者自动再均衡的功能,在多个消费者的情况下可以根据分区分配策略来自动分配各个消费者与分区的关系。当消费组内的消费者增加或减少时,分区分配关系会自动调整,以实现消费负载均衡及故障自动转移。
  • 通过assign()方法订阅分区时,是不具备消费者自动均衡的功能的。

1、订阅主题

  • 在创建好消费者之后,需要为该消费者订阅相关的主题。
  • 一个消费者可以使用subscribe()方法订阅一个或多个主题,既可以以集合的形式订阅多个主题,也可以以正则表达式的形式订阅特定模式的主题。
  • 如果前后两次订阅了不同的主题,那么消费者以最后一次的为准。

示例1:订阅一个主题,列表

from kafka import KafkaConsumer
consumer = KafkaConsumer(bootstrap_servers=['192.168.248.128:9092', '192.168.248.130:9092', '192.168.248.131:9092'])
consumer.subscribe(topics=['topic-name1'])
for message in consumer:
    print("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition, message.offset, message.key, message.value))

示例2:订阅多个主题,列表

from kafka import KafkaConsumer
consumer = KafkaConsumer(bootstrap_servers=['192.168.248.128:9092', '192.168.248.130:9092', '192.168.248.131:9092'])
consumer.subscribe(topics=['topic-name1', 'topic-name2'])
for message in consumer:
    print("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition, message.offset, message.key, message.value))

示例3:订阅多个主题,正则模式

from kafka import KafkaConsumer
consumer = KafkaConsumer(bootstrap_servers=['192.168.248.128:9092', '192.168.248.130:9092', '192.168.248.131:9092'])
consumer.subscribe(pattern='^topic.*')
for message in consumer:
    print("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition, message.offset, message.key, message.value))

2、订阅分区

  • 可以使用assign()方法直接订阅某些主题的特定分区。
  • TopicPartition类只有2个属性:topic和partition,分别代表分区所属的主题和自身的分区编号。
from kafka import KafkaConsumer, TopicPartition
consumer = KafkaConsumer(bootstrap_servers=['192.168.248.128:9092', '192.168.248.130:9092', '192.168.248.131:9092'])
consumer.assign([TopicPartition(topic='topic-name1', partition=0), TopicPartition(topic='topic-name1', partition=1)])
for message in consumer:
    print("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition, message.offset, message.key, message.value))

3、取消订阅

  • 可以使用KafkaConsumer中的unsubscribe()方法来取消主题的订阅。
    • 可以取消通过subscribe(Collection)方式实现的订阅。
    • 可以取消通过subscribe(Pattem)方式实现的订阅。
    • 可以取消通过assign(Collection)方式实现的订阅。

示例1:可以取消一下四种订阅方式

consumer = KafkaConsumer('topic-name1', bootstrap_servers=['192.168.248.128:9092', '192.168.248.131:9092'])
consumer.subscribe(topics=['topic-name1', 'topic-name2'])
consumer.subscribe(pattern='topic.*')
consumer.assign([TopicPartition(topic='topic-name1', partition=0), TopicPartition(topic='topic-name1', partition=1)])

consumer.unsubscribe()    #取消订阅

3、反序列化

示例1:

from kafka import KafkaConsumer
consumer = KafkaConsumer(value_deserializer=lambda m: m.decode('utf8'), key_deserializer=lambda m: m.decode('utf8'),
                         bootstrap_servers=['192.168.248.128:9092', '192.168.248.131:9092'])
consumer.subscribe(topics=['topic-name1'])
for message in consumer:
    print("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition, message.offset, message.key, message.value))

示例2:

###消费者
from kafka import KafkaConsumer
import json
consumer = KafkaConsumer(value_deserializer=lambda m: json.loads(m.decode('utf8')),
                         bootstrap_servers=['192.168.248.128:9092', '192.168.248.131:9092'])
consumer.subscribe(topics=['topic-name1'])
for message in consumer:
    print("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition, message.offset, message.key, message.value))

###生产者
from kafka import KafkaProducer
import json
producer = KafkaProducer(value_serializer=lambda m: json.dumps(m).encode('utf8'), bootstrap_servers=['192.168.248.128:9092', '192.168.248.130:9092', '192.168.248.131:9092'])
producer.send('topic-name1', {'key': 'value'})
producer.close()

4、拉取消息

  • 消息的消费一般有两种模式:推模式和拉模式。
    • 推模式是服务端主动将消息推送给消费者。
    • 拉模式是消费者主动向服务端发起请求来拉取消息(使用poll()方法手动拉取消息)。

1、推模式

  • 消费者消费到的每条消息的类型为ConsumerRecord(注意与ConsumerRecords的区别),这个和生产者发送的消息类型ProducerRecord相对应,不过ConsumerRecord中的内容更加丰富。
    • topic和partition这两个字段分别代表消息所属主题的名称和所在分区的编号。
    • offset表示消息在所属分区的偏移量。
    • timestamp表示时间戳,与此对应的timestampType表示时间戳的类型。
    • timestamp_Type有两种类型:CreateTime和LogAppendTime,分别代表消息创建的时间戳和消息追加到日志的时间戳。
    • key和value分别表示消息的键和消息的值,一般业务应用要读取的就是value
    • headers表示消息的头部内容。
    • checksum是CRC32的校验值。
    • serialized_key_size和serialized_value_size分别表示key和value经过序列化之后的大小,如果key为空,则serialized_key_size值为-1,同样,如果value为空,则serialized_value_size的值也会为-1。
from kafka import KafkaConsumer
consumer = KafkaConsumer('topic-name1', bootstrap_servers=['192.168.248.128:9092', '192.168.248.131:9092'])
for message in consumer:
    print(message)
    print("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition, message.offset, message.key, message.value))

<<<
ConsumerRecord(topic='topic-name1', partition=2, offset=31, timestamp=1634229071181, timestamp_type=0, key=None, value=b'{"key": "value"}', headers=[], checksum=None, serialized_key_size=-1, serialized_value_size=16, serialized_header_size=-1)
topic-name1:2:31: key=None value=b'{"key": "value"}'

2、拉模式

  • Kafka中的消息消费是一个不断轮询的过程,消费者所要做的就是重复地调用poll()方法,而poll()方法返回的是所订阅的主题(分区)上的一组消息。
  • 对于poll()方法而言,如果某些分区中没有可供消费的消息,那么此分区对应的消息拉取的结果就为空;如果订阅的所有分区中都没有可供消费的消息,那么poll()方法返回为空的消息集合。
  • poll()方法里有一个超时时间参数timeout,用来控制poll()方法的阻塞时间,在消费者的缓冲区里没有可用数据时会发生阻塞。
  • timeout的设置取决于应用程序对响应速度的要求,比如需要在多长时间内将控制权移交,给执行轮询的应用线程。
    • 可以直接将timeout设置为0,这样poll()方法会立刻返回,而不管是否已经拉取到了消息。
    • 如果应用线程唯一的工作就是从Kaftka中拉取并消费消息,则可以将个参数设置的大些。
  •  poll()方法的返回值类型是dict,它用来表示一次拉取操作所获得的消息集,内部包含了若干ConsumerRecord。
from kafka import KafkaConsumer
import time
consumer = KafkaConsumer('topic-name1', bootstrap_servers=['192.168.248.128:9092', '192.168.248.131:9092'])
while True:
    message = consumer.poll(timeout_ms=5)
    print(message)
    print(type(message))
    time.sleep(5)

<<<
{TopicPartition(topic='topic-name1', partition=3): [ConsumerRecord(topic='topic-name1', partition=3, offset=24, timestamp=1634229938005, timestamp_type=0, key=None, value=b'{"key": "value"}', headers=[], checksum=None, serialized_key_size=-1, serialized_value_size=16, serialized_header_size=-1), ConsumerRecord(topic='topic-name1', partition=3, offset=25, timestamp=1634229938005, timestamp_type=0, key=None, value=b'{"key": "value2"}', headers=[], checksum=None, serialized_key_size=-1, serialized_value_size=17, serialized_header_size=-1)]}
<class 'dict'>

5、位移提交

  • 对于Kafka中的分区而言,它的每条消息都有唯一的offset,用来表示消息在分区中对应的位置。对于消费者而言,它也有一个offset的概念,消费者使用offset来表示消费到分区中某个消息所在的位置。
  • 单词"offiet"可以翻译为“偏移量”,也可以翻译为“位移”。笔者对offset做了一些区分:
    • 对于消息在分区中的位置,将offset称为“偏移量”。
    • 对于消费者消费到的位置,将offset称为“位移”,或更明确地称之为“消费位移”。
    • 做这一区分的目的是让读者在遇到offset的时候可以很容易甄别出是在讲分区存储层面的内容,还是在讲消费层面的内容。
  • 当然,对于一条消息而言,它的偏移量和消费位移是相等的。

1、什么是位移提交

  • 在每次调用poll()方法时,它返回的是还没有被消费过的消息集,要做到这一点,就需要记录上一次消费时的消费位移。并且这个消费位移必须做持久化保存,而不是单单保存在内存中。
    • 否则消费者重启之后就无法知晓之前的消费位移。
    • 再考虑一种情况,当有新的消费者加入时,那么必然会有再均衡的动作,对于同一分区而言,它可能在再均衡动作之后分配给新的消费者,如果不持久化保存消费位移,那么这个新的消费者也无法知晓之前的消费位移。
  • 在旧消费者客户端中,消费位移是存储在ZooKeeper中的。而在新消费者客户端中,消费位移存储在Katka内部的主题__consumer_offsets中。
  • 把将消费位移存储起来(持久化)的动作称为“提交”,消费者在消费完消息之后需要执行消费位移的提交。

1、正常的位移提交

  • x表示某一次拉取操作中此分区消息的最大偏移量,假设当前消费者已经消费了x位置的消息,那么就可以说消费者的消费位移为x,图中也用了lastConsumedOffset这个单词来标识它。
  • 不过需要非常明确的是,当前消费者需要提交的消费位移并不是x,而是x+1,对应于图中的position,它表示下一条需要拉取的消息的位置。

    • lastConsumedOfset:消费者最后消费的消息的位移。
    • position:消费者消费消息后需要提交(未提交)的位移。
      • position(partition)方法:获取将要使用的下一条消息的位移。(java的方法)
    • committed offst:表示已提交过的消费位移。
      • committed(partition, metadata=False)方法:获取给定分区的最后提交的消费位移。(java的方法)

2、重复消费和消息丢失

  • 对于位移提交的具体时机的把握也很有讲究,有可能会造成重复消费和消息丢失的现象。
    • 当前一次poll()操作所拉取的消息集为[x+2, x+7], x+2代表上一次提交的消费位移,说明已经完成了x+1之前(包括x+1在内)的所有消息的消费, x+5表示当前正在处理的位置。如果拉取到消息之后就进行了位移提交,即提交了x+8,那么当前消费x+5的时候遇到了异常,在故障恢复之后,我们重新拉取的消息是从x+8开始的。也就是说, x+5至x+7之间的消息并未能被消费,如此便发生了消息丢失的现象。
    • 再考虑另外一种情形,位移提交的动作是在消费完所有拉取到的消息之后才执行的,那么当消费x+5的时候遇到了异常,在故障恢复之后,我们重新拉取的消息是从x+2开始的。也就是说, x+2至x+4之间的消息又重新消费了一遍,故而又发生了重复消费的现象。

2、自动位移提交

  • 在Kafka中默认的消费位移的提交方式是自动提交,这个由消费者客户端参数enable.auto.commit配置,默认值为true,当然这个默认的自动提交不是每消费一条消息就提交一次,而是定期提交,这个定期的周期时间由客户端参数auto.commit.interval.ms配置,默认值为5秒,此参数生效的前提是enable.auto.comlit参数为true。
  • 在默认的方式下,消费者每隔5秒会将拉取到的每个分区中最大的消息位移进行提交。自动位移提交的动作是在poll()方法的逻辑里完成的,在每次真正向服务端发起拉取请求之前会检查是否可以进行位移提交,如果可以,那么就会提交上一次轮询的位移。
  • 在Kafka消费的编程逻辑中位移提交是一大难点,自动提交消费位移的方式非常简便,它免去了复杂的位移提交逻辑,让编码更简洁。但随之而来的是重复消费和消息丢失的问题。
  • 自动位移提交时,可能出现重复消费和消息丢失的情况如下:
    • 假设刚刚提交完一次消费位移,然后拉取一批消息进行消费,在下一次自动提交消费位移之前,消费者崩溃了,那么又得从上一次位移提交的地方重新开始消费,这样便发生了重复消费的现象(对于再均衡的情况同样适用)。我们可以通过减小位移提交的时间间隔来减小重复消息的窗口大小,但这样并不能避免重复消费的发送,而且也会使位移提交更加频繁。
    • 如图所示,拉取线程A不断地拉取消息并存入本地缓存,比如在BlockingQueue中,另一个处理线程B从缓存中读取消息并进行相应的逻辑处理。假设目前进行到了第y+1次拉取,以及第m次位移提交的时候,也就是x+6之前的位移已经确认提交了,处理线程B却还正在消费x+3的消息。此时如果处理线程B发生了异常,待其恢复之后会从第m此位移提交处,也就是x+6的位置开始拉取消息,那么x+3至x+6之间的消息就没有得到相应的处理,这样便发生消息丢失的现象。

3、手动位移提交

  • 自动位移提交的方式在正常情况下不会发生消息丢失或重复消费的现象,但是在编程的世界里异常无可避免,与此同时,自动位移提交也无法做到精确的位移管理。在Katka中还提供了手动位移提交的方式,这样可以使得开发人员对消费位移的管理控制更加灵活。很多时候并不是说拉取到消息就算消费完成,而是需要将消息写入数据库、写入本地缓存,或者是更加复杂的业务处理。在这些场景下,所有的业务处理完成才能认为消息被成功消费,手动的提交方式可以让开发人员根据程序的逻辑在合适的地方进行位移提交。
  • 开启手动提交功能的前提是消费者客户端参数enable.auto.commit配置为false:
    • 手动提交可以细分为同步提交和异步提交,对应于KafkaConsumer中的commitSync()和commitAsync()两种类型的方法。
    • python中对应的是commit()和commit_async()方法。
  • commitSync()方法会根据poll()方法拉取的最新位移来进行提交,只要没有发生不可恢复的错误(Unrecoverable Error),它就会阻塞消费者线程直至位移提交完成。对于不可恢复的错误,比如CommitFailedException,WakeupException.InterruptException,AuthenticatiolException,AuthorizationException等,可以将其捕获并做针对性的处理。
  • 异步提交的方式(commitAsyncO)在执行的时候消费者线程不会被阻塞,可能在提交消费位移的结果还未返回之前就开始了新一次的拉取操作。异步提交可以使消费者的性能得到一定的增强。

6、控制或关闭消费

  • KafkaConsumer提供了对消费速度进行控制的方法,在有些应用场景下我们可能需要暂停某些分区的消费而先消费其他分区,当达到一定条件时再恢复这些分区的消费。
  • pause():暂停某些分区在拉取操作时返回数据给客户端。
  • resume():恢复某些分区(暂停的分区)向客户端返回数据的操作。
  • paused():返回被暂停的分区集合。
  • 使用一个while循环来包裹住poll()方法及相应的消费逻辑,如何优雅地退出这个循环也很有考究。
    • 不是以while(true)的形式做简单的包裹,而是使用while(isRunning.get())的方式,这样可以通过在其他地方设定isRunning.set(false)来退出while循环。

示例:一个相对完整的消费程序的逻辑可以参考下面的伪代码

consumer.subscribe(Arrays.asList(topic));
    try {
        while (running.get()) {
            //consumer.poll(***)
            //process the record
            //commit offset
	}
    } catch (WakeupException e) {
        //ingore the error
    } catch (Exception e) {
        //do some logic process.
    } finally {
        //maybe commit offset.
        consumer.close();
    }

7、指定位移消费

  • 正是有了消费位移的持久化,才使消费者在关闭、崩溃或者在遇到再均衡的时候,可以让接替的消费者能够根据存储的消费位移继续进行消费。

1、默认的消费位移

  • 试想一下,当一个新的消费组建立的时候,它根本没有可以查找的消费位移。或者消费组内的一个新消费者订阅了一个新的主题,它也没有可以查找的消费位移。当__consumer_ofisets主题中有关这个消费组的位移信息过期而被删除后,它也没有可以查找的消费位移。
  • 在Kafka中每当消费者查找不到所记录的消费位移时,就会根据消费者客户端参数auto.offset.reset的配置来决定从何处开始进行消费,这个参数为"latest"表示从分区末尾开始消费消息,这个参数为"earliest"表示从起始处开始消费消息。参考图3-9。
    • 为"latest"(默认值)时,消费者会从9开始进行消费(9是下一条要写入消息的位置),更加确切地说是从9开始拉取消息。
    • 为"earliest"时,那么消费者会从起始处,也就是0开始消费。

2、使用seek()方法指定消费位移

  • seek(partition, offset)方法中的参数partition表示分区,而offset参数用来指定从分区的哪个位置开始消费。
  • seek()方法只能重置消费者分配到的分区的消费位置,而分区的分配是在poll()方法的调用过程中实现的。也就是说,在执行seek()方法之前需要先执行一次poll()方法,等到分配到分区之后才可以重置消费位置。

示例:

  • 当poll()方法中的参数为0时,此方法立刻返回,那么poll()方法内部进行分区分配的逻辑就会来不及实施。也就是说,消费者此时并未分配到任何分区,如此assignment便是一个空集合。
  • 那么这里的timeout参数设置为多少合适呢?太短会使分配分区的动作失败,太长又有可能造成一些不必要的等待。可以通过KafkaConsumer的assignment()方法来判定是否分配到了相应的分区。
from kafka import KafkaConsumer, TopicPartition
import time
consumer = KafkaConsumer(bootstrap_servers=['192.168.248.130:9092', '192.168.248.131:9092'])
consumer.subscribe(topics=['topic-name1'])
lastConsumedOfset = -1
assignment = set()
while len(assignment) == 0:                               #在执行seek之前获取分区
    consumer.poll(10)
    assignment = consumer.assignment()
for i in assignment:
    beginning_offset = consumer.beginning_offsets([i])    #获取给定分区的第一个偏移量
    print('{}的第一个偏移量:{}'.format(i, beginning_offset[i]))
    end_offset = consumer.end_offsets([i])                #获取给定分区的最后偏移量
    print('{}的最后一个偏移量:{}'.format(i, end_offset[i]))
    consumer.seek(i, end_offset[i] - 5)
while True:
    message = consumer.poll(timeout_ms=1000)
    print(message)
    time.sleep(5)

3、offsetsforTimes()方法获取消费位移

  • 有时候我们并不知道特定的消费位置,却知道一个相关的时间点,比如我们想要消费昨天8点之后的消息,这个需求更符合正常的思维逻辑。此时我们无法直接使用seek()方法来追溯到相应的位置。KafkaConsumer同样考虑到了这种情况,它提供了一个offsetsforTimes()方法,通过timestamp来查询与此对应的分区位置。(python中为offsets_for_times)

8、再均衡

  • 再均衡是指分区的所属权从一个消费者转移到另一消费者的行为,它为消费组具备高可用性和伸缩性提供保障,使我们可以既方便又安全地删除消费组内的消费者或往消费组内添加消费者。
  • 不过在再均衡发生期间,消费组内的消费者是无法读取消息的。也就是说,在再均衡发生期间的这一小段时间内,消费组会变得不可用。另外,当一个分区被重新分配给另一个消费者时,消费者当前的状态也会丢失。比如消费者消费完某个分区中的一部分消息时还没有来得及提交消费位移就发生了再均衡操作,之后这个分区又被分配给了消费组内的另一个消费者,原来被消费完的那部分消息又被重新消费一遍,也就是发生了重复消费。
  • 一般情况下,应尽量避免不必要的再均衡的发生。
posted @ 2021-10-09 23:55  麦恒  阅读(230)  评论(0编辑  收藏  举报