在实际的后端开发中,Kafka 作为高吞吐、高可靠的消息中间件,被广泛用于业务解耦和数据异步传输。线上 Kafka 集群基本都会开启 SASL 安全认证,防止未授权访问。本文基于 Python 实现带 SASL 账号密码认证的 Kafka 生产者和消费者完整逻辑,同时解决了断线掉线、单条消息异常阻塞、会话超时等生产环境常见问题。
整体设计方案采用配置与代码分离、异常容错、服务稳定常驻、消息不阻塞的原则。所有 Kafka 集群地址、认证方式、消费分组、超时参数等均统一写入 config.ini 配置文件,业务代码仅专注于消息发送和消费逻辑。这样后续集群变更或参数调优时无需修改业务代码。
生产者核心实现基于官方 kafka-python 库,适配 SASL_PLAINTEXT 认证模式。通过读取配置中的账号、密码、加密机制和安全协议等参数,严格匹配线上集群认证规则。消息发送采用同步模式,通过 future.get() 阻塞获取发送结果,精准捕获 topic、分区和偏移量信息,方便日志排查。同步模式相比异步更适合对可靠性要求高的场景,确保每条消息都成功投递。同时配置了重试机制(retries=3),自动应对临时网络波动。自定义 JSON 序列化时指定 ensure_ascii=False,保证中文正常传输。
消费者的核心优化包括常驻式自动重连机制、规避会话超时掉线、单条消息异常隔离和手动提交偏移量。原生的 Kafka 消费者在断连后无法自动恢复,这里通过外层死循环加异常捕获和休眠重试来实现:一旦检测到连接异常或未知运行时异常,服务不会宕机,休眠5秒后自动重建消费者,保障7×24小时运行。在消费者配置中删除了 consumer_timeout_ms 参数,避免因超时被集群剔除,依赖原生长轮询自动维持心跳。
单条消息异常隔离的实现是在消费循环中用 try-except 包裹每条消息的处理逻辑,当某条消息格式错误或业务处理异常时,仅跳过本条并打印日志,不会中断整体消费流程。使用手动提交 offset(consumer.commit() 在每条消息成功处理后调用),确保消息处理成功后才标记消费完成,避免业务处理失败但偏移量已提交导致消息丢失。
配置文件的解耦设计将所有核心参数收纳在 config.ini 中,分为 [KAFKA_BASE]、[KAFKA_PRODUCER]、[KAFKA_CONSUMER] 三个模块,涵盖集群地址、topic、认证信息、超时时间、消费分组、初始偏移量策略等。开发、测试、生产环境只需切换配置文件即可快速适配。
以下是完整的代码实现和配置文件示例。
依赖安装:- pip install kafka-python==2.3.0
复制代码
配置文件 config.ini:- [KAFKA_BASE]
- bootstrap_servers = 192.168.0.10:9000
- topic = JavaToPython
- [KAFKA_PRODUCER]
- security_protocol = SASL_PLAINTEXT
- sasl_mechanism = PLAIN
- sasl_plain_username = admin
- sasl_plain_password = admin-secret
- max_request_size = 10485760
- send_timeout = 3000
- [KAFKA_CONSUMER]
- group_id = JavaToPythonGroup
- security_protocol = SASL_PLAINTEXT
- sasl_mechanism = PLAIN
- sasl_plain_username = admin
- sasl_plain_password = admin-secret
- auto_offset_reset = latest
- enable_auto_commit = False
复制代码
生产者代码 kafka_producer.py:- import configparser
- import json
- from kafka import KafkaProducer
- from kafka.errors import KafkaError
- import os
- script_dir = os.path.dirname(os.path.abspath(__file__))
- ini_path = os.path.join(script_dir, "config.ini")
- conf = configparser.ConfigParser()
- load_files = conf.read(ini_path, encoding="utf-8")
- print("成功加载的配置文件:", load_files)
- base_conf = conf["KAFKA_BASE"]
- prod_conf = conf["KAFKA_PRODUCER"]
- producer = KafkaProducer(
- bootstrap_servers=base_conf["bootstrap_servers"].split(","),
- security_protocol=prod_conf["security_protocol"],
- sasl_mechanism=prod_conf["sasl_mechanism"],
- sasl_plain_username=prod_conf["sasl_plain_username"],
- sasl_plain_password=prod_conf["sasl_plain_password"],
- max_request_size=int(prod_conf["max_request_size"]),
- retries=3,
- value_serializer=lambda v: json.dumps(v, ensure_ascii=False).encode("utf-8")
- )
- def send_sync_msg(msg_data: dict):
- try:
- future = producer.send(topic=base_conf["topic"], value=msg_data)
- meta = future.get(timeout=int(prod_conf["send_timeout"]) / 1000)
- print(f"发送成功 | topic:{meta.topic} 分区:{meta.partition} offset:{meta.offset}")
- return True
- except KafkaError as e:
- print(f"发送失败:{str(e)}")
- return False
- if __name__ == "__main__":
- test_msg = {"resourceId": 184501, "type": "文档", "resourceType": "txt"}
- for i in range(2):
- print(f"===== 第{i+1}次调用 =====")
- send_sync_msg(test_msg)
- producer.flush()
复制代码
消费者代码 kafka_consumer.py:- import configparser
- import json
- import time
- from kafka import KafkaConsumer
- from kafka.errors import KafkaError
- import os
- script_dir = os.path.dirname(os.path.abspath(__file__))
- ini_path = os.path.join(script_dir, "config.ini")
- conf = configparser.ConfigParser()
- load_files = conf.read(ini_path, encoding="utf-8")
- print("成功加载的配置文件:", load_files)
- base_conf = conf["KAFKA_BASE"]
- cons_conf = conf["KAFKA_CONSUMER"]
- def create_consumer():
- consumer = KafkaConsumer(
- base_conf["topic"],
- bootstrap_servers=base_conf["bootstrap_servers"].split(","),
- group_id=cons_conf["group_id"],
- security_protocol=cons_conf["security_protocol"],
- sasl_mechanism=cons_conf["sasl_mechanism"],
- sasl_plain_username=cons_conf["sasl_plain_username"],
- sasl_plain_password=cons_conf["sasl_plain_password"],
- auto_offset_reset=cons_conf["auto_offset_reset"],
- enable_auto_commit=conf.getboolean("KAFKA_CONSUMER", "enable_auto_commit"),
- value_deserializer=lambda x: json.loads(x.decode("utf-8"))
- )
- return consumer
- def consume_loop():
- print(f"消费任务启动,group:{cons_conf['group_id']} topic:{base_conf['topic']}")
- while True:
- try:
- consumer = create_consumer()
- print("Kafka消费者连接成功,持续监听新消息...")
- for msg in consumer:
- try:
- data = msg.value
- print("=" * 60)
- print(f"topic: {msg.topic}")
- print(f"partition: {msg.partition}")
- print(f"offset: {msg.offset}")
- print(f"headers: {msg.headers}")
- print(f"消息内容: {data}")
- resource_id = data["resourceId"]
- print("resourceId =", resource_id)
- # 在此处编写业务处理逻辑
- consumer.commit()
- except Exception as single_err:
- print(f"单条消息处理异常,跳过本条:{single_err}")
- continue
- except KafkaError as e:
- print(f"Kafka连接异常,5秒后重连:{str(e)}")
- except Exception as e:
- print(f"消费循环未知异常,5秒后重启:{str(e)}")
- time.sleep(5)
- if __name__ == "__main__":
- consume_loop()
复制代码
这套方案完全适配线上 SASL 认证集群,解决了原生 kafka-python 封装常见的中文乱码、服务掉线、消息阻塞、消息丢失和配置混乱等问题。代码轻量化、无冗余依赖,可直接用于数据同步、业务消息推送、日志采集等场景。同时代码结构清晰,可基于现有逻辑快速扩展消息重试、异常消息落盘、消费监控告警等功能。 |