一个优秀的rabbitmq消费者(consumer)设计,可直接上线使用

      RabbitMQ作为一个开源的消息中间件,在大数据量服务中起到关键作用,它可以将耗时的操作转为异步执行,来提升系统的吞吐量,在高峰时段,RabbitMQ可以缓存大量的消息,从而避免系统崩溃,并在低峰时段处理这些消息,提高了系统的稳定性。

      rabbitmq虽然可以缓存消息,但是也不能无限制、大量的缓存消息而不进行消费,否则会导致mq内存耗尽,服务down机,因此一个高效、稳定的消费者程序对于服务稳定显得尤为重要。

配图:摄影 by 公司摄影协会

该消费者设计的总思路如下: 

重点要说明的是,消息接收与消息处理要分开,消息接收只做接收,消息处理要批量进行。

1、代码实现

下面就以一个具体的实例来实现此消费者的设计,首先是写了一个类,实现基本对rabbitmq的操作(这个类可以拿去直接使用):

class DwRabbitMQ:
    def __init__(self, url, exchange, queue_name):
         self.async_queue = asyncio.Queue()
         self.url = url
         self.exchange = exchange
         self.queue_name = queue_name
         self.connection = None
         self.channel = None
         self.queue = None
    async def connect(self):
        try:
            self.connection = await aio_pika.connect_robust(
                self.url
            )
            self.channel = await self.connection.channel()
            self.exchange = await self.channel.declare_exchange(name=self.exchange, type='fanout')
            self.queue = await self.channel.declare_queue(name=self.queue_name, durable=True)
        except Exception as e:
            print(e)
    async def queue_bind_exchange(self):
        try:
            await self.queue.bind(exchange=self.exchange)
        except Exception as e:
            print(e)
    async def set_callback(self, callback):
        try:
            await self.queue.consume(callback)
        except Exception as e:
            print(e)
    async def callback(self, message: aio_pika.IncomingMessage):
        try:
            # print(f"Consumer 1 received: {message.body.decode()}")
            self.async_queue.put_nowait((message,message.body.decode()))
        except Exception:
            print(f" [!] Rejected: {message.body.decode()}")
    async def is_connected(self):
        return self.connection.is_closed
    def get_msg(self):
        return self.async_queue.get_nowait()

在上面代码中,重点看callback这个回调函数,我们发现,这个函数的实现逻辑非常简单,就是把消息放入到队列中,没有任何其他业务逻辑:

async def set_callback(self, callback):
  try:
    await self.queue.consume(callback)
  except Exception as e:
    print(e)

那消息的处理在哪呢?如开始所说,消息接收和消息处理分开,消息处理是业务逻辑,必定会耦合业务需求,每个需求都会各不相同,因此分开是正确的选择。

那我们接下来看一下如何利用上面的类实现对消息的处理:

async def main():
    buffer = []
    batch_size = 10
    last_time = time.time()
    url = "amqp://guest:guest@localhost:5672/"
    dwmq = DwRabbitMQ(url, "exchange_fan", "dw_clip_insert")
    await dwmq.connect()
    await dwmq.queue_bind_exchange()
    await dwmq.set_callback(dwmq.callback)
    print(" [*] Waiting for messages. To exit press CTRL+C")
    while True:
        # if await dwmq.is_connected():
        #     try:
        #         print("reconnecting...")
        #         dwmq = DwRabbitMQ(url, "exchange_fan", "dw_clip_insert")
        #         await dwmq.connect()
        #         await dwmq.set_callback(dwmq.callback)
        #     except Exception as e:
        #         print(e,",retry to connect")
        #         await asyncio.sleep(1)
        #         continue
        try:
            msg = dwmq.get_msg()
        except asyncio.QueueEmpty:
            await asyncio.sleep(1)
            msg = None
        if msg is not None:
            buffer.append(msg)
        if len(buffer) < batch_size and time.time() - last_time < 60:
            continue
        last_time = time.time()
        print("batch size : {},real size : {}, begin to process".format(batch_size,len(buffer)))
        # 根据实际需求,批量
        for msg in buffer:
            try:
                await msg[0].ack()
            except Exception as e:
                print(e)
            print(msg[1])
        buffer = []

在消费处理中需要说明的有两点:

  • 对消息进行打包处理

  • 如果消息不够设置的『包』,也要有处理策略,这里是按时间处理的,如果等待超过一定时间没有凑够这个『包』,那么就有多少算多少,先处理了。

代码实现在这里:

try:
    msg = dwmq.get_msg()
except asyncio.QueueEmpty:
    await asyncio.sleep(1)
    msg = None
if msg is not None:
    buffer.append(msg)
if len(buffer) < batch_size and time.time() - last_time < 60:
    continue

2、代码的健壮性考虑

     一般情况下,消费者都会设计成后台任务常驻,来消费队列中的消息,假如rabbitmq因某种异常导致了重启,后台任务与mq的连接就会失效,因此任务在运行过程中需要考虑mq异常断开的情况,mq恢复正常时,后台任务具有重连的能力,来保证消费的恢复,因此我们需要在代码中增加以下逻辑:

 

在循环消费的过程中,我们增加了对连接状态的判断,如果连接已断开,则启动重连。

在实际测试过程中,当关闭mq时,没有这部分代码,mq也会自动重连,这应该是【aio_pika】包内部实现了重连的机制。

但是为了保险起见,建议还是增加这部分代码。

以上就是这个异步消费者的全部了,这个思路以及代码可以直接拿来上线使用,只需要按照自己的业务逻辑进行适配就可以了,全部代码在这里:

https://github.com/liupengh3c/career/tree/main/rabbitmq/async

期待小伙伴们点个关注,聊聊技术,聊聊跑步,聊聊家常~~~~~~。


往期推荐:

2025年,我要做个自我介绍

Python web框架sanic+tortoise服务框架搭建(MVP版本)

命令行参数的艺术:Python、Golang、C++技术实现

supervisor,你理应知道。

借助tritonserver完成gpt2模型的本地私有化部署

GRPC开发全攻略:从环境搭建到代码实现

【续】开发triton客户端,访问clip-vit-large-patch14模型抽取图片特征。

NVIDIA tritonserver实现CLIP-ViT模型工程化:轻松获取图片特征(by grpc or http)

吭哧一天才搞定:elasticsearch向量检索需要的数据集以及768维向量生成

Kibana for Mac:极简安装教程

轻松搭建Elasticsearch:Mac系统下的安装指南

protobuf c++开发快速上手指南

项目踩坑记--RabbitMq连接过多导致的内存打满

RabbitMQ-死信队列(golang)

ElasticSearch向量检索技术方案实现

elasticsearch查询语言DSL构建包使用及实现原理(golang)

golang操作mysql之利器-gorm

Elasticsearch写入、读取、更新、删除以及批量操作(golang)

golang线程池ants-实现架构

RabbitMQ-topic exchange使用方法

RabbitMQ-直连交换机(direct)使用方法

tritonserver学习之四:tritonserver运行流程

tritonserver学习之三:tritonserver编译

微信小程序文章列表焕新颜:从丑小鸭到白天鹅的华丽蜕变

"谢广军女儿开盒"事件引关注,百度发声

跑步的第六年,才真正了解运动的意义

武汉抗疫英雄汪勇:平凡人的非凡之举。

李白:为何两次选择做了上门女婿?

纳兰性德-我是人间惆怅客,世间唯有『若』字,最难成真

RabbitMQ是一款开源的消息队列中间件软件,它实现了高效、可靠的消息传递机制。它采用AMQP(Advanced Message Queuing Protocol)作为消息通信的协议标准,为开发者提供了可靠的消息传递解决方案。 RabbitMQ的核心设计思想是生产者(Producer)将消息发送到队列(Queue),然后消费者Consumer)从队列中接收消息。这种消息模式实现了解耦和异步通信,生产者和消费者之间不需要直接交互,提高了系统的可扩展性和灵活性。 RabbitMQ具有许多强大的特性,例如:持久化存储消息、消息确认机制、消息路由和过滤、负载均衡等。这些特性使得开发者可以根据需求来设计和实现复杂的消息队列系统,用于解决分布式系统之间的通信问题。 在使用RabbitMQ时,首先需要安装和配置RabbitMQ服务端,然后通过客户端库来连接和交互。RabbitMQ提供了各种编程语言的客户端库,如Java、Python、C#等,开发者可以根据自己的编程环境选择适合的客户端。 对于C语言开发者而言,RabbitMQ提供了适用于C语言的AMQP客户端库。该库包含了丰富的API,可以通过调用相关函数来实现与RabbitMQ的通信。开发者需要了解连接、通道、交换器、队列等概念,并使用相关函数进行配置和操作。 使用RabbitMQ C库时,开发者需要先建立与RabbitMQ的连接,并创建一个通道。然后可以定义交换器和队列,并使用相关函数进行配置和绑定。接着可以发送消息到队列或者从队列中接收消息。 总之,RabbitMQ是一款功能强大的消息队列中间件,通过使用RabbitMQ C库,开发者可以在C语言环境下轻松实现消息的发送、接收和处理,帮助开发者构建高效可靠的分布式系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值