您现在的位置是:首页 >技术杂谈 >OpenSIPS 3.1 负载均衡 MRCP 服务器的实现网站首页技术杂谈

OpenSIPS 3.1 负载均衡 MRCP 服务器的实现

谈谈1974 2024-06-18 00:01:01
简介OpenSIPS 3.1 负载均衡 MRCP 服务器的实现

1. 方案设计

FreeSWITCH 通过 unimrcp 模块来对接 MRCP 服务器,该模块在启动时会根据 mrcp profile 配置文件创建 MRCP 客户端。在使用 MRCP 功能时,FreeSWITCH 通过 mrcp profile 的名称来指定要使用的 MRCP 客户端,进而决定要连接的 MRCP 服务器。在 MRCPv2 服务器的负载分发方案分析 中笔者分析到只要做好 SIP 信令的负载均衡就可以实现 MRCP 的负载均衡,基于此,一个可行的方案如下:

  1. FreeSWITCH 配置 mrcp 的 profile 指向 OpenSIPS 服务器,profile 中配置 ua 名称,作为 mrcp 请求的识别标志
  2. OpenSIPS 的脚本中判断 INVITE 请求中的 ua 名称,如果是 mrcp 请求,则做对应处理
  3. 在 OpenSIPS 数据库中插入对应的 mrcp 服务器地址记录,处理时调用 dispatcher 模块的 ds_select_dst 函数选中 mrcp 服务器,该函数会填充 $du 后将请求转发到 $du 指向的地址

在这里插入图片描述

2. 实现方式

2.1 FreeSWITCH 的配置

在 FreeSWITCH 的 conf/mrcp_profiles 目录 下新增一个配置文件,将其指向 OpenSIPS 服务器并指定 UA 名称,然后重启 FreeSWITCH

<include>
  <!-- UniMRCP Server MRCPv2 -->
  <profile name="opensips-tts-mrcp2" version="2">
    <!-- OpenSIPS 服务器地址 端口号-->
    <param name="server-ip" value="127.0.0.1"/>
    <param name="server-port" value="8060"/>

    <!-- FreeSWITCH IP、端口以及 SIP 传输方式 -->
    <param name="client-ip" value="$${local_ip_v4}" />
    <param name="client-port" value="5072"/>
    <param name="sip-transport" value="udp"/>

    <param name="speechsynth" value="speechsynthesizer"/>
    <param name="speechrecog" value="speechrecognizer"/>
    <!-- SIP 请求携带的 ua 名称 -->
    <param name="ua-name" value="OPENSIPS_TTS_MRCP_CLIENT"/> 

    <!-- Add any default MRCP params for SPEAK requests here -->
    <synthparams>
    </synthparams>

    <!-- Add any default MRCP params for RECOGNIZE requests here -->
    <recogparams>
      <!--param name="start-input-timers" value="false"/-->
    </recogparams>
  </profile>
</include>

2.2 OpenSIPS 3.1 的配置

  1. 注意,笔者在此强调了 OpenSIPS 版本为 3.1,这是因为 OpenSIPS 不同版本 api 有变化,如果版本不匹配本文中给出的脚本代码很可能发生异常
  2. 实践前读者必须保证 OpenSIPS 已经编译加载了 dispatcher 模块。如果 OpenSIPS 启动过程中报出模块找不到的异常,可以自行编译源码模块,然后将源码根目录下 modules目录中对应模块目录下的 so 动态库复制到异常提示的目录中即可

2.2.1 OpenSIPS 保存 MRCP 服务器地址

执行以下 SQL 语句,往 OpenSIPS 数据库的 dispatcher 表中插入目标 MRCP 服务器地址记录。这个表中 setid 是组概念的标识,脚本中调用 ds_select_dst 函数时需要指定该参数。如果在脚本中配置了 dispatcher 模块的数据库地址,则 OpenSIPS 启动时会从数据库中查询数据加载到内存,读者如有兴趣可参考 dispatcher 官方文档

dispatcher 表可以用 opensips-cli 工具 创建,由于权限问题无法创建时也可以在 OpenSIPS 源码根目录下的 scripts目录 中选择对应的数据库类型目录,查找该目录下相应的表创建文件,例如 scripts/mysql/dispatcher-create.sql,复制其内容到数据库中执行即可

INSERT INTO `dispatcher` (`setid`, `destination`, `state`, `weight`, `priority`, `attrs`, `description`) VALUES
    (19, 'sip:10.129.39.88:7010', 0, 1, 100, 'pstn=100', 'TTS_MRCP_CLIENT_FS_7010'),
    (19, 'sip:10.129.39.88:7011', 0, 1, 100, 'pstn=100', 'TTS_MRCP_CLIENT_FS_7011');

2.2.2 OpenSIPS 脚本开发

脚本最主要的分发代码如下,上文已经提到了具体处理,此处不再赘述,但仍然需要注意以下几点:

  1. 对于 FreeSWITCH 发起的 MRCP INVITE 请求,$rU 肯定为 null,如果脚本中有对这个变量的空判断,需要做相应规避。如读者对 OpenSIPS 的各个核心变量不了解,可前往 官方传送门
  2. OpenSIPS 启动时会对脚本进行语法检查,读者可以先用前台模式启动 OpenSIPS 然后观察启动日志,如有报错信息一一解决即可
route {
    # 省略无关代码 ...
    if (is_method("INVITE")) {
        xlog("ua = $ua , callid = $ci, fu = $fu , tu = $tu , ru = $ru , du =$du src:$si, $(rb{sdp.line,m})");
        $var(dlgPingTag) = "Pp";
        if ( $ua == "OPENSIPS_TTS_MRCP_CLIENT" ) {
               $var(dlgPingTag) = "";  # TTS 的SIP通道不能做 OPTION 探测
        }
        if ( !create_dialog("$var(dlgPingTag)")) {
            send_reply(500,"Internal Server Error");
            exit;
        }
        if ( $ua == "OPENSIPS_TTS_MRCP_CLIENT" ) {
             # 指定 setid 为 19,与插入 dispatcher 表的数据相匹配,选择可用的节点
             $var(lbRst) = ds_select_dst(19, 4);
             if($var(lbRst) == -1) {
                  xlog("Failed by dispatcher group_id: 19");
                  t_reply(480, "MRCP server Unavailable");
                  exit();
             }
             if ( $var(lbRst) > 0) {
                 $ru = "sip:" + $(du{uri.host}) + ":" + $dp;
                 xlog("[$fU->$rU] Route to $ru");
             } else {
                xlog("[$fU->$rU] No available server now");
                t_reply(480, "$var(node_type) Unavailable");
                exit();
             }
        } else {
            # 其他类型 INVITE 处理 .....
        }
    }
    route(relay);
}

route[relay] {
    # for INVITEs enable some additional helper routes
    if (isflagset("NAT")) {
        add_rr_param(";nat=yes");
    } 
    
    if (!t_relay()) {
        send_reply(500,"Internal Error");
    }
    exit;
}

2.3 实现效果

在这里插入图片描述

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。