在kafka的消费者中,有一个非常关键的机制,那就是offset机制。它使得Kafka在消费的过程中即使挂了或者引发再均衡问题重新分配Partation,当下次重新恢复消费时仍然可以知道从哪里开始消费。它好比看一本书中的书签标记,每次通过书签标记(offset)就能快速找到该从哪里开始看(消费)。

Kafka对于offset的处理有两种提交方式:(1) 自动提交(默认的提交方式)   (2) 手动提交(可以灵活地控制offset)

(1) 自动提交偏移量:

Kafka中偏移量的自动提交是由参数enable_auto_commit和auto_commit_interval_ms控制的,当enable_auto_commit=True时,Kafka在消费的过程中会以频率为auto_commit_interval_ms向Kafka自带的topic(__consumer_offsets)进行偏移量提交,具体提交到哪个Partation是以算法:partation=hash(group_id)%50来计算的。

如:group_id=test_group_1,则partation=hash("test_group_1")%50=28

自动提交偏移量示例:

  1. import pickle
  2. import uuid
  3. from kafka import KafkaConsumer
  4.  
  5. consumer = KafkaConsumer(
  6. bootstrap_servers=['192.168.33.11:9092'],
  7. group_id="test_group_1",
  8. client_id="{}".format(str(uuid.uuid4())),
  9. max_poll_records=500,
  10. enable_auto_commit=True, # 默认为True 表示自动提交偏移量
  11. auto_commit_interval_ms=100, # 控制自动提交偏移量的频率 单位ms 默认是5000ms
  12. key_deserializer=lambda k: pickle.loads(k),
  13. value_deserializer=lambda v: pickle.loads(v)
  14. )
  15.  
  16. # 订阅消费round_topic这个主题
  17. consumer.subscribe(topics=('round_topic',))
  18.  
  19. try:
  20. while True:
  21. consumer_records_dict = consumer.poll(timeout_ms=1000)
  22.  
  23. # consumer.assignment()可以获取每个分区的offset
  24. for partition in consumer.assignment():
  25. print('主题:{} 分区:{},需要从下面的offset开始消费:{}'.format(
  26. str(partition.topic),
  27. str(partition.partition),
  28. consumer.position(partition)
  29. ))
  30.  
  31. # 处理逻辑.
  32. for k, record_list in consumer_records_dict.items():
  33. print(k)
  34. for record in record_list:
  35. print("topic = {},partition = {},offset = {},key = {},value = {}".format(
  36. record.topic, record.partition, record.offset, record.key, record.value)
  37. )
  38.  
  39. finally:
  40. # 调用close方法的时候会触发偏移量的自动提交 close默认autocommit=True
  41. consumer.close()

返回结果:

在上述代码中,最后调用consumer.close()时候也会触发自动提交,因为它默认autocommit=True,源码如下:

  1. def close(self, autocommit=True):
  2. """Close the consumer, waiting indefinitely for any needed cleanup.
  3.  
  4. Keyword Arguments:
  5. autocommit (bool): If auto-commit is configured for this consumer,
  6. this optional flag causes the consumer to attempt to commit any
  7. pending consumed offsets prior to close. Default: True
  8. """
  9. if self._closed:
  10. return
  11. log.debug("Closing the KafkaConsumer.")
  12. self._closed = True
  13. self._coordinator.close(autocommit=autocommit)
  14. self._metrics.close()
  15. self._client.close()
  16. try:
  17. self.config['key_deserializer'].close()
  18. except AttributeError:
  19. pass
  20. try:
  21. self.config['value_deserializer'].close()
  22. except AttributeError:
  23. pass
  24. log.debug("The KafkaConsumer has closed.")

对于自动提交偏移量,如果auto_commit_interval_ms的值设置的过大,当消费者在自动提交偏移量之前异常退出,将导致kafka未提交偏移量,进而出现重复消费的问题,所以建议auto_commit_interval_ms的值越小越好。

(2) 手动提交偏移量:

鉴于Kafka自动提交offset的不灵活性和不精确性(只能是按指定频率的提交),Kafka提供了手动提交offset策略。手动提交能对偏移量更加灵活精准地控制,以保证消息不被重复消费以及消息不被丢失。

对于手动提交offset主要有3种方式:1.同步提交  2.异步提交  3.异步+同步 组合的方式提交

1.同步手动提交偏移量

同步模式下提交失败的时候一直尝试提交,直到遇到无法重试的情况下才会结束,同时同步方式下消费者线程在拉取消息会被阻塞,在broker对提交的请求做出响应之前,会一直阻塞直到偏移量提交操作成功或者在提交过程中发生异常,限制了消息的吞吐量。

  1. """
  2. 同步的方式10W条消息 4.58s
  3. """
  4.  
  5. import pickle
  6. import uuid
  7. import time
  8. from kafka import KafkaConsumer
  9.  
  10. consumer = KafkaConsumer(
  11. bootstrap_servers=['192.168.33.11:9092'],
  12. group_id="test_group_1",
  13. client_id="{}".format(str(uuid.uuid4())),
  14. enable_auto_commit=False, # 设置为手动提交偏移量.
  15. key_deserializer=lambda k: pickle.loads(k),
  16. value_deserializer=lambda v: pickle.loads(v)
  17. )
  18.  
  19. # 订阅消费round_topic这个主题
  20. consumer.subscribe(topics=('round_topic',))
  21.  
  22. try:
  23. start_time = time.time()
  24. while True:
  25. consumer_records_dict = consumer.poll(timeout_ms=100) # 在轮询中等待的毫秒数
  26. print("获取下一轮")
  27.  
  28. record_num = 0
  29. for key, record_list in consumer_records_dict.items():
  30. for record in record_list:
  31. record_num += 1
  32. print("---->当前批次获取到的消息个数是:{}<----".format(record_num))
  33. record_num = 0
  34.  
  35. for k, record_list in consumer_records_dict.items():
  36. for record in record_list:
  37. print("topic = {},partition = {},offset = {},key = {},value = {}".format(
  38. record.topic, record.partition, record.offset, record.key, record.value)
  39. )
  40.  
  41. try:
  42. # 轮询一个batch 手动提交一次
  43. consumer.commit() # 提交当前批次最新的偏移量. 会阻塞 执行完后才会下一轮poll
  44. end_time = time.time()
  45. time_counts = end_time - start_time
  46. print(time_counts)
  47. except Exception as e:
  48. print('commit failed', str(e))
  49.  
  50. finally:
  51. consumer.close() # 手动提交中close对偏移量提交没有影响

从上述可以看出,每轮循一个批次,手动提交一次,只有当前批次的消息提交完成时才会触发poll来获取下一轮的消息,经测试10W条消息耗时4.58s

2.异步手动提交偏移量+回调函数

异步手动提交offset时,消费者线程不会阻塞,提交失败的时候也不会进行重试,并且可以配合回调函数在broker做出响应的时候记录错误信息。

  1. """
  2. 异步的方式手动提交偏移量(异步+回调函数的模式) 10W条消息 3.09s
  3. """
  4.  
  5. import pickle
  6. import uuid
  7. import time
  8. from kafka import KafkaConsumer
  9.  
  10. consumer = KafkaConsumer(
  11. bootstrap_servers=['192.168.33.11:9092'],
  12. group_id="test_group_1",
  13. client_id="{}".format(str(uuid.uuid4())),
  14. enable_auto_commit=False, # 设置为手动提交偏移量.
  15. key_deserializer=lambda k: pickle.loads(k),
  16. value_deserializer=lambda v: pickle.loads(v)
  17. )
  18.  
  19. # 订阅消费round_topic这个主题
  20. consumer.subscribe(topics=('round_topic',))
  21.  
  22. def _on_send_response(*args, **kwargs):
  23. """
  24. 提交偏移量涉及回调函数
  25. :param args: args[0] --> {TopicPartition:OffsetAndMetadata} args[1] --> Exception
  26. :param kwargs:
  27. :return:
  28. """
  29. if isinstance(args[1], Exception):
  30. print('偏移量提交异常. {}'.format(args[1]))
  31. else:
  32. print('偏移量提交成功')
  33.  
  34. try:
  35. start_time = time.time()
  36. while True:
  37. consumer_records_dict = consumer.poll(timeout_ms=10)
  38.  
  39. record_num = 0
  40. for key, record_list in consumer_records_dict.items():
  41. for record in record_list:
  42. record_num += 1
  43. print("当前批次获取到的消息个数是:{}".format(record_num))
  44.  
  45. for record_list in consumer_records_dict.values():
  46. for record in record_list:
  47. print("topic = {},partition = {},offset = {},key = {},value = {}".format(
  48. record.topic, record.partition, record.offset, record.key, record.value))
  49.  
  50. # 避免频繁提交
  51. if record_num != 0:
  52. try:
  53. consumer.commit_async(callback=_on_send_response)
  54. except Exception as e:
  55. print('commit failed', str(e))
  56.  
  57. record_num = 0
  58.  
  59. finally:
  60. consumer.close()

对于args参数:args[0]是一个dict,key是TopicPartition,value是OffsetAndMetadata,表示该主题下的partition对应的offset;args[1]在提交成功是True,提交失败时是一个Exception类。

对于异步提交,由于不会进行失败重试,当消费者异常关闭或者触发了再均衡前,如果偏移量还未提交就会造成偏移量丢失。

3.异步+同步 组合的方式提交偏移量

针对异步提交偏移量丢失的问题,通过对消费者进行异步批次提交并且在关闭时同步提交的方式,这样即使上一次的异步提交失败,通过同步提交还能够进行补救,同步会一直重试,直到提交成功。

  1. """
  2. 同步和异步组合的方式提交偏移量
  3. """
  4.  
  5. import pickle
  6. import uuid
  7. import time
  8. from kafka import KafkaConsumer
  9.  
  10. consumer = KafkaConsumer(
  11. bootstrap_servers=['192.168.33.11:9092'],
  12. group_id="test_group_1",
  13. client_id="{}".format(str(uuid.uuid4())),
  14. enable_auto_commit=False, # 设置为手动提交偏移量.
  15. key_deserializer=lambda k: pickle.loads(k),
  16. value_deserializer=lambda v: pickle.loads(v)
  17. )
  18.  
  19. # 订阅消费round_topic这个主题
  20. consumer.subscribe(topics=('round_topic',))
  21.  
  22. def _on_send_response(*args, **kwargs):
  23. """
  24. 提交偏移量涉及的回调函数
  25. :param args:
  26. :param kwargs:
  27. :return:
  28. """
  29. if isinstance(args[1], Exception):
  30. print('偏移量提交异常. {}'.format(args[1]))
  31. else:
  32. print('偏移量提交成功')
  33.  
  34. try:
  35. start_time = time.time()
  36. while True:
  37. consumer_records_dict = consumer.poll(timeout_ms=100)
  38.  
  39. record_num = 0
  40. for key, record_list in consumer_records_dict.items():
  41. for record in record_list:
  42. record_num += 1
  43. print("---->当前批次获取到的消息个数是:<----".format(record_num))
  44. record_num = 0
  45.  
  46. for k, record_list in consumer_records_dict.items():
  47. print(k)
  48. for record in record_list:
  49. print("topic = {},partition = {},offset = {},key = {},value = {}".format(
  50. record.topic, record.partition, record.offset, record.key, record.value)
  51. )
  52.  
  53. try:
  54. # 轮询一个batch 手动提交一次
  55. consumer.commit_async(callback=_on_send_response)
  56. end_time = time.time()
  57. time_counts = end_time - start_time
  58. print(time_counts)
  59. except Exception as e:
  60. print('commit failed', str(e))
  61.  
  62. except Exception as e:
  63. print(str(e))
  64. finally:
  65. try:
  66. # 同步提交偏移量,在消费者异常退出的时候再次提交偏移量,确保偏移量的提交.
  67. consumer.commit()
  68. print("同步补救提交成功")
  69. except Exception as e:
  70. consumer.close()

通过finally在最后不管是否异常都会触发consumer.commit()来同步补救一次,确保偏移量不会丢失

Kafka提交offset机制的更多相关文章

  1. Kafka文件存储机制及partition和offset

    转载自:  https://yq.aliyun.com/ziliao/65771 参考:  Kafka集群partition replication默认自动分配分析    如何为kafka选择合适的p ...

  2. Kafka文件存储机制及offset存取

    Kafka是什么 Kafka是最初由Linkedin公司开发,是一个分布式.分区的.多副本的.多订阅者,基于zookeeper协调的分布式日志系统(也可以当做MQ系统),常见可以用于web/nginx ...

  3. kafka消费端提交offset的方式

    Kafka 提供了 3 种提交 offset 的方式 自动提交 复制 1234 consumer.commitSync(); 手动异步提交 offset 复制 1 consumer.commitAsy ...

  4. Kafka的存储机制以及可靠性

    一.kafka的存储机制 kafka通过topic来分主题存放数据,主题内有分区,分区可以有多个副本,分区的内部还细分为若干个segment. 所谓的分区其实就是在kafka对应存储目录下创建的文件夹 ...

  5. 图解 Kafka 水印备份机制

    高可用是很多分布式系统中必备的特征之一,Kafka 日志的高可用是通过基于 leader-follower 的多副本同步实现的,每个分区下有多个副本,其中只有一个是 leader 副本,提供发送和消费 ...

  6. kafka知识体系-kafka设计和原理分析-kafka文件存储机制

    kafka文件存储机制 topic中partition存储分布 假设实验环境中Kafka集群只有一个broker,xxx/message-folder为数据文件存储根目录,在Kafka broker中 ...

  7. Kafka文件存储机制那些事

    Kafka是什么 Kafka是最初由Linkedin公司开发,是一个分布式.分区的.多副本的.多订阅者,基于zookeeper协调的分布式日志系统(也可以当做MQ系统),常见可以用于web/nginx ...

  8. 关于SpringKafka消费者的几个监听器:[一次处理单条消息和一次处理一批消息]以及[自动提交offset和手动提交offset]

    自己在使用Spring Kafka 的消费者消费消息的时候的实践总结: 接口 KafkaDataListener 是spring-kafka提供的一个供消费者接受消息的顶层接口,也是一个空接口; pu ...

  9. spring-kafka手动提交offset

    <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...

随机推荐

  1. 安装nova后解决登录没账号问题

    找到laravel安装目录执行php artisan nova:user

  2. 三、Dockerfile的说明和编写

    一.docker hub的使用和说明: 我们要去找镜像,一般都会 https://hub.docker.com  的网站中去找,目前linux的容器,我们目前首选是alpine,因为容器非常小,可以满 ...

  3. 背包问题(01背包,完全背包,多重背包(朴素算法&&二进制优化))

    写在前面:我是一只蒟蒻~~~ 今天我们要讲讲动态规划中~~最最最最最~~~~简单~~的背包问题 1. 首先,我们先介绍一下  01背包 大家先看一下这道01背包的问题  题目  有m件物品和一个容量为 ...

  4. .NET框架 - NETCORE部署IIS

    .NET框架 - NETCORE部署IIS 1. 发布NETCORE项目. 2. 发布IIS 添加 网站 修改对应的程序池 为 ”无托管代码“. 3. 浏览网站. IIS 需提前安装好 .netcor ...

  5. H5_0003:JS禁用调试,禁用右键,监听F12事件的方法

    1,禁用调试 // 这个方法是防止恶意调试的 (function () { console["log"]("=============================== ...

  6. Node.js目录

    [相关学习] npm入门教程 [基础] (1) 初识Node.js (2) 开发环境和调试工具 (3) commonJs 规范 (4) node 概念(global.process进程.调试) (5) ...

  7. 一段充满bug的R程序,慎入 ...

    twitter的AnomalyDetection  官网效果图如下: 尝试写了下面这个R程序: get_specify_df <- function(start_ts,stop_ts,categ ...

  8. DataTable行列转置

    DataTable dtNew = new DataTable(); dtNew.Columns.Add("ColumnName", typeof(string)); ; i &l ...

  9. js中对cookie的操作及json数据与cookie结合的用法

    cookie的使用 添加cookie 添加cookie:document.cookie = “key=value”; // 一次写入一个键值对 document.cookie = 'test1=hel ...

  10. ruban后台项目启动。进入断点

    eclipse debug启动 进入断点显示-------------HikariPool-1 - Starting... 原因是 eclipse和tomcat的交互而产生的,在以debug模式启动t ...