☰
短消息中心业务功能全解析:从协议接入到限流去重的工程实践
2026/10/9 5:45:47 网站建设 项目流程

简介:这份PPT面向通信工程、移动网络运维方向的学习者与从业者,系统梳理短消息中心(SMSC)的业务功能与运行机制,帮助读者建立从消息提交到最终投递的完整认知框架。内容围绕短消息提交与转发、优先级处理、有效期管理、重复转发尝试、状态报告、用户鉴权、汉字短消息、虚拟短消息中心、多种调度方式、节日模式及网络短消息等模块展开,并延伸至长短消息转发、多目的地发送、网关与报表统计等扩展功能,适合作为技术培训或自学参考。资源包共1个文件,为pptx演示文稿,整体约444KB,以图文幻灯片形式呈现课程要点,便于按章节浏览与讲解。目前已有77人学习,内容结构清晰、知识点覆盖较全,可作为理解短消息中心业务流程与调度策略的入门与查阅材料。

1. 短消息中心业务功能:从一份 PPT 标题拆出可落地的短信平台设计

很多人第一次看到「试谈短消息中心业务功能.pptx」这个标题,会以为它只是一份汇报材料。但真做过短信平台的人知道,这个标题背后藏着一整套运营商级或企业级的消息基础设施:短消息中心(SMSC)到底承载哪些业务、这些业务怎么在协议层落地、计费与状态报告怎么闭环、群发和行业短信怎么限流。它解决的不是「怎么发一条短信」这种小问题,而是「一天几千万条短信怎么稳定进出、怎么保证不丢不乱不重复」这种工程问题。适合谁看?正在做短信网关、消息中台、验证码/通知服务的后端工程师,以及需要把短信能力接进业务系统的架构同学。下面我按自己搭过的一套方案,把这个标题拆成能复现的路径。

2. 短消息中心的业务功能地图:先分清哪些是协议、哪些是业务

短消息中心不是一个单点服务,它更像一个「协议接入 + 业务编排 + 计费结算 + 状态追踪」的组合体。很多团队翻车,是因为把协议层的事和业务层的事混在一张表里设计,最后短信一发多就互相拖死。所以第一步不是写代码,而是把功能分层画清楚。

2.1 四层功能划分与各自职责

我一般把短消息中心拆成四层,每层只干自己的事:

层级核心职责典型模块关键指标
接入层对接外部协议与通道SMPP、CMPP、HTTP 网关连接数、TPS
业务层短信类型编排与路由验证码、通知、营销、行业短信成功率、时延
计费层扣费、批价、结算预付费、后付费、阶梯价计费准确率
状态层状态报告与回执deliver_sm、回执匹配回执到达率

接入层负责把不同协议的消息统一成内部消息模型,业务层决定这条短信走哪条通道、用什么模板、是否要审核,计费层在提交时预扣、在回执后确认,状态层负责把运营商返回的状态报告匹配回原始消息。这四层如果耦合在一起,改一个营销限流规则就可能影响验证码通道,这是最常见的架构坑。

2.2 业务功能清单:验证码、通知、营销、行业短信的差异

同样是发短信,四类业务的工程要求完全不同:

  • 验证码:要求低时延(通常 3 秒内到达)、高优先级、强去重,通常走独立通道,不允许和营销混跑。
  • 通知类:如订单、物流、账单,允许秒级到分钟级时延,但要求高可靠、可重试。
  • 营销类:量大、可延迟、必须支持退订和频次控制,通道成本敏感。
  • 行业短信:如政务、金融的定向通知,往往有合规审核和签名报备要求。

这四类如果共用一条通道和一个队列,营销高峰会把验证码挤爆。常见做法是物理隔离通道 + 逻辑隔离队列,验证码单独一个高优先级队列,营销走低优先级并做令牌桶限流。

2.3 用一张内部消息模型表统一四类业务

不管外部协议是什么,进入短消息中心后都应该转成统一模型。下面这张表是我常用的字段设计:

-- 内部统一消息模型,四类业务共用 CREATE TABLE sms_message ( msg_id BIGINT PRIMARY KEY, -- 内部全局唯一ID biz_type TINYINT NOT NULL, -- 1验证码 2通知 3营销 4行业 src_addr VARCHAR(32) NOT NULL, -- 发送方号码/签名 dst_addr VARCHAR(32) NOT NULL, -- 接收方号码 content VARCHAR(1024) NOT NULL, -- 短信内容 channel_id INT NOT NULL, -- 路由到的通道 priority TINYINT DEFAULT 5, -- 优先级,越小越高 status TINYINT DEFAULT 0, -- 0待发 1已提交 2成功 3失败 submit_time DATETIME, -- 提交到通道时间 report_time DATETIME, -- 状态报告时间 retry_count INT DEFAULT 0, -- 重试次数 INDEX idx_dst (dst_addr), INDEX idx_status (status) );

biz_type决定优先级和限流策略,channel_id记录路由结果便于排查,status和report_time支撑状态闭环。这张表看起来简单,但它是后面所有业务功能的地基。字段设计时要注意:msg_id必须全局唯一且能反查,否则状态报告回来时无法匹配;retry_count要设上限,否则失败消息会无限重试拖垮队列。

3. 协议接入与消息收发:SMPP/CMPP 通道怎么接、怎么测

业务功能要落地,绕不开协议接入。短消息中心对外要么连运营商网关,要么连第三方通道,主流协议就是 SMPP 和 CMPP。这一章讲怎么把通道接进来、怎么保证收发不丢。

3.1 SMPP 会话建立与 bind 参数怎么设

SMPP 是基于 TCP 的会话协议,建立连接后要先 bind。常见做法是用现成的客户端库,比如 Python 的smpplib。下面是一个最小可跑的 bind 示例:

import smpplib.client import smpplib.gsm import smpplib.consts client = smpplib.client.Client('smsc.example.com', 2775) # system_id/password 由通道方提供,system_type 一般留空 client.connect() client.bind_transmitter( system_id='your_account', password='your_password', system_type='', addr_ton=1, # 地址类型:1 国际 addr_npi=1, # 编号计划:1 ISDN address_range='' # 一般留空 )

addr_ton和addr_npi是最容易设错的参数,设错会导致运营商侧拒收。system_type大多数通道留空,少数要求填特定值。bind 成功后要保持长连接,并做心跳(enquire_link),否则空闲几分钟就会被断开。我一般把心跳间隔设成 30 秒,比通道方的超时时间短一半。

3.2 提交短信与状态报告匹配的完整流程

提交短信用 submit_sm,状态报告通过 deliver_sm 回来。关键是要把 submit_sm 返回的 message_id 和原始 msg_id 做映射:

# 提交并记录通道返回的 message_id message_id = client.send_message( source_addr='10690000', destination_addr='13800000000', short_message=b'Your code is 1234', registered_delivery=True # 必须为True才会回状态报告 ) # 把通道 message_id 写回映射表,供回执匹配 save_mapping(internal_msg_id, message_id)

registered_delivery=True是状态报告的前提,很多新手忘了设,结果永远收不到回执。状态报告回来时,从 deliver_sm 的 receipted_message_id 里取出通道 message_id,反查内部 msg_id,再更新sms_message表。这一步如果映射表丢了或没落库,回执就成了黑匣子,对不上账。

3.3 长短信与编码:UDH 拼接和 GSM7/UCS2 选择

超过 70 个字符的短信要拆成多条,通过 UDH 头拼接。编码选择直接影响长度和费用:

  • GSM7 编码:单条 160 字符,带 UDH 时 153 字符,适合纯英文数字。
  • UCS2 编码:单条 70 字符,带 UDH 时 67 字符,中文必须用这个。
import smpplib.gsm # 自动按内容选择编码并拆分 parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts( '您的验证码是1234,5分钟内有效' ) for part in parts: client.send_message( source_addr='10690000', destination_addr='13800000000', short_message=part, data_coding=encoding_flag, # 编码标志 esm_class=msg_type_flag # UDH 标志 )

data_coding和esm_class必须和拆分逻辑配套,否则接收端拼不起来。中文短信按 UCS2 算,一条 67 字,成本是英文的两倍多,做营销短信时这个账要提前算。

4. 业务功能落地:验证码、通知、营销的限流与去重

协议通了只是第一步,真正体现短消息中心业务功能的是限流、去重、路由和退订。这一章讲怎么把这些业务规则做成可配置、可观测的模块。

4.1 验证码短信的频次控制与去重

验证码最怕被刷。常见做法是三层控制:手机号维度、IP 维度、业务维度。用 Redis 做计数器:

import redis r = redis.Redis() def can_send_code(phone, ip, biz): # 同一手机号 60 秒内只能发一次 if not r.set(f'code:phone:{phone}', 1, ex=60, nx=True): return False, 'phone_too_fast' # 同一手机号一天最多 10 次 day_key = f'code:phone:day:{phone}' if r.incr(day_key) > 10: r.expire(day_key, 86400) return False, 'phone_daily_limit' # 同一 IP 一小时最多 20 次 ip_key = f'code:ip:{ip}' if r.incr(ip_key) > 20: r.expire(ip_key, 3600) return False, 'ip_limit' return True, 'ok'

set用nx=True保证原子性,避免并发下重复发送。日限和时限分开计数,过期时间要设对,否则计数器永远不清零。去重还要在内容层做,同一验证码短时间内重复提交要拦截。

4.2 营销短信的退订与频次合规

营销短信必须支持退订,且要维护退订名单。常见做法是维护一张退订表,发送前先过滤:

-- 退订名单表 CREATE TABLE sms_unsubscribe ( phone VARCHAR(32) PRIMARY KEY, biz_type TINYINT, -- 退订的业务类型 create_time DATETIME ); -- 发送前过滤:排除已退订号码 SELECT COUNT(*) FROM sms_unsubscribe WHERE phone = ?;

退订关键词一般包括「TD」「退订」「取消」等,收到后要立即写入退订表并回执确认。频次上,同一用户同一营销活动一周内不超过 2 次,这个规则要可配置,不能写死在代码里。合规是营销短信的红线,退订没做好,通道被封是迟早的事。

4.3 通道路由与故障切换策略

一条短信走哪条通道,取决于业务类型、号码归属、通道成本和实时健康度。我一般用权重路由 + 健康检查:

def select_channel(biz_type, phone): candidates = get_channels(biz_type, phone) # 按业务和号段筛 healthy = [c for c in candidates if c.health_score > 80] if not healthy: healthy = candidates # 全部不健康时降级 # 按权重随机 total = sum(c.weight for c in healthy) r = random.uniform(0, total) upto = 0 for c in healthy: upto += c.weight if upto >= r: return c return healthy[-1]

health_score由发送成功率、回执时延、连接状态综合计算,低于阈值自动降权。故障切换要设熔断,连续失败 N 次后暂时摘除通道,避免持续打挂。路由策略要能热更新,不能改一次重启一次。

5. 避坑与排查:短消息中心最常见的 5 个翻车现场

这一章是我血泪经验最集中的地方。短消息中心的问题往往不是功能不会写,而是细节没处理好,线上才暴露。

5.1 状态报告对不上:现象、原因、解决

现象:发送成功率看着正常,但回执匹配率只有六七成,账对不上。原因通常是映射表没落库、message_id 重复、或者回执里的 receipted_message_id 格式和提交时不一致。解决:映射关系必须持久化,不能只放内存;message_id 要做唯一索引;回执解析时先打印原始报文再解析,确认字段格式。我一般会在映射表加一个channel_msg_id唯一索引,重复直接报错而不是覆盖。

5.2 长短信被拆乱:现象、原因、解决

现象:用户收到半截短信,或者两条顺序颠倒。原因是 UDH 拼接参数设错,或者多条分片走了不同通道。解决:同一逻辑消息的所有分片必须走同一通道、同一连接,esm_class和data_coding要一致。拆分前先算好总长度和分片数,分片序号在 UDH 里要连续。如果通道不支持 UDH,就要降级为单条或换通道。

5.3 验证码被刷爆:现象、原因、解决

现象:某手机号或某 IP 短时间内产生大量验证码请求,通道费用飙升。原因是限流只做了单机内存计数,多实例部署下失效。解决:限流必须用 Redis 等集中式存储,set nx保证原子性;手机号、IP、设备指纹多维度组合;对异常号码做黑名单。我还会加一个全局速率告警,超过阈值直接人工介入。

5.4 通道被限速拖垮:现象、原因、解决

现象:营销短信一发,验证码延迟飙升甚至超时。原因是共用通道和队列,没有优先级隔离。解决:验证码和营销物理隔离通道,至少逻辑隔离队列;营销发送用令牌桶限速,速率不超过通道方承诺的 TPS;队列积压时优先丢弃低优先级消息。这个坑我踩过,后来把验证码单独拉了一条通道才稳住。

5.5 计费与回执不一致:现象、原因、解决

现象:预扣了费用但回执是失败,用户被多扣。原因是计费在提交时预扣,回执失败后没有冲正。解决:预扣和确认要成对,回执失败必须触发退款或冲正;对账任务每天跑一次,比对提交量、成功量、计费量。计费表要有唯一约束,防止重复扣费。这块涉及钱,宁可多对几次账。

6. 进阶技巧:用回执时延分布反推通道质量

前面讲的都是怎么把功能做出来,这一章讲一个我常用的进阶技巧:用回执时延分布来评估通道质量,而不是只看成功率。成功率会骗人,时延分布不会。

具体做法是:把每条消息的report_time - submit_time算出来,按通道分组,统计 P50、P90、P99。一个健康的通道,P90 应该在几秒内,P99 不超过十几秒。如果某个通道 P99 突然拉长,说明它开始拥塞,即使成功率还没掉,也应该提前降权。

-- 按通道统计回执时延分位 SELECT channel_id, COUNT(*) AS total, AVG(TIMESTAMPDIFF(SECOND, submit_time, report_time)) AS avg_delay, MAX(TIMESTAMPDIFF(SECOND, submit_time, report_time)) AS max_delay FROM sms_message WHERE status = 2 AND report_time >= DATE_SUB(NOW(), INTERVAL 1 HOUR) GROUP BY channel_id;

这个查询每小时跑一次,结果写进监控。avg_delay看整体,max_delay看极端情况。如果某通道max_delay超过 60 秒,基本可以判定它有问题,不用等成功率报警。我还会把时延分布画成直方图,双峰分布往往意味着通道在重试,这种通道要重点盯。

另一个技巧是回执内容分析。失败回执里的错误码能告诉你很多信息:ABSENT_SUBSCRIBER是用户关机,UNKNOWN_SUBSCRIBER是空号,DELIVERY_FAILURE是通道问题。把这些错误码分类统计,能区分是用户侧问题还是通道侧问题。用户侧的空号要清理,通道侧的失败要切换。我一般每周看一次错误码分布,空号率突然升高往往意味着号码库该更新了。

最后说个习惯:任何通道上线前,先用测试号码跑一轮全流程,包括长短信、中文、状态报告、失败重试,确认回执时延在预期内再放量。这个习惯帮我省过好几次后悔药。短消息中心这行,细节决定成败,把回执时延和错误码这两个指标盯住,大部分问题都能提前发现。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询