funboost 49.6__py3-none-any.whl → 49.8__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/concurrent_pool/async_pool_executor.py +16 -15
- funboost/constant.py +19 -0
- funboost/consumers/base_consumer.py +30 -8
- funboost/consumers/faststream_consumer.py +1 -1
- funboost/consumers/http_consumer.py +12 -7
- funboost/consumers/kafka_consumer_manually_commit.py +0 -2
- funboost/consumers/tcp_consumer.py +11 -10
- funboost/consumers/udp_consumer.py +9 -6
- funboost/consumers/zeromq_consumer.py +18 -11
- funboost/core/exceptions.py +7 -0
- funboost/core/func_params_model.py +18 -7
- funboost/core/function_result_status_saver.py +15 -0
- funboost/core/msg_result_getter.py +51 -1
- funboost/core/serialization.py +28 -1
- funboost/factories/consumer_factory.py +1 -1
- funboost/factories/publisher_factotry.py +1 -1
- funboost/funboost_config_deafult.py +2 -2
- funboost/function_result_web/__pycache__/app.cpython-39.pyc +0 -0
- funboost/publishers/base_publisher.py +16 -2
- funboost/publishers/http_publisher.py +7 -1
- funboost/publishers/tcp_publisher.py +10 -8
- funboost/publishers/udp_publisher.py +8 -6
- funboost/publishers/zeromq_publisher.py +5 -1
- funboost/timing_job/timing_push.py +3 -1
- {funboost-49.6.dist-info → funboost-49.8.dist-info}/METADATA +165 -171
- {funboost-49.6.dist-info → funboost-49.8.dist-info}/RECORD +31 -31
- {funboost-49.6.dist-info → funboost-49.8.dist-info}/WHEEL +1 -1
- {funboost-49.6.dist-info → funboost-49.8.dist-info}/LICENSE +0 -0
- {funboost-49.6.dist-info → funboost-49.8.dist-info}/entry_points.txt +0 -0
- {funboost-49.6.dist-info → funboost-49.8.dist-info}/top_level.txt +0 -0
funboost/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ import threading
|
|
|
6
6
|
import time
|
|
7
7
|
import traceback
|
|
8
8
|
from threading import Thread
|
|
9
|
+
import traceback
|
|
9
10
|
|
|
10
11
|
from funboost.concurrent_pool.base_pool_type import FunboostBaseConcurrentPool
|
|
11
12
|
from funboost.core.loggers import FunboostFileLoggerMixin
|
|
@@ -53,14 +54,17 @@ class AsyncPoolExecutor(FunboostFileLoggerMixin,FunboostBaseConcurrentPool):
|
|
|
53
54
|
使api和线程池一样,最好的性能做法是submit也弄成 async def,生产和消费在同一个线程同一个loop一起运行,但会对调用链路的兼容性产生破坏,从而调用方式不兼容线程池。
|
|
54
55
|
"""
|
|
55
56
|
|
|
56
|
-
def __init__(self, size,
|
|
57
|
+
def __init__(self, size, specify_async_loop=None,
|
|
58
|
+
is_auto_start_specify_async_loop_in_child_thread=True):
|
|
57
59
|
"""
|
|
58
60
|
|
|
59
61
|
:param size: 同时并发运行的协程任务数量。
|
|
60
|
-
:param loop
|
|
62
|
+
:param specify_loop: 可以指定loop,异步三方包的连接池发请求不能使用不同的loop去使用连接池.
|
|
61
63
|
"""
|
|
62
64
|
self._size = size
|
|
63
|
-
self.
|
|
65
|
+
self._specify_async_loop = specify_async_loop
|
|
66
|
+
self._is_auto_start_specify_async_loop_in_child_thread = is_auto_start_specify_async_loop_in_child_thread
|
|
67
|
+
self.loop = specify_async_loop or asyncio.new_event_loop()
|
|
64
68
|
asyncio.set_event_loop(self.loop)
|
|
65
69
|
self._diff_init()
|
|
66
70
|
# self._lock = threading.Lock()
|
|
@@ -120,11 +124,16 @@ class AsyncPoolExecutor(FunboostFileLoggerMixin,FunboostBaseConcurrentPool):
|
|
|
120
124
|
# self.loop.run_until_complete(asyncio.wait([self._consume() for _ in range(self._size)], loop=self.loop))
|
|
121
125
|
# self._can_be_closed_flag = True
|
|
122
126
|
[self.loop.create_task(self._consume()) for _ in range(self._size)]
|
|
123
|
-
|
|
127
|
+
if self._specify_async_loop is None:
|
|
124
128
|
self.loop.run_forever()
|
|
125
|
-
|
|
126
|
-
self.
|
|
127
|
-
|
|
129
|
+
else:
|
|
130
|
+
if self._is_auto_start_specify_async_loop_in_child_thread:
|
|
131
|
+
try:
|
|
132
|
+
self.loop.run_forever() #如果是指定的loop不能多次启动一个loop.
|
|
133
|
+
except Exception as e:
|
|
134
|
+
self.logger.warning(f'{e} {traceback.format_exc()}') # 如果多个线程使用一个loop,不能重复启动loop,否则会报错。
|
|
135
|
+
else:
|
|
136
|
+
pass # 用户需要自己在自己的业务代码中去手动启动loop.run_forever()
|
|
128
137
|
|
|
129
138
|
|
|
130
139
|
# def shutdown(self):
|
|
@@ -139,14 +148,6 @@ class AsyncPoolExecutor(FunboostFileLoggerMixin,FunboostBaseConcurrentPool):
|
|
|
139
148
|
|
|
140
149
|
|
|
141
150
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
151
|
if __name__ == '__main__':
|
|
151
152
|
def test_async_pool_executor():
|
|
152
153
|
from funboost.concurrent_pool import CustomThreadPoolExecutor as ThreadPoolExecutor
|
funboost/constant.py
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class BrokerEnum:
|
|
6
|
+
"""
|
|
7
|
+
在funboost中万物皆可为消息队列broker,funboost内置了所有 知名的正经经典消息队列作为broker,
|
|
8
|
+
也支持了基于 内存 各种数据库 文件系统 tcp/udp/http这些socket 模拟作为broker.
|
|
9
|
+
funboost也内置支持了各种python三方包和消费框架作为broker,例如 sqlachemy kombu celery rq dramtiq huey nameko 等等
|
|
10
|
+
|
|
11
|
+
用户也可以按照文档4.21章节,轻松扩展任何物质概念作为funboost的broker.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
# funboost框架能轻松兼容消息队列各种工作模式, 拉模式/推模式/轮询模式,单条获取 批量获取
|
|
15
|
+
"""
|
|
16
|
+
funboost 的 consumer的 _shedual_task 非常灵活,用户实现把从消息队列取出的消息通过_submit_task方法
|
|
17
|
+
丢到并发池,他不是强制用户重写实现怎么取一条消息,例如强制你实现一个 _get_one_message的法,
|
|
18
|
+
那就不灵活和限制扩展任意东西作为broker了,而是用户完全自己来写灵活代码。
|
|
19
|
+
所以无论获取消息是 拉模式 还是推模式 还是轮询模式,是单条获取 还是多条批量获取,
|
|
20
|
+
不管你的新中间件和rabbitmq api用法差别有多么巨大,都能轻松扩展任意东西作为funboost的中间件。
|
|
21
|
+
所以你能看到funboost源码中能轻松实现任物质概念作为funboost的broker。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
EMPTY = 'EMPTY' # 空的实现,需要搭配 boost入参的 consumer_override_cls 和 publisher_override_cls使用,或者被继承。
|
|
7
26
|
|
|
8
27
|
RABBITMQ_AMQPSTORM = 'RABBITMQ_AMQPSTORM' # 使用 amqpstorm 包操作rabbitmq 作为 分布式消息队列,支持消费确认.强烈推荐这个作为funboost中间件。
|
|
@@ -37,7 +37,7 @@ from funboost.core.current_task import funboost_current_task, FctContext
|
|
|
37
37
|
from funboost.core.loggers import develop_logger
|
|
38
38
|
|
|
39
39
|
from funboost.core.func_params_model import BoosterParams, PublisherParams, BaseJsonAbleModel
|
|
40
|
-
from funboost.core.serialization import Serialization
|
|
40
|
+
from funboost.core.serialization import PickleHelper, Serialization
|
|
41
41
|
from funboost.core.task_id_logger import TaskIdLogger
|
|
42
42
|
from funboost.constant import FunctionKind
|
|
43
43
|
|
|
@@ -448,10 +448,10 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
|
|
|
448
448
|
"""
|
|
449
449
|
raise NotImplementedError
|
|
450
450
|
|
|
451
|
-
def
|
|
451
|
+
def _convert_msg_before_run(self, msg: typing.Union[str, dict]) -> dict:
|
|
452
452
|
"""
|
|
453
453
|
转换消息,消息没有使用funboost来发送,并且没有extra相关字段时候
|
|
454
|
-
用户也可以按照4.21文档,继承任意Consumer
|
|
454
|
+
用户也可以按照4.21文档,继承任意Consumer类,并实现方法 _user_convert_msg_before_run,先转换不规范的消息.
|
|
455
455
|
"""
|
|
456
456
|
""" 一般消息至少包含这样
|
|
457
457
|
{
|
|
@@ -469,6 +469,7 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
|
|
|
469
469
|
extra_params = {'task_id': task_id, 'publish_time': round(time.time(), 4),
|
|
470
470
|
'publish_time_format': time.strftime('%Y-%m-%d %H:%M:%S')}
|
|
471
471
|
"""
|
|
472
|
+
msg = self._user_convert_msg_before_run(msg)
|
|
472
473
|
msg = Serialization.to_dict(msg)
|
|
473
474
|
# 以下是清洗补全字段.
|
|
474
475
|
if 'extra' not in msg:
|
|
@@ -481,10 +482,17 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
|
|
|
481
482
|
if 'publish_time_format':
|
|
482
483
|
extra['publish_time_format'] = MsgGenerater.generate_publish_time_format()
|
|
483
484
|
return msg
|
|
485
|
+
|
|
486
|
+
def _user_convert_msg_before_run(self, msg: typing.Union[str, dict]) -> dict:
|
|
487
|
+
"""
|
|
488
|
+
用户也可以提前清洗数据
|
|
489
|
+
"""
|
|
490
|
+
# print(msg)
|
|
491
|
+
return msg
|
|
484
492
|
|
|
485
493
|
def _submit_task(self, kw):
|
|
486
494
|
|
|
487
|
-
kw['body'] = self.
|
|
495
|
+
kw['body'] = self._convert_msg_before_run(kw['body'])
|
|
488
496
|
self._print_message_get_from_broker(kw['body'])
|
|
489
497
|
if self._judge_is_daylight():
|
|
490
498
|
self._requeue(kw)
|
|
@@ -635,8 +643,9 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
|
|
|
635
643
|
async def aio_user_custom_record_process_info_func(self, current_function_result_status: FunctionResultStatus): # 这个可以继承
|
|
636
644
|
pass
|
|
637
645
|
|
|
638
|
-
def _convert_real_function_only_params_by_conusuming_function_kind(self, function_only_params: dict):
|
|
646
|
+
def _convert_real_function_only_params_by_conusuming_function_kind(self, function_only_params: dict,extra_params:dict):
|
|
639
647
|
"""对于实例方法和classmethod 方法, 从消息队列的消息恢复第一个入参, self 和 cls"""
|
|
648
|
+
can_not_json_serializable_keys = extra_params.get('can_not_json_serializable_keys',[])
|
|
640
649
|
if self.consumer_params.consuming_function_kind in [FunctionKind.CLASS_METHOD, FunctionKind.INSTANCE_METHOD]:
|
|
641
650
|
real_function_only_params = copy.copy(function_only_params)
|
|
642
651
|
method_first_param_name = None
|
|
@@ -658,8 +667,14 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
|
|
|
658
667
|
obj = method_cls(**method_first_param_value[ConstStrForClassMethod.OBJ_INIT_PARAMS])
|
|
659
668
|
real_function_only_params[method_first_param_name] = obj
|
|
660
669
|
# print(real_function_only_params)
|
|
670
|
+
if can_not_json_serializable_keys:
|
|
671
|
+
for key in can_not_json_serializable_keys:
|
|
672
|
+
real_function_only_params[key] = PickleHelper.to_obj(real_function_only_params[key])
|
|
661
673
|
return real_function_only_params
|
|
662
674
|
else:
|
|
675
|
+
if can_not_json_serializable_keys:
|
|
676
|
+
for key in can_not_json_serializable_keys:
|
|
677
|
+
function_only_params[key] = PickleHelper.to_obj(function_only_params[key])
|
|
663
678
|
return function_only_params
|
|
664
679
|
|
|
665
680
|
# noinspection PyProtectedMember
|
|
@@ -758,7 +773,7 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
|
|
|
758
773
|
self.logger.warning(f'取消运行 {task_id} {function_only_params}')
|
|
759
774
|
return function_result_status
|
|
760
775
|
function_run = kill_remote_task.kill_fun_deco(task_id)(function_run) # 用杀死装饰器包装起来在另一个线程运行函数,以便等待远程杀死。
|
|
761
|
-
function_result_status.result = function_run(**self._convert_real_function_only_params_by_conusuming_function_kind(function_only_params))
|
|
776
|
+
function_result_status.result = function_run(**self._convert_real_function_only_params_by_conusuming_function_kind(function_only_params,kw['body']['extra']))
|
|
762
777
|
# if asyncio.iscoroutine(function_result_status.result):
|
|
763
778
|
# log_msg = f'''异步的协程消费函数必须使用 async 并发模式并发,请设置消费函数 {self.consuming_function.__name__} 的concurrent_mode 为 ConcurrentModeEnum.ASYNC 或 4'''
|
|
764
779
|
# # self.logger.critical(msg=f'{log_msg} \n')
|
|
@@ -813,6 +828,9 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
|
|
|
813
828
|
self.logger.error(msg=log_msg, exc_info=self._get_priority_conf(kw, 'is_print_detail_exception'))
|
|
814
829
|
# traceback.print_exc()
|
|
815
830
|
function_result_status.exception = f'{e.__class__.__name__} {str(e)}'
|
|
831
|
+
function_result_status.exception_msg = str(e)
|
|
832
|
+
function_result_status.exception_type = e.__class__.__name__
|
|
833
|
+
|
|
816
834
|
function_result_status.result = FunctionResultStatus.FUNC_RUN_ERROR
|
|
817
835
|
return function_result_status
|
|
818
836
|
|
|
@@ -899,7 +917,7 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
|
|
|
899
917
|
logger=self.logger,queue_name=self.queue_name,)
|
|
900
918
|
fct.set_fct_context(fct_context)
|
|
901
919
|
try:
|
|
902
|
-
corotinue_obj = self.consuming_function(**self._convert_real_function_only_params_by_conusuming_function_kind(function_only_params))
|
|
920
|
+
corotinue_obj = self.consuming_function(**self._convert_real_function_only_params_by_conusuming_function_kind(function_only_params,kw['body']['extra']))
|
|
903
921
|
if not asyncio.iscoroutine(corotinue_obj):
|
|
904
922
|
log_msg = f'''当前设置的并发模式为 async 并发模式,但消费函数不是异步协程函数,请不要把消费函数 {self.consuming_function.__name__} 的 concurrent_mode 设置错误'''
|
|
905
923
|
# self.logger.critical(msg=f'{log_msg} \n')
|
|
@@ -946,6 +964,8 @@ class AbstractConsumer(LoggerLevelSetterMixin, metaclass=abc.ABCMeta, ):
|
|
|
946
964
|
# self.error_file_logger.error(msg=f'{log_msg} \n', exc_info=self._get_priority_conf(kw, 'is_print_detail_exception'))
|
|
947
965
|
self.logger.error(msg=log_msg, exc_info=self._get_priority_conf(kw, 'is_print_detail_exception'))
|
|
948
966
|
function_result_status.exception = f'{e.__class__.__name__} {str(e)}'
|
|
967
|
+
function_result_status.exception_msg = str(e)
|
|
968
|
+
function_result_status.exception_type = e.__class__.__name__
|
|
949
969
|
function_result_status.result = FunctionResultStatus.FUNC_RUN_ERROR
|
|
950
970
|
return function_result_status
|
|
951
971
|
|
|
@@ -1106,7 +1126,9 @@ class ConcurrentModeDispatcher(FunboostFileLoggerMixin):
|
|
|
1106
1126
|
# pool_type = ProcessPoolExecutor
|
|
1107
1127
|
if self._concurrent_mode == ConcurrentModeEnum.ASYNC:
|
|
1108
1128
|
self.consumer._concurrent_pool = self.consumer.consumer_params.specify_concurrent_pool or pool_type(
|
|
1109
|
-
self.consumer.consumer_params.concurrent_num,
|
|
1129
|
+
self.consumer.consumer_params.concurrent_num,
|
|
1130
|
+
specify_async_loop=self.consumer.consumer_params.specify_async_loop,
|
|
1131
|
+
is_auto_start_specify_async_loop_in_child_thread=self.consumer.consumer_params.is_auto_start_specify_async_loop_in_child_thread)
|
|
1110
1132
|
else:
|
|
1111
1133
|
# print(pool_type)
|
|
1112
1134
|
self.consumer._concurrent_pool = self.consumer.consumer_params.specify_concurrent_pool or pool_type(self.consumer.consumer_params.concurrent_num)
|
|
@@ -22,7 +22,7 @@ class FastStreamConsumer(EmptyConsumer):
|
|
|
22
22
|
# print(logger.name)
|
|
23
23
|
# return self.consuming_function(*args, **kwargs) # 如果没有声明 autoretry_for ,那么消费函数出错了就不会自动重试了。
|
|
24
24
|
# print(msg)
|
|
25
|
-
function_only_params = delete_keys_and_return_new_dict(
|
|
25
|
+
function_only_params = delete_keys_and_return_new_dict(Serialization.to_dict(msg))
|
|
26
26
|
if self._consuming_function_is_asyncio:
|
|
27
27
|
result = await self.consuming_function(**function_only_params)
|
|
28
28
|
else:
|
|
@@ -10,20 +10,25 @@ import json
|
|
|
10
10
|
from funboost.consumers.base_consumer import AbstractConsumer
|
|
11
11
|
from funboost.core.lazy_impoter import AioHttpImporter
|
|
12
12
|
|
|
13
|
+
|
|
13
14
|
class HTTPConsumer(AbstractConsumer, ):
|
|
14
15
|
"""
|
|
15
16
|
http 实现消息队列,不支持持久化,但不需要安装软件。
|
|
16
17
|
"""
|
|
17
|
-
|
|
18
|
+
BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'host': '127.0.0.1', 'port': None}
|
|
18
19
|
|
|
19
20
|
# noinspection PyAttributeOutsideInit
|
|
20
21
|
def custom_init(self):
|
|
21
|
-
try:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
except BaseException as e:
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
# try:
|
|
23
|
+
# self._ip, self._port = self.queue_name.split(':')
|
|
24
|
+
# self._port = int(self._port)
|
|
25
|
+
# except BaseException as e:
|
|
26
|
+
# self.logger.critical(f'http作为消息队列时候,队列名字必须设置为 例如 192.168.1.101:8200 这种, ip:port')
|
|
27
|
+
# raise e
|
|
28
|
+
self._ip = self.consumer_params.broker_exclusive_config['host']
|
|
29
|
+
self._port = self.consumer_params.broker_exclusive_config['port']
|
|
30
|
+
if self._port is None:
|
|
31
|
+
raise ValueError('please specify port')
|
|
27
32
|
|
|
28
33
|
# noinspection DuplicatedCode
|
|
29
34
|
def _shedual_task(self):
|
|
@@ -72,8 +72,6 @@ class KafkaConsumerManuallyCommit(AbstractConsumer):
|
|
|
72
72
|
f'从kafka的 [{self._queue_name}] 主题,分区 {msg.partition()} 中 的 offset {msg.offset()} 取出的消息是: {msg.value()}') # noqa
|
|
73
73
|
self._submit_task(kw)
|
|
74
74
|
|
|
75
|
-
# kw = {'consumer': consumer, 'message': message, 'body': json.loads(message.value)}
|
|
76
|
-
# self._submit_task(kw)
|
|
77
75
|
|
|
78
76
|
def _manually_commit(self):
|
|
79
77
|
"""
|
|
@@ -13,32 +13,34 @@ class TCPConsumer(AbstractConsumer, ):
|
|
|
13
13
|
socket 实现消息队列,不支持持久化,但不需要安装软件。
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
BUFSIZE = 10240
|
|
16
|
+
BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'host': '127.0.0.1', 'port': None, 'bufsize': 10240}
|
|
18
17
|
|
|
19
18
|
# noinspection PyAttributeOutsideInit
|
|
20
19
|
def custom_init(self):
|
|
21
|
-
ip__port_str = self.queue_name.split(':')
|
|
22
|
-
ip_port = (ip__port_str[0], int(ip__port_str[1]))
|
|
23
|
-
self._ip_port_raw = ip_port
|
|
24
|
-
self._ip_port = ('', ip_port[1])
|
|
20
|
+
# ip__port_str = self.queue_name.split(':')
|
|
21
|
+
# ip_port = (ip__port_str[0], int(ip__port_str[1]))
|
|
22
|
+
# self._ip_port_raw = ip_port
|
|
23
|
+
# self._ip_port = ('', ip_port[1])
|
|
25
24
|
# ip_port = ('', 9999)
|
|
25
|
+
self._ip_port = (self.consumer_params.broker_exclusive_config['host'],
|
|
26
|
+
self.consumer_params.broker_exclusive_config['port'])
|
|
27
|
+
self.bufsize = self.consumer_params.broker_exclusive_config['bufsize']
|
|
26
28
|
|
|
27
29
|
# noinspection DuplicatedCode
|
|
28
30
|
def _shedual_task(self):
|
|
29
|
-
|
|
31
|
+
|
|
30
32
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # tcp协议
|
|
31
33
|
server.bind(self._ip_port)
|
|
32
34
|
server.listen(128)
|
|
33
35
|
self._server = server
|
|
34
36
|
while True:
|
|
35
37
|
tcp_cli_sock, addr = self._server.accept()
|
|
36
|
-
Thread(target=self.__handle_conn, args=(tcp_cli_sock,)).start()
|
|
38
|
+
Thread(target=self.__handle_conn, args=(tcp_cli_sock,)).start() # 服务端多线程,可以同时处理多个tcp长链接客户端发来的消息。
|
|
37
39
|
|
|
38
40
|
def __handle_conn(self, tcp_cli_sock):
|
|
39
41
|
try:
|
|
40
42
|
while True:
|
|
41
|
-
data = tcp_cli_sock.recv(self.
|
|
43
|
+
data = tcp_cli_sock.recv(self.bufsize)
|
|
42
44
|
# print('server收到的数据', data)
|
|
43
45
|
if not data:
|
|
44
46
|
break
|
|
@@ -56,4 +58,3 @@ class TCPConsumer(AbstractConsumer, ):
|
|
|
56
58
|
|
|
57
59
|
def _requeue(self, kw):
|
|
58
60
|
pass
|
|
59
|
-
|
|
@@ -12,14 +12,17 @@ class UDPConsumer(AbstractConsumer, ):
|
|
|
12
12
|
socket 实现消息队列,不支持持久化,但不需要安装软件。
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'host': '127.0.0.1', 'port': None, 'bufsize': 10240}
|
|
16
16
|
|
|
17
17
|
# noinspection PyAttributeOutsideInit
|
|
18
18
|
def custom_init(self):
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
self.__udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
21
|
-
ip__port_str = self.queue_name.split(':')
|
|
22
|
-
self.__ip_port = (ip__port_str[0], int(ip__port_str[1]))
|
|
21
|
+
# ip__port_str = self.queue_name.split(':')
|
|
22
|
+
# self.__ip_port = (ip__port_str[0], int(ip__port_str[1]))
|
|
23
|
+
self.__ip_port = (self.consumer_params.broker_exclusive_config['host'],
|
|
24
|
+
self.consumer_params.broker_exclusive_config['port'])
|
|
25
|
+
self._bufsize = self.consumer_params.broker_exclusive_config['bufsize']
|
|
23
26
|
self.__udp_client.connect(self.__ip_port)
|
|
24
27
|
|
|
25
28
|
# noinspection DuplicatedCode
|
|
@@ -29,7 +32,7 @@ class UDPConsumer(AbstractConsumer, ):
|
|
|
29
32
|
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # udp协议
|
|
30
33
|
server.bind(ip_port)
|
|
31
34
|
while True:
|
|
32
|
-
data, client_addr = server.recvfrom(self.
|
|
35
|
+
data, client_addr = server.recvfrom(self._bufsize)
|
|
33
36
|
# print('server收到的数据', data)
|
|
34
37
|
# self._print_message_get_from_broker(f'udp {ip_port}', data.decode())
|
|
35
38
|
server.sendto('has_recived'.encode(), client_addr)
|
|
@@ -41,4 +44,4 @@ class UDPConsumer(AbstractConsumer, ):
|
|
|
41
44
|
|
|
42
45
|
def _requeue(self, kw):
|
|
43
46
|
self.__udp_client.send(json.dumps(kw['body']).encode())
|
|
44
|
-
data = self.__udp_client.recv(self.
|
|
47
|
+
data = self.__udp_client.recv(self._bufsize)
|
|
@@ -67,31 +67,38 @@ class ZeroMqConsumer(AbstractConsumer):
|
|
|
67
67
|
zeromq 中间件的消费者,zeromq基于socket代码,不会持久化,且不需要安装软件。
|
|
68
68
|
"""
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
BROKER_EXCLUSIVE_CONFIG_DEFAULT = {'port': None}
|
|
71
|
+
|
|
72
|
+
def custom_init(self):
|
|
73
|
+
self._port = self.consumer_params.broker_exclusive_config['port']
|
|
74
|
+
if self._port is None:
|
|
75
|
+
raise ValueError('please specify port')
|
|
76
|
+
|
|
77
|
+
def _start_broker_port(self):
|
|
71
78
|
# threading.Thread(target=self._start_broker).start()
|
|
72
79
|
# noinspection PyBroadException
|
|
73
80
|
try:
|
|
74
|
-
if not (10000 < int(self.
|
|
75
|
-
raise ValueError("
|
|
81
|
+
if not (10000 < int(self._port) < 65535):
|
|
82
|
+
raise ValueError("请设置port是一个 10000到65535的之间的一个端口数字")
|
|
76
83
|
except BaseException:
|
|
77
|
-
self.logger.critical(f"
|
|
84
|
+
self.logger.critical(f" 请设置port是一个 10000到65535的之间的一个端口数字")
|
|
78
85
|
# noinspection PyProtectedMember
|
|
79
86
|
os._exit(444)
|
|
80
|
-
if check_port_is_used('127.0.0.1', int(self.
|
|
81
|
-
self.logger.debug(f"""{int(self.
|
|
87
|
+
if check_port_is_used('127.0.0.1', int(self._port)):
|
|
88
|
+
self.logger.debug(f"""{int(self._port)} router端口已经启动(或占用) """)
|
|
82
89
|
return
|
|
83
|
-
if check_port_is_used('127.0.0.1', int(self.
|
|
84
|
-
self.logger.debug(f"""{int(self.
|
|
90
|
+
if check_port_is_used('127.0.0.1', int(self._port) + 1):
|
|
91
|
+
self.logger.debug(f"""{int(self._port) + 1} dealer 端口已经启动(或占用) """)
|
|
85
92
|
return
|
|
86
|
-
multiprocessing.Process(target=start_broker, args=(int(self.
|
|
93
|
+
multiprocessing.Process(target=start_broker, args=(int(self._port), int(self._port) + 1)).start()
|
|
87
94
|
|
|
88
95
|
# noinspection DuplicatedCode
|
|
89
96
|
def _shedual_task(self):
|
|
90
|
-
self.
|
|
97
|
+
self._start_broker_port()
|
|
91
98
|
context = ZmqImporter().zmq.Context()
|
|
92
99
|
# noinspection PyUnresolvedReferences
|
|
93
100
|
zsocket = context.socket(ZmqImporter().zmq.REP)
|
|
94
|
-
zsocket.connect(f"tcp://localhost:{int(self.
|
|
101
|
+
zsocket.connect(f"tcp://localhost:{int(self._port) + 1}")
|
|
95
102
|
|
|
96
103
|
while True:
|
|
97
104
|
message = zsocket.recv()
|
funboost/core/exceptions.py
CHANGED
|
@@ -11,6 +11,11 @@ class ExceptionForRetry(FunboostException):
|
|
|
11
11
|
class ExceptionForRequeue(FunboostException):
|
|
12
12
|
"""框架检测到此错误,重新放回当前队列中"""
|
|
13
13
|
|
|
14
|
+
class FunboostWaitRpcResultTimeout(FunboostException):
|
|
15
|
+
"""等待rpc结果超过了指定时间"""
|
|
16
|
+
|
|
17
|
+
class FunboostRpcResultError(FunboostException):
|
|
18
|
+
"""rpc结果是错误状态"""
|
|
14
19
|
|
|
15
20
|
class ExceptionForPushToDlxqueue(FunboostException):
|
|
16
21
|
"""框架检测到ExceptionForPushToDlxqueue错误,发布到死信队列"""
|
|
@@ -40,3 +45,5 @@ def f(x):
|
|
|
40
45
|
|
|
41
46
|
def __str__(self):
|
|
42
47
|
return self.new_version_change_hint
|
|
48
|
+
|
|
49
|
+
|
|
@@ -109,8 +109,8 @@ class FunctionResultStatusPersistanceConfig(BaseJsonAbleModel):
|
|
|
109
109
|
flogger.warning(f'你设置的过期时间为 {value} ,设置的时间过长。 ')
|
|
110
110
|
return value
|
|
111
111
|
|
|
112
|
-
@root_validator(skip_on_failure=True
|
|
113
|
-
def
|
|
112
|
+
@root_validator(skip_on_failure=True)
|
|
113
|
+
def check_values(cls, values: dict):
|
|
114
114
|
if not values['is_save_status'] and values['is_save_result']:
|
|
115
115
|
raise ValueError(f'你设置的是不保存函数运行状态但保存函数运行结果。不允许你这么设置')
|
|
116
116
|
return values
|
|
@@ -134,7 +134,9 @@ class BoosterParams(BaseJsonAbleModel):
|
|
|
134
134
|
concurrent_mode: str = ConcurrentModeEnum.THREADING # 并发模式,支持THREADING,GEVENT,EVENTLET,ASYNC,SINGLE_THREAD并发,multi_process_consume 支持协程/线程 叠加多进程并发,性能炸裂.
|
|
135
135
|
concurrent_num: int = 50 # 并发数量,并发种类由concurrent_mode决定
|
|
136
136
|
specify_concurrent_pool: typing.Optional[FunboostBaseConcurrentPool] = None # 使用指定的线程池/携程池,可以多个消费者共使用一个线程池,节约线程.不为None时候。threads_num失效
|
|
137
|
+
|
|
137
138
|
specify_async_loop: typing.Optional[asyncio.AbstractEventLoop] = None # 指定的async的loop循环,设置并发模式为async才能起作用。 有些包例如aiohttp,发送请求和httpclient的实例化不能处在两个不同的loop中,可以传过来.
|
|
139
|
+
is_auto_start_specify_async_loop_in_child_thread: bool = True # 是否在子线程中自动启动指定的async的loop循环,设置并发模式为async才能起作用。如果是False,用户自己在自己的代码中去手动启动自己的loop.run_forever()
|
|
138
140
|
|
|
139
141
|
"""qps:
|
|
140
142
|
强悍的控制功能,指定1秒内的函数执行次数,例如可以是小数0.01代表每100秒执行一次,也可以是50代表1秒执行50次.为None则不控频。 设置qps时候,不需要指定并发数量,funboost的能够自适应智能动态调节并发池大小."""
|
|
@@ -159,10 +161,18 @@ class BoosterParams(BaseJsonAbleModel):
|
|
|
159
161
|
consumin_function_decorator: typing.Optional[typing.Callable] = None # 函数的装饰器。因为此框架做参数自动转指点,需要获取精准的入参名称,不支持在消费函数上叠加 @ *args **kwargs的装饰器,如果想用装饰器可以这里指定。
|
|
160
162
|
function_timeout: typing.Union[int, float,None] = None # 超时秒数,函数运行超过这个时间,则自动杀死函数。为0是不限制。 谨慎使用,非必要别去设置超时时间,设置后性能会降低(因为需要把用户函数包装到另一个线单独的程中去运行),而且突然强制超时杀死运行中函数,可能会造成死锁.(例如用户函数在获得线程锁后突然杀死函数,别的线程再也无法获得锁了)
|
|
161
163
|
|
|
162
|
-
|
|
164
|
+
"""
|
|
165
|
+
log_level:
|
|
166
|
+
logger_name 对应的 日志级别
|
|
167
|
+
消费者和发布者的日志级别,建议设置DEBUG级别,不然无法知道正在运行什么消息.
|
|
168
|
+
这个是funboost每个队列的单独命名空间的日志级别,丝毫不会影响改变用户其他日志以及root命名空间的日志级别,所以DEBUG级别就好,
|
|
169
|
+
用户不要压根不懂什么是python logger 的name,还去手痒调高级别.
|
|
170
|
+
不懂python日志命名空间的小白去看nb_log文档,或者直接问 ai大模型 python logger name的作用是什么.
|
|
171
|
+
"""
|
|
172
|
+
log_level: int = logging.DEBUG # 不需要改这个级别,请看上面原因
|
|
163
173
|
logger_prefix: str = '' # 日志名字前缀,可以设置前缀
|
|
164
174
|
create_logger_file: bool = True # 发布者和消费者是否创建文件文件日志,为False则只打印控制台不写文件.
|
|
165
|
-
logger_name: str = '' # 队列消费者发布者的日志命名空间.
|
|
175
|
+
logger_name: typing.Union[str, None] = '' # 队列消费者发布者的日志命名空间.
|
|
166
176
|
log_filename: typing.Union[str, None] = None # 消费者发布者的文件日志名字.如果为None,则自动使用 funboost.队列 名字作为文件日志名字. 日志文件夹是在nb_log_config.py的 LOG_PATH中决定的.
|
|
167
177
|
is_show_message_get_from_broker: bool = False # 运行时候,是否记录从消息队列获取出来的消息内容
|
|
168
178
|
is_print_detail_exception: bool = True # 消费函数出错时候,是否打印详细的报错堆栈,为False则只打印简略的报错信息不包含堆栈.
|
|
@@ -221,7 +231,7 @@ class BoosterParams(BaseJsonAbleModel):
|
|
|
221
231
|
|
|
222
232
|
|
|
223
233
|
|
|
224
|
-
@root_validator(skip_on_failure=True)
|
|
234
|
+
@root_validator(skip_on_failure=True, )
|
|
225
235
|
def check_values(cls, values: dict):
|
|
226
236
|
|
|
227
237
|
|
|
@@ -307,7 +317,7 @@ class PriorityConsumingControlConfig(BaseJsonAbleModel):
|
|
|
307
317
|
eta: typing.Union[datetime.datetime, str,None] = None # 时间对象, 或 %Y-%m-%d %H:%M:%S 字符串。
|
|
308
318
|
misfire_grace_time: typing.Union[int, None] = None
|
|
309
319
|
|
|
310
|
-
other_extra_params: dict = None # 其他参数, 例如消息优先级 , priority_control_config=PriorityConsumingControlConfig(other_extra_params={'priroty': priorityxx}),
|
|
320
|
+
other_extra_params: typing.Optional[dict] = None # 其他参数, 例如消息优先级 , priority_control_config=PriorityConsumingControlConfig(other_extra_params={'priroty': priorityxx}),
|
|
311
321
|
|
|
312
322
|
"""filter_str:
|
|
313
323
|
用户指定过滤字符串, 例如函数入参是 def fun(userid,username,sex,user_description),
|
|
@@ -318,6 +328,7 @@ class PriorityConsumingControlConfig(BaseJsonAbleModel):
|
|
|
318
328
|
"""
|
|
319
329
|
filter_str :typing.Optional[str] = None
|
|
320
330
|
|
|
331
|
+
can_not_json_serializable_keys: typing.List[str] = None # 不能json序列化的入参名字,反序列化时候需要使用pickle来反序列化这些字段(这个是自动生成的,用户不需要手动指定此入参。)
|
|
321
332
|
@root_validator(skip_on_failure=True)
|
|
322
333
|
def cehck_values(cls, values: dict):
|
|
323
334
|
if values['countdown'] and values['eta']:
|
|
@@ -356,4 +367,4 @@ if __name__ == '__main__':
|
|
|
356
367
|
# print(PriorityConsumingControlConfig().get_str_dict())
|
|
357
368
|
|
|
358
369
|
print(BoosterParams(queue_name='3213', specify_concurrent_pool=FlexibleThreadPool(100)).json_pre())
|
|
359
|
-
print(PublisherParams.schema_json())
|
|
370
|
+
# print(PublisherParams.schema_json()) # 注释掉,因为 PublisherParams 包含 Callable 类型字段,无法生成 JSON Schema
|
|
@@ -51,6 +51,9 @@ class FunctionResultStatus():
|
|
|
51
51
|
self.result = None
|
|
52
52
|
self.run_times = 0
|
|
53
53
|
self.exception = None
|
|
54
|
+
self.exception_type = None
|
|
55
|
+
self.exception_msg = None
|
|
56
|
+
self.rpc_chain_error_msg_dict:dict = None
|
|
54
57
|
self.time_start = time.time()
|
|
55
58
|
self.time_cost = None
|
|
56
59
|
self.time_end = None
|
|
@@ -62,6 +65,15 @@ class FunctionResultStatus():
|
|
|
62
65
|
self._has_kill_task = False
|
|
63
66
|
self.rpc_result_expire_seconds = None
|
|
64
67
|
|
|
68
|
+
@classmethod
|
|
69
|
+
def parse_status_and_result_to_obj(cls,status_dict:dict):
|
|
70
|
+
obj = cls(status_dict['queue_name'],status_dict['function'],status_dict['msg_dict'])
|
|
71
|
+
for k,v in status_dict.items():
|
|
72
|
+
# if k.startswith('_'):
|
|
73
|
+
# continue
|
|
74
|
+
setattr(obj,k,v)
|
|
75
|
+
return obj
|
|
76
|
+
|
|
65
77
|
def get_status_dict(self, without_datetime_obj=False):
|
|
66
78
|
self.time_end = time.time()
|
|
67
79
|
if self.run_status == RunStatus.running:
|
|
@@ -102,6 +114,9 @@ class FunctionResultStatus():
|
|
|
102
114
|
def __str__(self):
|
|
103
115
|
return f'''{self.__class__} {Serialization.to_json_str(self.get_status_dict())}'''
|
|
104
116
|
|
|
117
|
+
def to_pretty_json_str(self):
|
|
118
|
+
return json.dumps(self.get_status_dict(),indent=4,ensure_ascii=False)
|
|
119
|
+
|
|
105
120
|
|
|
106
121
|
class ResultPersistenceHelper(MongoMixin, FunboostFileLoggerMixin):
|
|
107
122
|
TASK_STATUS_DB = 'task_status'
|
|
@@ -12,13 +12,29 @@ from funboost.utils.redis_manager import RedisMixin
|
|
|
12
12
|
from funboost.utils.redis_manager import AioRedisMixin
|
|
13
13
|
from funboost.core.serialization import Serialization
|
|
14
14
|
|
|
15
|
+
from funboost.core.function_result_status_saver import FunctionResultStatus
|
|
16
|
+
|
|
15
17
|
class HasNotAsyncResult(Exception):
|
|
16
18
|
pass
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
NO_RESULT = 'no_result'
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
def _judge_rpc_function_result_status_obj(status_and_result_obj:FunctionResultStatus,raise_exception:bool):
|
|
24
|
+
if status_and_result_obj is None:
|
|
25
|
+
raise FunboostWaitRpcResultTimeout(f'等待 {status_and_result_obj.task_id} rpc结果超过了指定时间')
|
|
26
|
+
if status_and_result_obj.success is True:
|
|
27
|
+
return status_and_result_obj
|
|
28
|
+
else:
|
|
29
|
+
raw_erorr = status_and_result_obj.exception
|
|
30
|
+
if status_and_result_obj.exception_type == 'FunboostRpcResultError':
|
|
31
|
+
raw_erorr = json.loads(status_and_result_obj.exception_msg) # 使canvas链式报错json显示更美观
|
|
32
|
+
error_msg_dict = {'task_id':status_and_result_obj.task_id,'raw_error':raw_erorr}
|
|
33
|
+
if raise_exception:
|
|
34
|
+
raise FunboostRpcResultError(json.dumps(error_msg_dict,indent=4,ensure_ascii=False))
|
|
35
|
+
else:
|
|
36
|
+
status_and_result_obj.rpc_chain_error_msg_dict = error_msg_dict
|
|
37
|
+
return status_and_result_obj
|
|
22
38
|
class AsyncResult(RedisMixin):
|
|
23
39
|
default_callback_run_executor = FlexibleThreadPoolMinWorkers0(200,work_queue_maxsize=50)
|
|
24
40
|
|
|
@@ -63,6 +79,14 @@ class AsyncResult(RedisMixin):
|
|
|
63
79
|
return self._status_and_result
|
|
64
80
|
return None
|
|
65
81
|
return self._status_and_result
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def status_and_result_obj(self) -> FunctionResultStatus:
|
|
85
|
+
"""这个是为了比字典有更好的ide代码补全效果"""
|
|
86
|
+
if self.status_and_result is not None:
|
|
87
|
+
return FunctionResultStatus.parse_status_and_result_to_obj(self.status_and_result)
|
|
88
|
+
|
|
89
|
+
rpc_data =status_and_result_obj
|
|
66
90
|
|
|
67
91
|
def get(self):
|
|
68
92
|
# print(self.status_and_result)
|
|
@@ -104,6 +128,14 @@ class AsyncResult(RedisMixin):
|
|
|
104
128
|
async_result.set_callback(show_result) # 使用回调函数在线程池中并发的运行函数结果
|
|
105
129
|
'''
|
|
106
130
|
self.callback_run_executor.submit(self._run_callback_func, callback_func)
|
|
131
|
+
|
|
132
|
+
def wait_rpc_data_or_raise(self,raise_exception:bool=True)->FunctionResultStatus:
|
|
133
|
+
return _judge_rpc_function_result_status_obj(self.status_and_result_obj,raise_exception)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def batch_wait_rpc_data_or_raise(cls,r_list:typing.List['AsyncResult'],raise_exception:bool=True)->typing.List[FunctionResultStatus]:
|
|
137
|
+
return [ _judge_rpc_function_result_status_obj(r.status_and_result_obj,raise_exception)
|
|
138
|
+
for r in r_list]
|
|
107
139
|
|
|
108
140
|
|
|
109
141
|
class AioAsyncResult(AioRedisMixin):
|
|
@@ -169,6 +201,14 @@ if __name__ == '__main__':
|
|
|
169
201
|
return None
|
|
170
202
|
return self._status_and_result
|
|
171
203
|
|
|
204
|
+
@property
|
|
205
|
+
async def status_and_result_obj(self) -> FunctionResultStatus:
|
|
206
|
+
"""这个是为了比字典有更好的ide代码补全效果"""
|
|
207
|
+
sr = await self.status_and_result
|
|
208
|
+
if sr is not None:
|
|
209
|
+
return FunctionResultStatus.parse_status_and_result_to_obj(sr)
|
|
210
|
+
|
|
211
|
+
rpc_data =status_and_result_obj
|
|
172
212
|
async def get(self):
|
|
173
213
|
# print(self.status_and_result)
|
|
174
214
|
if (await self.status_and_result) is not None:
|
|
@@ -192,6 +232,16 @@ if __name__ == '__main__':
|
|
|
192
232
|
async def set_callback(self, aio_callback_func: typing.Callable):
|
|
193
233
|
asyncio.create_task(self._run_callback_func(callback_func=aio_callback_func))
|
|
194
234
|
|
|
235
|
+
async def wait_rpc_data_or_raise(self,raise_exception:bool=True)->FunctionResultStatus:
|
|
236
|
+
return _judge_rpc_function_result_status_obj(await self.status_and_result_obj,raise_exception)
|
|
237
|
+
|
|
238
|
+
@classmethod
|
|
239
|
+
async def batch_wait_rpc_data_or_raise(cls,r_list:typing.List['AioAsyncResult'],raise_exception:bool=True)->typing.List[FunctionResultStatus]:
|
|
240
|
+
return [ _judge_rpc_function_result_status_obj(await r.status_and_result_obj,raise_exception)
|
|
241
|
+
for r in r_list]
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
195
245
|
|
|
196
246
|
class ResultFromMongo(MongoMixin):
|
|
197
247
|
"""
|