funboost 50.2__py3-none-any.whl → 50.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of funboost might be problematic. Click here for more details.
- funboost/__init__.py +1 -1
- funboost/constant.py +4 -0
- funboost/consumers/base_consumer.py +95 -96
- funboost/consumers/celery_consumer.py +1 -1
- funboost/consumers/dramatiq_consumer.py +0 -5
- funboost/consumers/grpc_consumer.py +2 -19
- funboost/consumers/http_consumer.py +107 -40
- funboost/consumers/http_consumer_aiohttp_old.py +113 -0
- funboost/consumers/huey_consumer.py +2 -5
- funboost/consumers/kafka_consumer.py +1 -6
- funboost/consumers/kafka_consumer_manually_commit.py +0 -1
- funboost/consumers/kombu_consumer.py +0 -39
- funboost/consumers/mysql_cdc_consumer.py +1 -3
- funboost/consumers/pulsar_consumer.py +10 -5
- funboost/consumers/rabbitmq_amqpstorm_consumer.py +7 -8
- funboost/consumers/rabbitmq_complex_routing_consumer.py +54 -0
- funboost/consumers/redis_consumer.py +1 -1
- funboost/consumers/redis_consumer_ack_able.py +1 -1
- funboost/consumers/redis_consumer_ack_using_timeout.py +2 -6
- funboost/consumers/redis_consumer_priority.py +1 -1
- funboost/consumers/redis_stream_consumer.py +1 -3
- funboost/consumers/tcp_consumer.py +1 -1
- funboost/consumers/udp_consumer.py +1 -1
- funboost/consumers/zeromq_consumer.py +1 -1
- funboost/contrib/save_function_result_status/__init__.py +0 -0
- funboost/contrib/{save_result_status_to_sqldb.py → save_function_result_status/save_result_status_to_sqldb.py} +8 -41
- funboost/contrib/save_function_result_status/save_result_status_use_dataset.py +47 -0
- funboost/core/booster.py +38 -3
- funboost/core/broker_kind__exclusive_config_default_define.py +229 -0
- funboost/core/funboost_time.py +10 -45
- funboost/core/func_params_model.py +28 -4
- funboost/core/helper_funs.py +9 -8
- funboost/core/msg_result_getter.py +27 -0
- funboost/factories/broker_kind__publsiher_consumer_type_map.py +13 -3
- funboost/funboost_config_deafult.py +0 -3
- funboost/function_result_web/templates/fun_result_table.html +1 -1
- funboost/publishers/base_publisher.py +8 -2
- funboost/publishers/http_publisher.py +20 -2
- funboost/publishers/rabbitmq_amqpstorm_publisher.py +8 -7
- funboost/publishers/rabbitmq_complex_routing_publisher.py +84 -0
- funboost/utils/redis_manager.py +11 -5
- {funboost-50.2.dist-info → funboost-50.4.dist-info}/METADATA +159 -98
- {funboost-50.2.dist-info → funboost-50.4.dist-info}/RECORD +46 -41
- {funboost-50.2.dist-info → funboost-50.4.dist-info}/WHEEL +1 -1
- funboost-50.2.dist-info/LICENSE +0 -203
- {funboost-50.2.dist-info → funboost-50.4.dist-info}/entry_points.txt +0 -0
- {funboost-50.2.dist-info → funboost-50.4.dist-info}/top_level.txt +0 -0
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
# @Author : ydf
|
|
3
3
|
# @Time : 2022/8/8 0008 13:32
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import logging
|
|
5
|
+
import threading
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
from flask import Flask, request
|
|
9
9
|
|
|
10
10
|
from funboost.consumers.base_consumer import AbstractConsumer
|
|
11
|
-
from funboost.core.
|
|
11
|
+
from funboost.core.function_result_status_saver import FunctionResultStatus
|
|
12
|
+
from funboost.core.msg_result_getter import FutureStatusResult
|
|
13
|
+
from funboost.core.serialization import Serialization
|
|
14
|
+
|
|
15
|
+
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
class HTTPConsumer(AbstractConsumer, ):
|
|
15
19
|
"""
|
|
16
|
-
|
|
20
|
+
flask 作为消息队列实现 consumer
|
|
17
21
|
"""
|
|
18
|
-
|
|
22
|
+
|
|
19
23
|
|
|
20
24
|
# noinspection PyAttributeOutsideInit
|
|
21
25
|
def custom_init(self):
|
|
@@ -30,42 +34,105 @@ class HTTPConsumer(AbstractConsumer, ):
|
|
|
30
34
|
if self._port is None:
|
|
31
35
|
raise ValueError('please specify port')
|
|
32
36
|
|
|
33
|
-
# noinspection DuplicatedCode
|
|
34
37
|
def _shedual_task(self):
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
38
|
+
"""
|
|
39
|
+
使用Flask实现HTTP服务器
|
|
40
|
+
相比aiohttp,Flask是同步框架,避免了异步阻塞问题
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# 创建Flask应用
|
|
45
|
+
flask_app = Flask(__name__)
|
|
46
|
+
# 关闭Flask的日志,避免干扰funboost的日志
|
|
47
|
+
flask_app.logger.disabled = True
|
|
48
|
+
logging.getLogger('werkzeug').disabled = True
|
|
49
|
+
|
|
50
|
+
@flask_app.route('/', methods=['GET'])
|
|
51
|
+
def hello():
|
|
52
|
+
"""健康检查接口"""
|
|
53
|
+
return "Hello, from funboost (Flask version)"
|
|
54
|
+
|
|
55
|
+
@flask_app.route('/queue', methods=['POST'])
|
|
56
|
+
def recv_msg():
|
|
57
|
+
"""
|
|
58
|
+
接收消息的核心接口
|
|
59
|
+
支持两种调用类型:
|
|
60
|
+
1. publish: 异步发布,立即返回
|
|
61
|
+
2. sync_call: 同步调用,等待结果返回
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
# 获取请求数据
|
|
65
|
+
msg = request.form.get('msg')
|
|
66
|
+
call_type = request.form.get('call_type', 'publish')
|
|
67
|
+
|
|
68
|
+
if not msg:
|
|
69
|
+
return {"error": "msg parameter is required"}, 400
|
|
70
|
+
|
|
71
|
+
# 构造消息数据
|
|
72
|
+
kw = {
|
|
73
|
+
'body': msg,
|
|
74
|
+
'call_type': call_type,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if call_type == 'sync_call':
|
|
78
|
+
# 同步调用:需要等待执行结果
|
|
79
|
+
future_status_result = FutureStatusResult(call_type=call_type)
|
|
80
|
+
kw['future_status_result'] = future_status_result
|
|
81
|
+
|
|
82
|
+
# 提交任务到线程池执行
|
|
83
|
+
self._submit_task(kw)
|
|
84
|
+
|
|
85
|
+
# 等待任务完成(带超时)
|
|
86
|
+
if future_status_result.wait_finish(self.consumer_params.rpc_timeout):
|
|
87
|
+
# 返回执行结果
|
|
88
|
+
result = future_status_result.get_staus_result_obj()
|
|
89
|
+
return Serialization.to_json_str(
|
|
90
|
+
result.get_status_dict(without_datetime_obj=True)
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
# 超时处理
|
|
94
|
+
self.logger.error(f'sync_call wait timeout after {self.consumer_params.rpc_timeout}s')
|
|
95
|
+
return {"error": "execution timeout"}, 408
|
|
96
|
+
|
|
97
|
+
else:
|
|
98
|
+
# 异步发布:直接提交任务,立即返回
|
|
99
|
+
self._submit_task(kw)
|
|
100
|
+
return "finish"
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
self.logger.error(f'处理HTTP请求时出错: {e}', exc_info=True)
|
|
104
|
+
return {"error": str(e)}, 500
|
|
105
|
+
|
|
106
|
+
# 启动Flask服务器
|
|
107
|
+
# 注意:Flask默认是单线程的,但funboost使用线程池处理任务,所以这里threaded=True
|
|
108
|
+
self.logger.info(f'启动Flask HTTP服务器,监听 {self._ip}:{self._port}')
|
|
109
|
+
|
|
110
|
+
# flask_app.run(
|
|
111
|
+
# host='0.0.0.0', # 监听所有接口
|
|
112
|
+
# port=self._port,
|
|
113
|
+
# debug=False, # 生产环境关闭debug
|
|
114
|
+
# threaded=True, # 开启多线程支持
|
|
115
|
+
# use_reloader=False, # 关闭自动重载
|
|
116
|
+
# )
|
|
117
|
+
|
|
118
|
+
import waitress
|
|
119
|
+
waitress.serve(flask_app, host='0.0.0.0', port=self._port,threads=self.consumer_params.concurrent_num)
|
|
120
|
+
|
|
121
|
+
def _frame_custom_record_process_info_func(self, current_function_result_status: FunctionResultStatus, kw: dict):
|
|
122
|
+
"""
|
|
123
|
+
任务执行完成后的回调函数
|
|
124
|
+
对于sync_call模式,需要通知等待的HTTP请求
|
|
125
|
+
"""
|
|
126
|
+
if kw['call_type'] == "sync_call":
|
|
127
|
+
future_status_result: FutureStatusResult = kw['future_status_result']
|
|
128
|
+
future_status_result.set_staus_result_obj(current_function_result_status)
|
|
129
|
+
future_status_result.set_finish()
|
|
130
|
+
# self.logger.info('sync_call任务执行完成,通知HTTP请求返回结果')
|
|
66
131
|
|
|
67
132
|
def _confirm_consume(self, kw):
|
|
68
|
-
|
|
133
|
+
"""HTTP模式没有确认消费的功能"""
|
|
134
|
+
pass
|
|
69
135
|
|
|
70
136
|
def _requeue(self, kw):
|
|
137
|
+
"""HTTP模式没有重新入队的功能"""
|
|
71
138
|
pass
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Author : ydf
|
|
3
|
+
# @Time : 2022/8/8 0008 13:32
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
# from aiohttp import web
|
|
8
|
+
# from aiohttp.web_request import Request
|
|
9
|
+
|
|
10
|
+
from funboost.consumers.base_consumer import AbstractConsumer
|
|
11
|
+
from funboost.core.function_result_status_saver import FunctionResultStatus
|
|
12
|
+
from funboost.core.lazy_impoter import AioHttpImporter
|
|
13
|
+
from funboost.core.serialization import Serialization
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AioFutureStatusResult:
|
|
17
|
+
def __init__(self,call_type:str):
|
|
18
|
+
self.execute_finish_event = asyncio.Event()
|
|
19
|
+
self.staus_result_obj: FunctionResultStatus = None
|
|
20
|
+
self.call_type = call_type # sync_call or publish
|
|
21
|
+
|
|
22
|
+
def set_finish(self):
|
|
23
|
+
self.execute_finish_event.set()
|
|
24
|
+
|
|
25
|
+
async def wait_finish(self,rpc_timeout):
|
|
26
|
+
return await self.execute_finish_event.wait()
|
|
27
|
+
|
|
28
|
+
def set_staus_result_obj(self, staus_result_obj:FunctionResultStatus):
|
|
29
|
+
self.staus_result_obj = staus_result_obj
|
|
30
|
+
|
|
31
|
+
def get_staus_result_obj(self):
|
|
32
|
+
return self.staus_result_obj
|
|
33
|
+
|
|
34
|
+
class HTTPConsumer(AbstractConsumer, ):
|
|
35
|
+
"""
|
|
36
|
+
aiohttp 实现消息队列,不支持持久化,但不需要安装软件。
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# noinspection PyAttributeOutsideInit
|
|
41
|
+
def custom_init(self):
|
|
42
|
+
# try:
|
|
43
|
+
# self._ip, self._port = self.queue_name.split(':')
|
|
44
|
+
# self._port = int(self._port)
|
|
45
|
+
# except BaseException as e:
|
|
46
|
+
# self.logger.critical(f'http作为消息队列时候,队列名字必须设置为 例如 192.168.1.101:8200 这种, ip:port')
|
|
47
|
+
# raise e
|
|
48
|
+
self._ip = self.consumer_params.broker_exclusive_config['host']
|
|
49
|
+
self._port = self.consumer_params.broker_exclusive_config['port']
|
|
50
|
+
if self._port is None:
|
|
51
|
+
raise ValueError('please specify port')
|
|
52
|
+
|
|
53
|
+
# noinspection DuplicatedCode
|
|
54
|
+
def _shedual_task(self):
|
|
55
|
+
# flask_app = Flask(__name__)
|
|
56
|
+
#
|
|
57
|
+
# @flask_app.route('/queue', methods=['post'])
|
|
58
|
+
# def recv_msg():
|
|
59
|
+
# msg = request.form['msg']
|
|
60
|
+
# kw = {'body': json.loads(msg)}
|
|
61
|
+
# self._submit_task(kw)
|
|
62
|
+
# return 'finish'
|
|
63
|
+
#
|
|
64
|
+
# flask_app.run('0.0.0.0', port=self._port,debug=False)
|
|
65
|
+
|
|
66
|
+
routes = AioHttpImporter().web.RouteTableDef()
|
|
67
|
+
|
|
68
|
+
# noinspection PyUnusedLocal
|
|
69
|
+
@routes.get('/')
|
|
70
|
+
async def hello(request):
|
|
71
|
+
return AioHttpImporter().web.Response(text="Hello, from funboost")
|
|
72
|
+
|
|
73
|
+
@routes.post('/queue')
|
|
74
|
+
async def recv_msg(request: AioHttpImporter().Request):
|
|
75
|
+
data = await request.post()
|
|
76
|
+
msg = data['msg']
|
|
77
|
+
call_type = data['call_type']
|
|
78
|
+
kw = {'body': msg,'call_type': call_type,}
|
|
79
|
+
if call_type == 'sync_call':
|
|
80
|
+
aio_future_status_result = AioFutureStatusResult(call_type=call_type)
|
|
81
|
+
kw['aio_future_status_result'] = aio_future_status_result
|
|
82
|
+
self._submit_task(kw)
|
|
83
|
+
if data['call_type'] == 'sync_call':
|
|
84
|
+
await aio_future_status_result.wait_finish(self.consumer_params.rpc_timeout)
|
|
85
|
+
return AioHttpImporter().web.Response(text=Serialization.to_json_str(
|
|
86
|
+
aio_future_status_result.get_staus_result_obj().get_status_dict(without_datetime_obj=True)))
|
|
87
|
+
return AioHttpImporter().web.Response(text="finish")
|
|
88
|
+
|
|
89
|
+
app = AioHttpImporter().web.Application()
|
|
90
|
+
app.add_routes(routes)
|
|
91
|
+
loop = asyncio.new_event_loop()
|
|
92
|
+
asyncio.set_event_loop(loop)
|
|
93
|
+
AioHttpImporter().web.run_app(app, host='0.0.0.0', port=self._port, )
|
|
94
|
+
|
|
95
|
+
def _frame_custom_record_process_info_func(self,current_function_result_status: FunctionResultStatus,kw:dict):
|
|
96
|
+
if kw['call_type'] == "sync_call":
|
|
97
|
+
aio_future_status_result: AioFutureStatusResult = kw['aio_future_status_result']
|
|
98
|
+
aio_future_status_result.set_staus_result_obj(current_function_result_status)
|
|
99
|
+
aio_future_status_result.set_finish()
|
|
100
|
+
self.logger.info(f'aio_future_status_result.set_finish()')
|
|
101
|
+
|
|
102
|
+
# async def _aio_frame_custom_record_process_info_func(self,current_function_result_status: FunctionResultStatus,kw:dict):
|
|
103
|
+
# self.logger.info(666666)
|
|
104
|
+
# if kw['call_type'] == "sync_call":
|
|
105
|
+
# aio_future_status_result: AioFutureStatusResult = kw['aio_future_status_result']
|
|
106
|
+
# aio_future_status_result.set_staus_result_obj(current_function_result_status)
|
|
107
|
+
# aio_future_status_result.set_finish()
|
|
108
|
+
# self.logger.info(f'aio_future_status_result.set_finish()')
|
|
109
|
+
def _confirm_consume(self, kw):
|
|
110
|
+
pass # 没有确认消费的功能。
|
|
111
|
+
|
|
112
|
+
def _requeue(self, kw):
|
|
113
|
+
pass
|
|
@@ -12,11 +12,8 @@ class HueyConsumer(AbstractConsumer):
|
|
|
12
12
|
huey作为中间件实现的。
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
retries=0, retry_delay=0, priority=None, context=False,
|
|
18
|
-
name=None, expires=None, **kwargs
|
|
19
|
-
"""
|
|
15
|
+
|
|
16
|
+
|
|
20
17
|
|
|
21
18
|
def custom_init(self):
|
|
22
19
|
# 这就是核心,
|
|
@@ -22,8 +22,6 @@ class KafkaConsumer(AbstractConsumer):
|
|
|
22
22
|
可以让消费函数内部 sleep60秒,突然停止消费代码,使用 kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --describe --group funboost 来证实自动确认消费和手动确认消费的区别。
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'group_id': 'funboost_kafka', 'auto_offset_reset': 'earliest','num_partitions':10,'replication_factor':1,}
|
|
26
|
-
# not_all_brokers_general_settings配置 ,支持独立的中间件配置参数是 group_id 和 auto_offset_reset
|
|
27
25
|
"""
|
|
28
26
|
auto_offset_reset 介绍
|
|
29
27
|
auto_offset_reset (str): A policy for resetting offsets on
|
|
@@ -60,10 +58,7 @@ class KafkaConsumer(AbstractConsumer):
|
|
|
60
58
|
# REMIND 好处是并发高。topic像翻书一样,随时可以设置偏移量重新消费。多个分组消费同一个主题,每个分组对相同主题的偏移量互不干扰 。
|
|
61
59
|
for message in consumer:
|
|
62
60
|
# 注意: message ,value都是原始的字节数据,需要decode
|
|
63
|
-
|
|
64
|
-
self.logger.debug(
|
|
65
|
-
f'从kafka的 [{message.topic}] 主题,分区 {message.partition} 中 取出的消息是: {message.value.decode()}')
|
|
66
|
-
kw = {'consumer': consumer, 'message': message, 'body': message.value}
|
|
61
|
+
kw = {'consumer': consumer, 'message': message, 'body': message.value.decode('utf-8')}
|
|
67
62
|
self._submit_task(kw)
|
|
68
63
|
|
|
69
64
|
def _confirm_consume(self, kw):
|
|
@@ -31,7 +31,6 @@ class KafkaConsumerManuallyCommit(AbstractConsumer):
|
|
|
31
31
|
可以让消费函数内部 sleep 60秒,突然停止消费代码,使用 kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --describe --group frame_group 来证实自动确认消费和手动确认消费的区别。
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
|
-
BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'group_id': 'funboost_kafka', 'auto_offset_reset': 'earliest','num_partitions':10,'replication_factor':1,}
|
|
35
34
|
|
|
36
35
|
def custom_init(self):
|
|
37
36
|
self._lock_for_operate_offset_dict = threading.Lock()
|
|
@@ -81,45 +81,6 @@ class KombuConsumer(AbstractConsumer, ):
|
|
|
81
81
|
使用kombu作为中间件,这个能直接一次性支持很多种小众中间件,但性能很差,除非是分布式函数调度框架没实现的中间件种类用户才可以用这种,用户也可以自己对比性能。
|
|
82
82
|
"""
|
|
83
83
|
|
|
84
|
-
BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'kombu_url': None, # 如果这里也配置了kombu_url,则优先使用跟着你的kombu_url,否则使用funboost_config. KOMBU_URL
|
|
85
|
-
'transport_options': {}, # transport_options是kombu的transport_options 。
|
|
86
|
-
'prefetch_count': 500
|
|
87
|
-
}
|
|
88
|
-
# prefetch_count 是预获取消息数量
|
|
89
|
-
''' transport_options是kombu的transport_options 。
|
|
90
|
-
例如使用kombu使用redis作为中间件时候,可以设置 visibility_timeout 来决定消息取出多久没有ack,就自动重回队列。
|
|
91
|
-
kombu的每个中间件能设置什么 transport_options 可以看 kombu的源码中的 transport_options 参数说明。
|
|
92
|
-
|
|
93
|
-
例如kombu redis的Transport Options 说明
|
|
94
|
-
D:\ProgramData\Miniconda3\envs\py311\Lib\site-packages\kombu\transport\redis.py
|
|
95
|
-
|
|
96
|
-
Transport Options
|
|
97
|
-
=================
|
|
98
|
-
* ``sep``
|
|
99
|
-
* ``ack_emulation``: (bool) If set to True transport will
|
|
100
|
-
simulate Acknowledge of AMQP protocol.
|
|
101
|
-
* ``unacked_key``
|
|
102
|
-
* ``unacked_index_key``
|
|
103
|
-
* ``unacked_mutex_key``
|
|
104
|
-
* ``unacked_mutex_expire``
|
|
105
|
-
* ``visibility_timeout``
|
|
106
|
-
* ``unacked_restore_limit``
|
|
107
|
-
* ``fanout_prefix``
|
|
108
|
-
* ``fanout_patterns``
|
|
109
|
-
* ``global_keyprefix``: (str) The global key prefix to be prepended to all keys
|
|
110
|
-
used by Kombu
|
|
111
|
-
* ``socket_timeout``
|
|
112
|
-
* ``socket_connect_timeout``
|
|
113
|
-
* ``socket_keepalive``
|
|
114
|
-
* ``socket_keepalive_options``
|
|
115
|
-
* ``queue_order_strategy``
|
|
116
|
-
* ``max_connections``
|
|
117
|
-
* ``health_check_interval``
|
|
118
|
-
* ``retry_on_timeout``
|
|
119
|
-
* ``priority_steps``
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
'''
|
|
123
84
|
|
|
124
85
|
def custom_init(self):
|
|
125
86
|
self.kombu_url = self.consumer_params.broker_exclusive_config['kombu_url'] or BrokerConnConfig.KOMBU_URL
|
|
@@ -21,9 +21,7 @@ class MysqlCdcConsumer(AbstractConsumer):
|
|
|
21
21
|
This broker is consumer-driven; it automatically generates tasks from database changes.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
'BinLogStreamReaderConfig': {},
|
|
26
|
-
}
|
|
24
|
+
|
|
27
25
|
|
|
28
26
|
def custom_init(self):
|
|
29
27
|
"""Validates the essential configuration."""
|
|
@@ -32,10 +32,7 @@ class PulsarConsumer(AbstractConsumer, ):
|
|
|
32
32
|
pulsar作为中间件实现的。
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
'replicate_subscription_state_enabled': True,
|
|
37
|
-
'consumer_type': ConsumerType.Shared,
|
|
38
|
-
}
|
|
35
|
+
|
|
39
36
|
|
|
40
37
|
def custom_init(self):
|
|
41
38
|
pass
|
|
@@ -46,9 +43,17 @@ class PulsarConsumer(AbstractConsumer, ):
|
|
|
46
43
|
except ImportError:
|
|
47
44
|
raise ImportError('需要用户自己 pip install pulsar-client ,')
|
|
48
45
|
self._client = pulsar.Client(BrokerConnConfig.PULSAR_URL, )
|
|
46
|
+
|
|
47
|
+
consumer_type_map = {
|
|
48
|
+
'Exclusive':ConsumerType.Exclusive,
|
|
49
|
+
'Shared':ConsumerType.Shared,
|
|
50
|
+
'Failover':ConsumerType.Failover,
|
|
51
|
+
'KeyShared':ConsumerType.KeyShared,
|
|
52
|
+
}
|
|
53
|
+
consumer_type_obj = consumer_type_map[self.consumer_params.broker_exclusive_config['consumer_type']]
|
|
49
54
|
self._consumer = self._client.subscribe(self._queue_name, schema=schema.StringSchema(), consumer_name=f'funboost_consumer_{os.getpid()}',
|
|
50
55
|
subscription_name=self.consumer_params.broker_exclusive_config['subscription_name'],
|
|
51
|
-
consumer_type=
|
|
56
|
+
consumer_type=consumer_type_obj,
|
|
52
57
|
replicate_subscription_state_enabled=self.consumer_params.broker_exclusive_config['replicate_subscription_state_enabled'])
|
|
53
58
|
while True:
|
|
54
59
|
msg = self._consumer.receive()
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
# @Time : 2022/8/8 0008 13:30
|
|
4
4
|
|
|
5
5
|
import amqpstorm
|
|
6
|
-
from funboost.constant import BrokerEnum
|
|
7
6
|
from funboost.consumers.base_consumer import AbstractConsumer
|
|
8
|
-
|
|
9
|
-
from funboost.core.func_params_model import PublisherParams
|
|
7
|
+
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
class RabbitmqConsumerAmqpStorm(AbstractConsumer):
|
|
@@ -14,7 +12,8 @@ class RabbitmqConsumerAmqpStorm(AbstractConsumer):
|
|
|
14
12
|
使用AmqpStorm实现的,多线程安全的,不用加锁。
|
|
15
13
|
funboost 强烈推荐使用这个做消息队列中间件。
|
|
16
14
|
"""
|
|
17
|
-
|
|
15
|
+
|
|
16
|
+
# _rabbitmq_pb_cls = RabbitmqPublisherUsingAmqpStorm
|
|
18
17
|
|
|
19
18
|
def _shedual_task(self):
|
|
20
19
|
# noinspection PyTypeChecker
|
|
@@ -24,13 +23,14 @@ class RabbitmqConsumerAmqpStorm(AbstractConsumer):
|
|
|
24
23
|
kw = {'amqpstorm_message': amqpstorm_message, 'body': body}
|
|
25
24
|
self._submit_task(kw)
|
|
26
25
|
|
|
27
|
-
rp =
|
|
28
|
-
|
|
26
|
+
# rp = self._rabbitmq_pb_cls(publisher_params=PublisherParams(queue_name=self.queue_name,broker_kind=self.consumer_params.broker_kind,
|
|
27
|
+
# broker_exclusive_config=self.consumer_params.broker_exclusive_config))
|
|
28
|
+
rp = self.bulid_a_new_publisher_of_same_queue()
|
|
29
29
|
rp.init_broker()
|
|
30
30
|
rp.channel_wrapper_by_ampqstormbaic.qos(self.consumer_params.concurrent_num)
|
|
31
31
|
rp.channel_wrapper_by_ampqstormbaic.consume(callback=callback, queue=self.queue_name, no_ack=self.consumer_params.broker_exclusive_config['no_ack'],
|
|
32
32
|
)
|
|
33
|
-
self._rp=rp
|
|
33
|
+
self._rp = rp
|
|
34
34
|
rp.channel.start_consuming(auto_decode=True)
|
|
35
35
|
|
|
36
36
|
def _confirm_consume(self, kw):
|
|
@@ -48,4 +48,3 @@ class RabbitmqConsumerAmqpStorm(AbstractConsumer):
|
|
|
48
48
|
# kw['amqpstorm_message'].reject(requeue=True)
|
|
49
49
|
# kw['amqpstorm_message'].ack()
|
|
50
50
|
# self.publisher_of_same_queue.publish(kw['body'])
|
|
51
|
-
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Author : ydf
|
|
3
|
+
# @Time : 2022/8/8 0008 13:30
|
|
4
|
+
import amqpstorm
|
|
5
|
+
from funboost.consumers.rabbitmq_amqpstorm_consumer import RabbitmqConsumerAmqpStorm
|
|
6
|
+
from amqpstorm.queue import Queue as AmqpStormQueue
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RabbitmqComplexRoutingConsumer(RabbitmqConsumerAmqpStorm):
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
def custom_init(self):
|
|
14
|
+
super().custom_init()
|
|
15
|
+
rp = self.bulid_a_new_publisher_of_same_queue()
|
|
16
|
+
rp.init_broker() # 发布者那边只声明了交换机
|
|
17
|
+
|
|
18
|
+
# 消费者负责声明队列和唯一的绑定逻辑
|
|
19
|
+
AmqpStormQueue(rp.channel).declare(**rp.queue_declare_params)
|
|
20
|
+
|
|
21
|
+
# 消费者负责唯一的绑定逻辑
|
|
22
|
+
if rp._exchange_name:
|
|
23
|
+
self.logger.info(f'消费者开始绑定: 队列 [{self._queue_name}] <--> 交换机 [{rp._exchange_name}] (类型: {rp._exchange_type})')
|
|
24
|
+
|
|
25
|
+
routing_key_bind = self.consumer_params.broker_exclusive_config.get('routing_key_for_bind')
|
|
26
|
+
arguments_for_bind = None
|
|
27
|
+
|
|
28
|
+
if rp._exchange_type == 'fanout':
|
|
29
|
+
routing_key_bind = '' # fanout 必须使用空 routing_key
|
|
30
|
+
elif rp._exchange_type == 'headers':
|
|
31
|
+
routing_key_bind = '' # headers 必须使用空 routing_key
|
|
32
|
+
arguments_for_bind = self.consumer_params.broker_exclusive_config.get('headers_for_bind', {})
|
|
33
|
+
arguments_for_bind['x-match'] = self.consumer_params.broker_exclusive_config.get('x_match_for_bind', 'all')
|
|
34
|
+
elif routing_key_bind is None: # 用户未指定绑定键时,根据交换机类型设置默认值
|
|
35
|
+
if rp._exchange_type == 'topic':
|
|
36
|
+
routing_key_bind = '#' # topic 默认订阅所有
|
|
37
|
+
else: # direct
|
|
38
|
+
routing_key_bind = self._queue_name
|
|
39
|
+
|
|
40
|
+
AmqpStormQueue(rp.channel).bind(queue=self._queue_name, exchange=rp._exchange_name,
|
|
41
|
+
routing_key=routing_key_bind, arguments=arguments_for_bind)
|
|
42
|
+
self._rp = rp
|
|
43
|
+
|
|
44
|
+
def _shedual_task(self):
|
|
45
|
+
# 重写父类的方法,以支持更复杂的绑定逻辑
|
|
46
|
+
def callback(amqpstorm_message: amqpstorm.Message):
|
|
47
|
+
body = amqpstorm_message.body
|
|
48
|
+
kw = {'amqpstorm_message': amqpstorm_message, 'body': body}
|
|
49
|
+
self._submit_task(kw)
|
|
50
|
+
|
|
51
|
+
rp = self._rp
|
|
52
|
+
rp.channel_wrapper_by_ampqstormbaic.qos(self.consumer_params.concurrent_num)
|
|
53
|
+
rp.channel_wrapper_by_ampqstormbaic.consume(callback=callback, queue=self.queue_name, no_ack=self.consumer_params.broker_exclusive_config['no_ack'])
|
|
54
|
+
rp.channel.start_consuming(auto_decode=True)
|
|
@@ -20,7 +20,7 @@ class RedisConsumer(AbstractConsumer, RedisMixin):
|
|
|
20
20
|
这个是复杂版,一次性拉取100个,减少和redis的交互,简单版在 funboost/consumers/redis_consumer_simple.py
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
|
|
25
25
|
# noinspection DuplicatedCode
|
|
26
26
|
def _shedual_task(self):
|
|
@@ -99,7 +99,7 @@ class RedisConsumerAckAble(ConsumerConfirmMixinWithTheHelpOfRedisByHearbeat, Abs
|
|
|
99
99
|
# print(script_4(keys=["text_pipelien1","text_pipelien1b"]))
|
|
100
100
|
"""
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
|
|
104
104
|
def _shedual_task000(self):
|
|
105
105
|
# 可以采用lua脚本,也可以采用redis的watch配合pipeline使用。比代码分两行pop和zadd比还能减少一次io交互,还能防止丢失小概率一个任务。
|
|
@@ -15,13 +15,9 @@ class RedisConsumerAckUsingTimeout(AbstractConsumer, RedisMixin):
|
|
|
15
15
|
使用超时未能ack就自动重入消息队列,例如消息取出后,由于突然断电或重启或其他原因,导致消息以后再也不能主动ack了,超过一定时间就重新放入消息队列
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'ack_timeout': 3600}
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@boost(BoosterParams(queue_name='test_redis_ack__use_timeout', broker_kind=BrokerEnum.REIDS_ACK_USING_TIMEOUT,
|
|
23
|
-
concurrent_num=5, log_level=20, broker_exclusive_config={'ack_timeout': 30}))
|
|
24
|
-
'''
|
|
19
|
+
|
|
20
|
+
|
|
25
21
|
|
|
26
22
|
def custom_init(self):
|
|
27
23
|
self._unack_zset_name = f'{self._queue_name}__unack_using_timeout'
|
|
@@ -13,11 +13,9 @@ class RedisStreamConsumer(AbstractConsumer, RedisMixin):
|
|
|
13
13
|
"""
|
|
14
14
|
redis 的 stream 结构 作为中间件实现的。需要redis 5.0以上,redis stream结构 是redis的消息队列,概念类似kafka,功能远超 list结构。
|
|
15
15
|
"""
|
|
16
|
-
GROUP = 'funboost_group'
|
|
17
|
-
BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'group': 'funboost_group','pull_msg_batch_size': 100}
|
|
18
16
|
|
|
19
17
|
def custom_init(self):
|
|
20
|
-
self.group = self.consumer_params.broker_exclusive_config['group']
|
|
18
|
+
self.group = self.consumer_params.broker_exclusive_config['group']
|
|
21
19
|
|
|
22
20
|
def start_consuming_message(self):
|
|
23
21
|
redis_server_info_dict = self.redis_db_frame.info()
|
|
File without changes
|