FreeSwitch中mod_dptools和mod_easyroute两个模块及应用场景
FreeSWITCH 中的 mod_dptools
和 mod_easyroute
是两个功能不同的模块,分别服务于呼叫控制和动态路由场景。以下是详细介绍:
- mod_dptools
功能概述
mod_dptools
(Dialplan Tools)是 FreeSWITCH 最核心的模块之一,提供了丰富的 Application(App) 和工具,用于在拨号计划(Dialplan)中实现呼叫控制逻辑14。主要功能包括:
- 基础呼叫操作:如
answer
(接听)、bridge
(桥接)、hangup
(挂断)等。 - 媒体处理:如
playback
(播放音频)、record
(录音)、set_audio_level
(调节音量)。 - DTMF 交互:通过
read
、capture
等应用捕获用户按键输入^2。 - 资源限制:通过
limit
应用限制并发呼叫数(Capacity)或每秒呼叫数(CPS)^5。 - 虚拟端点:提供
user
端点,简化呼叫字符串配置(如user/1000
映射到 SIP 地址)^1。
应用场景
- IVR 系统:结合
playback
和read
实现语音菜单交互。 - 呼叫中心:通过
bridge
转接呼叫,利用limit
控制并发量。 - 会议系统:管理多方通话的媒体流和权限。
- 资源限制:限制特定 IP 或线路的呼叫频率,防止过载^5。
- mod_easyroute
功能概述
mod_easyroute
是一个轻量级的 动态路由引擎,通过数据库查询实现基于 DID(直接拨入号码)的路由决策69。核心特性包括:
- 数据库驱动:从数据库(如 MySQL、PostgreSQL)中查询路由规则。
- 灵活路由:根据来电号码动态选择目标(如 SIP 地址、网关)。
- 简化配置:替代复杂的拨号计划逻辑,适合多租户或动态路由需求。
应用场景
- 多运营商线路:根据 DID 选择成本最优的运营商网关^9。
- 企业分机路由:将不同 DID 映射到内部用户或部门。
- 动态号码分配:结合外部系统实现按需路由(如临时号码池)。
对比与协作
- 互补性:
mod_dptools
提供底层呼叫控制能力,而mod_easyroute
专注于高层路由逻辑。例如,可通过mod_easyroute
查询数据库确定目标,再通过mod_dptools
的bridge
发起呼叫。 - 典型用例:
- 来电进入后,
mod_easyroute
查询数据库获取目标地址。 mod_dptools
的bridge
执行呼叫桥接,并通过limit
限制并发量59。
- 来电进入后,
总结
mod_dptools
:是 FreeSWITCH 的“瑞士军刀”,适用于基础呼叫控制、媒体操作和资源管理12^5。mod_easyroute
:简化动态路由配置,适合需要灵活 DID 路由的场景69。
两者结合可构建高效、可扩展的 VoIP 系统。
在高并发低延迟毫秒级场景下王者组合
在高并发、低延迟场景下,结合 mod_dptools
和 mod_easyroute
实现动态路由的核心思路是:通过 mod_easyroute
快速查询 MySQL 路由表,再通过 mod_dptools
的 bridge
和 limit
实现高效呼叫控制。以下是具体实现方法:
一、模块协作方法
mod_easyroute
:- 动态查询 MySQL 路由表,根据主叫/被叫号码、时间、地区等条件筛选最优路由。
- 支持主叫号码的完全匹配(
=
)和前缀匹配(LIKE
),被叫前缀的规则匹配。
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
);
三、配置文件示例
-
MySQL 连接配置(
vars.xml
)
xml
-
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=′prefix′AND′{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 = ' destinationnumber′LIKECONCAT(calleeprefix,′ANDTIME(NOW())BETWEENoutboundtimestartANDoutboundtimeendANDregion=′{region}’ – 假设地区信息通过变量传递
ORDER BY priority ASC
LIMIT 1
-
拨号计划(
dialplan.xml
)
xml
四、优化建议
- 索引优化:
- 对
caller_pattern
、callee_prefix
、region
字段建立复合索引。
- 对
- 缓存路由:
- 使用 Redis 缓存高频路由规则,减少 MySQL 查询次数17。
- 连接池配置:
- 在
mod_db
中启用连接池,避免频繁建立数据库连接。
- 在
- 系统参数调优:
- 调整
ulimit -n
和 UDP 缓冲区大小,提升并发处理能力13。
- 调整
五、典型场景
外呼系统动态路由:
- 主叫号码
10123456789
匹配前缀101
,被叫前缀+86
,当前时间在允许范围内。 mod_easyroute
查询到目标 IP10.0.0.5:5060
,并应用被叫重写规则。mod_dptools
的limit
确保并发不超过 1000,bridge
完成低延迟呼叫。
通过上述配置,可实现 毫秒级路由决策 + 高并发控制,满足严苛的实时性要求16^7。
easyroute.conf.xml
文件通常位于 FreeSWITCH 的配置目录下的 conf/autoload_configs/
子目录中^3。完整路径示例如下:
/usr/local/freeswitch/conf/autoload_configs/easyroute.conf.xml
说明:
- FreeSWITCH 配置结构:
FreeSWITCH 的配置文件默认集中在conf/
目录下,模块的配置文件(如mod_easyroute
)一般存放在conf/autoload_configs/
中^3。 - 模块加载逻辑:
FreeSWITCH 在启动时会自动加载autoload_configs/
中的模块配置,确保mod_easyroute
的路由规则生效。
验证方法:
- 通过命令行查看路径:
bash
进入 FreeSWITCH 配置目录
cd /usr/local/freeswitch/conf/autoload_configs/
检查文件是否存在
ls -l easyroute.conf.xml
如果未找到文件,可能需要手动创建该配置文件并启用模块。
实现动态控制并发
要实现动态并发限制,需结合 mod_dptools
的 limit
应用与 mod_easyroute
的路由查询功能,并通过 MySQL 动态获取限制值。以下是具体步骤和配置示例:
- 修改 MySQL 表结构
在route_rules
表中新增concurrency_limit
字段:
sql
ALTER TABLE route_rules ADD COLUMN concurrency_limit INT DEFAULT 1000;
- 调整
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=′prefix′AND′{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
-
动态设置并发限制
在拨号计划中,通过set
应用将concurrency_limit
保存为通道变量,并在limit
中引用:
xml
关键点说明
- 动态变量传递:
easyroute_result(concurrency_limit)
从路由查询结果中提取concurrency_limit
字段^2。expr()
函数用于处理空值,若数据库未返回限制值,则默认使用 1000^2。
- 高并发优化:
- 结合文献1的建议,确保系统已调整文件描述符(
ulimit -n 65535
)和 UDP 缓冲区^1。
- 结合文献1的建议,确保系统已调整文件描述符(
- 路由匹配逻辑:
- 主叫号码支持完全匹配和前缀匹配,被叫号码支持前缀规则^3。
验证方法
- 数据库写入测试值:
sql
INSERT INTO route_rules (ip, port, caller_pattern, callee_prefix, concurrency_limit)
VALUES (‘10.0.0.5’, 5060, ‘101’, ‘+86’, 500); - 查看日志:
检查 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 格式的路由信息,包含
ip
、port
、callee_rewrite_rule
等字段,例如:
json
{
“ip”: “10.0.0.5”,
“port”: 5060,
“callee_rewrite_rule”: “^\d+$”,
“concurrency_limit”: 500
}
问题二:添加 Redis 后的配置文件代码
-
Redis 连接配置(
vars.xml
)
xml
-
拨号计划(
dialplan.xml
)
xml
- 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
- 缓存写入脚本(
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 小时
关键点说明
- 键设计:支持完全匹配和前缀匹配,通过不同键前缀区分逻辑67。
- 性能优化:使用 Lua 脚本减少网络往返,结合
setex
设置过期时间避免内存泄漏45。 - 高并发适配:通过
mod_dptools
的limit
动态控制并发量,确保系统稳定性^2。
1456^7
场景二
还要考虑主叫号码/被叫前缀可能没限制,只限制/主叫号码/被叫号码这种情况,请重新分析
在 FreeSWITCH 中处理主叫/被叫号码的灵活限制,需结合 拨号计划动态匹配 和 数据库规则设计,以下是具体方案:
- 数据库表结构设计
在route_rules
表中新增限制标识字段,支持独立控制主叫/被叫限制:
sql
ALTER TABLE route_rules
ADD COLUMN caller_restricted BOOLEAN DEFAULT FALSE,
ADD COLUMN callee_restricted BOOLEAN DEFAULT FALSE;
caller_restricted
:TRUE
时启用主叫号码匹配,FALSE
时忽略主叫限制。callee_restricted
:TRUE
时启用被叫前缀匹配,FALSE
时忽略被叫限制13。
- 动态 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=′prefix′AND′{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。
-
拨号计划动态适配
在dialplan.xml
中通过变量控制路由逻辑:
xml
- 场景示例
限制类型caller_restricted
callee_restricted
匹配逻辑
仅主叫限制 TRUE
FALSE
匹配主叫号码,忽略被叫前缀
仅被叫限制 FALSE
TRUE
匹配被叫前缀,忽略主叫号码
双重限制 TRUE
TRUE
同时匹配主叫号码和被叫前缀
无限制 FALSE
FALSE
匹配所有呼叫
- 性能优化建议
- 索引优化:对
caller_restricted
和callee_restricted
建立复合索引^1。 - 缓存策略:结合 Redis 缓存高频路由规则,减少数据库查询压力13。
通过上述配置,可实现 主叫/被叫独立限制 的灵活路由控制,满足复杂业务场景需求13^4。
给出缓存到redis的键值对情况
在 Redis 中缓存键值对时,需根据业务场景设计键结构和存储格式。以下是典型配置方案:
- 键(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
- 仅主叫限制:
- 值(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
- 操作示例
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
- 性能优化建议
- 批量操作:使用
MSET
或Pipeline
减少网络往返次数^2 - 过期时间:通过
SETEX
设置 TTL 避免内存泄漏^1
通过合理设计键结构和存储格式,可高效管理路由规则缓存31。
换一种思路,以ip和端口号为redis的键,主叫号码及被叫前缀和同等规则为一组,以json缓存到redis中,这个方案可行吗
以IP和端口号为Redis键,主叫号码及被叫前缀规则为JSON值的方案可行但需优化。具体分析如下:
方案可行性分析
-
键设计合理性
- 使用
${ip}:${port}
作为键(如10.0.0.5:5060
)能唯一标识路由目标服务器14。 - 但需注意:同一IP端口可能对应多组主叫/被叫规则,此时需在值中存储规则集合而非单条规则。
- 使用
-
值存储方式
-
JSON格式:适合存储结构化规则数据,例如:
json
{
“rules”:
{“caller_prefix”: “10086”, “callee_prefix”: “+86”, “concurrency_limit”: 500},
{“caller_prefix”: “138”, “callee_prefix”: “+1”, “callee_rewrite_rule”: “^\d+$”}}
通过JSON可灵活扩展字段14。
-
-
查询效率
- 优势:直接通过IP端口定位路由目标,适合反向查询场景(已知目标服务器时获取规则)^4。
- 劣势:若需根据主叫/被叫前缀正向查询IP端口,需遍历所有键,时间复杂度为 O ( n ) O(n) O(n)27。
优化建议
-
复合键设计
结合主叫/被叫前缀生成辅助键,建立双向映射:
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。 -
缓存更新策略
- 主动更新:规则变更时同步更新Redis^2。
- 过期时间:对低频规则设置TTL(如
SETEX key 3600 value
)15。
配置示例
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。
在FreeSWITCH中,mod_dptools
和 mod_easyroute
是两个功能不同的模块,分别用于呼叫流程控制和路由管理。以下是它们的详细介绍和应用场景分析:
1. mod_dptools:核心工具模块
mod_dptools
是FreeSWITCH的核心模块之一,默认自动加载。它提供了大量Dialplan应用(Application)和API命令,用于控制呼叫流程、操作通道变量、播放媒体文件等基础功能。
主要功能及常用指令
-
呼叫控制:
answer
:应答呼叫(触发媒体协商)。hangup
:挂断呼叫,可指定挂机原因(如hangup NORMAL_CLEARING
)。transfer
:将呼叫转移到其他分机或拨号方案上下文。
-
媒体操作:
playback
:播放音频文件(如playback /path/to/file.wav
)。record_session
:录制通话音频(支持多种格式)。speak
:使用TTS引擎合成语音。
-
变量操作:
set
:设置通道变量(如set my_var=123
)。export
:将变量传递到后续的桥接通道。
-
逻辑控制:
bridge
:桥接两个终端(如bridge user/1001
)。park
:将通话暂存到停车场,等待后续处理。
-
信息获取:
info
:打印当前通道的详细信息(调试用途)。
应用场景
- IVR系统:通过
playback
和read
实现交互式语音菜单。 - 呼叫转接:使用
transfer
或bridge
将通话转接到其他分机。 - 录音功能:
record_session
用于合规性录音或语音信箱。 - 动态路由:结合
set
和条件判断,根据变量调整路由逻辑。 - 调试工具:
info
或log
帮助实时查看通道状态。
示例 Dialplan 配置:
<extension name="example">
<condition field="destination_number" expression="^1234$">
<action application="answer"/>
<action application="playback" data="/sounds/welcome.wav"/>
<action application="bridge" data="user/1001"/>
</condition>
</extension>
2. mod_easyroute:动态路由模块
mod_easyroute
并非FreeSWITCH的官方模块,而是一个第三方或社区贡献模块(需确认具体来源),其设计目的是简化复杂路由规则的配置,通常通过外部数据源(如数据库、API)实现动态路由决策。
核心功能
-
动态路由规则:
- 基于数据库查询、HTTP API响应或脚本结果动态选择路由目标。
- 支持按主叫号码、被叫号码、时间、地理位置等条件路由。
-
集成外部系统:
- 与CRM或业务系统对接,实现客户专属路由(如VIP客户直连专属坐席)。
-
负载均衡:
- 根据坐席忙闲状态或技能组分配呼叫。
应用场景
- 多租户呼叫中心:根据租户ID从数据库加载不同路由策略。
- 智能路由:基于时间规则路由到夜间服务或节假日语音提示。
- 故障转移:主线路繁忙时自动切换到备用网关。
- 全球化部署:根据呼叫来源国家选择本地网关降低成本。
示例配置思路(假设模块存在):
<extension name="dynamic_route">
<condition field="destination_number" expression="^.+$">
<action application="easyroute" data="mysql://user:pass@host/db?query=SELECT route FROM rules WHERE dst=${dst_number}"/>
</condition>
</extension>
模块对比与协作
特性 | mod_dptools | mod_easyroute |
---|---|---|
定位 | 基础工具集 | 高级路由扩展 |
配置方式 | 内联Dialplan XML | 外部数据源(DB/API) |
灵活性 | 依赖硬编码逻辑 | 动态实时更新规则 |
适用场景 | 简单、静态路由 | 复杂、业务耦合路由 |
协作示例:
通过mod_easyroute
从数据库获取目标号码,再使用mod_dptools
的bridge
发起呼叫,结合set
传递变量记录路由信息。
注意事项
-
mod_easyroute的可用性:
需确认模块是否存在或是否更名(如社区模块可能命名为mod_lcr
、mod_http_routing
等)。若无此模块,可通过以下替代方案实现类似功能:- mod_curl:通过HTTP API获取路由信息。
- mod_lua:编写Lua脚本实现动态逻辑。
- mod_v8:使用JavaScript定义路由规则。
-
性能考量:
频繁查询外部数据可能增加延迟,建议缓存路由结果或使用内存数据库(如Redis)。
总结
- mod_dptools 是FreeSWITCH的“瑞士军刀”,用于实现基础呼叫控制功能。
- mod_easyroute(或类似模块)适合需要业务逻辑深度集成的动态路由场景,需依赖外部数据源增强灵活性。
开发者可根据需求选择原生工具或扩展模块,或通过脚本语言(Lua/JavaScript)实现自定义路由逻辑。
在高并发,低延迟,毫秒级响应的需求下,给出这两个模块同时使用的方法,场景及配置文件的示例代码,假设路由信息存储在mysql数据表中,表结构有ip,端口号,主叫号码【完全匹配及前缀匹配动态选择】,被叫前缀,被叫前缀还写规则,外呼时间,外呼地区等基础信息,如果存在多个相同ip及端口号的不同主叫/被叫,会在数据表中存储多条记录。
高并发低延迟场景下 mod_dptools
与 mod_easyroute
协同方案
在高并发、低延迟(毫秒级响应)需求下,需通过 动态路由缓存、异步查询 和 高效SQL设计 优化性能。以下是实现方法及配置示例。
1. 数据表设计(MySQL)
CREATE TABLE route_rules (
id INT AUTO_INCREMENT PRIMARY KEY,
caller_match_type ENUM('exact', 'prefix') NOT NULL, -- 主叫匹配类型:完全匹配或前缀
caller_pattern VARCHAR(32) NOT NULL, -- 主叫号码规则
callee_prefix VARCHAR(16) NOT NULL, -- 被叫号码前缀
ip VARCHAR(15) NOT NULL, -- 目标IP
port INT NOT NULL, -- 目标端口
priority INT DEFAULT 10, -- 优先级(数值越小优先级越高)
region VARCHAR(32), -- 外呼地区(如CN/US)
valid_time TIME, -- 外呼有效时间段(如09:00-18:00)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建索引加速查询
CREATE INDEX idx_callee_prefix ON route_rules(callee_prefix);
CREATE INDEX idx_caller ON route_rules(caller_match_type, caller_pattern);
CREATE INDEX idx_region_time ON route_rules(region, valid_time);
2. 配置 mod_easyroute
动态路由
假设 mod_easyroute
支持从MySQL动态查询路由(若需替代方案,可用 mod_lua
或 mod_curl
)。
模块配置(easyroute.conf.xml
)
<configuration name="easyroute.conf">
<routes>
<route name="mysql_route"
enabled="true"
driver="mysql"
host="localhost"
port="3306"
database="freeswitch"
user="fs_user"
password="password"
query="SELECT ip, port, priority
FROM route_rules
WHERE
(caller_match_type = 'exact' AND caller_pattern = '${caller_id_number}') OR
(caller_match_type = 'prefix' AND '${caller_id_number}' LIKE CONCAT(caller_pattern, '%'))
AND callee_prefix = LEFT('${destination_number}', LENGTH(callee_prefix))
AND region = '${region}'
AND (valid_time IS NULL OR CURTIME() BETWEEN valid_time_start AND valid_time_end)
ORDER BY priority ASC
LIMIT 1"/>
</routes>
</configuration>
3. 高并发优化策略
3.1 路由缓存(Redis)
使用Redis缓存查询结果,减少数据库压力:
- 键设计:
route:${caller}:${callee}:${region}:${time}
。 - 过期时间:5秒(平衡实时性与缓存有效性)。
3.2 异步查询
通过FreeSWITCH的 异步模式 避免阻塞呼叫线程:
<action application="easyroute_async" data="mysql_route"/>
<action application="park"/>
3.3 连接池优化
- MySQL连接池:配置
mod_easyroute
使用至少20个连接。 - 查询超时:设置超时时间100ms,超时后触发备用路由。
4. 拨号计划(dialplan.xml
)
<context name="dynamic_routing">
<extension name="dynamic_route">
<condition field="destination_number" expression="^(\d+)$">
<!-- 设置必要变量 -->
<action application="set" data="region=CN"/> <!-- 从IP或数据库获取实际地区 -->
<!-- 异步查询路由(非阻塞) -->
<action application="easyroute_async" data="mysql_route"/>
<action application="park"/>
<!-- 结果处理(从通道变量读取路由) -->
<action application="bridge"
data="${if( ${easyroute_status} == success ? sofia/gateway/${easyroute_ip}:${easyroute_port}/${destination_number} : error/NO_ROUTE)}"/>
<!-- 失败处理 -->
<action application="log" data="WARNING Route failed: ${easyroute_error}"/>
<action application="hangup"/>
</condition>
</extension>
</context>
5. 场景示例
场景:国际外呼动态选路
- 主叫:
+8613812345678
- 被叫:
+447712345678
- 流程:
- 根据被叫前缀
+44
匹配英国路由。 - 主叫完全匹配
+8613812345678
或前缀匹配+86
。 - 根据当前时间(如14:00)筛选有效路由。
- 按优先级选择IP:Port组合(如
gw_uk1:5060
)。
- 根据被叫前缀
6. 性能保障措施
- 数据库优化:
- 使用内存表(
MEMORY
引擎)存储热点路由。 - 定期清理过期规则。
- 使用内存表(
- FreeSWITCH调优:
<!-- settings.conf.xml --> <param name="max-sessions" value="10000"/> <param name="session-per-sec" value="1000"/> <param name="enable-async-park" value="true"/>
- 监控:
- 实时监控MySQL查询延迟(Prometheus + Grafana)。
- 告警规则:路由查询超时率 > 1% 或错误率 > 0.5%。
7. 容灾与降级方案
- 本地缓存文件:当MySQL不可用时,加载静态路由规则。
<route name="fallback_route" driver="csv" file="/etc/freeswitch/fallback_routes.csv"/>
- 默认网关:无路由时转发至默认网关(如SIP中继)。
总结
通过 mod_easyroute
实现动态路由决策,结合 mod_dptools
完成最终呼叫控制,配合缓存、异步查询和数据库优化,可满足高并发低延迟需求。关键点在于减少实时数据库访问、优先内存操作,并通过合理索引提升SQL效率。