先说几个核心判断。Twilio不是黑名单服务,它只负责通信执行,不提供号码阻断功能。黑名单阻断需要业务代码在调用前查询缓存,严格校验号码格式、From号合法性及TwiML结构,同时控制并发避免限流。

Twilio不是黑名单服务,别把它当防火墙用
Twilio本身不提供“黑名单阻断”能力——它只负责外呼或发信息,既不存储号码状态,也不拦截请求。所谓“阻断”,实际是你的业务代码在调用前做判断:查本地或远程黑名单,决定是否跳过twilio.CreateCall或twilio.CreateMessage。强行把Twilio当网关用,会误判错误码,比如把21212 Invalid 'From' number当成黑名单命中,反而掩盖真实问题。
黑名单该存在哪?别放内存里硬编码
高频、低延迟的号码查询必须走有TTL的缓存,不能每次查数据库或硬编码map。生产环境推荐两种组合:
- Redis + SET结构存黑名单号码(如
blacklist:phone),用SMEMBERS批量查,EXPIRE自动清理 - 若需复杂规则,比如“同一号码24小时内最多触发3次语音”,用Redis的
INCR+EXPIRE实现滑动窗口计数 - 绝对不要用
sync.Map存全局黑名单——重启就丢,且无法跨实例共享;也不要直接读配置文件,改一次要发版
调用Twilio前必须做的三件事
漏掉任意一项,轻则静默失败,重则被Twilio封禁IP或扣费:
- 检查号码格式:用
libphonenumber库标准化输入,+8613800138000和13800138000是两个不同key,Redis查不到 - 验证From号码合法性:Sandbox环境只能用Twilio分配的号码或已验证号码;生产环境必须完成
Verified Caller ID流程,否则CreateCall直接返回21212 - 构造合法TwiML:语音必须传XML字符串,例如
,不能传纯文本您已被限制访问
并发控制不是可选项,是保命线
Twilio对/Calls接口默认限流1 QPS,不是1通电话每秒,而是1次HTTP请求每秒。没做节流就发100个goroutine,99%请求会收到20005 Request was throttled,且后续几分钟内所有请求可能被临时拒绝。
正确做法:
- 用带缓冲的channel控制并发:
sem := make(chan struct{}, 1),每次调用前sem <- struct{}{},结束后<-sem - 别用
time.Sleep模拟节流——网络抖动会让实际间隔远小于1秒,照样触发限流 - 生产环境升配High Volume Calling套餐后,仍需在代码层做基础限流,避免突发流量打垮下游
真正难的是错误分层处理:网络超时可重试,32203 Invalid To number这类业务错误重试毫无意义,但日志里混在一起就很难定位是号码问题还是网络问题。
