funboost 49.1__py3-none-any.whl → 49.2__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.

Files changed (26) hide show
  1. funboost/__init__.py +1 -1
  2. funboost/concurrent_pool/async_helper.py +20 -1
  3. funboost/concurrent_pool/async_pool_executor.py +40 -56
  4. funboost/concurrent_pool/backup/async_pool_executor_back.py +4 -2
  5. funboost/constant.py +27 -3
  6. funboost/consumers/base_consumer.py +11 -6
  7. funboost/core/active_cousumer_info_getter.py +51 -4
  8. funboost/function_result_web/__pycache__/app.cpython-313.pyc +0 -0
  9. funboost/function_result_web/__pycache__/app.cpython-37.pyc +0 -0
  10. funboost/function_result_web/__pycache__/functions.cpython-313.pyc +0 -0
  11. funboost/function_result_web/app.py +16 -3
  12. funboost/function_result_web/app_debug_start.py +4 -0
  13. funboost/function_result_web/templates/queue_op.html +149 -192
  14. funboost/publishers/faststream_publisher.py +3 -2
  15. funboost/set_frame_config.py +2 -1
  16. funboost/timing_job/apscheduler_use_redis_store.py +8 -1
  17. funboost/timing_job/timing_job_base.py +20 -7
  18. funboost/timing_job/timing_push.py +7 -5
  19. funboost/utils/dependency_packages_in_pythonpath/aioredis/readme.md +0 -6
  20. funboost/utils/dependency_packages_in_pythonpath/readme.md +0 -6
  21. {funboost-49.1.dist-info → funboost-49.2.dist-info}/METADATA +6 -5
  22. {funboost-49.1.dist-info → funboost-49.2.dist-info}/RECORD +26 -24
  23. {funboost-49.1.dist-info → funboost-49.2.dist-info}/LICENSE +0 -0
  24. {funboost-49.1.dist-info → funboost-49.2.dist-info}/WHEEL +0 -0
  25. {funboost-49.1.dist-info → funboost-49.2.dist-info}/entry_points.txt +0 -0
  26. {funboost-49.1.dist-info → funboost-49.2.dist-info}/top_level.txt +0 -0
@@ -76,7 +76,15 @@
76
76
  <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
77
77
  </div>
78
78
  <div class="modal-body">
79
- <canvas id="queueDataChart"></canvas>
79
+ <!-- 新增:时间范围筛选控件 -->
80
+ <div style="margin-bottom: 10px; display: flex; align-items: center;">
81
+ <label style="margin-right:5px;">起始时间:</label>
82
+ <input type="datetime-local" id="chartStartTime" style="margin-right: 10px;">
83
+ <label style="margin-right:5px;">结束时间:</label>
84
+ <input type="datetime-local" id="chartEndTime" style="margin-right: 10px;">
85
+ <button class="btn btn-primary btn-sm" onclick="reloadQueueChartWithTimeRange()">查询</button>
86
+ </div>
87
+ <canvas id="queueDataChart" style="height:600px;max-height:600px;"></canvas>
80
88
  </div>
81
89
  <div class="modal-footer">
82
90
  <button type="button" class="btn btn-default" data-dismiss="modal">关闭</button>
@@ -159,22 +167,36 @@
159
167
  frozen: true,
160
168
  formatter: function(cell, formatterParams, onRendered) {
161
169
  const queueName = cell.getValue();
162
- // 根据 isAutoRefreshing 状态动态设置按钮的显示样式
163
- const buttonDisplay = isAutoRefreshing ? 'inline-block' : 'none';
170
+ // 让按钮始终显示,不再依赖 isAutoRefreshing
164
171
  return `<div style="display: flex; justify-content: space-between; align-items: center;">
165
172
  <span>${queueName}</span>
166
173
  <button class="btn btn-xs btn-info view-chart-btn"
167
174
  data-queue-name="${queueName}"
168
- style="margin-left: 10px; display: ${buttonDisplay};"
175
+ style="margin-left: 10px; display: inline-block;"
169
176
  onclick="showQueueChart('${queueName}')">
170
177
  <i class="glyphicon glyphicon-stats"></i> 查看曲线图
171
178
  </button>
172
179
  </div>`;
173
180
  }
174
181
  },
182
+ { title: "<br><br>consumer数量", field: "consumer_count", sorter: "number", width: 200,
183
+ formatter: function(cell) {
184
+ const row = cell.getRow().getData();
185
+ var consumers = row.active_consumers;
186
+ return `
187
+ <div style="display: flex; align-items: center; justify-content: space-between; width: 100%; padding-right: 10px;">
188
+ <span style="min-width: 50px; text-align: right; padding-right: 15px;">${cell.getValue() || ''}</span>
189
+ <button class="btn btn-primary btn-sm" onclick='showConsumerDetails(${JSON.stringify(consumers)}, "${row.queue_name}")'>
190
+ 查看消费者详情
191
+ </button>
192
+ </div>
193
+ `;
194
+ }
195
+ },
175
196
 
176
197
  { title: "<br>broker<br>类型", field: "broker_kind", sorter: "string" },
177
198
  { title: "<br>消费<br>函数", field: "consuming_function_name", sorter: "string" },
199
+
178
200
  { title: "<br>历史运<br>行次数", field: "history_run_count", sorter: "number", width: 150 },
179
201
  { title: "<br>历史运<br>行失败<br>次数", field: "history_run_fail_count", sorter: "number", width: 150 },
180
202
  { title: "<br>近10秒<br>完成", field: "all_consumers_last_x_s_execute_count", sorter: "number", width: 100 },
@@ -196,20 +218,7 @@
196
218
  `;
197
219
  }
198
220
  },
199
- { title: "<br><br>consumer数量", field: "consumer_count", sorter: "number", width: 200,
200
- formatter: function(cell) {
201
- const row = cell.getRow().getData();
202
- var consumers = row.active_consumers;
203
- return `
204
- <div style="display: flex; align-items: center; justify-content: space-between; width: 100%; padding-right: 10px;">
205
- <span style="min-width: 50px; text-align: right; padding-right: 15px;">${cell.getValue() || ''}</span>
206
- <button class="btn btn-primary btn-sm" onclick='showConsumerDetails(${JSON.stringify(consumers)}, "${row.queue_name}")'>
207
- 查看消费者详情
208
- </button>
209
- </div>
210
- `;
211
- }
212
- },
221
+
213
222
  {
214
223
  title: "暂停<br>消费<br>状态",
215
224
  field: "pause_flag",
@@ -256,16 +265,6 @@
256
265
  all_consumers_avarage_function_spend_time_from_start:data.all_consumers_avarage_function_spend_time_from_start
257
266
  }));
258
267
 
259
- const processedTableDataForHistory = tableData.map(item => {
260
- // 确保 msg_count 用于历史记录时是数字
261
- const numericMsgCount = parseFloat(item.msg_count);
262
- return {
263
- ...item,
264
- msg_count_numeric: isNaN(numericMsgCount) ? 0 : numericMsgCount
265
- };
266
- });
267
- updateHistoricalData(processedTableDataForHistory);
268
-
269
268
  return tableData;
270
269
  },
271
270
  });
@@ -502,9 +501,8 @@
502
501
  let isAutoRefreshing = false;
503
502
  let autoRefreshIntervalId = null;
504
503
  const AUTO_REFRESH_INTERVAL = 10000; // 10 秒
505
- let historicalData = {}; // 结构: { queueName: [{timestamp: Date.now(), history_run_count: val, ...}, ...], ... }
506
- const MAX_DATA_AGE_MS = 60 * 60 * 1000; // 1 小时
507
-
504
+ let chartInstance = null; // 用于存储Chart.js的实例
505
+
508
506
  // 定义需要记录并展示在图表中的列字段及其显示名称
509
507
  const CHARTABLE_FIELDS_MAP = {
510
508
  "history_run_count": "历史运行次数",
@@ -513,186 +511,165 @@
513
511
  "all_consumers_last_x_s_execute_count_fail": "近10秒失败",
514
512
  "all_consumers_last_x_s_avarage_function_spend_time": "近10秒函数运行平均耗时",
515
513
  "all_consumers_avarage_function_spend_time_from_start": "累计函数运行平均耗时",
516
- "msg_count_numeric": "消息数量" // 注意:这是我们将在ajaxResponse中处理得到的数值型消息数量
514
+ "msg_num_in_broker": "消息数量"
517
515
  };
518
- let chartInstance = null; // 用于存储Chart.js的实例
519
- let activeChartQueueName = null; // 新增:跟踪当前活动图表的队列名称
520
-
521
- // 新增:预定义的颜色数组,用于图表线条 (更新后的高区分度颜色)
516
+ // 预定义颜色
522
517
  const PREDEFINED_COLORS = [
523
- '#E60012', // 鲜红 (Red)
524
- '#005AC8', // 鲜蓝 (Blue)
525
- '#00A600', // 鲜绿 (Green)
526
- '#FF9900', // 亮橙 (Orange)
527
- '#8B28B7', // 紫色 (Purple)
528
- '#9A6324', // 棕色 (Brown)
529
- '#5E8C78', // 暗青色 (Teal)
530
- '#F58231', // 亮橙红 (Vermilion)
531
- '#42D4F4', // 天蓝色 (Cyan)
532
- '#BF6131', // 锈红色 (Rust)
533
- '#3CB44B', // 另一种绿色 (Lime Green)
534
- '#4363D8', // 另一种蓝色 (Indigo)
535
- '#F032E6', // 品红色 (Magenta)
536
- '#BCF60C', // 黄绿色 (Chartreuse)
537
- '#FABEBE', // 浅粉色 (Light Pink)
538
- '#AAFFC3', // 浅绿色 (Mint Green)
539
- '#E6BEFF', // 浅紫色 (Lavender)
540
- '#FFFAC8' // 浅黄色 (Cream)
518
+ '#E60012', '#005AC8', '#00A600', '#FF9900', '#8B28B7', '#9A6324', '#5E8C78', '#F58231', '#42D4F4', '#BF6131', '#3CB44B', '#4363D8', '#F032E6', '#BCF60C', '#FABEBE', '#AAFFC3', '#E6BEFF', '#FFFAC8'
541
519
  ];
542
520
 
543
- function updateHistoricalData(newDataFromTable) {
544
- const now = Date.now();
545
- newDataFromTable.forEach(item => {
546
- const queueName = item.queue_name;
547
- if (!historicalData[queueName]) {
548
- historicalData[queueName] = [];
549
- }
550
-
551
- let dataPoint = { timestamp: now };
552
- Object.keys(CHARTABLE_FIELDS_MAP).forEach(fieldKey => {
553
- dataPoint[fieldKey] = item[fieldKey]; // item已经包含了转换后的msg_count_numeric
554
- });
555
- historicalData[queueName].push(dataPoint);
556
-
557
- // 清理超过1小时的旧数据
558
- historicalData[queueName] = historicalData[queueName].filter(dp => (now - dp.timestamp) <= MAX_DATA_AGE_MS);
559
- });
560
- updateActiveChartIfVisible(); // 在历史数据更新后,尝试更新活动图表
561
- }
562
-
563
521
  function refreshTableData() {
564
- console.log("Auto-refresh: refreshing table data...");
565
- table.replaceData() // replaceData会重新调用ajaxURL,进而触发ajaxResponse中的updateHistoricalData
522
+ table.replaceData()
566
523
  .then(() => {
567
524
  console.log("Auto-refresh: table data refreshed successfully.");
568
525
  })
569
526
  .catch(error => {
570
527
  console.error("Auto-refresh: error refreshing table data:", error);
571
- // 可以考虑在这里停止自动刷新或通知用户
572
528
  });
573
529
  }
574
530
 
575
531
  function toggleAutoRefresh() {
576
532
  const button = document.getElementById("toggle-auto-refresh");
577
- // const chartButtons = document.querySelectorAll(".view-chart-btn"); // 不再直接通过此方式控制显隐
578
-
579
- if (isAutoRefreshing) { // 如果正在刷新,则停止
533
+ if (isAutoRefreshing) {
580
534
  clearInterval(autoRefreshIntervalId);
581
535
  isAutoRefreshing = false;
582
536
  button.textContent = "启动自动刷新";
583
537
  button.classList.remove("btn-danger");
584
538
  button.classList.add("btn-success");
585
- // chartButtons.forEach(btn => btn.style.display = 'none'); // Formatter将处理
586
- console.log("Auto-refresh stopped.");
587
539
  if (table) {
588
- table.redraw(true); // 强制表格重绘,formatter会根据isAutoRefreshing=false隐藏按钮
540
+ table.redraw(true);
589
541
  }
590
- } else { // 如果未刷新,则启动
542
+ } else {
591
543
  isAutoRefreshing = true;
592
544
  button.textContent = "暂停自动刷新";
593
545
  button.classList.remove("btn-success");
594
546
  button.classList.add("btn-danger");
595
- // chartButtons.forEach(btn => btn.style.display = 'inline-block'); // Formatter将处理
596
-
597
- refreshTableData(); // 立即执行一次刷新, 这会调用replaceData, 触发重绘, formatter会显示按钮
547
+ refreshTableData();
598
548
  autoRefreshIntervalId = setInterval(refreshTableData, AUTO_REFRESH_INTERVAL);
599
- console.log("Auto-refresh started. Interval ID:", autoRefreshIntervalId);
600
549
  }
601
550
  }
602
551
 
603
552
  document.getElementById("toggle-auto-refresh").addEventListener("click", toggleAutoRefresh);
604
553
 
605
- // 这个函数由表格中每行的"查看图表"按钮调用
554
+ let currentChartQueueName = null;
555
+ let endTimeUserChanged = false;
556
+ document.getElementById("chartEndTime").addEventListener("input", function() {
557
+ endTimeUserChanged = true;
558
+ });
559
+ // 工具函数:将Date对象转为input[type=datetime-local]需要的本地时间字符串
560
+ function toDatetimeLocalString(date) {
561
+ const pad = n => n < 10 ? '0' + n : n;
562
+ return date.getFullYear() + '-' +
563
+ pad(date.getMonth() + 1) + '-' +
564
+ pad(date.getDate()) + 'T' +
565
+ pad(date.getHours()) + ':' +
566
+ pad(date.getMinutes());
567
+ }
606
568
  function showQueueChart(queueName) {
607
- const dataForChart = historicalData[queueName] || [];
608
- document.getElementById("chartQueueName").textContent = queueName; // 设置模态框标题中的队列名
609
- activeChartQueueName = queueName; // 设置当前活动的图表队列名
610
-
611
- if (chartInstance) {
612
- chartInstance.destroy(); // 销毁已存在的图表实例,避免重复渲染
569
+ currentChartQueueName = queueName;
570
+ document.getElementById("chartQueueName").textContent = queueName;
571
+ // 设置默认时间范围:最近1小时(本地时区字符串)
572
+ const now = new Date();
573
+ const start = new Date(now.getTime() - 60 * 60 * 1000); // 1小时
574
+ const startStr = toDatetimeLocalString(start);
575
+ const endStr = toDatetimeLocalString(now);
576
+ const minStart = toDatetimeLocalString(new Date(now.getTime() - 24 * 60 * 60 * 1000)); // 24小时
577
+ document.getElementById("chartStartTime").value = startStr;
578
+ document.getElementById("chartEndTime").value = endStr;
579
+ document.getElementById("chartStartTime").setAttribute('max', endStr);
580
+ document.getElementById("chartStartTime").setAttribute('min', minStart);
581
+ document.getElementById("chartEndTime").removeAttribute('max');
582
+ document.getElementById("chartEndTime").setAttribute('min', minStart);
583
+ endTimeUserChanged = false; // 重置
584
+ loadQueueChartData(queueName, Math.floor(start.getTime() / 1000), Math.floor(now.getTime() / 1000));
585
+ }
586
+ function reloadQueueChartWithTimeRange() {
587
+ const start = document.getElementById("chartStartTime").value;
588
+ const end = document.getElementById("chartEndTime").value;
589
+ let start_ts = start ? (new Date(start).getTime() / 1000) : null;
590
+ let end_ts = end ? (new Date(end).getTime() / 1000) : null;
591
+ if (endTimeUserChanged) {
592
+ console.log('用户手动填写了结束时间:', end);
593
+ } else {
594
+ console.log('结束时间为默认值:', end);
613
595
  }
614
-
596
+ loadQueueChartData(currentChartQueueName, start_ts, end_ts);
597
+ }
598
+ function loadQueueChartData(queueName, start_ts, end_ts) {
599
+ if (chartInstance) chartInstance.destroy();
615
600
  const chartCanvas = document.getElementById('queueDataChart');
616
601
  const ctx = chartCanvas.getContext('2d');
617
-
618
- if (dataForChart.length === 0) {
619
- // alert("队列 " + queueName + " 暂无历史数据可供展示。请等待自动刷新收集更多数据。");
620
- // 可以在canvas上绘制提示信息
621
- ctx.clearRect(0, 0, chartCanvas.width, chartCanvas.height); // 清空画布
622
- ctx.font = "16px Arial";
623
- ctx.textAlign = "center";
624
- ctx.fillText("队列 " + queueName + " 暂无历史数据,请等待数据收集。", chartCanvas.width / 2, chartCanvas.height / 2);
625
- $('#chartModal').modal('show');
626
- return;
627
- }
628
-
629
- const labels = dataForChart.map(dp => new Date(dp.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }));
630
-
631
- const datasets = Object.keys(CHARTABLE_FIELDS_MAP).map((fieldKey, index) => {
632
- const displayName = CHARTABLE_FIELDS_MAP[fieldKey];
633
-
634
- // 设置默认显示或隐藏
635
- const isDefaultVisible = fieldKey === 'all_consumers_last_x_s_execute_count' || fieldKey === 'all_consumers_last_x_s_execute_count_fail';
636
-
637
- return {
638
- label: displayName,
639
- data: dataForChart.map(dp => dp[fieldKey] === undefined || dp[fieldKey] === null ? NaN : dp[fieldKey]), // 处理可能存在的undefined或null
640
- fill: false,
641
- borderColor: PREDEFINED_COLORS[index % PREDEFINED_COLORS.length], // 使用预定义颜色
642
- backgroundColor: PREDEFINED_COLORS[index % PREDEFINED_COLORS.length] + '33', // 面积图或点的背景颜色(带透明度)
643
- tension: 0.1,
644
- pointRadius: 3, // 点的大小
645
- pointHoverRadius: 5, // 鼠标悬浮时点的大小
646
- borderWidth: 2, // 线条宽度
647
- hidden: !isDefaultVisible // 控制默认是否隐藏
648
- };
649
- });
650
-
651
- chartInstance = new Chart(ctx, {
652
- type: 'line',
653
- data: {
654
- labels: labels,
655
- datasets: datasets
656
- },
657
- options: {
658
- responsive: true,
659
- maintainAspectRatio: false, // 允许图表填充模态框主体
660
- scales: {
661
- x: {
662
- title: {
663
- display: true,
664
- text: '时间'
665
- }
666
- },
667
- y: {
668
- beginAtZero: false, // Y轴不一定从0开始,根据数据自适应
669
- title: {
670
- display: true,
671
- text: '数值'
672
- }
673
- }
674
- },
675
- plugins: {
676
- legend: {
677
- position: 'top',
602
+ ctx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
603
+ ctx.font = "16px Arial";
604
+ ctx.textAlign = "center";
605
+ ctx.fillText("正在加载数据...", chartCanvas.width / 2, chartCanvas.height / 2);
606
+ let url = `/queue/get_time_series_data/${queueName}`;
607
+ let params = [];
608
+ if (start_ts) params.push(`start_ts=${start_ts}`);
609
+ if (end_ts) params.push(`end_ts=${end_ts}`);
610
+ if (params.length > 0) url += '?' + params.join('&');
611
+ $.get(url, function(response) {
612
+ if (!response || response.length === 0) {
613
+ ctx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
614
+ ctx.fillText("暂无历史数据", chartCanvas.width / 2, chartCanvas.height / 2);
615
+ $('#chartModal').modal('show');
616
+ return;
617
+ }
618
+ // 横坐标用本地时间字符串显示
619
+ const labels = response.map(dp => {
620
+ const d = new Date(dp.report_ts * 1000);
621
+ return d.toLocaleString('zh-CN', { hour12: false });
622
+ });
623
+ const datasets = Object.keys(CHARTABLE_FIELDS_MAP).map((fieldKey, index) => {
624
+ const displayName = CHARTABLE_FIELDS_MAP[fieldKey];
625
+ const isDefaultVisible = fieldKey === 'all_consumers_last_x_s_execute_count' || fieldKey === 'all_consumers_last_x_s_execute_count_fail';
626
+ return {
627
+ label: displayName,
628
+ data: response.map(dp => {
629
+ let v = dp.report_data[fieldKey];
630
+ if (typeof v === 'string') v = parseFloat(v);
631
+ return v === undefined || v === null ? NaN : v;
632
+ }),
633
+ fill: false,
634
+ borderColor: PREDEFINED_COLORS[index % PREDEFINED_COLORS.length],
635
+ backgroundColor: PREDEFINED_COLORS[index % PREDEFINED_COLORS.length] + '33',
636
+ tension: 0.4,
637
+ pointRadius: 1,
638
+ pointHoverRadius: 3,
639
+ borderWidth: 2,
640
+ hidden: !isDefaultVisible
641
+ };
642
+ });
643
+ chartInstance = new Chart(ctx, {
644
+ type: 'line',
645
+ data: { labels, datasets },
646
+ options: {
647
+ responsive: true,
648
+ maintainAspectRatio: false,
649
+ scales: {
650
+ x: {
651
+ title: { display: true, text: '时间' },
652
+ ticks: {
653
+ autoSkip: true,
654
+ maxTicksLimit: 20
655
+ }
656
+ },
657
+ y: { beginAtZero: false, title: { display: true, text: '数值' } }
678
658
  },
679
- title: {
680
- display: true,
681
- text: `队列 [${queueName}] 各项指标变化趋势`
659
+ plugins: {
660
+ legend: { position: 'top' },
661
+ title: { display: true, text: `队列 [${queueName}] 各项指标变化趋势` },
662
+ tooltip: { mode: 'index', intersect: false }
682
663
  },
683
- tooltip: {
684
- mode: 'index',
685
- intersect: false,
686
- }
687
- },
688
- interaction: { // 增强交互性
689
- mode: 'nearest',
690
- axis: 'x',
691
- intersect: false
664
+ interaction: { mode: 'nearest', axis: 'x', intersect: false }
692
665
  }
693
- }
666
+ });
667
+ $('#chartModal').modal('show');
668
+ }).fail(function() {
669
+ ctx.clearRect(0, 0, chartCanvas.width, chartCanvas.height);
670
+ ctx.fillText("获取数据失败", chartCanvas.width / 2, chartCanvas.height / 2);
671
+ $('#chartModal').modal('show');
694
672
  });
695
- $('#chartModal').modal('show'); // 显示模态框
696
673
  }
697
674
 
698
675
  // 新增:模态框关闭时的处理
@@ -701,35 +678,15 @@
701
678
  chartInstance.destroy();
702
679
  chartInstance = null;
703
680
  }
704
- activeChartQueueName = null; // 清除活动图表队列名
705
681
  console.log("Chart modal closed and instance destroyed.");
706
682
  });
707
683
 
708
- // 新增:如果图表可见,则用最新数据更新它
709
- function updateActiveChartIfVisible() {
710
- if (activeChartQueueName && chartInstance && historicalData[activeChartQueueName]) {
711
- const dataForChart = historicalData[activeChartQueueName];
712
- if (dataForChart.length > 0) {
713
- const newLabels = dataForChart.map(dp => new Date(dp.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }));
714
-
715
- chartInstance.data.labels = newLabels;
716
- Object.keys(CHARTABLE_FIELDS_MAP).forEach((fieldKey, index) => {
717
- if (chartInstance.data.datasets[index]) {
718
- chartInstance.data.datasets[index].data = dataForChart.map(dp => dp[fieldKey] === undefined || dp[fieldKey] === null ? NaN : dp[fieldKey]);
719
- }
720
- });
721
- chartInstance.update(); // 更新图表
722
- console.log(`Chart for ${activeChartQueueName} updated dynamically.`);
723
- }
724
- }
725
- }
726
-
727
684
  // 新增:显示说明模态框的函数
728
685
  function showExplanationModal() {
729
686
  const explanationTextHtml = `
730
687
  <li>消息队列的各项指标数据是 funboost 消费者每隔10秒周期上报到 redis 的,所以不是毫秒级实时,而是10秒级实时。</li>
731
688
  <li>"更新所有队列消息数量"按钮和表格"消息数量"列的获取按钮,是实时查询 broker 的消息数量,不是基于消费者上报到 redis 的数据。(因为有的队列可能没有启动相应的消费者,也就没有上报方)</li>
732
- <li>点击"启动自动刷新"按钮后,会每隔10秒更新表格的数据,并在前端内存中维护近1小时内的各种指标数据;同时,"消息队列"这一列的每行会出现"查看曲线图"按钮,点击后弹出的曲线图会动态显示内存中保存的指标数据变化趋势。</li>
689
+
733
690
  `;
734
691
  document.getElementById('explanation-text').innerHTML = explanationTextHtml;
735
692
  $('#explanationModal').modal('show');
@@ -9,6 +9,7 @@ import time
9
9
  import typing
10
10
 
11
11
  from funboost import PriorityConsumingControlConfig
12
+ from funboost.concurrent_pool.async_helper import get_or_create_event_loop
12
13
  from funboost.core.serialization import Serialization
13
14
  from funboost.publishers.base_publisher import AbstractPublisher
14
15
  from funboost.assist.faststream_helper import app,get_broker
@@ -23,13 +24,13 @@ class FastStreamPublisher(AbstractPublisher, metaclass=abc.ABCMeta):
23
24
  pass
24
25
  # asyncio.get_event_loop().run_until_complete(broker.start())
25
26
  self.broker = get_broker()
26
- asyncio.get_event_loop().run_until_complete(self.broker.connect())
27
+ get_or_create_event_loop().run_until_complete(self.broker.connect())
27
28
 
28
29
  def publish(self, msg: typing.Union[str, dict], task_id=None,
29
30
  priority_control_config: PriorityConsumingControlConfig = None) :
30
31
  msg, msg_function_kw, extra_params, task_id = self._convert_msg(msg, task_id, priority_control_config)
31
32
  t_start = time.time()
32
- faststream_result = asyncio.get_event_loop().run_until_complete(self.broker.publish(Serialization.to_json_str(msg), self.queue_name))
33
+ faststream_result = get_or_create_event_loop().run_until_complete(self.broker.publish(Serialization.to_json_str(msg), self.queue_name))
33
34
  self.logger.debug(f'向{self._queue_name} 队列,推送消息 耗时{round(time.time() - t_start, 4)}秒 {msg_function_kw}') # 显示msg太长了。
34
35
  with self._lock_for_count:
35
36
  self.count_per_minute += 1
@@ -165,7 +165,8 @@ def _auto_creat_config_file_to_project_root_path():
165
165
  # return
166
166
  if '/lib/python' in sys.path[1] or r'\lib\python' in sys.path[1] or '.zip' in sys.path[1]:
167
167
  raise EnvironmentError(f'''如果是cmd 或者shell启动而不是pycharm 这种ide启动脚本,请先在会话窗口设置临时PYTHONPATH为你的项目路径,
168
- windwos 使用 set PYTHONNPATH=你的当前python项目根目录,
168
+ windwos cmd 使用 set PYTHONNPATH=你的当前python项目根目录,
169
+ windows powershell 使用 $env:PYTHONPATH=你的当前python项目根目录,
169
170
  linux 使用 export PYTHONPATH=你的当前你python项目根目录,
170
171
  PYTHONPATH 作用是python的基本常识,请百度一下。
171
172
  需要在会话窗口命令行设置临时的环境变量,而不是修改linux配置文件的方式设置永久环境变量,每个python项目的PYTHONPATH都要不一样,不要在配置文件写死
@@ -20,10 +20,11 @@ class FunboostBackgroundSchedulerProcessJobsWithinRedisLock(FunboostBackgroundSc
20
20
  继承 Custom schedulers https://apscheduler.readthedocs.io/en/3.x/extending.html 可以重写 _create_lock
21
21
  """
22
22
 
23
- process_jobs_redis_lock_key = f'funboost.BackgroundSchedulerProcessJobsWithinRedisLock'
23
+ process_jobs_redis_lock_key = None
24
24
 
25
25
  def set_process_jobs_redis_lock_key(self, lock_key):
26
26
  self.process_jobs_redis_lock_key = lock_key
27
+ return self
27
28
 
28
29
  # def _create_lock(self):
29
30
  # return RedisDistributedBlockLockContextManager(RedisMixin().redis_db_frame,self.process_jobs_redis_lock_key,) 这个类的写法不适合固定的单例,
@@ -40,6 +41,8 @@ class FunboostBackgroundSchedulerProcessJobsWithinRedisLock(FunboostBackgroundSc
40
41
  # return 0.1
41
42
 
42
43
  def _process_jobs(self):
44
+ if self.process_jobs_redis_lock_key is None:
45
+ raise ValueError('process_jobs_redis_lock_key is not set')
43
46
  with RedisDistributedBlockLockContextManager(RedisMixin().redis_db_frame, self.process_jobs_redis_lock_key, ):
44
47
  return super()._process_jobs()
45
48
 
@@ -50,6 +53,10 @@ jobstores = {
50
53
  username=BrokerConnConfig.REDIS_USERNAME, jobs_key='funboost.apscheduler.jobs',run_times_key="funboost.apscheduler.run_times")
51
54
  }
52
55
 
56
+ """
57
+ 建议不要亲自使用这个 funboost_background_scheduler_redis_store 对象,而是 ApsJobAdder来添加定时任务,自动多个apscheduler对象实例,
58
+ 尤其是redis作为jobstores时候,使用不同的jobstores,每个消费函数使用各自单独的jobs_key和 run_times_key
59
+ """
53
60
  funboost_background_scheduler_redis_store = FunboostBackgroundSchedulerProcessJobsWithinRedisLock(timezone=FunboostCommonConfig.TIMEZONE, daemon=False, jobstores=jobstores)
54
61
 
55
62
 
@@ -131,13 +131,14 @@ class FunboostBackgroundScheduler(BackgroundScheduler):
131
131
  #
132
132
  # threading.Thread(target=_block_exit,).start() # 既不希望用BlockingScheduler阻塞主进程也不希望定时退出。
133
133
  # self._daemon = False
134
- def _when_exit():
135
- while 1:
136
- # print('阻止退出')
137
- time.sleep(100)
138
-
139
- if block_exit:
140
- atexit.register(_when_exit)
134
+ # def _when_exit():
135
+ # while 1:
136
+ # # print('阻止退出')
137
+ # time.sleep(100)
138
+
139
+ # if block_exit:
140
+ # atexit.register(_when_exit)
141
+ self._daemon = False # 这里强制默认改成非守护线程。默认是守护线程,主线程退出会报错。
141
142
  super().start(paused=paused, )
142
143
  # _block_exit() # python3.9 判断守护线程结束必须主线程在运行。你自己在你的运行代碼的最末尾加上 while 1: time.sleep(100) ,来阻止主线程退出。
143
144
 
@@ -179,10 +180,22 @@ class FunboostBackgroundScheduler(BackgroundScheduler):
179
180
  FsdfBackgroundScheduler = FunboostBackgroundScheduler # 兼容一下名字,fsdf是 function-scheduling-distributed-framework 老框架名字的缩写
180
181
  # funboost_aps_scheduler定时配置基于内存的,不可以跨机器远程动态添加/修改/删除定时任务配置。如果需要动态增删改查定时任务,可以使用funboost_background_scheduler_redis_store
181
182
 
183
+ """
184
+ 建议不要亲自使用这个 funboost_aps_scheduler 对象,而是 ApsJobAdder来添加定时任务,自动多个apscheduler对象实例,
185
+ 尤其是redis作为jobstores时候,使用不同的jobstores,每个消费函数使用各自单独的jobs_key和 run_times_key
186
+ """
182
187
  funboost_aps_scheduler = FunboostBackgroundScheduler(timezone=FunboostCommonConfig.TIMEZONE, daemon=False, )
183
188
  fsdf_background_scheduler = funboost_aps_scheduler # 兼容一下老名字。
184
189
 
190
+
191
+
185
192
  if __name__ == '__main__':
193
+
194
+ """
195
+ 下面的例子过时了,可以用但不建议,建议统一使用 ApsJobAdder 来添加定时任务。
196
+
197
+ """
198
+
186
199
  # 定时运行消费演示
187
200
  import datetime
188
201
  from funboost import boost, BrokerEnum, fsdf_background_scheduler, timing_publish_deco, run_forever
@@ -6,7 +6,7 @@ from funboost.timing_job.timing_job_base import funboost_aps_scheduler, undefine
6
6
  from funboost.timing_job.apscheduler_use_redis_store import FunboostBackgroundSchedulerProcessJobsWithinRedisLock
7
7
  from funboost.funboost_config_deafult import FunboostCommonConfig
8
8
  from apscheduler.schedulers.base import BaseScheduler
9
-
9
+ from funboost.constant import RedisKeys
10
10
 
11
11
  class ApsJobAdder:
12
12
  """
@@ -52,12 +52,13 @@ class ApsJobAdder:
52
52
  redis_jobstores = {
53
53
 
54
54
  "default": RedisJobStore(**redis_manager.get_redis_conn_kwargs(),
55
- jobs_key=f'funboost.apscheduler.{queue_name}.jobs',
56
- run_times_key=f'funboost.apscheduler.{queue_name}.run_times',
55
+ jobs_key=RedisKeys.gen_funboost_redis_apscheduler_jobs_key_by_queue_name(queue_name),
56
+ run_times_key=RedisKeys.gen_funboost_redis_apscheduler_run_times_key_by_queue_name(queue_name),
57
57
  )
58
58
  }
59
59
  redis_aps = FunboostBackgroundSchedulerProcessJobsWithinRedisLock(timezone=FunboostCommonConfig.TIMEZONE,
60
60
  daemon=False, jobstores=redis_jobstores)
61
+ redis_aps.set_process_jobs_redis_lock_key(RedisKeys.gen_funboost_apscheduler_redis_lock_key_by_queue_name(queue_name))
61
62
  cls.queue__redis_aps_map[queue_name] = redis_aps
62
63
  return redis_aps
63
64
 
@@ -107,6 +108,7 @@ class ApsJobAdder:
107
108
  if __name__ == '__main__':
108
109
  """
109
110
  2025年后定时任务现在推荐使用 ApsJobAdder 写法 ,用户不需要亲自选择使用 apscheduler对象来添加定时任务
111
+ 特别是使用redis作为jobstores时候,你可以看源码就知道了。
110
112
  """
111
113
  from funboost import boost, BrokerEnum, ctrl_c_recv, BoosterParams, ApsJobAdder
112
114
 
@@ -118,7 +120,7 @@ if __name__ == '__main__':
118
120
  print(f'The sum of {x} and {y} is {result}')
119
121
 
120
122
  # 启动消费者
121
- sum_two_numbers.consume()
123
+ # sum_two_numbers.consume()
122
124
 
123
125
  # 发布任务
124
126
  sum_two_numbers.push(3, 5)
@@ -155,4 +157,4 @@ if __name__ == '__main__':
155
157
  replace_existing=True,
156
158
  id='cron_job1')
157
159
 
158
- ctrl_c_recv()
160
+ ctrl_c_recv() # 启动了守护线程的定时器,一定要阻止主线程退出。 你可以代码最末尾加这个 ctrl_c_recv() 或者加个 while 1:time.sleep(10)
@@ -1,9 +1,3 @@
1
- ---
2
- noteId: "6d96e13045e411f0ab0bf59d9f569ed4"
3
- tags: []
4
-
5
- ---
6
-
7
1
 
8
2
  ## 1 asioredis包将不再更新 ,ptyhon 3.11 版本 import aioredis会出错。
9
3
 
@@ -1,9 +1,3 @@
1
- ---
2
- noteId: "6d97084045e411f0ab0bf59d9f569ed4"
3
- tags: []
4
-
5
- ---
6
-
7
1
  ## 这个文件夹被添加到 sys.path中去了。
8
2
 
9
3
  funboost __init__.py 第一行就把这个添加到 sys.path了,相当于 export PYTHONPATH 了。