django+gunicorn如何正确使用KafkaConsumer
当我们使用Gunicorn部署应用,想去消费kafka消息的时候,又不想把这部分逻辑拆出去的时候,我们一定会尝试在当前项目下直接添加消费逻辑,但是因为gunicorn会启动多个实例,会造成重复消费的问题,网上有一个解决方法:
import fcntl
import atexit
def init_tasks():
'''我们只想要在一个进程中运行的任务'''
f = open("scheduler.lock", "wb")
def unlock():
fcntl.flock(f, fcntl.LOCK_UN)
f.close()
# 注册非阻塞互斥锁
atexit.register(unlock)
def init_ok():
''' 这里可以写可以让多个进程都执行的任务'''
# 为唯一任务加上文本锁,第一个进程初始化后会占用锁,其他进程执行时就无法操作锁,从而无法执行任务
try:
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
init_tasks()
except:
pass
pass
init_ok()
其实这个方法误导我们了,我在使用的时候,因为我的任务是一个循环for message in self.c,就会导致一个gunicorn进程无法执行后面的任务,而且supervisorctl restart也无法正常关闭它,新进程会提示端口占用无法启动。
解决方法:
-
使用kafka消费者组:当我们使用了消费者组的时候,我们就无需关心启动了几个消费实例,消息会被均衡到各个消费者组成员。
-
我们把消费任务
for message in self.c放到线程中,不去阻塞主进程。但是使用supervisorctl还是无法停止gunicorn进程(解决方法见下条)
xxx/wsgi.py
def start_kafka_consumer():
c = KafkaConsumerApi("gohw-push-result", group_id="hardware")
def process_messages():
c.start_push_hardware_syncto_db() # 一个死循环
message_thread = threading.Thread(target=process_messages)
# 启动线程
message_thread.start()
if enable_consumer == "1":
start_kafka_consumer()
- 解决supervisorctl无法关闭gunicorn的问题 gunicorn信号 docs.gunicorn.org/en/stable/s…
def start_kafka_consumer():
c = KafkaConsumerApi("gohw-push-result", group_id="hardware")
def process_messages():
c.start_push_hardware_syncto_db()
message_thread = threading.Thread(target=process_messages)
# 启动线程
message_thread.start()
def handle_term_to_quit(signum, frame):
os.kill(os.getpid(), signal.SIGHUP) # SIGTERM 转换成 SIGHUP,我也不知道为啥SIGQUIT不行
if enable_consumer == "1":
signal.signal(signal.SIGTERM, handle_term_to_quit) # 关闭supervisord及其所有子进程
start_kafka_consumer()
到此kafka消费者问题完美解决