FreeSWITCH 的 mod_curl 模块是一个用于通过 HTTP/HTTPS 协议与外部服务进行交互的核心模块。它允许 FreeSWITCH 在呼叫处理过程中发起 HTTP 请求(如 GET、POST 等),并将响应结果集成到呼叫流程中。以下是关于 mod_curl 的详细介绍:
1. 主要功能
- 发起 HTTP 请求:支持 GET、POST、PUT、DELETE 等 HTTP 方法。
- 动态数据交互:从外部服务获取动态配置(如拨号计划路由、用户鉴权信息),或推送事件数据(如 CDR 话单、通话状态)。
- 事件绑定 :将 HTTP 请求绑定到 FreeSWITCH 的特定事件(如
CHANNEL_CREATE
、CHANNEL_HANGUP
),实现实时回调。 - 变量注入:在请求中插入 FreeSWITCH 变量(如主叫号码、被叫号码),实现动态参数传递。
- 响应处理:解析 HTTP 响应内容(如 JSON/XML),并根据结果控制呼叫流程(如放音、转接)。
2. 配置与使用
2.1 加载模块
在 FreeSWITCH 配置文件 conf/autoload_configs/modules.conf.xml
中启用模块:
xml
<load module="mod_curl"/>
2.2 配置文件
模块的配置文件位于 conf/autoload_configs/curl.conf.xml
,常用配置项如下:
xml
<configuration name="curl.conf">
<settings>
<!-- 超时时间(毫秒) -->
<param name="timeout" value="5000"/>
<!-- 是否启用 SSL 验证 -->
<param name="ssl-verify-peer" value="false"/>
<param name="ssl-verify-host" value="false"/>
<!-- 代理服务器 -->
<param name="proxy" value="http://proxy.example.com:8080"/>
</settings>
</configuration>
2.3 在拨号计划中使用
通过 curl
应用在拨号计划中发起 HTTP 请求,并根据响应控制流程:
xml
<extension name="dynamic_route">
<condition field="destination_number" expression="^(\d+)$">
<action application="curl"
data="http://api.example.com/route?called=${destination_number}"/>
<action application="bridge" data="${curl_response_data}"/>
</condition>
</extension>
curl_response_data
变量保存 HTTP 响应内容。- 若响应为 JSON/XML,可配合
mod_json
或mod_xml_curl
解析。
2.4 事件订阅与回调
通过 event
命令绑定事件到 HTTP 回调:
xml
<params>
<param name="url" value="http://api.example.com/cdr"/>
<param name="event-type" value="CHANNEL_HANGUP"/>
<param name="event-data" value="Unique-ID: ${uuid}"/>
</params>
3. 核心 API 方法
-
curl :发起同步 HTTP 请求,直接返回响应。
lua-- Lua 示例 local response = curl("http://api.example.com/auth?user=${caller_id_number}")
-
curl_async:发起异步 HTTP 请求,不阻塞当前会话。
-
curl_perform:结合会话变量动态构建请求。
4. 典型应用场景
4.1 动态路由
通过 HTTP 请求从外部服务获取路由目标(如根据主叫号码选择网关):
xml
<action application="curl" data="http://api.example.com/routing?called=${destination_number}"/>
<action application="bridge" data="${curl_response_data}"/>
4.2 用户鉴权
验证主叫号码是否有权限发起呼叫:
xml
<action application="curl" data="http://api.example.com/auth?user=${caller_id_number}"/>
<action application="hangup" data="NORMAL_CLEARING" if="${curl_response_code} != 200"/>
4.3 CDR 话单推送
通话结束后推送话单到外部系统:
xml
<event type="CHANNEL_HANGUP">
<params>
<param name="url" value="http://api.example.com/cdr"/>
<param name="method" value="POST"/>
<param name="data" value="caller=${caller_id_number}&duration=${billsec}"/>
</params>
</event>
4.4 动态 IVR 菜单
从外部服务获取 IVR 菜单配置:
lua
local menu = curl("http://api.example.com/ivr/${destination_number}")
session:execute("playback", menu.audio_file)
5. 高级特性
-
多请求并行 :通过
curl_async
发起多个异步请求提升性能。 -
自定义 Headers :添加 HTTP 头:
xml<param name="http-headers" value="X-API-Key: my-secret-key"/>
-
Basic Auth :支持身份验证:
xml<param name="auth-username" value="user"/> <param name="auth-password" value="pass"/>
-
SSL/TLS 支持 :通过
https://
URL 启用加密通信。
6. 注意事项
- 性能影响:同步请求会阻塞会话,高并发时建议使用异步模式。
- 错误处理 :检查
curl_response_code
和curl_response_phrase
处理异常。 - 超时设置 :合理配置
timeout
避免长时间阻塞。 - 安全性 :启用 SSL 验证(
ssl-verify-peer
)避免中间人攻击。
7. 调试与日志
-
日志查看 :在 FreeSWITCH 日志文件(
log/freeswitch.log
)中搜索mod_curl
相关条目。 -
调试命令 :通过 CLI 命令查看请求详情:
curl debug on curl http://api.example.com/test
8. 与其他模块对比
- mod_xml_curl:专用于 XML 配置的动态获取(如拨号计划、用户目录)。
- mod_http :提供 HTTP 服务器功能(如 REST API 接口),而
mod_curl
是客户端。
通过 mod_curl,FreeSWITCH 可以灵活地与 Web 服务集成,实现高度动态的通信解决方案。如需进一步优化,建议结合脚本语言(如 Lua)处理复杂的逻辑和响应解析。
在 FreeSWITCH 中,通过 mod_curl 直接发起 HTTP 请求与通过 LuaJIT(使用 Lua 脚本)发起请求,存在显著差异。以下是两者的对比分析:
1. 实现方式
mod_curl
- 原生模块:是 FreeSWITCH 的内置模块(C 语言实现),直接集成到 FreeSWITCH 核心。
- 同步/异步模式 :
- 同步请求:通过
curl
应用发起请求,阻塞当前会话,直到获取响应。 - 异步请求:通过
curl_async
发起请求,非阻塞,但需通过事件或回调处理响应。
- 同步请求:通过
- 配置驱动:通常通过 XML 拨号计划或配置文件定义请求逻辑。
LuaJIT
- 脚本驱动 :通过 Lua 脚本(LuaJIT 加速)发起 HTTP 请求,依赖第三方库(如
LuaSocket
、LuaSec
或Lua-cURL
)。 - 灵活性:可在脚本中自由编写复杂逻辑(如条件判断、循环、数据处理)。
- 异步支持 :需自行实现异步逻辑(例如协程或结合
event
模块),默认多为同步请求。
2. 性能对比
场景 | mod_curl | LuaJIT |
---|---|---|
同步请求 | 高性能(C 原生实现,低延迟) | 较低(Lua 脚本解析 + 库开销) |
高并发 | 更适合(异步模式可减少阻塞) | 需谨慎处理(脚本阻塞易导致性能瓶颈) |
资源占用 | 较低(直接集成,无额外依赖) | 较高(Lua 运行时 + 第三方库) |
3. 功能与灵活性
mod_curl
- 核心优势 :
- 简单直接:适合简单的 HTTP 请求(如鉴权、路由查询)。
- 事件集成:可与 FreeSWITCH 事件系统绑定(如通话结束后推送 CDR)。
- 变量注入 :直接在 URL 或请求体中插入 FreeSWITCH 变量(如
${caller_id}
)。
- 局限性 :
- 逻辑简单:难以处理复杂响应(如 JSON/XML 解析需依赖其他模块)。
- 依赖配置:动态逻辑需通过拨号计划或事件钩子实现,灵活性受限。
LuaJIT
- 核心优势 :
- 复杂逻辑处理 :可直接解析 JSON/XML(如使用
cjson
库)、动态生成请求参数。 - 流程控制:支持条件分支、循环、错误重试等高级逻辑。
- 与其他模块交互 :可结合
db
、redis
等模块实现混合操作。
- 复杂逻辑处理 :可直接解析 JSON/XML(如使用
- 局限性 :
- 开发复杂度高:需编写和维护 Lua 脚本。
- 依赖管理:需确保第三方库(如 HTTP 客户端)正确安装。
4. 代码示例对比
mod_curl(拨号计划中同步请求)
xml
<extension name="auth">
<condition field="destination_number" expression="^(\d+)$">
<action application="curl" data="http://api.example.com/auth?user=${caller_id_number}"/>
<action application="log" data="INFO Response: ${curl_response_data}"/>
<action application="bridge" data="${curl_response_data}" if="curl_response_code == 200"/>
<action application="hangup" data="NORMAL_CLEARING" if="curl_response_code != 200"/>
</condition>
</extension>
LuaJIT(脚本中发起请求)
lua
local http = require("socket.http")
local ltn12 = require("ltn12")
function auth(session)
local url = "http://api.example.com/auth?user=" .. session:getVariable("caller_id_number")
local response = {}
local res, code = http.request{
url = url,
sink = ltn12.sink.table(response)
}
response = table.concat(response)
if code == 200 then
session:execute("bridge", response)
else
session:hangup("NORMAL_CLEARING")
end
end
5. 适用场景
场景 | 推荐方案 | 理由 |
---|---|---|
简单请求 | mod_curl | 快速集成,无需编码,性能高 |
复杂逻辑/数据处理 | LuaJIT | 灵活处理 JSON/XML、重试逻辑、多步骤交互 |
高并发异步任务 | mod_curl(异步模式) | 减少阻塞,提升系统吞吐量 |
动态路由/鉴权 | mod_curl | 直接通过变量注入实现,配置简单 |
外部系统深度集成 | LuaJIT | 可结合数据库、缓存、其他 API 混合操作 |
6. 关键差异总结
维度 | mod_curl | LuaJIT |
---|---|---|
开发效率 | 高(配置驱动) | 中(需编写脚本) |
执行性能 | 高(C 原生) | 中(LuaJIT 优化,但仍依赖脚本解释) |
灵活性 | 低(仅限 HTTP 请求) | 高(支持任意 Lua 代码) |
维护成本 | 低(集中配置) | 中(需维护脚本和依赖库) |
调试难度 | 低(日志直接查看响应) | 中(需调试脚本逻辑) |
7. 如何选择?
- 选
mod_curl
如果 :- 需求简单(如发起 HTTP 请求并获取结果)。
- 需要高性能、低延迟。
- 避免引入额外依赖或脚本维护。
- 选
LuaJIT
如果 :- 需要复杂逻辑(如解析响应、重试机制、多服务交互)。
- 已熟悉 Lua 或需与其他 Lua 模块(如数据库)配合。
- 接受一定的性能损耗以换取灵活性。
8. 混合使用建议
- 将
mod_curl
用于核心的 HTTP 交互(如鉴权、路由),确保性能。 - 用
LuaJIT
处理复杂业务逻辑(如动态 IVR、话单分析)。 - 通过
event
机制将两者结合,例如用mod_curl
推送事件,再用 Lua 脚本处理后续流程。
通过合理选择,可以充分发挥两者的优势,构建高效灵活的 FreeSWITCH 应用。
在 FreeSWITCH 中,mod_cidlookup 和 mod_blacklist 是专门用于主叫号码归属地查询和黑白名单管理的模块,而 mod_curl 是一个通用的 HTTP 客户端模块。是否需要在已启用这两个模块的情况下额外启用 mod_curl,取决于具体业务场景的复杂性和灵活性需求。以下是详细分析:
1. 模块功能对比
模块 | 核心功能 | 适用场景 |
---|---|---|
mod_cidlookup | 通过本地数据库或简单 HTTP 请求查询主叫号码归属地(如区号、运营商)。 | 基础归属地查询、来电显示格式化。 |
mod_blacklist | 基于本地配置文件或数据库检查主叫/被叫号码是否在黑名单中,拦截呼叫。 | 静态黑白名单管理、简单拦截逻辑。 |
mod_curl | 通过 HTTP/HTTPS 与外部服务动态交互(如获取动态配置、调用复杂业务逻辑 API)。 | 需要外部实时数据或复杂逻辑的场景。 |
2. 是否需要启用 mod_curl?
场景 1:仅需基础功能
- 不需要启用 mod_curl :
- 如果业务需求仅限于:
- 使用
mod_cidlookup
的本地数据库或简单 HTTP 接口查询归属地。 - 使用
mod_blacklist
的静态配置文件管理黑白名单。
- 使用
- 此时
mod_cidlookup
和mod_blacklist
已足够。
- 如果业务需求仅限于:
场景 2:需要动态扩展或复杂逻辑
- 需要启用 mod_curl :
- 如果业务需求包含以下任意一项:
- 动态数据源:黑白名单或归属地数据需要从外部 API 实时获取(如对接云服务、风控系统)。
- 复杂逻辑处理:查询结果需结合其他条件(如时间、地理位置、用户状态)动态决策。
- 数据格式化:主叫/被叫号码需要根据外部规则动态格式化(如国际号码转换、掩码处理)。
- 统一管理:希望将归属地、黑白名单等逻辑集中到外部服务,而非分散在 FreeSWITCH 配置中。
- 如果业务需求包含以下任意一项:
3. 典型场景示例
案例 1:动态黑白名单
- 需求 :
黑名单需要从外部风控系统实时拉取,且拦截逻辑需结合主叫号码的归属地(如只拦截境外号码)。 - 实现 :
- 使用
mod_curl
发起 HTTP 请求到风控 API,获取动态黑名单和拦截规则。 - 结合
mod_cidlookup
的归属地信息,执行复杂拦截逻辑(如 Lua 脚本判断)。
- 使用
案例 2:主叫号码深度格式化
- 需求 :
根据外部服务的规则,将主叫号码转换为特定格式(如+86 13800138000
→008613800138000
)。 - 实现 :
- 通过
mod_curl
调用外部 API 获取格式化规则,动态修改主叫号码(mod_cidlookup
仅支持简单格式转换)。
- 通过
案例 3:归属地扩展查询
- 需求 :
不仅需要归属地,还需查询号码的风险评分(如高频呼叫标记)。 - 实现 :
mod_cidlookup
提供基础归属地,mod_curl
调用风控 API 补充风险数据,综合决策是否接续呼叫。
4. 性能与维护权衡
方案 | 优点 | 缺点 |
---|---|---|
仅用 mod_cidlookup/mod_blacklist | 高性能、低延迟(本地操作)。 | 灵活性低,无法适应动态需求。 |
结合 mod_curl | 灵活对接外部系统,支持复杂逻辑。 | 依赖网络请求,可能增加延迟和故障点。 |
5. 配置建议
如果启用 mod_curl
- 优化 HTTP 请求 :
- 使用 异步请求 (
curl_async
)避免阻塞呼叫流程。 - 设置合理超时(如 3 秒)和重试机制。
- 使用 异步请求 (
- 缓存策略 :
- 对频繁查询的数据(如黑白名单)在 FreeSWITCH 内存或 Redis 中缓存,减少外部调用。
- 安全加固 :
- 启用 HTTPS 并配置 SSL 验证(
ssl-verify-peer=true
)。 - 通过 HTTP Headers 传递认证信息(如 API Key)。
- 启用 HTTPS 并配置 SSL 验证(
示例:动态黑名单检查
xml
<!-- 拨号计划中结合 mod_curl 和 Lua 脚本 -->
<extension name="dynamic_blacklist">
<condition field="destination_number" expression="^(\d+)$">
<action application="lua" data="check_blacklist.lua"/>
</condition>
</extension>
check_blacklist.lua:
lua
local curl = require "freeswitch.curl"
function check_blacklist(session)
local caller = session:getVariable("caller_id_number")
local url = "https://api.security.com/blacklist?number=" .. caller
-- 同步请求(高并发时建议异步)
local response = curl.get(url, {timeout=3000})
if response.code == 200 and response.body == "blocked" then
session:execute("hangup", "USER_BUSY")
end
end
6. 结论
- 无需启用 mod_curl :
如果业务仅依赖静态数据(如本地归属地数据库、固定黑白名单),且无需复杂逻辑扩展。 - 建议启用 mod_curl :
如果需实现以下功能:- 动态数据源(外部 API、数据库)。
- 复杂决策逻辑(组合归属地、风控、用户状态)。
- 统一管理多系统的交互(如同时对接归属地服务、黑名单服务、CRM 系统)。
7. 替代方案
如果希望减少模块依赖,也可通过以下方式替代 mod_curl
:
- mod_xml_curl:专用于动态获取 XML 配置(如拨号计划、用户目录),适合简单查询。
- Lua 脚本 + 第三方库 :使用 Lua 的
LuaSec
或Lua-cURL
库直接发起 HTTP 请求(灵活性高,但需编码和维护)。
最终决策需结合业务场景的实时性要求、系统架构复杂度和运维成本综合权衡。
在需要 多级回退查询 (Redis → MySQL → HTTP)的复杂场景下,FreeSWITCH 的模块选择和架构设计需兼顾 灵活性 、性能 和 可维护性。以下是针对该场景的完整解决方案:
1. 核心需求分析
- 分层查询:优先查询 Redis 缓存,未命中则查 MySQL,最后回退到 HTTP 外部 API。
- 数据一致性:若数据最终从 MySQL 或 HTTP 获取,需回写 Redis 缓存。
- 性能优化:减少外部 HTTP 调用频率,降低延迟。
- 容错机制:单层查询失败时自动降级到下一层,避免服务中断。
2. 模块选择与组合
(1) 必选模块
- Lua 脚本模块(mod_lua) :
- 核心作用:通过 LuaJIT 实现复杂逻辑控制(分层查询、错误处理、缓存回写)。
- 优势:灵活编写多级查询逻辑,支持 Redis/MySQL/HTTP 混合操作。
- mod_curl :
- 核心作用:在 Lua 脚本中发起最终 HTTP 请求(如未命中缓存和数据库时)。
- 优势:原生支持异步请求,性能优于 Lua 第三方 HTTP 库。
(2) 可选模块
- mod_redis 或 Lua Redis 客户端库 :
- 用于直接连接 Redis,提升查询效率(若使用 Lua 库需安装
lua-resty-redis
)。
- 用于直接连接 Redis,提升查询效率(若使用 Lua 库需安装
- mod_odbc 或 Lua MySQL 客户端库 :
- 用于连接 MySQL(若使用 Lua 库需安装
luasql.mysql
)。
- 用于连接 MySQL(若使用 Lua 库需安装
3. 推荐架构设计
plaintext
FreeSWITCH 呼叫流程
│
▼
Lua 脚本
│
├─1. 查询 Redis
│ │
│ ├─ 命中 → 返回归属地
│ ▼
├─2. 未命中 → 查询 MySQL
│ │
│ ├─ 命中 → 回写 Redis → 返回归属地
│ ▼
├─3. 未命中 → 调用 HTTP API(mod_curl)
│ │
│ ├─ 成功 → 回写 Redis + MySQL → 返回归属地
│ ▼
└─4. 失败 → 返回默认值或挂断
4. 实现步骤
步骤 1:启用必要模块
-
加载模块 :在
modules.conf.xml
中启用:xml<load module="mod_lua"/> <load module="mod_curl"/> <!-- 若使用 mod_redis 或 mod_odbc --> <load module="mod_redis"/> <load module="mod_odbc"/>
步骤 2:编写 Lua 脚本
lua
-- 示例脚本:get_caller_location.lua
local redis = require "resty.redis"
local mysql = require "luasql.mysql"
local curl = freeswitch.curl()
function get_location(session)
local caller = session:getVariable("caller_id_number")
local location = nil
-- 1. 查询 Redis
local red = redis:new()
local ok, err = red:connect("redis.example.com", 6379)
if ok then
location = red:get("caller_location:" .. caller)
red:set_keepalive(10000, 100) -- 连接池复用
end
-- 2. Redis 未命中 → 查询 MySQL
if not location then
local env = mysql.mysql()
local conn = env:connect("db_name", "db_user", "db_pass", "db_host")
if conn then
local cursor = conn:execute("SELECT location FROM caller_locations WHERE number = '" .. caller .. "'")
local row = cursor:fetch({}, "a")
if row then
location = row.location
-- 回写 Redis(异步避免阻塞)
freeswitch.background_exec("lua", "redis_set.lua " .. caller .. " " .. location)
end
conn:close()
env:close()
end
end
-- 3. MySQL 未命中 → HTTP 请求
if not location then
local response = curl.get("http://api.example.com/lookup?number=" .. caller, {timeout=3000})
if response.code == 200 then
location = response.body
-- 异步回写 Redis 和 MySQL
freeswitch.background_exec("lua", "persist_location.lua " .. caller .. " " .. location)
end
end
-- 4. 返回结果或默认处理
if location then
session:setVariable("caller_location", location)
else
session:execute("hangup", "NO_ROUTE_DESTINATION")
end
end
步骤 3:辅助脚本(异步回写)
-
redis_set.lua(异步写入 Redis):
lualocal caller = argv[1] local location = argv[2] local red = redis:new() red:connect("redis.example.com", 6379) red:set("caller_location:" .. caller, location) red:expire("caller_location:" .. caller, 3600) -- 缓存1小时 red:close()
-
persist_location.lua(异步写入 MySQL):
lualocal caller = argv[1] local location = argv[2] local env = luasql.mysql() local conn = env:connect("db_name", "db_user", "db_pass", "db_host") conn:execute("INSERT INTO caller_locations (number, location) VALUES ('" .. caller .. "', '" .. location .. "')") conn:close() env:close()
步骤 4:集成到拨号计划
xml
<extension name="dynamic_cid_lookup">
<condition field="destination_number" expression="^(\d+)$">
<action application="lua" data="get_caller_location.lua"/>
<action application="set" data="effective_caller_id_name=${caller_location}"/>
<action application="bridge" data="user/${destination_number}"/>
</condition>
</extension>
5. 性能优化建议
- 连接池管理 :
- Redis/MySQL 使用长连接和连接池(如
set_keepalive
)。
- Redis/MySQL 使用长连接和连接池(如
- 异步回写 :
- 通过
freeswitch.background_exec
异步写入数据库,避免阻塞主线程。
- 通过
- 缓存策略 :
- 设置合理的 Redis 过期时间(如 1 小时),平衡缓存命中率和数据新鲜度。
- 超时控制 :
- Redis/MySQL/HTTP 均设置超时(如 Redis 500ms,MySQL 1s,HTTP 3s)。
- 降级策略 :
- 任一层次查询失败时直接跳过,记录日志并继续下一层。
6. 模块选择对比
方案 | 优点 | 缺点 |
---|---|---|
纯 Lua 脚本 + 第三方库 | 灵活性高,统一逻辑控制 | 依赖外部库安装,需维护 Lua 脚本 |
mod_redis + mod_odbc | 性能更高(C 原生模块) | 配置复杂,不支持动态 HTTP 回退 |
混合方案(推荐) | 平衡性能与灵活性 | 需同时管理多个模块和脚本 |
7. 关键决策点
- 选择 Lua 为主 :
必须使用 Lua 脚本实现多级回退逻辑,其他模块(如mod_cidlookup
)无法满足复杂分层查询需求。 - 启用 mod_curl :
最终 HTTP 回退需依赖此模块,且其异步模式优于 Lua 的第三方 HTTP 库。 - Redis/MySQL 连接方式 :
- 若对性能要求极高,优先使用
mod_redis
和mod_odbc
(C 原生模块)。 - 若追求部署简便,直接使用 Lua 库(需安装
resty.redis
和luasql.mysql
)。
- 若对性能要求极高,优先使用
8. 部署注意事项
-
依赖安装 :
-
Lua 库安装示例:
bash# 安装 luarocks(Lua 包管理器) apt-get install luarocks # 安装 Redis/MySQL 客户端库 luarocks install lua-resty-redis luarocks install luasql-mysql
-
-
错误日志 :
-
在 Lua 脚本中记录各层查询结果和错误信息:
luafreeswitch.consoleLog("WARNING", "MySQL query failed: " .. err)
-
-
压力测试 :
- 使用
sipp
或sngrep
模拟高并发,验证分层查询的稳定性。
- 使用
9. 总结
- 必须启用模块 :
mod_lua
(核心逻辑) +mod_curl
(HTTP 回退)。 - 推荐组合 :Lua 脚本 + Lua Redis/MySQL 库 +
mod_curl
。 - 优势:通过脚本控制多层回退、异步回写、灵活降级,满足动态数据交互需求。
结论
是的,通过 Lua 脚本 + 多级缓存(Redis → MySQL → HTTP) + mod_curl 的方案,可以在不启用 mod_cidlookup
和 mod_blacklist
的情况下,实现更灵活的号码格式化、归属地查询和黑白名单管理,并满足高并发、低延迟(毫秒级响应)的需求。但需注意以下关键点:
1. 功能替代可行性
模块功能 | 原生模块实现(mod_cidlookup/mod_blacklist) | Lua 脚本 + 多级缓存方案 |
---|---|---|
归属地查询 | 基于本地数据库或简单 HTTP 请求 | 支持多级回退(Redis → MySQL → HTTP) |
号码格式化 | 内置规则(如国际号补全) | 可自定义规则(如正则匹配、外部 API 动态规则) |
黑白名单管理 | 基于静态配置文件或简单数据库查询 | 支持动态规则(如实时风控、多维度拦截逻辑) |
性能 | 高(C 原生实现,低延迟) | 取决于缓存命中率和网络优化(需精细调优) |
灵活性 | 低(依赖模块配置) | 高(可自由扩展逻辑、集成外部系统) |
2. 高并发低延迟场景的实现关键
(1) 缓存策略优化
-
Redis 热数据缓存 :
- 将高频查询的号码(如近期通话号码)预热到 Redis,缓存命中率需 ≥95%。
- 设置合理的 TTL(如 1 小时),避免缓存雪崩。
-
本地内存缓存 (可选):
- 在 Lua 脚本中增加内存缓存层(如
lua_shared_dict
),进一步减少 Redis 访问延迟。
- 在 Lua 脚本中增加内存缓存层(如
-
示例缓存结构 :
lua-- 伪代码:多级缓存查询 local function get_caller_info(caller) -- 1. 本地内存缓存 local cache = ngx.shared.caller_cache local info = cache:get(caller) if info then return info end -- 2. Redis 缓存 local info = redis.get("caller:" .. caller) if info then cache:set(caller, info, 60) -- 内存缓存60秒 return info end -- 3. 回退到 MySQL/HTTP -- ... end
(2) 异步与非阻塞设计
-
异步 HTTP 请求 :
- 使用
curl_async
发起 HTTP 请求,避免阻塞 FreeSWITCH 会话。 - 通过
event
机制或协程处理异步响应。
- 使用
-
非阻塞数据库操作 :
- 使用连接池(如 Redis
set_keepalive
、MySQL 连接池),减少连接建立开销。
- 使用连接池(如 Redis
-
示例异步 HTTP 请求 :
lualocal curl = require "freeswitch.curl" local caller = session:getVariable("caller_id_number") -- 异步请求(回调函数处理响应) curl.async_get("http://api.example.com/lookup?caller=" .. caller, { callback = function(response) if response.code == 200 then session:setVariable("caller_location", response.body) end end })
(3) 性能调优
优化方向 | 具体措施 |
---|---|
减少网络延迟 | - Redis/MySQL 部署在 FreeSWITCH 同机房或内网。 - HTTP API 启用 CDN 或全球加速。 |
批量查询 | 合并多个号码的查询请求(如 GET /batch_lookup?numbers=13800138000,13900139000 )。 |
无锁化设计 | 避免 Lua 脚本中的全局锁,使用无状态设计。 |
JIT 编译优化 | 确保 LuaJIT 启用,关键代码段使用 jit.on() 和 jit.off() 精细控制。 |
3. 功能实现对比
(1) 号码格式化
-
原生模块(mod_cidlookup) :
xml<!-- 示例:简单补全国际码 --> <param name="default-country" value="CN"/> <param name="prefix" value="+86"/>
-
Lua 脚本方案 :
lualocal function format_number(caller) -- 自定义规则(如 +86 转换、去除前缀0) if string.match(caller, "^0") then return "+86" .. string.sub(caller, 2) end return caller end
(2) 黑白名单拦截
-
原生模块(mod_blacklist) :
xml<!-- 示例:静态黑名单 --> <list name="blacklist"> <node type="allow" cidr="192.168.1.0/24"/> <node type="deny" cidr="10.0.0.5/32"/> </list>
-
Lua 脚本方案 :
lualocal function check_blacklist(caller) -- 动态规则(如实时风控 API) local risk_score = curl.get("http://api.risk.com/check?caller=" .. caller) return risk_score > 80 -- 风险分高于80则拦截 end
4. 性能基准参考
场景 | mod_cidlookup/mod_blacklist | Lua 脚本方案 |
---|---|---|
单次查询延迟 | 0.1~1 ms(本地查询) | 0.5~5 ms(依赖缓存命中率) |
并发能力 | 10,000+ TPS(C 原生) | 3,000~5,000 TPS(LuaJIT 优化后) |
资源占用 | 低(独立进程) | 中(Lua 虚拟机 + 网络连接) |
5. 何时选择原生模块?
- 需求简单:仅需静态归属地查询或固定黑白名单。
- 极致性能:业务对延迟极度敏感(如金融级实时交易系统)。
- 避免外部依赖:无可用 Redis/MySQL/HTTP 服务,或要求纯本地化部署。
6. 何时选择 Lua 脚本方案?
- 动态规则:需实时更新归属地、黑白名单(如结合风控系统)。
- 多数据源融合:需同时查询归属地、用户画像、历史行为等。
- 业务扩展性:未来可能增加更多定制化逻辑(如 VIP 号码优先路由)。
7. 最终建议
- 混合架构 (平衡性能与灵活性):
- 核心高频功能 (如黑白名单基础拦截)使用
mod_blacklist
。 - 动态扩展功能(如风控、多级归属地查询)使用 Lua 脚本 + 多级缓存。
- 核心高频功能 (如黑白名单基础拦截)使用
- 全量替代条件 :
- 已具备高性能 Redis/MySQL 集群和低延迟 HTTP 服务。
- 团队熟悉 Lua 开发和 FreeSWITCH 异步事件机制。
通过合理设计,Lua 脚本方案完全可以在高并发场景下替代 mod_cidlookup
和 mod_blacklist
,但需在性能、灵活性和维护成本之间谨慎权衡。
结论
是的,通过 Lua 脚本 + 多级缓存(Redis → MySQL → HTTP) 的方案,可以完全替代 mod_cidlookup
和 mod_blacklist
,实现更灵活、动态的号码格式化、归属地查询和黑白名单管理,彻底无需启用原生模块。但需满足以下条件:
1. 完全替代的前提
模块功能 | 替代方案 | 关键要求 |
---|---|---|
归属地查询 | Lua 脚本查询 Redis/MySQL/HTTP,动态返回归属地信息 | Redis 缓存命中率 ≥95%,HTTP 接口响应时间 ≤50ms |
号码格式化 | Lua 脚本自定义规则(如正则匹配、外部 API 动态规则) | 格式化逻辑需封装为可复用的 Lua 函数,避免重复编码 |
黑白名单管理 | Lua 脚本结合动态数据源(如 Redis 实时黑名单、风控 API) | 黑名单数据更新需实时同步到 Redis,拦截逻辑需毫秒级响应 |
性能 | 通过多级缓存和异步设计优化,达到与原生模块相近的延迟(≤5ms 平均响应) | Redis 部署在 FreeSWITCH 同机房,HTTP 服务启用 CDN 或边缘计算 |
2. 原生模块 vs. Lua 脚本方案对比
维度 | mod_cidlookup/mod_blacklist | Lua 脚本方案 |
---|---|---|
功能实现 | 简单、静态规则(本地数据库/配置文件) | 复杂、动态规则(多级缓存、外部 API、实时风控) |
性能 | 超高(C 原生实现,微秒级延迟) | 高(依赖缓存命中率和网络优化,毫秒级延迟) |
灵活性 | 低(配置驱动,无法动态扩展) | 极高(可自由编码,支持任意逻辑) |
维护成本 | 低(模块配置简单,无需编码) | 中高(需维护 Lua 脚本、缓存策略、外部服务) |
适用场景 | 静态数据、固定规则、极致性能需求 | 动态数据、复杂规则、高扩展性需求 |
3. 完全替代的实现方案
(1) 归属地查询与号码格式化
lua
-- 示例:动态归属地查询 + 号码格式化
function get_formatted_caller(session)
local caller = session:getVariable("caller_id_number")
-- 1. 查询 Redis 缓存
local location = redis.get("caller_location:" .. caller)
if not location then
-- 2. 查询 MySQL
location = mysql.query("SELECT location FROM caller_locations WHERE number = ?", caller)
if not location then
-- 3. 回退到 HTTP API
local response = curl.get("http://api.example.com/lookup?caller=" .. caller)
location = response.body or "Unknown"
-- 异步回写缓存
freeswitch.background_exec("lua", "redis_set.lua " .. caller .. " " .. location)
end
redis.set("caller_location:" .. caller, location, "EX", 3600)
end
-- 自定义格式化(如 +86 前缀)
local formatted_caller = "+86" .. string.gsub(caller, "^0", "")
session:setVariable("effective_caller_id_number", formatted_caller)
session:setVariable("caller_location", location)
end
(2) 黑白名单动态拦截
lua
-- 示例:动态黑名单检查(支持实时风控)
function check_blacklist(session)
local caller = session:getVariable("caller_id_number")
local destination = session:getVariable("destination_number")
-- 1. 检查本地缓存黑名单
if redis.sismember("local_blacklist", caller) == 1 then
session:hangup("USER_BUSY")
return
end
-- 2. 调用风控 API(异步非阻塞)
curl.async_get("http://api.risk.com/check?caller=" .. caller, {
callback = function(response)
if response.code == 200 and tonumber(response.body) > 80 then
-- 高风险号码,加入实时黑名单并拦截
redis.sadd("realtime_blacklist", caller)
session:hangup("USER_BUSY")
end
end
})
end
4. 性能优化关键措施
- Redis 热数据预热
- 定期将高频号码的归属地、黑名单数据预加载到 Redis。
- 使用
LFU
淘汰策略,优先保留热点数据。
- 批量查询与异步回写
- 对批量呼叫请求合并查询(如
MGET
批量获取 Redis 数据)。 - 异步回写数据库和缓存,避免阻塞主线程。
- 对批量呼叫请求合并查询(如
- LuaJIT 性能调优
- 使用
FFI
调用 C 库加速关键代码。 - 避免全局变量和频繁的 GC 操作。
- 使用
- 网络架构优化
- Redis/MySQL 部署在 FreeSWITCH 同一可用区,网络延迟 ≤1ms。
- HTTP 服务启用 HTTP/2 和压缩,减少传输开销。
5. 何时不建议完全替代?
场景 | 建议 |
---|---|
超高性能需求 | 需微秒级响应(如金融级实时交易系统),保留原生模块。 |
无运维团队支持 | 缺乏维护 Lua 脚本和缓存服务的能力时,使用原生模块更稳定。 |
纯静态规则 | 数据极少变动(如国际区号表),原生模块配置更简单。 |
6. 最终建议
- 完全替代的条件 :
- 业务需要动态、实时数据交互。
- 团队具备 Lua 开发和缓存架构维护能力。
- 已部署高性能 Redis/MySQL 集群和低延迟 HTTP 服务。
- 保留原生模块的条件 :
- 业务规则简单且静态。
- 对性能要求达到极致(如每秒万级查询)。
通过合理设计,Lua 脚本方案可以完全取代 mod_cidlookup
和 mod_blacklist
,但需在性能、灵活性和运维成本之间精细权衡。