django+gunicorn如何正确使用KafkaConsumer

209 阅读2分钟

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()

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消费者问题完美解决