You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails集成Kafka Python消费者/生产者阻塞问题求助

问题描述

我正在开发一个Rails服务器,需要运行作为Kafka消费者/生产者的Python脚本。服务器要执行脚本并从消费者接收处理后的数据,渲染到网站上。目前能运行脚本,但解决不了消费者连接的问题:消费者要么持续监听消息,要么在while循环里运行,从Ruby启动消费者后会一直阻塞,没法执行其他脚本。

理想的消息流是:已登录用户的邮箱→Kafka生产者→消息队列→Kafka消费者生成数据写入数据库→查询生产者从数据库取数→消息队列→消费者接收数据并渲染到网站。

我试过用一个管理脚本完成所有工作,但因为消费者持续监听导致脚本无法正常运行。以下是相关代码:

生产者1代码

from kafka import KafkaProducer
from faker import Faker
import json
import time
class producer1():

        '''
        fr_instance= Faker()
        def get_new_user():
        return {"email_address":fr_instance.email(),"first_name": fr_instance.first_name(),
        "lastname": fr_instance.last_name(), "occupation":fr_instance.job()}
        '''
        def __init__(self):
                self
                

        def json_serializer(self, data):
                return json.dumps(data).encode("utf-8")



        def send(self,email):
                print(email)
                producer = KafkaProducer(bootstrap_servers='localhost:9092',
                        value_serializer = self.json_serializer)
                registred_user = {"email": email}
                future = producer.send("NewUserTopic", registred_user)
                print (registred_user)
                result = future.get(timeout=10)
                

p = producer1()
if __name__ == '__main__':   
        email = "testmail@aaaaaaaa.com"
        p.send(email)

第一个消费者代码

from kafka import KafkaConsumer
import json
import random
from sqlalchemy.orm import sessionmaker
import dbservice
import time

class consumer1():
    def __init__(self) -> None:
        self
    def email(self):
        consumer = KafkaConsumer('NewUserTopic',
        bootstrap_servers='localhost:9092',
        auto_offset_reset = 'latest', enable_auto_commit= False)
        for msg in consumer:
            
            
            msg_out = json.loads(msg.value)
            for value in  msg_out.values():
                

            
            #return print(msg_out)
                return (value)
            
    #generate dummy address , eth
    def gen_ETHw (self):
         numbers = str(random.randint(11111,99999))
         wallet_num = str("Ox"+numbers)
         return (wallet_num)
    #generate dummy address , btc
    def gen_BTCw (self):

         numbers = str(random.randint(11111,99999))
         wallet_num = str("Ox"+numbers)
         return (wallet_num)
    
    def commit_db (self, email, ETHw, BTCw):
        Session = sessionmaker(bind=dbservice.engine)
        s = Session()
        
        input = dbservice.walletdb( email,ETHw, BTCw)
        time.sleep(2)
        s.add(input)
        s.commit()
    

         
if __name__ =='__main__':
    while True:
        c = consumer1()
        c.commit_db(c.email(),c.gen_ETHw(),c.gen_BTCw())

查询生产者代码

import dbservice
import dbservice
from sqlalchemy.orm import sessionmaker
from kafka import KafkaProducer
import json


class query_prod ():
        def __init__(self, email) -> None:
                self = self
                self.email = email
               
        
        def json_serializer(data):
                return json.dumps(data).encode("utf-8")

        producer = KafkaProducer(bootstrap_servers='localhost:9092',
                        value_serializer = json_serializer)

        Session = sessionmaker(bind=dbservice.engine)
        s = Session()
        
        def query_address(self,email):
                Session = sessionmaker(bind=dbservice.engine)
                s = Session()
                for s in s.query(dbservice.walletdb).filter_by(email=email):
                       return {"email":s.email,"ETH_w":s.ETH_w,"BTC_w":s.BTC_w}

        def send(self, email):
                data_to_send = self.query_address(email)
        
                future = self.producer.send("QueryAdressToServer", data_to_send)
                print (data_to_send)
                result = future.get(timeout=10)

if __name__ == '__main__':
        email = "testmail@aaaaaaaa.com"
        query_prod=query_prod(email)
        query_prod.send(email)

网站数据接收消费者代码

from kafka import KafkaConsumer
import json
import time

class consume_for_web():
    string=""
    def __init__(self) -> None:
        self = self
        string = self.string
    
    
    def consumer(self):
        consumer = KafkaConsumer('QueryAdressToServer',
            bootstrap_servers='localhost:9092',
            auto_offset_reset = 'latest', enable_auto_commit= False)
        print('starting consumer')
        for msg in consumer:
            data = (('{}'.format(json.loads(msg.value))))
            self.string = self.string + data
            return print(data)
        
    def read_str(self):
        return print(self.string)

if __name__ =='__main__':
    while True:
        c = consume_for_web()
        c.consumer()
        ##print("reading")
        #c.read_str()

Rails Pages控制器代码

class PagesController < ApplicationController
  def home
  end

  def about
  end

  before_action :require_login
  def genw
    our_input = current_user.email
    puts our_input
    
    @consumer_result = `python3 /Users/samuelrybar/python_projects/Kafka_demo1/kafka-prod-coms/consumer2.py`

  end

  
  def mywa
  end
  def save
    
  end
end
解决思路

1. 让消费者脱离Rails请求周期运行

Rails请求是同步阻塞的,绝对不能在请求流程里运行持续监听的Kafka消费者。应该把消费者作为独立后台进程运行:

  • 用nohup python3 consumer1.py &让脚本在后台常驻
  • 配置成systemd服务,确保进程崩溃后自动重启

2. 优化消费者逻辑,避免资源浪费

第一个消费者的while True会不断创建新实例,完全没必要。修改为单例消费者持续监听:

if __name__ =='__main__':
    c = consumer1()
    consumer = KafkaConsumer('NewUserTopic',
        bootstrap_servers='localhost:9092',
        auto_offset_reset='latest',
        enable_auto_commit=False)
    for msg in consumer:
        msg_out = json.loads(msg.value)
        email = next(iter(msg_out.values()))
        eth_w = c.gen_ETHw()
        btc_w = c.gen_BTCw()
        c.commit_db(email, eth_w, btc_w)
        consumer.commit()  # 手动提交偏移量,避免重复消费

3. Rails直接查询数据库,简化流程

你的消费者已经把数据写入数据库,Rails完全可以直接查询数据库获取结果,不需要再通过Kafka绕圈。修改genw方法:

def genw
  our_input = current_user.email
  # 假设对应ActiveRecord模型为Wallet
  @wallet = Wallet.find_by(email: our_input)
  # 触发生产者发送消息时,用&让脚本后台运行,不阻塞请求
  system("python3 /path/to/producer1.py #{our_input} &")
end

4. 用异步任务处理Kafka交互

如果必须通过Kafka传递数据,可借助Sidekiq等异步框架:

  • 创建Sidekiq任务负责调用Python生产者发送消息
  • 消费者写入数据库后,Rails页面通过ActionCable(WebSocket)实时获取更新,或者前端轮询接口

内容的提问来源于stack exchange,提问作者SamXcode

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 21:25:37