参考:
为什么 nameko 投递消息的速度这么慢?
nameko 投递不同大小消息的速率对比

前言

nameko 的瓶颈 —— 投递消息太慢

动机:最近使用 nameko + rabbitmq 做的系统的流量太低了,系统的 QPS 在 1k-2k 之间。所以需要优化系统的 QPS,自己用 kombu 裸写了生产者消费者的 demo 代码,即便开了消息持久化,发现吞吐量可以随随便便过 2w+/s(特指的是生产者投递消息的速率),所以一定有问题。

最后经过查阅资料,发现是 nameko 默认在消息投递的时候,开启了消息确认。

生产者的消息确认就是:client 投递任务到 rabbitmq server 的时候,会等 rabbitmq server 返回一个 ack,表示 rabbitmq server 已经可靠的接收到了这条消息。

而且这个过程是同步的,也就是,投一个 message 等到 ack 之后,才能继续投递下一个 message。


amqp client 投递消息时,背后的网络请求

看下看图说话,图就使用 wireshark 抓取 amqp 协议的流量:

开启消息确认

使用 wireshark 抓包开启 confirm_publish 的截图

其中 192.168.26.130 是 client ; 192.168.38.191 是 rabbitmq server

可以看出来,投递消息的时候,是 client 发出一个 Basic.Publish 然后,rabbitmq sever 返回一个 Basic.Ack, 不断重复

图片.png

关闭消息确认

如果我们关闭 confirm_publish

可以看到,此时,只有 client 到 server 的 Basic.Publish,而没有 server 到 cient 的 Basic.Ack

图片.png

是否应该开启 confirm_publish

这个要结合『QPS大小』和『消息的重要程度』两方面考虑

上面列出来一些具体数值,可以作为『流量』的一个参考

而『消息的重要程度』就自己衡量吧

关于 rabbitmq server 常见问题的 QA

Q:kombu 投递消息到 rabbitmq 的时候,可以批量投递吗?
A:如果走的是 5672 这个 amqp 协议端口,是不能的;那走 15672 这个 http 端口可以吗?也不行。所以总的就是不行。

Q:rabbitmq 投递消息可以异步确认吗?
A:我不知道,反正 kombu 貌似没有相关 api 来干这个事情

Q:rabbitmq 投递消息可以批量异步吗?
A:我不知道,反正 kombu 貌似没有相关 api 来干这个事情

Q:往 Rabbitmq 投递 message 的时候,是开启消息持久化对 QPS 的影响大,还是开启消息确认的影响大?
A:开启消息确认的影响更大,因为网络是对磁盘更慢的存在


事件模式

事件模式就是生产者消费者异步解耦模式

脱机的

nameko 的事件脱机模式

比如 flask、fastapi、Django 等等接口需要投递消息到 rabbitmq 的时候,为了可以和 nameko 集成,我们就可以使用到 nameko 提供的脱机模式

seo 优化:

  • flask 如何集成 nameko
  • fastapi 如何集成 nameko
  • flask 如何通过 nameko 投递消息到 rabbitmq 的队列中?
  • fastapi 如何通过 nameko 投递消息到 rabbitmq 的队列中?

投递的代码:

import json
import settings
from nameko.standalone.rpc import ClusterRpcProxy
from nameko.standalone.events import event_dispatcher
from nameko.constants import NON_PERSISTENT, PERSISTENT
from loguru import logger


config = {
    'AMQP_URI': f'amqp://{settings.RABBITMQ_CONFIG.username}:'
                f'{settings.RABBITMQ_CONFIG.password}@{settings.RABBITMQ_CONFIG.host}:'
                f'{settings.RABBITMQ_CONFIG.port}/{settings.RABBITMQ_CONFIG.vhost}'
}


dispatch = event_dispatcher(
    config, delivery_mode=NON_PERSISTENT, use_confirms=False)

data = {
    'name': 'jike',
    'age': 18,
    'score': {
        'math': 100,
        'science': 99.5,
        'english': 59
    }
}

for _ in range(10000000):
    dispatch(
        'worker_service',
        'to_rubbish',
        json.dumps(data)
    )

消费者代码:

from typing import Callable
from nameko.rpc import rpc
from nameko.timer import timer
from nameko.constants import NON_PERSISTENT
from nameko.events import EventDispatcher, event_handler
from loguru import logger


class TrashCanService:
    name = 'trash_scan_service'

    @event_handler('worker_service', 'to_rubbish')
    def clear(self, message: str):
        pass

来看看速度:

图片.png

可以看到,速度是非常的凶猛的,单连接可以直接到 7k+

如果是多进程投递呢?来试试多进程生产者版本吧!

用了一个进程池,开 8 个进程一起干

import json
import multiprocessing
import settings
from nameko.standalone.rpc import ClusterRpcProxy
from nameko.standalone.events import event_dispatcher
from nameko.constants import NON_PERSISTENT, PERSISTENT
from loguru import logger
from loggers import logger


config = {
    'AMQP_URI': f'amqp://{settings.RABBITMQ_CONFIG.username}:'
                f'{settings.RABBITMQ_CONFIG.password}@{settings.RABBITMQ_CONFIG.host}:'
                f'{settings.RABBITMQ_CONFIG.port}/{settings.RABBITMQ_CONFIG.vhost}'
}


dispatch = event_dispatcher(
    config, delivery_mode=NON_PERSISTENT, use_confirms=False)


def run():
    data = {
        'name': 'jike',
        'age': 18,
        'score': {
            'math': 100,
            'science': 99.5,
            'english': 59
        }
    }

    for _ in range(10000000):
        dispatch(
            'worker_service',
            'to_rubbish',
            json.dumps(data)
        )


if __name__ == "__main__":
    pool = multiprocessing.Pool(processes=8)
    for i in range(100000):
        pool.apply_async(run)
    pool.close()
    pool.join()

可以看到,这个时候,投递速度到了 10k+ 了,有很大的进步哦

图片.png

这个时候 rabbitmq server 的 CPU 占用率不高不低;网络 io 也没有到瓶颈

图片.png

联机的

nameko 联机模式是什么?前面讲到了脱机模式,脱机模式可以让我们将 flask、fastapi 中的消息通过 nameko 需要的规则进行消息投递。
而联机模式就是 nameko service 之间相互投递消息的方式。

很简单,只要在实例化 EventDispatcher 示例的时候,加上对应参数就好了 delivery_mode=NON_PERSISTENT, use_confirms=False

from typing import Callable
from nameko.rpc import rpc
from nameko.timer import timer
from nameko.constants import NON_PERSISTENT
from nameko.events import EventDispatcher, event_handler
from loguru import logger


class WorkerService:
    name = 'worker_service'

    event_dispatch: Callable = EventDispatcher(
        delivery_mode=NON_PERSISTENT, use_confirms=False)

    @timer(1)
    def receive(self):
        logger.debug(f'开始')
        for _ in range(100000):
            self.event_dispatch(
                'to_rubbish',
                'hahahahah'
            )


class TrashCanService:
    name = 'trash_scan_service'

    @event_handler('worker_service', 'to_rubbish')
    def clear(self, message: str):
        pass

rpc 模式

todo 目前没有找到方法

脱机的

联机的


universe_king
3.4k 声望678 粉丝