影刀RPA店群自动化教程:Python协同任务执行时长预估与SLA保障实战


在这里插入图片描述

一个客服回复任务应该在两分钟内完成,但流程跑了十分钟才结束。

拼多多店群自动化上架方案

客户已经走了,自动回复才姗姗来迟——这种自动化,反而伤害了店铺评分。

在这里插入图片描述

店群运营对时效性的要求远比想象的高。
拼多多的退款处理有48小时窗口,看起来宽裕,但一旦滞后就影响店铺流量权重。TEMU的上架审核如果在促销开始前未完成,整批商品错过活动。TikTok Shop的直播中客户提问,30秒内没人理就流失。

自动化系统不仅要把事情做对,还要在正确的时间做完。

我们在早期只关注了“成功率”,完全没有度量“时效性”。直到运营反馈“自动回复经常慢半拍”,我们才开始系统性地建设任务执行时长的预估能力,并围绕SLA(服务水平协议)重构了任务调度策略。
在这里插入图片描述

这篇文章就围绕这个主题展开。

TEMU店群如何管理运营?


一、SLA的定义:不是越快越好,而是有边界

在这里插入图片描述

SLA不是追求极致速度,而是为每个任务类型设定一个可接受的完成时限,然后确保绝大多数任务在这个时限内完成。

我们为不同任务类型定义了SLA指标:

  • 客服自动回复:P99 ≤ 90秒

    • 订单发货处理:P99 ≤ 5分钟
    • 商品上架:P95 ≤ 8分钟
    • 活动报名:P99 ≤ 3分钟
  • 在这里插入图片描述

  • 在这里插入图片描述

    • 数据采集:P95 ≤ 10分钟
      SLA指标储存在配置中,并关联到任务优先级和超时策略。
from dataclasses import dataclass

@dataclass
class SLAProfile:
    task_type: str
        p99_limit_seconds: float
            p95_limit_seconds: float
                critical: bool          # 超过SLA是否触发告警
                    max_execution_seconds: float  # 硬超时
SLA_REGISTRY = {
    "reply_customer": SLAProfile("reply_customer", 90, 60, True, 180),
        "handle_order": SLAProfile("handle_order", 300, 240, True, 600),
            "upload_product": SLAProfile("upload_product", 480, 420, False, 900),
                "campaign_signup": SLAProfile("campaign_signup", 180, 150, True, 300),
                    "collect_data": SLAProfile("collect_data", 600, 540, False, 1200),
                    }
                    ```
这些阈值不是拍脑袋定的,而是基于历史数据的统计和业务需求得来。

---

## 二、历史执行时间的采集与存储

要预估未来任务的执行时长,首先得有历史数据。

我们在每个任务结束时,将完整的执行耗时(包括调度等待、浏览器启动、步骤执行)写入专门的时序数据库。同时记录影响因素:任务类型、平台、店铺ID、时间段、代理类型、图片数量等。

```python
async def record_execution_time(task_id: str, duration: float, metadata: dict):
    await db.execute(
            """INSERT INTO task_execution_times 
                       (task_id, duration_ms, task_type, platform, shop_id, hour, 
                                   proxy_tier, image_count, steps_count, recorded_at)
                                              VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())""",
                                                      task_id,
                                                              int(duration * 1000),
                                                                      metadata["task_type"],
                                                                              metadata["platform"],
                                                                                      metadata["shop_id"],
                                                                                              datetime.now().hour,
                                                                                                      metadata.get("proxy_tier", "standard"),
                                                                                                              metadata.get("image_count", 0),
                                                                                                                      metadata.get("steps_count", 0)
                                                                                                                          )
                                                                                                                          ```
---

## 三、基于历史数据的分位数预估模型

预估任务的执行时间不能靠平均值——因为分布是偏态的,有个别任务可能因为异常而耗时极长。我们需要的是分位数(如P50、P95)。

我们按任务类型、平台、时间段等维度分组,查询历史P50、P95作为预估基准。

```python
class ExecutionTimePredictor:
    def __init__(self, db_pool):
            self.db = db_pool
    async def predict(self, task: dict) -> dict:
            task_type = task["task_type"]
                    platform = task.get("platform", "")
                            hour = datetime.now().hour
                                    # 按类型、平台、时间段查询历史分位数
                                            row = await self.db.fetchrow("""
                                                        SELECT 
                                                                        percentile_cont(0.5) WITHIN GROUP (ORDER BY duration_ms) AS p50,
                                                                                        percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) AS p95,
                                                                                                        COUNT(*) AS sample_count
                                                                                                                    FROM task_execution_times
                                                                                                                                WHERE task_type = $1 
                                                                                                                                              AND platform = $2
                                                                                                                                                            AND hour BETWEEN $3 AND $3+2
                                                                                                                                                                          AND recorded_at > NOW() - INTERVAL '14 days'
                                                                                                                                                                                  """, task_type, platform, hour)
                                                                                                                                                                                          if row and row["sample_count"] > 10:
                                                                                                                                                                                                      return {
                                                                                                                                                                                                                      "p50_seconds": row["p50"] / 1000 if row["p50"] else None,
                                                                                                                                                                                                                                      "p95_seconds": row["p95"] / 1000 if row["p95"] else None,
                                                                                                                                                                                                                                                      "confidence": min(row["sample_count"] / 50, 1.0)
                                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                                                          # 样本不足,使用全局平均值
                                                                                                                                                                                                                                                                                  row = await self.db.fetchrow("""
                                                                                                                                                                                                                                                                                              SELECT AVG(duration_ms) AS avg_ms FROM task_execution_times
                                                                                                                                                                                                                                                                                                          WHERE task_type = $1 AND recorded_at > NOW() - INTERVAL '7 days'
                                                                                                                                                                                                                                                                                                                  """, task_type)
                                                                                                                                                                                                                                                                                                                          avg = row["avg_ms"] / 1000 if row and row["avg_ms"] else 60
                                                                                                                                                                                                                                                                                                                                  return {"p50_seconds": avg, "p95_seconds": avg * 2, "confidence": 0.3}
                                                                                                                                                                                                                                                                                                                                  ```
这样每个任务在创建时就能获得一个预期耗时范围。调度器利用这个信息来决定是否有可能满足SLA。

---

## 四、调度中的SLA感知

当任务到达时,调度器不仅考虑优先级和资源,还检查**预期完成时间是否超出SLA**。  
如果预估已经超过SLA上限,调度器会采取特殊措施:

- 提升该任务的优先级,减少排队时间
- - 分配更优质的代理IP(减少网络延迟)
- - 如果可能,为任务分配已预热好的浏览器实例,省去启动时间
- - 如果仍然无法满足SLA,发出预警
```python
class SLAAwareScheduler:
    def __init__(self, predictor, sla_registry, task_queue):
            self.predictor = predictor
                    self.sla = sla_registry
                            self.queue = task_queue
    async def schedule(self, task: dict):
            profile = self.sla.get(task["task_type"])
                    if not profile:
                                await self.queue.enqueue(task, priority=5)
                                            return
        prediction = await self.predictor.predict(task)
                p95_est = prediction.get("p95_seconds", 300)
                        sla_limit = profile.p95_limit_seconds
        # 预估排队时间(当前队列中同平台同优先级任务数 * 平均耗时)
                queue_wait_est = await self.queue.estimate_wait_time(task["platform"], task.get("priority", 5))
        total_est = queue_wait_est + p95_est
        if total_est > sla_limit * 1.2:
                    # SLA有风险,提升优先级
                                task["priority"] = max(1, task.get("priority", 5) - 3)
                                            task["sla_risk"] = True
                                                        logger.warning(f"SLA risk for {task['task_id']}: est {total_est}s > limit {sla_limit}s")
                                                                elif total_est > sla_limit:
                                                                            task["priority"] = max(1, task.get("priority", 5) - 1)
        await self.queue.enqueue(task, priority=task.get("priority", 5))
        ```
当SLA风险较高时,系统也会通知运营:  
“预计任务 xxx 可能超时,已自动提升优先级,请关注。”

**这个问题其实在高并发阶段特别容易暴露。任务一多,低优先级的采集任务可能被挤到几十分钟后才执行,完全超过了运营需要看最新数据的时间窗口。**

---

## 五、实时SLA监控与预警

任务开始执行后,实际耗时可能偏离预估。我们需要实时追踪,若发现某任务正在逼近SLA边界,立刻触发动作。

```python
class SLAMonitor:
    def __init__(self, redis, sla_registry, alert_mgr):
            self.redis = redis
                    self.sla = sla_registry
                            self.alert = alert_mgr
    async def check_running_tasks(self):
            running = await self.redis.keys("task:running:*")
                    now = time.time()
                            for key in running:
                                        task_data = await self.redis.hgetall(key)
                                                    start_time = float(task_data.get(b"started_at", 0))
                                                                elapsed = now - start_time
                                                                            task_type = task_data.get(b"task_type", b"").decode()
                                                                                        profile = self.sla.get(task_type)
                                                                                                    if not profile:
                                                                                                                    continue
                                                                                                                                limit = profile.p99_limit_seconds
                                                                                                                                            if elapsed > limit * 0.8:  # 达到SLA限制的80%,告警
                                                                                                                                                            await self.alert.send(
                                                                                                                                                                                f"任务 {task_data[b'task_id'].decode()} 已执行 {elapsed:.0f}s,接近SLA上限 {limit}s"
                                                                                                                                                                                                )
                                                                                                                                                                                                            if elapsed > profile.max_execution_seconds:
                                                                                                                                                                                                                            # 硬超时,强制终止
                                                                                                                                                                                                                                            await self._force_terminate(task_data[b'task_id'].decode())
                                                                                                                                                                                                                                            ```
硬超时后的终止会触发之前设计的任务生命周期钩子,释放资源并标记失败,由重试机制接手。

---

## 六、SLA日报与持续优化

SLA不是一成不变的。随着平台改版、流程优化、代理调整,任务耗时也在变化。

我们每日凌晨生成SLA日报,统计前一天各任务类型的P95、P99耗时,对比SLA目标。  
如果连续多日超出SLA,自动创建优化工单分配给开发团队。

```python
async def generate_sla_daily_report():
    rows = await db.fetch("""
            SELECT task_type,
                           percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) AS p95,
                                          percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms) AS p99,
                                                         COUNT(*) AS total
                                                                 FROM task_execution_times
                                                                         WHERE recorded_at > NOW() - INTERVAL '1 day'
                                                                                 GROUP BY task_type
                                                                                     """)
                                                                                         report_lines = ["SLA日报"]
                                                                                             for row in rows:
                                                                                                     task_type = row["task_type"]
                                                                                                             p95 = row["p95"] / 1000 if row["p95"] else 0
                                                                                                                     p99 = row["p99"] / 1000 if row["p99"] else 0
                                                                                                                             profile = SLA_REGISTRY.get(task_type)
                                                                                                                                     p95_status = "OK" if not profile or p95 <= profile.p95_limit_seconds else "超标"
                                                                                                                                             p99_status = "OK" if not profile or p99 <= profile.p99_limit_seconds else "超标"
                                                                                                                                                     report_lines.append(f"{task_type}: P95={p95:.1f}s [{p95_status}], P99={p99:.1f}s [{p99_status}], 总量={row['total']}")
                                                                                                                                                         await send_report("\n".join(report_lines))
                                                                                                                                                         ```
运营和开发通过这份日报能直观判断系统是否在时效性上出了问题。

---

## 七、与规则引擎和成本优化的联动

SLA保障也会和之前建设的规则引擎、成本优化系统联动。

例如,当大量任务面临SLA风险时,规则引擎可触发临时扩容(提升浏览器实例数),成本优化系统会暂时放宽“必须使用廉价代理”的限制,允许使用高质量代理来提速。

这意味着SLA不是一个孤立的监控指标,而是调优决策的输入信号。

---

## 八、踩坑记录

**样本不足导致预估偏差。**  
新上线的任务类型没有历史数据,预估失准,导致调度误判。我们为新任务设置了默认高优先级,待累积足够样本后才启用SLA调度策略。

**硬超时误杀。**  
某次平台大促,正常上货时间从3分钟延长到8分钟,硬超时却仍为5分钟,导致大量任务被误杀。  
我们为硬超时增加了动态系数:如果过去一小时内同类型任务平均耗时显著增加,临时放宽硬超时阈值。

**SLA日报的盲目信任。**  
P95指标看起来正常,但细查发现是因为失败任务不计入耗时统计,导致失真。  
我们改为:无论成功或失败,只要执行了步骤,都计入SLA统计,并增加“失败率”指标与SLA并列观察。

---

## 九、写在最后

自动化系统的成功,不仅取决于任务完成的正确性,还有它的及时性。

SLA体系让“及时”从模糊的感觉变成了可量化、可监控、可优化的工程指标。  
预估、调度、监控、日报,四个环节形成了时效性的闭环管理。

> 当每一次自动回复都在客户离开之前到达,每一个上架商品都赶在活动开始前通过审核,自动化才真正赢得了运营团队的信任。
---

*作者:林焱*
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐