rabbitmq 生产者 消费者(多个线程消费同一个队列里面的任务。) 一个通用rabbitmq消费确认,快速并发运行的框架。
rabbitmq作为消息队列可以有消息消费确认机制,之前写个基于redis的通用生产者 消费者 并发框架,redis的list结构可以简单充当消息队列,但不具备消费确认机制,随意关停程序,会丢失一部分正在程序中处理但还没执行完的消息。基于redis的与基于rabbitmq相比对消息消费速度和消息数量没有天然的支持。
使用rabbitmq的最常用库pika
不管是写代码还是运行起来都比celery使用更简单,基本能够满足绝大多数场景使用,用来取代celery worker模式(celery有三个模式,worker模式最常用,其余是定时和间隔时间两种模式)的后台异步的作用。
# coding=utf-8
"""
一个通用的rabbitmq生产者和消费者。使用多个线程消费同一个消息队列。
"""
from collections import Callable
import functools
import time
from threading import Lock
from pika import BasicProperties
# noinspection PyUnresolvedReferences
from app.utils_ydf import (LoggerMixin, LogManager, decorators, RabbitMqHelper, BoundedThreadPoolExecutor) class RabbitmqPublisher(LoggerMixin):
def __init__(self, queue_name, log_level_int=1):
self._queue_name = queue_name
self.logger.setLevel(log_level_int * 10)
channel = RabbitMqHelper().creat_a_channel()
channel.queue_declare(queue=queue_name, durable=True)
self.channel = channel
self.lock = Lock()
self._current_time = None
self.count_per_minute = None
self._init_count()
self.logger.info(f'{self.__class__} 被实例化了') def _init_count(self):
self._current_time = time.time()
self.count_per_minute = 0 def publish(self, msg):
with self.lock: # 亲测pika多线程publish会出错。
self.channel.basic_publish(exchange='',
routing_key=self._queue_name,
body=msg,
properties=BasicProperties(
delivery_mode=2, # make message persistent
)
)
self.logger.debug(f'放入 {msg} 到 {self._queue_name} 队列中')
self.count_per_minute += 1
if time.time() - self._current_time > 60:
self._init_count()
self.logger.info(f'一分钟内推送了 {self.count_per_minute} 条消息到 {self.channel.connection} 中') class RabbitmqConsumer(LoggerMixin):
def __init__(self, queue_name, consuming_function: Callable = None, threads_num=100, max_retry_times=3, log_level=1, is_print_detail_exception=True):
"""
:param queue_name:
:param consuming_function: 处理消息的函数,函数有且只能有一个参数,参数表示消息。是为了简单,放弃策略和模板来强制参数。
:param threads_num:
:param max_retry_times:
:param log_level:
:param is_print_detail_exception:
"""
self._queue_name = queue_name
self.consuming_function = consuming_function
self.threadpool = BoundedThreadPoolExecutor(threads_num)
self._max_retry_times = max_retry_times
self.logger.setLevel(log_level * 10)
self.logger.info(f'{self.__class__} 被实例化')
self._is_print_detail_exception = is_print_detail_exception
self.rabbitmq_helper = RabbitMqHelper(heartbeat_interval=30)
channel = self.rabbitmq_helper.creat_a_channel()
channel.queue_declare(queue=self._queue_name, durable=True)
channel.basic_qos(prefetch_count=threads_num)
self.channel = channel
LogManager('pika.heartbeat').get_logger_and_add_handlers(1) @decorators.keep_circulating(1) # 是为了保证无论rabbitmq异常中断多久,无需重启程序就能保证恢复后,程序正常。
def start_consuming_message(self):
def callback(ch, method, properties, body):
msg = body.decode()
self.logger.debug(f'从rabbitmq取出的消息是: {msg}')
# ch.basic_ack(delivery_tag=method.delivery_tag)
self.threadpool.submit(self.__consuming_function, ch, method, properties, msg) self.channel.basic_consume(callback,
queue=self._queue_name,
# no_ack=True
)
self.channel.start_consuming() @staticmethod
def ack_message(channelx, delivery_tagx):
"""Note that `channel` must be the same pika channel instance via which
the message being ACKed was retrieved (AMQP protocol constraint).
"""
if channelx.is_open:
channelx.basic_ack(delivery_tagx)
else:
# Channel is already closed, so we can't ACK this message;
# log and/or do something that makes sense for your app in this case.
pass def __consuming_function(self, ch, method, properties, msg, current_retry_times=0):
if current_retry_times < self._max_retry_times:
# noinspection PyBroadException
try:
self.consuming_function(msg)
# ch.basic_ack(delivery_tag=method.delivery_tag)
self.rabbitmq_helper.connection.add_callback_threadsafe(functools.partial(self.ack_message, ch, method.delivery_tag))
except Exception as e:
self.logger.error(f'函数 {self.consuming_function} 第{current_retry_times+1}次发生错误,\n 原因是{e}', exc_info=self._is_print_detail_exception)
self.__consuming_function(ch, method, properties, msg, current_retry_times + 1)
else:
self.logger.critical(f'达到最大重试次数 {self._max_retry_times} 后,仍然失败')
# ch.basic_ack(delivery_tag=method.delivery_tag)
self.rabbitmq_helper.connection.add_callback_threadsafe(functools.partial(self.ack_message, ch, method.delivery_tag)) if __name__ == '__main__':
rabbitmq_publisher = RabbitmqPublisher('queue_test')
[rabbitmq_publisher.publish(str(i)) for i in range(1000)] def f(msg):
print('.... ', msg)
time.sleep(10) # 模拟做某事需要10秒种。 rabbitmq_consumer = RabbitmqConsumer('queue_test', consuming_function=f, threads_num=20)
rabbitmq_consumer.start_consuming_message()
1、放入任务 (图片鼠标右键点击新标签打开查看原图)
/2、
2、开启消费者,写一个函数传给消费者类。
3、并发运行效果。
rabbitmq这个专业的消息中间件就是比redis作为消息中间件专业了很多。
rabbitmq 生产者 消费者(多个线程消费同一个队列里面的任务。) 一个通用rabbitmq消费确认,快速并发运行的框架。的更多相关文章
- 消息队列,IPC机制(进程间通信),生产者消费者模型,线程及相关
消息队列 创建 ''' Queue是模块multiprocessing中的一个类我们也可以这样导入from multiprocessing import Queue,创 建时queue = Queue ...
- day35——生产者消费者模型、线程
day35 进程:生产者消费者模型 编程思想,模型,设计模式,理论等等,都是交给你一种编程的方法,以后你遇到类似的情况,套用即可 生产者消费者模型的三要素 生产者:产生数据的 消费者:接收数据做进一步 ...
- 4、网络并发编程--僵尸进程、孤儿进程、守护进程、互斥锁、消息队列、IPC机制、生产者消费者模型、线程理论与实操
昨日内容回顾 操作系统发展史 1.穿孔卡片 CPU利用率极低 2.联机批处理系统 CPU效率有所提升 3.脱机批处理系统 CPU效率极大提升(现代计算机雏形) 多道技术(单核CPU) 串行:多个任务依 ...
- python_way ,day11 线程,怎么写一个多线程?,队列,生产者消费者模型,线程锁,缓存(memcache,redis)
python11 1.多线程原理 2.怎么写一个多线程? 3.队列 4.生产者消费者模型 5.线程锁 6.缓存 memcache redis 多线程原理 def f1(arg) print(arg) ...
- Android-Java多线程通讯(生产者 消费者)&10条线程对-等待唤醒/机制的管理
上一篇博客 Android-Java多线程通讯(生产者 消费者)&等待唤醒机制 是两条线程(Thread-0 / Thread-1) 在被CPU随机切换执行: 而今天这篇博客是,在上一篇博客A ...
- RabbitMQ使用教程(五)如何保证队列里的消息99.99%被消费?
1. 前情回顾 RabbitMQ使用教程(一)RabbitMQ环境安装配置及Hello World示例 RabbitMQ使用教程(二)RabbitMQ用户管理,角色管理及权限设置 RabbitMQ使用 ...
- RabbitMQ生产者消费者
package com.ra.car.rabbitMQ; import java.io.IOException; import java.util.HashMap; import java.util. ...
- RabbitMQ生产者消费者模型构建(三)
ConnectionFactory:获取连接(地址,端口号,用户名,密码,虚拟主机等) Connection:一个连接 Channel:数据通信信道,可发送.接收消息 Queue:具体的消息存储队列 ...
- 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例
wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...
随机推荐
- C++程序设计方法3:强制类型转换
强制类型转换(显示转换) dynamic_cast<Dst_Type>(Src_var) Src_var必须是引用或者指针类型,Dst_Type类中含有虚函数,否则会有编译错误: 若目标类 ...
- Marked Ancestor [AOJ2170] [并查集]
题意: 有一个树,有些节点染色,每次有两种操作,第一,统计该节点到离它最近的染色父亲结点的的号码(Q),第二,为某一个节点染色(M),求第一种操作和. 输入: 输入由多个数据集组成.每个数据集都有以下 ...
- 开车旅行 [NOIP 2012]
Description 小A 和小B决定利用假期外出旅行,他们将想去的城市从1到N 编号,且编号较小的城市在编号较大的城市的西边,已知各个城市的海拔高度互不相同,记城市 i的海拔高度为Hi,城市 i ...
- 3ds max学习笔记-- 动画
栗子:若要使茶壶从a点运动到b点,是需要动画实现的:动画与传统意义的移动不同,与时间是存在关系的: 时间线,时间滑条: [时间配置]按钮: 弹出面板: 动画时间轴默认时间是从0帧开始100结束:总长度 ...
- Mssql数据库与Excel导数据
*.xls 2003的excel有行数限制,65535行好像,所以数据库行数多的时候,选择导出为*.xlsx文件 要装一下Microsoft.ACE.OLEDB.12.0(以下简称 ACE 引擎) ...
- flask内容学习之蓝图以及单元测试
蓝图的概念: 简单来说,蓝图是一个存储操作方法的容器.这些操作在这个蓝图被注册到一个应用之后就可以被调用.Flask可以通过蓝图来制止URL以及处理请求.Flask使用蓝图来让应用实现模块化,在Fla ...
- elasticsearch服务安装采坑
笔者这里下载的elastic search,是5.3.0版本,下载地址:https://www.elastic.co/downloads/past-releases/elasticsearch-5-3 ...
- web端设计和web前端开发 的区别
- web建包创建类
1.创建一个人类名
- [CentOS7]redis设置开机启动,设置密码
简介 上篇文章介绍了如何安装redis,但每次重启服务器之后redis不会自启,这里将介绍如何进行自启设置,以及如何设置redis的密码,进行密码验证登陆. 上篇文章: Centos7安装Redis ...