You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GoESL结合Temporal对接FreeSWITCH时呼叫阻塞问题求助

GoESL+Temporal+FreeSWITCH外呼阻塞问题

问题背景

我正在集成GoESL与Temporal,基于FreeSWITCH搭建自动化外呼系统。配置支持1000并发通道、每秒50次呼叫(CPS),每次拨号尝试都会启动一个Temporal工作流,通过活动发起呼叫。

运行时出现异常:成功发起约96次呼叫(数量不固定)后,FreeSWITCH不再处理后续呼叫。CLI无相关日志,事件套接字层也没有后续呼叫尝试的事件。但停止Temporal worker后,之前"阻塞"的呼叫会立刻出现在FreeSWITCH CLI中——说明这些呼叫被GoESL客户端排队了。我已经确认worker并未阻塞,仍在持续发起线索工作流。

相关代码片段

线索处理循环

for _, lead := range leadResult.Leads {
    // [省略的初始化与检查]

    // 检查通道可用性并休眠以遵守CPS限制
    workflow.Await(ctx, func() bool {
        return dialerQueryResponse.AvailableChannels > 0
    })

    timeToSleep := time.Second / time.Duration(dialerQueryResponse.CallsPerSecondLimit)
    workflow.Sleep(ctx, timeToSleep)

    // 呼叫线索
    fmt.Printf("dialing lead %s\n", lead)
    dialLead(lead, selectedDialer.Id, callTimeout) 
    fmt.Print("lead dialed\n\n")
}

呼叫线索逻辑

dialLead := func(lead string, selectedDialerId, dialerCallTimeout int) {
    // 设置带唯一ID的子工作流上下文
    cwo.WorkflowID = fmt.Sprintf("Campaign_Call_%s", lead)
    childCtx := workflow.WithChildOptions(ctx, cwo)

    // 传递给子工作流的输入结构体
    input := domain.CallWorkflowInput{
        Lead:                lead,
        DialerId:            selectedDialerId,
        CampaignName:        cds.CampaignName,
        DialplanExtension:   cc.Survey.DialplanExtension,
        CallTimeout:         dialerCallTimeout,
    }

    // 执行子工作流并处理其Future
    future := workflow.ExecuteChildWorkflow(childCtx, CallWorkflow, input)
    var dialerId int
    selector.AddFuture(future, func(f workflow.Future) {
        err := f.Get(ctx, &dialerId)
        // 错误处理与并发状态更新
        // ...
    })
}

Call Workflow函数

func CallWorkflow(ctx workflow.Context, input domain.CallWorkflowInput) (int, error) {
    // [省略的初始化]

    // 执行发起呼叫的活动
    var dialLeadResult domain.DialLeadResponse
    if err := workflow.ExecuteActivity(ctx, activity.Dialer.OriginateCallActivity, dialInput).Get(ctx, &dialLeadResult); err != nil {
        // 错误处理
    }

    // [省略的呼叫后处理]
}

发起呼叫的活动

func (a *DialerActivities) OriginateCallActivity(ctx context.Context, input domain.DialLeadRequest) (domain.DialLeadResponse, error) {
    // [省略的客户端选择]

    // 发起呼叫的命令
    cmd := fmt.Sprintf("originate {%s}%s/%s/%s 704 XML default test %s 10", variables, protocol, gateway, input.DestinationNumber, input.OriginatingNumber)
    err := selectedClient.BgApi(cmd)

    if err != nil {
        // 错误处理
    }

    // [省略的响应准备]
    return domain.DialLeadResponse{}, nil
}

已尝试的排查操作

  • 严格遵守限流规则(通道数、CPS限制)
  • 通过FreeSWITCH CLI调试并检查CDR记录
  • 排查FreeSWITCH日志寻找异常
  • 尝试提升FreeSWITCH中GoESL事件日志级别,但未生成对应日志文件
  • 修改workflow.Sleep时长(从几毫秒改为5-10秒),排除网络延迟影响
  • 确认工作流终止前无代码错误、日志报错
  • 停止FreeSWITCH实例排查通信问题:仅停止时日志显示通信失败,正常运行时无相关日志
  • 调研相关问题:找到FreeSWITCH用户邮件列表中的类似案例,但无有效解决方案

提问

有没有人遇到过GoESL或Temporal类似的呼叫排队后无法执行的问题?请问如何调试该场景,或是为什么终止Temporal worker会触发排队呼叫的处理?

内容的提问来源于stack exchange,提问作者Pierre van der Westhuizen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 00:24:56