FreeSwitch中mod_dptools和mod_easyroute两个模块及应用场景

FreeSWITCH 中的 mod_dptoolsmod_easyroute 是两个功能不同的模块,分别服务于呼叫控制和动态路由场景。以下是详细介绍:

  1. mod_dptools
    功能概述
    mod_dptools(Dialplan Tools)是 FreeSWITCH 最核心的模块之一,提供了丰富的 Application(App) 和工具,用于在拨号计划(Dialplan)中实现呼叫控制逻辑14。主要功能包括:
  • 基础呼叫操作:如 answer(接听)、bridge(桥接)、hangup(挂断)等。
  • 媒体处理:如 playback(播放音频)、record(录音)、set_audio_level(调节音量)。
  • DTMF 交互:通过 readcapture 等应用捕获用户按键输入^2。
  • 资源限制:通过 limit 应用限制并发呼叫数(Capacity)或每秒呼叫数(CPS)^5。
  • 虚拟端点:提供 user 端点,简化呼叫字符串配置(如 user/1000 映射到 SIP 地址)^1。

应用场景

  • IVR 系统:结合 playbackread 实现语音菜单交互。
  • 呼叫中心:通过 bridge 转接呼叫,利用 limit 控制并发量。
  • 会议系统:管理多方通话的媒体流和权限。
  • 资源限制:限制特定 IP 或线路的呼叫频率,防止过载^5。

  1. mod_easyroute
    功能概述
    mod_easyroute 是一个轻量级的 动态路由引擎,通过数据库查询实现基于 DID(直接拨入号码)的路由决策69。核心特性包括:
  • 数据库驱动:从数据库(如 MySQL、PostgreSQL)中查询路由规则。
  • 灵活路由:根据来电号码动态选择目标(如 SIP 地址、网关)。
  • 简化配置:替代复杂的拨号计划逻辑,适合多租户或动态路由需求。

应用场景

  • 多运营商线路:根据 DID 选择成本最优的运营商网关^9。
  • 企业分机路由:将不同 DID 映射到内部用户或部门。
  • 动态号码分配:结合外部系统实现按需路由(如临时号码池)。

对比与协作

  • 互补性:mod_dptools 提供底层呼叫控制能力,而 mod_easyroute 专注于高层路由逻辑。例如,可通过 mod_easyroute 查询数据库确定目标,再通过 mod_dptoolsbridge 发起呼叫。
  • 典型用例:
    1. 来电进入后,mod_easyroute 查询数据库获取目标地址。
    2. mod_dptoolsbridge 执行呼叫桥接,并通过 limit 限制并发量59。

总结

  • mod_dptools:是 FreeSWITCH 的“瑞士军刀”,适用于基础呼叫控制、媒体操作和资源管理12^5。
  • mod_easyroute:简化动态路由配置,适合需要灵活 DID 路由的场景69。
    两者结合可构建高效、可扩展的 VoIP 系统。

在高并发低延迟毫秒级场景下王者组合


在高并发、低延迟场景下,结合 mod_dptoolsmod_easyroute 实现动态路由的核心思路是:通过 mod_easyroute 快速查询 MySQL 路由表,再通过 mod_dptoolsbridgelimit 实现高效呼叫控制。以下是具体实现方法:


一、模块协作方法

  1. mod_easyroute
    • 动态查询 MySQL 路由表,根据主叫/被叫号码、时间、地区等条件筛选最优路由。
    • 支持主叫号码的完全匹配(=)和前缀匹配(LIKE),被叫前缀的规则匹配。
  2. mod_dptools
    • 使用 limit 控制并发呼叫量,避免过载。
    • 通过 bridge 快速桥接呼叫,减少延迟。

二、MySQL 表结构示例
sql
CREATE TABLE route_rules (
id INT PRIMARY KEY AUTO_INCREMENT,
ip VARCHAR(15) NOT NULL, – 目标服务器 IP
port INT NOT NULL, – 目标端口
caller_match_type ENUM(‘exact’, ‘prefix’), – 主叫匹配类型
caller_pattern VARCHAR(32), – 主叫号码(完全匹配或前缀)
callee_prefix VARCHAR(16), – 被叫前缀
callee_rewrite_rule VARCHAR(64), – 被叫重写规则(如 ^(\d+)$1\1
outbound_time_start TIME, – 外呼时间范围(起始)
outbound_time_end TIME, – 外呼时间范围(结束)
region VARCHAR(32), – 外呼地区(如 us-east
priority INT DEFAULT 10, – 路由优先级(数值越小优先级越高)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);


三、配置文件示例

  1. MySQL 连接配置(vars.xml
    xml




  2. mod_easyroute 路由查询(easyroute.conf.xml
    xml




    SELECT ip, port, callee_rewrite_rule
    FROM route_rules
    WHERE
    (caller_match_type = ‘exact’ AND caller_pattern = ‘ c a l l e r i d n u m b e r ′ ) O R ( c a l l e r m a t c h t y p e = ′ p r e f i x ′ A N D ′ {caller_id_number}') OR (caller_match_type = 'prefix' AND ' calleridnumber)OR(callermatchtype=prefixAND{caller_id_number}’ LIKE CONCAT(caller_pattern, ‘%’))
    AND ‘ d e s t i n a t i o n n u m b e r ′ L I K E C O N C A T ( c a l l e e p r e f i x , ′ A N D T I M E ( N O W ( ) ) B E T W E E N o u t b o u n d t i m e s t a r t A N D o u t b o u n d t i m e e n d A N D r e g i o n = ′ {destination_number}' LIKE CONCAT(callee_prefix, '%') AND TIME(NOW()) BETWEEN outbound_time_start AND outbound_time_end AND region = ' destinationnumberLIKECONCAT(calleeprefix,ANDTIME(NOW())BETWEENoutboundtimestartANDoutboundtimeendANDregion={region}’ – 假设地区信息通过变量传递
    ORDER BY priority ASC
    LIMIT 1



  3. 拨号计划(dialplan.xml
    xml



四、优化建议

  1. 索引优化:
    • caller_patterncallee_prefixregion 字段建立复合索引。
  2. 缓存路由:
    • 使用 Redis 缓存高频路由规则,减少 MySQL 查询次数17。
  3. 连接池配置:
    • mod_db 中启用连接池,避免频繁建立数据库连接。
  4. 系统参数调优:
    • 调整 ulimit -n 和 UDP 缓冲区大小,提升并发处理能力13。

五、典型场景
外呼系统动态路由:

  1. 主叫号码 10123456789 匹配前缀 101,被叫前缀 +86,当前时间在允许范围内。
  2. mod_easyroute 查询到目标 IP 10.0.0.5:5060,并应用被叫重写规则。
  3. mod_dptoolslimit 确保并发不超过 1000,bridge 完成低延迟呼叫。

通过上述配置,可实现 毫秒级路由决策 + 高并发控制,满足严苛的实时性要求16^7。


easyroute.conf.xml 文件通常位于 FreeSWITCH 的配置目录下的 conf/autoload_configs/ 子目录中^3。完整路径示例如下:
/usr/local/freeswitch/conf/autoload_configs/easyroute.conf.xml

说明:

  1. FreeSWITCH 配置结构:
    FreeSWITCH 的配置文件默认集中在 conf/ 目录下,模块的配置文件(如 mod_easyroute)一般存放在 conf/autoload_configs/ 中^3。
  2. 模块加载逻辑:
    FreeSWITCH 在启动时会自动加载 autoload_configs/ 中的模块配置,确保 mod_easyroute 的路由规则生效。

验证方法:

  • 通过命令行查看路径:
    bash
    进入 FreeSWITCH 配置目录
    cd /usr/local/freeswitch/conf/autoload_configs/
    检查文件是否存在
    ls -l easyroute.conf.xml

如果未找到文件,可能需要手动创建该配置文件并启用模块。


实现动态控制并发


要实现动态并发限制,需结合 mod_dptoolslimit 应用与 mod_easyroute 的路由查询功能,并通过 MySQL 动态获取限制值。以下是具体步骤和配置示例:


  1. 修改 MySQL 表结构
    route_rules 表中新增 concurrency_limit 字段:
    sql
    ALTER TABLE route_rules ADD COLUMN concurrency_limit INT DEFAULT 1000;

  1. 调整 mod_easyroute 查询
    更新 easyroute.conf.xml 的 SQL 查询,返回 concurrency_limit 字段:
    xml

    SELECT ip, port, callee_rewrite_rule, concurrency_limit
    FROM route_rules
    WHERE
    (caller_match_type = ‘exact’ AND caller_pattern = ‘ c a l l e r i d n u m b e r ′ ) O R ( c a l l e r m a t c h t y p e = ′ p r e f i x ′ A N D ′ {caller_id_number}') OR (caller_match_type = 'prefix' AND ' calleridnumber)OR(callermatchtype=prefixAND{caller_id_number}’ LIKE CONCAT(caller_pattern, ‘%’))
    AND ‘${destination_number}’ LIKE CONCAT(callee_prefix, ‘%’)
    AND TIME(NOW()) BETWEEN outbound_time_start AND outbound_time_end
    ORDER BY priority ASC
    LIMIT 1

  1. 动态设置并发限制
    在拨号计划中,通过 set 应用将 concurrency_limit 保存为通道变量,并在 limit 中引用:
    xml



关键点说明

  1. 动态变量传递:
    • easyroute_result(concurrency_limit) 从路由查询结果中提取 concurrency_limit 字段^2。
    • expr() 函数用于处理空值,若数据库未返回限制值,则默认使用 1000^2。
  2. 高并发优化:
    • 结合文献1的建议,确保系统已调整文件描述符(ulimit -n 65535)和 UDP 缓冲区^1。
  3. 路由匹配逻辑:
    • 主叫号码支持完全匹配和前缀匹配,被叫号码支持前缀规则^3。

验证方法

  1. 数据库写入测试值:
    sql
    INSERT INTO route_rules (ip, port, caller_pattern, callee_prefix, concurrency_limit)
    VALUES (‘10.0.0.5’, 5060, ‘101’, ‘+86’, 500);
  2. 查看日志:
    检查 FreeSWITCH 日志,确认 dynamic_limit 是否正确加载:
    bash
    freeswitch -nc -nonat -rp

通过上述配置,可实现 基于路由规则的动态并发控制,满足高并发场景的灵活需求23。


场景一
如果将mysql数据表缓存到redis的8号库,动态路由先查询redis,不存在,再查询mysql,依旧不存在,返回404报错,问题一:缓存到redis是键值对该如何配置?问题二:给出添加redis后的配置文件代码


问题一:Redis 键值对配置方法
在 Redis 8 号库中,键值对设计需包含路由规则的核心字段,建议采用以下格式:

  • 键(Key):route:${caller_id_number}:${destination_number}
    其中 ${caller_id_number} 为主叫号码,${destination_number} 为被叫号码。若需支持前缀匹配,可扩展为:
    plaintext
    route:exact: c a l l e r i d n u m b e r : {caller_id_number}: calleridnumber:{callee_prefix} 完全匹配
    route:prefix: c a l l e r p r e f i x : {caller_prefix}: callerprefix:{callee_prefix} 前缀匹配
  • 值(Value):存储 JSON 格式的路由信息,包含 ipportcallee_rewrite_rule 等字段,例如:
    json
    {
    “ip”: “10.0.0.5”,
    “port”: 5060,
    “callee_rewrite_rule”: “^\d+$”,
    “concurrency_limit”: 500
    }

问题二:添加 Redis 后的配置文件代码

  1. Redis 连接配置(vars.xml
    xml



  2. 拨号计划(dialplan.xml
    xml


  1. Lua 脚本示例(redis_query.lua
    lua
    local caller = argv1
    local callee = argv2
    local redis = require “redis”
    local client = redis.connect(os.getenv(“redis_host”), os.getenv(“redis_port”))
    client:auth(os.getenv(“redis_password”))
    client:select(8) – 选择 8 号库

– 尝试完全匹配
local key_exact = “route:exact:” … caller … “:” … callee
local result = client:get(key_exact)

– 若未命中,尝试前缀匹配
if not result then
local key_prefix = “route:prefix:” … string.sub(caller, 1, 3) … “:” … callee
result = client:get(key_prefix)
end

if result then
session:setVariable(“easyroute_result”, result)
session:answer()
else
session:setVariable(“skip_easyroute”, “false”) – 继续执行后续动作
end

  1. 缓存写入脚本(redis_set.lua
    lua
    local caller = argv1
    local callee = argv2
    local route_data = argv3
    local redis = require “redis”
    local client = redis.connect(os.getenv(“redis_host”), os.getenv(“redis_port”))
    client:auth(os.getenv(“redis_password”))
    client:select(8)

– 根据匹配类型设置键(示例仅存储完全匹配)
local key = “route:exact:” … caller … “:” … callee
client:setex(key, 3600, route_data) – 缓存 1 小时

关键点说明

  1. 键设计:支持完全匹配和前缀匹配,通过不同键前缀区分逻辑67。
  2. 性能优化:使用 Lua 脚本减少网络往返,结合 setex 设置过期时间避免内存泄漏45。
  3. 高并发适配:通过 mod_dptoolslimit 动态控制并发量,确保系统稳定性^2。

1456^7


场景二
还要考虑主叫号码/被叫前缀可能没限制,只限制/主叫号码/被叫号码这种情况,请重新分析


在 FreeSWITCH 中处理主叫/被叫号码的灵活限制,需结合 拨号计划动态匹配 和 数据库规则设计,以下是具体方案:


  1. 数据库表结构设计
    route_rules 表中新增限制标识字段,支持独立控制主叫/被叫限制:
    sql
    ALTER TABLE route_rules
    ADD COLUMN caller_restricted BOOLEAN DEFAULT FALSE,
    ADD COLUMN callee_restricted BOOLEAN DEFAULT FALSE;
  • caller_restrictedTRUE 时启用主叫号码匹配,FALSE 时忽略主叫限制。
  • callee_restrictedTRUE 时启用被叫前缀匹配,FALSE 时忽略被叫限制13。

  1. 动态 SQL 查询优化
    修改 mod_easyroute 的查询逻辑,支持条件组合:
    xml

    SELECT ip, port, concurrency_limit
    FROM route_rules
    WHERE
    (caller_restricted = FALSE
    OR (caller_match_type = ‘exact’ AND caller_pattern = ‘ c a l l e r i d n u m b e r ′ ) O R ( c a l l e r m a t c h t y p e = ′ p r e f i x ′ A N D ′ {caller_id_number}') OR (caller_match_type = 'prefix' AND ' calleridnumber)OR(callermatchtype=prefixAND{caller_id_number}’ LIKE CONCAT(caller_pattern, ‘%’)))
    AND
    (callee_restricted = FALSE
    OR ‘${destination_number}’ LIKE CONCAT(callee_prefix, ‘%’))
    AND TIME(NOW()) BETWEEN outbound_time_start AND outbound_time_end
    ORDER BY priority ASC
    LIMIT 1

    逻辑说明:
  • caller_restricted=FALSE 时,跳过主叫匹配条件。
  • callee_restricted=FALSE 时,跳过被叫前缀匹配14。

  1. 拨号计划动态适配
    dialplan.xml 中通过变量控制路由逻辑:
    xml


  1. 场景示例
    限制类型 caller_restricted callee_restricted 匹配逻辑

仅主叫限制 TRUE FALSE 匹配主叫号码,忽略被叫前缀
仅被叫限制 FALSE TRUE 匹配被叫前缀,忽略主叫号码
双重限制 TRUE TRUE 同时匹配主叫号码和被叫前缀
无限制 FALSE FALSE 匹配所有呼叫


  1. 性能优化建议
  • 索引优化:对 caller_restrictedcallee_restricted 建立复合索引^1。
  • 缓存策略:结合 Redis 缓存高频路由规则,减少数据库查询压力13。

通过上述配置,可实现 主叫/被叫独立限制 的灵活路由控制,满足复杂业务场景需求13^4。


给出缓存到redis的键值对情况


在 Redis 中缓存键值对时,需根据业务场景设计键结构和存储格式。以下是典型配置方案:

  1. 键(Key)设计
  • 层级结构:使用冒号分隔业务标识和参数,例如:
    route:caller:主叫号码:被叫前缀route:callee:被叫号码^3
    示例:route:exact:10086:+86137(主叫 10086,被叫前缀 +86137)

  • 动态组合:根据限制条件动态生成键:

    • 仅主叫限制:route:caller:${caller_id_number}
    • 仅被叫限制:route:callee:${destination_number}
    • 双重限制:route:exact:${caller_id_number}:${destination_number}^3
  1. 值(Value)格式
  • JSON 字符串:存储路由规则详情,例如:
    json
    {
    “ip”: “10.0.0.5”,
    “port”: 5060,
    “concurrency_limit”: 500,
    “callee_rewrite_rule”: “^\+86\d+$”
    }13

  • 哈希表(Hash):使用 HSET 存储独立字段:
    bash
    HSET route:exact:10086:+86137 ip 10.0.0.5 port 5060 limit 500^1

  1. 操作示例
    python
    import redis

连接 Redis
r = redis.Redis(host=‘localhost’, port=6379, db=8)

设置键值对(JSON 格式)
route_data = {
“ip”: “10.0.0.5”,
“port”: 5060,
“concurrency_limit”: 500
}
r.set(“route:exact:10086:+86137”, json.dumps(route_data))

获取值
result = r.get(“route:exact:10086:+86137”)
print(json.loads(result)) 输出路由信息^1

  1. 性能优化建议
  • 批量操作:使用 MSETPipeline 减少网络往返次数^2
  • 过期时间:通过 SETEX 设置 TTL 避免内存泄漏^1

通过合理设计键结构和存储格式,可高效管理路由规则缓存31。


换一种思路,以ip和端口号为redis的键,主叫号码及被叫前缀和同等规则为一组,以json缓存到redis中,这个方案可行吗


以IP和端口号为Redis键,主叫号码及被叫前缀规则为JSON值的方案可行但需优化。具体分析如下:

方案可行性分析

  1. 键设计合理性

    • 使用${ip}:${port}作为键(如10.0.0.5:5060)能唯一标识路由目标服务器14。
    • 但需注意:同一IP端口可能对应多组主叫/被叫规则,此时需在值中存储规则集合而非单条规则。
  2. 值存储方式

    • JSON格式:适合存储结构化规则数据,例如:
      json
      {
      “rules”:
      {“caller_prefix”: “10086”, “callee_prefix”: “+86”, “concurrency_limit”: 500},
      {“caller_prefix”: “138”, “callee_prefix”: “+1”, “callee_rewrite_rule”: “^\d+$”}

      }
      通过JSON可灵活扩展字段14。

  3. 查询效率

    • 优势:直接通过IP端口定位路由目标,适合反向查询场景(已知目标服务器时获取规则)^4。
    • 劣势:若需根据主叫/被叫前缀正向查询IP端口,需遍历所有键,时间复杂度为 O ( n ) O(n) O(n)27。

优化建议

  1. 复合键设计
    结合主叫/被叫前缀生成辅助键,建立双向映射:
    python
    主键:存储规则集合
    redis.set(“10.0.0.5:5060”, json.dumps(rules))

    辅助键:支持正向查询
    redis.hset(“route:caller:10086”, “callee:+86”, “10.0.0.5:5060”)
    通过哈希表实现 O ( 1 ) O(1) O(1)复杂度查询27。

  2. 缓存更新策略

    • 主动更新:规则变更时同步更新Redis^2。
    • 过期时间:对低频规则设置TTL(如SETEX key 3600 value15。

配置示例
python
import redis
import json

连接Redis
r = redis.Redis(host=‘localhost’, port=6379, db=8)

存储规则
rules =
{“caller_prefix”: “10086”, “callee_prefix”: “+86”, “limit”: 500},
{“caller_prefix”: “138”, “callee_prefix”: “+1”, “rewrite_rule”: “^\d+$”}

r.set(“10.0.0.5:5060”, json.dumps(rules))

建立辅助索引
for rule in rules:
hash_key = f"route:caller:{rule’caller_prefix’}"
r.hset(hash_key, f"callee:{rule’callee_prefix’}", “10.0.0.5:5060”)

适用场景

  • 反向路由查询:已知目标服务器IP端口,快速获取关联规则。
  • 低频规则管理:结合TTL避免内存浪费25。

若需高频正向查询(主叫/被叫→IP端口),建议采用双向键设计或结合关系型数据库78。

你可能感兴趣的:(VOIP那些事,freeswitch)