深入了解如何基于Python读写Kafka


Posted in Python onDecember 31, 2019

这篇文章主要介绍了深入了解如何基于Python读写Kafka,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

本篇会给出如何使用python来读写kafka, 包含生产者和消费者.

以下使用kafka-python客户端

生产者

爬虫大多时候作为消息的发送端, 在消息发出去后最好能记录消息被发送到了哪个分区, offset是多少, 这些记录在很多情况下可以帮助快速定位问题, 所以需要在send方法后加入callback函数, 包括成功和失败的处理

# -*- coding: utf-8 -*-

'''
callback也是保证分区有序的, 比如2条消息, a先发送, b后发送, 对于同一个分区, 那么会先回调a的callback, 再回调b的callback
'''

import json
from kafka import KafkaProducer

topic = 'demo'


def on_send_success(record_metadata):
  print(record_metadata.topic)
  print(record_metadata.partition)
  print(record_metadata.offset)


def on_send_error(excp):
  print('I am an errback: {}'.format(excp))


def main():
  producer = KafkaProducer(
    bootstrap_servers='localhost:9092'
  )
  producer.send(topic, value=b'{"test_msg":"hello world"}').add_callback(on_send_success).add_callback(
    on_send_error)
  # close() 方法会阻塞等待之前所有的发送请求完成后再关闭 KafkaProducer
  producer.close()


def main2():
  '''
  发送json格式消息
  :return:
  '''
  producer = KafkaProducer(
    bootstrap_servers='localhost:9092',
    value_serializer=lambda m: json.dumps(m).encode('utf-8')
  )
  producer.send(topic, value={"test_msg": "hello world"}).add_callback(on_send_success).add_callback(
    on_send_error)
  # close() 方法会阻塞等待之前所有的发送请求完成后再关闭 KafkaProducer
  producer.close()
if __name__ == '__main__':
  # main()
  main2()

消费者

kafka的消费模型比较复杂, 我会分以下几种情况来进行说明

1.不使用消费组(group_id=None)

不使用消费组的情况下可以启动很多个消费者, 不再受限于分区数, 即使消费者数量 > 分区数, 每个消费者也都可以收到消息

# -*- coding: utf-8 -*-

'''
消费者: group_id=None
'''
from kafka import KafkaConsumer
topic = 'demo'
def main():
  consumer = KafkaConsumer(
    topic,
    bootstrap_servers='localhost:9092',
    auto_offset_reset='latest',
    # auto_offset_reset='earliest',
  )
  for msg in consumer:
    print(msg)
    print(msg.value)
  consumer.close()
if __name__ == '__main__':
  main()

2.指定消费组

以下使用pool方法来拉取消息

pool 每次拉取只能拉取一个分区的消息, 比如有2个分区1个consumer, 那么会拉取2次

pool 是如果有消息马上进行拉取, 如果timeout_ms内没有新消息则返回空dict, 所以可能出现某次拉取了1条消息, 某次拉取了max_records条

# -*- coding: utf-8 -*-

'''
消费者: 指定group_id
'''

from kafka import KafkaConsumer

topic = 'demo'
group_id = 'test_id'


def main():
  consumer = KafkaConsumer(
    topic,
    bootstrap_servers='localhost:9092',
    auto_offset_reset='latest',
    group_id=group_id,

  )
  while True:
    try:
      # return a dict
      batch_msgs = consumer.poll(timeout_ms=1000, max_records=2)
      if not batch_msgs:
        continue
      '''
      {TopicPartition(topic='demo', partition=0): [ConsumerRecord(topic='demo', partition=0, offset=42, timestamp=1576425111411, timestamp_type=0, key=None, value=b'74', headers=[], checksum=None, serialized_key_size=-1, serialized_value_size=2, serialized_header_size=-1)]}
      '''
      for tp, msgs in batch_msgs.items():
        print('topic: {}, partition: {} receive length: '.format(tp.topic, tp.partition, len(msgs)))
        for msg in msgs:
          print(msg.value)
    except KeyboardInterrupt:
      break

  consumer.close()


if __name__ == '__main__':
  main()

关于消费组

我们根据配置参数分为以下几种情况

  • group_id=None
    • auto_offset_reset='latest': 每次启动都会从最新出开始消费, 重启后会丢失重启过程中的数据
    • auto_offset_reset='latest': 每次从最新的开始消费, 不会管哪些任务还没有消费
  • 指定group_id
    • 全新group_id
      • auto_offset_reset='latest': 只消费启动后的收到的数据, 重启后会从上次提交offset的地方开始消费
      • auto_offset_reset='earliest': 从最开始消费全量数据
    • 旧group_id(即kafka集群中还保留着该group_id的提交记录)
      • auto_offset_reset='latest': 从上次提交offset的地方开始消费
      • auto_offset_reset='earliest': 从上次提交offset的地方开始消费

性能测试

以下是在本地进行的测试, 如果要在线上使用kakfa, 建议提前进行性能测试

producer

# -*- coding: utf-8 -*-

'''
producer performance

environment:
  mac
  python3.7
  broker 1
  partition 2
'''

import json
import time
from kafka import KafkaProducer

topic = 'demo'
nums = 1000000


def main():
  producer = KafkaProducer(
    bootstrap_servers='localhost:9092',
    value_serializer=lambda m: json.dumps(m).encode('utf-8')
  )
  st = time.time()
  cnt = 0
  for _ in range(nums):
    producer.send(topic, value=_)
    cnt += 1
    if cnt % 10000 == 0:
      print(cnt)

  producer.flush()

  et = time.time()
  cost_time = et - st
  print('send nums: {}, cost time: {}, rate: {}/s'.format(nums, cost_time, nums // cost_time))


if __name__ == '__main__':
  main()

'''
send nums: 1000000, cost time: 61.89236712455749, rate: 16157.0/s
send nums: 1000000, cost time: 61.29534196853638, rate: 16314.0/s
'''

consumer

# -*- coding: utf-8 -*-

'''
consumer performance
'''

import time
from kafka import KafkaConsumer

topic = 'demo'
group_id = 'test_id'


def main1():
  nums = 0
  st = time.time()

  consumer = KafkaConsumer(
    topic,
    bootstrap_servers='localhost:9092',
    auto_offset_reset='latest',
    group_id=group_id
  )
  for msg in consumer:
    nums += 1
    if nums >= 500000:
      break
  consumer.close()

  et = time.time()
  cost_time = et - st
  print('one_by_one: consume nums: {}, cost time: {}, rate: {}/s'.format(nums, cost_time, nums // cost_time))


def main2():
  nums = 0
  st = time.time()

  consumer = KafkaConsumer(
    topic,
    bootstrap_servers='localhost:9092',
    auto_offset_reset='latest',
    group_id=group_id
  )
  running = True
  batch_pool_nums = 1
  while running:
    batch_msgs = consumer.poll(timeout_ms=1000, max_records=batch_pool_nums)
    if not batch_msgs:
      continue
    for tp, msgs in batch_msgs.items():
      nums += len(msgs)
      if nums >= 500000:
        running = False
        break

  consumer.close()

  et = time.time()
  cost_time = et - st
  print('batch_pool: max_records: {} consume nums: {}, cost time: {}, rate: {}/s'.format(batch_pool_nums, nums,
                                              cost_time,
                                              nums // cost_time))


if __name__ == '__main__':
  # main1()
  main2()

'''
one_by_one: consume nums: 500000, cost time: 8.018627166748047, rate: 62354.0/s
one_by_one: consume nums: 500000, cost time: 7.698841094970703, rate: 64944.0/s


batch_pool: max_records: 1 consume nums: 500000, cost time: 17.975456953048706, rate: 27815.0/s
batch_pool: max_records: 1 consume nums: 500000, cost time: 16.711708784103394, rate: 29919.0/s

batch_pool: max_records: 500 consume nums: 500369, cost time: 6.654940843582153, rate: 75187.0/s
batch_pool: max_records: 500 consume nums: 500183, cost time: 6.854053258895874, rate: 72976.0/s

batch_pool: max_records: 1000 consume nums: 500485, cost time: 6.504687070846558, rate: 76942.0/s
batch_pool: max_records: 1000 consume nums: 500775, cost time: 7.047331809997559, rate: 71058.0/s
'''

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
深入解析Python中的线程同步方法
Jun 14 Python
python密码错误三次锁定(实例讲解)
Nov 14 Python
使用python生成目录树
Mar 29 Python
PyQt5实现无边框窗口的标题拖动和窗口缩放
Apr 19 Python
Python检查和同步本地时间(北京时间)的实现方法
Dec 03 Python
python执行CMD指令,并获取返回的方法
Dec 19 Python
python实现按行分割文件
Jul 22 Python
OpenCV里的imshow()和Matplotlib.pyplot的imshow()的实现
Nov 25 Python
python next()和iter()函数原理解析
Feb 07 Python
Django 设置admin后台表和App(应用)为中文名的操作方法
May 10 Python
基于python实现操作redis及消息队列
Aug 27 Python
pycharm 关闭search everywhere的解决操作
Jan 15 Python
Python面向对象之继承原理与用法案例分析
Dec 31 #Python
pytorch中nn.Conv1d的用法详解
Dec 31 #Python
Python实现剪刀石头布小游戏(与电脑对战)
Dec 31 #Python
Pytorch之卷积层的使用详解
Dec 31 #Python
Python中bisect的使用方法
Dec 31 #Python
pytorch中tensor张量数据类型的转化方式
Dec 31 #Python
Pytorch之parameters的使用
Dec 31 #Python
You might like
我的论坛源代码(四)
2006/10/09 PHP
PHP has encountered an Access Violation 错误的解决方法
2010/01/17 PHP
php操作excel文件 基于phpexcel
2010/07/02 PHP
PHP中的命名空间相关概念浅析
2015/01/22 PHP
php实现将上传word文件转为html的方法
2015/06/03 PHP
php自定义扩展名获取函数示例
2016/12/12 PHP
POST一个JSON格式的数据给Restful服务实例详解
2017/04/07 PHP
checkbox全选/取消全选以及checkbox遍历jQuery实现代码
2009/12/02 Javascript
JQuery自定义事件的应用 JQuery最佳实践
2010/08/01 Javascript
JQuery伸缩导航练习示例
2013/11/13 Javascript
JSON取值前判断
2014/12/23 Javascript
JS实现控制表格行文本对齐的方法
2015/03/30 Javascript
jQuery获取URL请求参数的方法
2015/07/18 Javascript
js数组去重的5种算法实现
2015/11/04 Javascript
详解Bootstrap的aria-label和aria-labelledby应用
2016/01/04 Javascript
JS判断字符串变量是否含有某个字串的实现方法
2016/06/03 Javascript
JQuery学习总结【一】
2016/12/01 Javascript
微信小程序之picker日期和时间选择器
2017/02/09 Javascript
Angular在模板驱动表单中自定义校验器的方法
2017/08/09 Javascript
你点的 ES6一些小技巧,请查收
2018/04/25 Javascript
详解如何在vscode里面调试js和node.js的方法步骤
2018/12/24 Javascript
详解微信小程序-获取用户session_key,openid,unionid - 后端为nodejs
2019/04/29 NodeJs
微信小程序开发之map地图组件定位并手动修改位置偏差
2019/08/17 Javascript
[50:02]完美世界DOTA2联赛PWL S2 Magma vs FTD 第三场 11.29
2020/12/03 DOTA
Python的一些用法分享
2012/10/07 Python
关于Django显示时间你应该知道的一些问题
2017/12/25 Python
Python实现的服务器示例小结【单进程、多进程、多线程、非阻塞式】
2019/05/23 Python
Python中注释(多行注释和单行注释)的用法实例
2019/08/28 Python
python 定义类时,实现内部方法的互相调用
2019/12/25 Python
Python如何基于rsa模块实现非对称加密与解密
2020/01/03 Python
pyinstaller打包成无控制台程序时运行出错(与popen冲突的解决方法)
2020/04/15 Python
Python开发.exe小工具的详细步骤
2021/01/27 Python
幼儿园教师工作感言
2014/02/15 职场文书
最新离婚协议书范本
2014/08/19 职场文书
实习报告范文之电话客服岗位
2019/07/26 职场文书
 python中的元类metaclass详情
2022/05/30 Python