Python 实现Kafka SASL认证生产消费
Python 实现Kafka SASL认证生产消费,解决断线重连、消息异常问题
在实际的后端开发、数据流转场景中,Kafka 作为高吞吐、高可靠的消息中间件,被广泛用于业务解耦、数据异步传输。而线上 Kafka 集群基本都会开启 SASL 安全认证,避免未授权访问。本文基于 Python 实现带SASL账号密码认证的 Kafka 生产者、消费者完整逻辑,同时解决了日常开发中常见的断线掉线、单条消息异常阻塞、会话超时等问题,适配生产环境使用。
一、整体方案设计思路
本次封装的 Kafka 工具分为生产者和消费者两大模块,全程采用配置文件解耦核心参数,避免硬编码服务器地址、账号密码、超时时间等关键配置,提升代码可维护性和通用性。
核心设计原则:配置与代码分离、异常容错、服务稳定常驻、消息不阻塞。所有 Kafka 集群地址、认证方式、消费分组、超时参数等均统一写入 ini 配置文件,业务代码仅专注于消息发送和消息消费逻辑,便于后续集群变更、参数调优,无需修改业务代码。
二、Kafka 生产者核心实现与优势
1. SASL 安全认证初始化
生产者基于官方 kafka-python 库初始化,完整适配 SASL_PLAINTEXT 认证模式,配置文件读取账号、密码、加密机制、安全协议等参数,严格匹配线上 Kafka 集群认证规则,保障消息传输的安全性,杜绝匿名访问风险。
2. 同步可靠消息发送机制
采用同步发送模式生产消息,通过 future.get() 阻塞获取消息发送结果,精准捕获消息的 Topic、分区、偏移量信息,方便日志排查和问题追溯。相较于异步发送,同步模式更适合对消息可靠性要求较高的业务场景,确保每条消息都成功投递。
同时配置了消息重试机制,针对临时网络波动、集群短暂不可用等问题,自动重试发送,大幅提升消息投递成功率。并且自定义 JSON 序列化规则,支持中文正常传输,避免默认编码导致的中文乱码问题。
3. 异常容错处理
对 Kafka 专属异常进行单独捕获,发送失败时打印详细报错信息并返回失败标识,方便上层业务做重试、告警等拓展逻辑。测试场景下通过循环调用验证接口稳定性,搭配手动刷新缓冲区,确保消息即时推送至集群。
三、Kafka 消费者核心优化与亮点
1. 常驻式自动重连机制
原生 Kafka 消费者存在断连后无法自动恢复的问题,网络波动、集群重启都会导致消费服务终止。本次封装通过外层死循环+异常捕获休眠重试的方案,实现消费者断线自动重建,一旦检测到 Kafka 连接异常、未知运行异常,服务不会宕机,休眠5秒后自动重启消费任务,保障服务7×24小时常驻运行。
2. 规避会话超时掉线问题
优化原生消费者配置,删除多余的会话超时参数,依托 Kafka 原生长轮询机制,自动维持心跳会话,持续与 Broker 保持连接,有效避免常规配置不当导致的消费者被集群剔除、频繁掉线等问题,极大提升消费稳定性。
3. 单条消息异常隔离
消费循环中增加单条消息独立异常捕获逻辑,当某一条消息格式错误、字段缺失、业务处理异常时,不会中断整体消费流程,仅跳过当前异常消息并打印日志,保证后续正常消息可以持续消费,避免单点异常导致整个消费服务瘫痪。
4. 手动提交偏移量,精准可控
采用手动提交 offset 的方式,在单条消息完整处理完成后再提交偏移量。相较于自动提交,手动提交可以精准保证消息处理成功才标记消费完成,避免业务处理失败但偏移量已提交,导致消息丢失的问题,完美适配业务一致性要求较高的场景。
四、配置文件解耦设计
所有核心参数统一收纳在 config.ini 配置文件中,分为基础集群配置、生产者配置、消费者配置三大模块。涵盖集群地址、主题名称、认证信息、超时时间、消费分组、初始偏移量策略等全部参数。
这种设计极大提升了代码的通用性,开发、测试、生产环境可通过切换配置文件快速适配,后续集群扩容、认证信息更新、参数调优,无需改动业务代码,降低维护成本。
五、实战落地总结
这套 Kafka 生产消费方案,完全适配线上 SASL 认证集群,解决了 Python 原生 Kafka 封装常见的中文乱码、服务掉线、消息阻塞、消息丢失、配置混乱等痛点。整体代码轻量化、无冗余依赖,容错机制完善、稳定性高,可直接复用至日常数据同步、业务消息推送、日志采集等各类业务场景。
同时代码结构清晰、拓展性强,可基于现有逻辑快速新增消息重试、异常消息落盘、消费监控告警、批量消费等功能,适配复杂的生产环境需求。
依赖包
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 #auto_commit_interval_ms = 1000 #consumer_timeout_ms = 5000
生产者 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__))
# 拼接config.ini完整路径
ini_path = os.path.join(script_dir, "config.ini")
# 加载ini配置
conf = configparser.ConfigParser()
# read返回成功读取的文件列表,可打印校验
load_files = conf.read(ini_path, encoding="utf-8")
print("成功加载的配置文件:", load_files)
base_conf = conf["KAFKA_BASE"]
prod_conf = conf["KAFKA_PRODUCER"]
# 初始化带SASL认证生产者
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,
# json序列化
value_serializer=lambda v: json.dumps(v, ensure_ascii=False).encode("utf-8")
)
def send_sync_msg(msg_data: dict):
"""同步发送json业务消息"""
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
# 读取ini配置
# 获取当前脚本文件所在文件夹
script_dir = os.path.dirname(os.path.abspath(__file__))
# 拼接config.ini完整路径
ini_path = os.path.join(script_dir, "config.ini")
# 加载ini配置
conf = configparser.ConfigParser()
# read返回成功读取的文件列表,可打印校验
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"),
# auto_commit_interval_ms=int(cons_conf["auto_commit_interval_ms"]),
# 删掉 consumer_timeout_ms,避免会话超时掉线
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消费者连接成功,持续监听新消息...")
# 原生长轮询,自动发送心跳维持会话,不会被broker剔除
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)}")
# 异常后休眠5秒再重试
time.sleep(5)
if __name__ == "__main__":
consume_loop()
浙公网安备 33010602011771号