ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

在线计费系统OCS原理与实时扣费工程实践

在线计费系统OCS原理与实时扣费工程实践 简介《OCS计费原理与实现》是一份面向通信行业计费领域技术人员的专业文档系统讲解在线计费系统OCS从基础概念到具体实现的完整知识体系。文档从序篇、扫盲篇到计费流程、批价部分层层递进既有原理剖析也有落地实现说明内容涵盖计费系统从离线到实时的演进过程详细说明TAG、业务用量、费用项、产品、订购、入帐关系等核心术语并结合事件驱动机制梳理事件捕获、事件处理、生成账单、结算等关键计费流程以及实时信用评估与额度控制等细节同时涉及计费引擎、消息中间件等实现组件与安全考虑适合电信运营商BSS/OSS系统架构师、计费软件开发工程师以及通信专业学生阅读参考。压缩包内包含1个doc文档大小1.87MB排版整理后便于检索和阅读已有297人学习浏览是理解OCS计费原理和实现逻辑的实用参考资料。1. OCS在计费链路中的位置为什么实时扣费不能靠改一遍余额做通信或物联网计费的人迟早会碰到这样一个需求用户套餐快用完系统要在额度用尽的瞬间停掉服务而不是等月底出账再追缴。早期做后付费话单攒到月底统一批价用户超套了也不知道月底一算吓一跳。语音时代问题还不大到了流量时代一个视频会议能把套餐跑穿后付费模式根本扛不住。OCSOnline Charging System在线计费系统就是为解决这个问题出现的它在每一次会话发生时实时参与信令交互预先计算可用额度、按配额下发、用完后立即终止或降级。OCS不是数据库上加一个扣减接口那么简单。它的核心是计费控制和业务控制的耦合——计费系统要在呼叫建立之前就知道这次呼叫能持续多久而不是等呼叫结束再倒扣余额。这套机制包含余额管理、配额授权、计费事件上报、超时控制、防重复扣费等一整套流程。本文围绕在线计费系统的原理和工程实现按架构、流程、状态机、参数、踩坑、验证六步展开读者如果是做计费平台、需要对接OCS网元、或者想仿照OCS的机制给自己的系统加实时扣费能力可以直接照着落地。2. 在线计费的核心流程从呼叫开始到余额扣减五步闭环2.1 OCS在电信网络里的位置信令面怎么和业务面衔接先捋清楚OCS和其他计费系统的区别。离线计费Offline Charging是事后批价业务网元如PGW、MSC产生话单CDR交给计费网关再进批价系统整个过程发生在业务结束之后。在线计费是实时交互业务网元在会话开始前主动向OCS发起额度请求OCS返回可用的配额比如时长600秒或流量100MB业务网元执行到额度用完时再找OCS申请下一批申请不到就终止业务。OCS在网络里的位置通常是这样的业务控制点比如PCRF、MSC、PGW、AS通过Diameter Ro接口或CAP协议向OCS发送信用控制请求CCR。OCS内部拆成几个逻辑功能——统一余额管理、在线计费控制功能、批价引擎、配额管理、话单生成。语音走CAP数据流量走Ro接口核心网元只认接口不关心里面的余额到底怎么算的。实际部署里OCS通常和BOSS、CRM、账务系统通过内部接口互通。用户充值时CRM往OCS发余额变更指令余额不足时OCS会联动智能网触发放音通知用户。这里有一个关键设计OCS只持有可用余额的缓存和扣费所需的计费上下文正式账本在账务系统里两者通过异步对账保持一致。如果账务系统直接实时操作数据库的余额字段高并发扣费很容易把数据库拖垮这也是OCS要单独存在的原因。2.2 第一次会话请求信用控制请求的完整交互逻辑一次标准的在线计费会话以用户打电话为例CCR/CCA交互通常分三种初始请求INITIAL、更新请求UPDATE、终止请求TERMINATION。流程是这样的——用户拨号MSC收到呼叫请求后向OCS发送CCR INITIAL消息里携带用户标识、业务类型、被叫号码、计费方式等AVP。OCS先做鉴权确认用户状态正常、余额充足接着批价引擎计算费率比如每分钟0.1元再根据余额算出能授予的时长配额比如余额10元就能授权6000秒。OCS返回CCA消息里面带上Granted-Service-Unit授予的服务量和Validity-Time有效期。MSC收到后继续接续呼叫。用户通话过程中如果授权时长快到期MSC会发送CCR UPDATE请求OCS再次计算余额并下发新的配额。如果余额不够则在CCA中返回最终配额Final-Unit-IndicationMSC让用户听欠费提示音。用户挂机MSC发CCR TERMINATION携带实际用量。OCS完成最后扣费、生成话单关闭会话上下文。整个过程还有心跳机制MSC与OCS之间有定期探测防止一方异常后对方继续授权造成计费丢失。2.3 两种计费模式即时扣费和会话扣费别选错了OCS实际应用中按场景分两种模式即时计费Immediate Charging和会话计费Session Based Charging。两者差异很大对接时如果选错轻则余额对不上重则产生大额欠费。即时计费适用于点播类业务比如下载一首歌、看一次付费内容。流程简单用户发起请求OCS直接扣费并返回成功或失败业务网元只在扣费成功后才允许用户使用。扣费是一次性的没有后续授权。真实案例里不少厂商把点播误做成会话计费导致用户看完视频才发现余额被分批扣光。会话计费适用于持续消耗型业务比如语音、流量、游戏时长。OCS分批授权业务网元在额度耗尽前提前申请下一批。这种模式的精髓是“预授权动态扣费”——OCS给的不是一次扣费结果而是一段时间内的可用额度。真正的资费扣减发生在会话结束时OCS根据实际用量和当时费率算出最终费用。如果用户中途退出多授权的额度要返还。这里最容易踩的坑是返还处理很多实现只扣不还用户余额莫名变少这就是OCS和账务系统对账对不上的主要来源之一。2.4 一个可以复用的参考流程一次性扣费的最简实现如果产品要快速验证OCS机制先不做会话管理只做“余额预检-扣费-回调”三步逻辑如下。先定义余额操作接口class BalanceManager: def __init__(self, initial_balances): self.balances initial_balances def has_enough_balance(self, user_id, required_amount): balance self.balances.get(user_id, 0) return balance required_amount def deduct(self, user_id, amount): if self.has_enough_balance(user_id, amount): self.balances[user_id] - amount return True, self.balances[user_id] return False, self.balances[user_id] def refund(self, user_id, amount): self.balances[user_id] amount return self.balances[user_id]扣费服务通过一个计费请求入口对外暴露请求里带上用户ID和消费金额def charge(user_id, amount, request_id): # 先做幂等检查同一个request_id只处理一次 if already_processed(request_id): return {code: 0, msg: duplicated request} success, new_balance balance_manager.deduct(user_id, amount) if not success: return {code: 1001, msg: insufficient balance} save_charge_record(user_id, amount, request_id) return {code: 0, balance: new_balance}调用方调用charge接口时必须携带唯一request_id。因为业务网元和OCS之间的消息交互有重发机制同一个请求可能被网络层重复投递如果扣费逻辑不做幂等控制用户一次话费会被扣两次。这个设计不分语音流量还是物联网计费所有在线计费系统都必须有。上面的代码演示的是最原始的余额操作完整实现还需要引入事务、锁、日志这些放到后面的状态机实现和参数设计里展开。3. 用状态机实现OCS会话控制状态定义、迁移与超时处理3.1 OCS会话状态机的五个状态从Initial到TerminatedOCS的会话管理天然适合用有限状态机FSM实现。一次会话从建立到释放状态转换是确定的收到CCR INITIAL时新建会话收到CCA/UPD时续授权收到CCR TERMINATION时关闭。状态机让整个会话控制逻辑可预测、可回溯也让异常处理有了明确的落点。一组实践中常用的状态定义INITIAL初始CCR刚收到、PENDING_AUTH等待授权结果已向余额侧发起扣费预占、ACTIVE会话进行中配额已下发、SUSPENDED配额耗尽暂停等待下一次授权、TERMINATING收到终止请求正在做最终扣费和话单生成。状态迁移的触发事件对应CCR消息类型新建会话、更新会话、终止会话。有一个细节容易被忽略PENDING_AUTH状态下如果余额查询服务超时会话要能自动回退到INITIAL并拒绝呼叫不能一直等待导致业务无响应。3.2 会话续期机制配额耗尽前主动更新而不是等耗尽OCS会话中最核心的循环是配额更新业务网元使用配额配额快用完时发CCR UPD申请新配额OCS检查余额后下发新额度。这里的“快用完”由两个阈值控制——配额剩余比例阈值和配额剩余时间阈值。比如授权600秒业务网元会在剩余60秒时发起更新请求授权流量100MB则在剩余10%时发起更新。这个机制决定了OCS的并发能力。配额给得越大更新请求越少OCS压力越小但余额变动不及时透支风险变大配额给得越小计费越精确但信令风暴风险变大。真实网络里OCS和业务网元之间每秒可能要处理上万条更新请求所以很多OCS实现了“配额缓存”机制同一个用户短时间内重复请求直接复用缓存。续期逻辑实现上需要维护会话的配额上下文包括已用配额、剩余配额、授权时间戳。等到CCA响应返回新配额后旧配额作废剩余未用配额要返还。返还逻辑是防止多扣费的保障很多系统在这里“翻车”只记录新配额忘了返还旧配额。3.3 用一段伪代码描述状态机的核心迁移逻辑以下用Python描述会话状态机的迁移可以直接对照迁移成Java或Go实现class ChargingSessionFSM: def __init__(self, session_id, user_id): self.session_id session_id self.user_id user_id self.state INITIAL self.granted_quota 0 self.used_quota 0 self.pending_ccr None def on_ccr_initial(self, requested_quota): if self.state not in (INITIAL, TERMINATING): raise IllegalStateError(fcannot handle CCR_INITIAL in {self.state}) # 预占配额并置为等待授权 self.pending_ccr requested_quota self.state PENDING_AUTH return self.send_auth_request(requested_quota) def on_cca_success(self, granted_quota): if self.state ! PENDING_AUTH: return self.granted_quota granted_quota self.state ACTIVE def on_ccr_update(self, used_quota, requested_quota): if self.state not in (ACTIVE, SUSPENDED): return self.used_quota used_quota remaining self.granted_quota - used_quota if remaining 0: self.state SUSPENDED self.pending_ccr requested_quota self.state PENDING_AUTH return self.send_auth_request(requested_quota)on_ccr_initial里最关键的是“非法状态跳转”的拦截。真实生产环境中业务网元可能因为网络重发、超时重试等原因在错误的时间段发送错误的消息类型。状态机的一个核心作用就是把这些非法消息挡在外面让会话上下文始终可控。收到CCA成功后必须校验CCA里携带的会话ID和本次请求是否匹配否则直接丢弃。代码里还有一个隐含设计PENDING_AUTH与超时定时器联动。每个会话从进入PENDING_AUTH开始启动一个超时定时器一般在3到10秒之间定时器触发时会话强制关闭并告警防止余额查询服务异常时会话卡死。3.4 会话恢复与对账重启之后黑匣子怎么打开OCS是一个要跑5个9可用性的系统重启和故障切换必须有会话恢复机制。生产环境常见的做法是把会话上下文持久化到Redis或内存数据库中每次状态迁移都同步写一份快照。OCS重启后从快照恢复所有处于PENDING_AUTH和ACTIVE状态的会话并主动向业务网元发送重新授权请求RAR让业务侧重新同步状态。这里最容易出问题的是会话快照的一致性。状态机迁移分两步先改内存状态再写持久化。如果写持久化失败内存状态和实际计费状态就不一致。我会把状态机的迁移操作设计成“先持久化、后生效”先写入新的会话状态快照返回成功后再变更内存状态。这样即使进程在中间崩溃重启后恢复的还是旧快照话单不会凭空消失。对账是所有OCS上线前的必选项。OCS本地会记录每一次授权的流水账务系统记录实际扣费流水。每天凌晨跑一次对账任务比对两侧的授权总量和扣费总量把差异单挑出来。真正投产前先跑一周对账把差异清零否则上线后余额差错会变成扯皮的黑匣子。4. 配额、余额与话单参数线程安全之外这四处最容易被调坏4.1 配额步长与最小配额量授多少、多久授一次配额步长Quota Granularity是OCS参数里最影响用户体验的一个。语音业务常见时长配额流量业务常见流量配额还有个混合配额模式。配额给得太大用户额度用完但OCS不知道透支时长变长配额给得太小信令交互变多OCS和业务网元的CPU都吃不消。工程上的经验值是语音配额按60秒到600秒之间配置流量配额按1MB到10MB之间配置。视频类高消耗业务建议配额给小一点比如2MB让OCS更频繁地检查余额普通网页浏览流量配额可以给到5MB。最小配额量Minimum Quota用于限制业务网元的下限申请防止业务侧频繁申请小额配额造成信令风暴。比如配置最小配额为10秒或100KB业务网元申请低于这个值就直接按最小值下发。配额参数调整后一定要做并发回归。之前有一次调整了语音配额从600秒改为300秒结果呼叫量翻倍OCS的CPU直接打满业务网元等不到CCA响应纷纷超时释放呼叫。4.2 Validity-Time与心跳授权有效期设短了会怎样Validity-Time授权有效期是CCA里通知业务网元的参数告诉它这批配额在什么时间之前必须用完或发起续授权。这个参数通常和配额步长联动配额越大有效期越长。语音600秒配额配900秒有效期流量5MB配额配600秒有效期。有效期设短业务网元会在配额还没用完时频繁发起更新请求有效期设长如果OCS这边余额异常变动比如用户充值未到账、被其他业务扣光OCS无法及时回收配额。更严重的场景是业务网元断续续授权OCS侧会话已经关闭但业务侧还在使用旧配额。Validity-Time过期后业务网元必须无条件终止会话或重新发起初始请求这是协议层面的强制要求。心跳机制Heartbeat是另一个关键参数。Ro接口和CAP协议都支持心跳业务网元定期发探测消息确认OCS存活。心跳间隔一般配置为30到90秒。心跳间隔太短信令负荷高间隔太长OCS宕机后业务网元要等很久才能发现这段时间内的新呼叫会全部失败。如果发现业务侧报告大量超时先查心跳和Validity-Time这两项参数。参数典型值调小的影响调大的影响配额步长语音60-600秒信令增多OCS压力大余额透支风险增大配额步长流量1-10MB同上同上Validity-Time配额的1.2-1.5倍频繁续授权配额回收不及时心跳间隔30-90秒信令负荷高故障发现延迟4.3 余额不足的“放音阈值”提前通知还是立即终止余额不足时的策略分两档预通知阈值和终止阈值。预通知阈值用于触发IVR放音提醒用户余额不足比如余额低于5元或配额剩余10%时OCS在CCA里带上Final-Unit-Indication业务网元播报欠费提示。终止阈值则是硬边界余额为0或配额用尽后OCS直接拒绝新的授权请求。这里有个实际参数放音文本和放音次数。有的运营商允许设置“配额耗尽前放音一次”有的允许“循环放音直到用户挂机”。放音次数配置不当会导致用户听到重复欠费提示又无法继续通话投诉率飙升。一般建议欠费提示只播报一遍。放音过程中产生的费用要能单独记录不能混入用户正常通话费。还有一类业务要单独配置紧急呼叫、客服热线、报警电话。这类号码在余额不足时仍然允许呼叫OCS需要在鉴权规则里加白名单。真出过事故停机用户拨打紧急电话因为OCS在初始请求阶段直接拒绝导致呼叫失败事后被运营商严肃追责。4.4 话单输出与流量控制OCS不只要扣钱还要吐数据OCS的话单输出是整个系统的数据出口。每次会话结束OCS生成一条计费话单包含用户ID、会话ID、业务类型、开始时间、结束时间、用量、费用、批次ID。话单不是写一条丢一条而是先写本地缓冲再往上级计费系统推送。生产环境通常按5分钟或15分钟批量输出话单避免每产生一条话单就触发一次网络I/O。推送失败要支持本地缓存和重推。这块涉及两个参数话单文件大小阈值和推送超时时间。文件大小阈值到了就立即封文件超时未推送的话单要进入死信队列人工处理。话单字段里最容易出错的是“费用精度”。OCS批价引擎算出来的费用要按运营商规则做四舍五入到分但用量字段时长、流量必须保留原始精度不能因为四舍五入导致话单里的用量对不上否则对账必挂。另一个容易被忽略的是时区OCS跨地域部署时话单里的时间统一存UTC时间展示层再做本地化否则夏令时切换或时区配置错误会让账期错乱。4.5 并发扣费的三个陷阱超卖、死锁与余额负数在线计费高并发下最常见的三个故障模式超卖、死锁、余额负数。超卖发生在“先检查余额再扣费”两步操作之间两个并发请求同时读到余额充足都执行扣费最终余额变负数。解决办法是使用原子扣减操作或悲观锁SQL里一句UPDATEbalance balance - amount WHERE balance amount就能避免超卖。死锁多发于多业务并发扣费时比如用户同时使用语音和数据业务两个会话分别锁定了用户的账户记录互相等待。解决办法是统一加锁顺序——所有扣费请求按用户ID哈希分桶同一个用户的扣费请求串行执行。余额负数则是前面两种问题的直接后果一旦出现要立即启动补偿流程回滚异常扣费。OCS生产调试中扣费并发问题通常不是代码逻辑复杂而是数据库隔离级别和索引没配好。账户表的主键设计要用用户ID做哈希分布避免所有扣费集中在同一个数据库节点上。热点账户如大流量用户要单独设计必要时做分片。5. 常见问题排查从误扣费到连环超时的五条真实事故5.1 用户多次重复扣费没有幂等控制的下场现象用户在一次通话结束后发现余额被扣了多次每一笔扣费金额相同。原因业务网元在发送CCR TERMINATION后没有收到CCA响应按协议重发了一次OCS没有识别出这是同一个请求ID。重发的终止请求被当成新会话处理用户被扣了两次通话费。解决所有CCR请求在入口处做去重以Session-ID和CC-Request-Number组合为唯一键已处理的请求直接返回上次的CCA响应。这里有个坑CC-Request-Number在不同业务网元实现里可能从0开始或从1开始要充分测试兼容性。5.2 OCS重启后大量呼叫失败会话状态全部丢失现象OCS完成一次版本升级重启后紧接着的一分钟内呼叫失败率飙升。原因升级时没做会话持久化恢复。重启后内存里的会话全部清空业务网元还在按旧会话发CCR UPDATEOCS找不到对应会话直接拒绝。解决升级流程增加“会话导出-导入”步骤重启前将正在进行的会话上下文快照写入Redis或本地文件重启后先加载再对外服务。测试环境必须验证会话恢复数据完整率至少99.9%以上的会话能恢复。5.3 配额返还失效余额越用越少现象用户每次只使用少量流量但余额消耗速度远超实际流量费用。原因业务网元申请10MB配额实际用了2MB就主动释放会话剩余8MB应返还给用户。但OCS处理终止请求时没有执行配额返还逻辑直接把10MB全额扣掉。多次小流量会话累积后用户余额快速归零。解决会话终止时必须计算实际用量和已授权配额之差将未用部分返还账户。返还操作要记录流水便于对账。这个问题的隐蔽性在于出账系统看不到“返还记录”时不会报警只有用户投诉才暴露。5.4 心跳超时设置过长故障半小时才被发现现象OCS一侧发生进程假死进程还在但无法处理新请求业务网元在近半个小时内没有发现异常期间所有新呼叫失败。原因心跳间隔配置为90秒心跳重试次数也配置得比较宽松业务网元连续多次心跳失败后才判定OCS不可用。解决将心跳间隔调整为30秒重试次数设置为3次超过90秒无有效响应就主动终止受影响会话并隔离。注意心跳异常和网络抖动的区别心跳失败时先做一次快速重连重连成功不需要切换主备节点避免误切换引发更大的抖动。5.5 批价引擎返回负数费用话单直接卡住现象某国际漫游业务的话单批价结果出现负数话单推送到计费系统后被拦截整批话单无法入库。原因批价引擎里配置了错误的优惠参数——优惠封顶值取错了字段导致打折后费用比原始费用还高。负数费用被计费系统当作异常话单拦截。解决批价引擎必须增加费用上下界校验负数和超过设定阈值的话单直接标记异常不进正常话单流。同时批价规则下发前要有测试话单验证不能直接改生产配置。这类问题属于“规则配置事故”比代码故障更难排查因为话单本身没有报错。6. 验证与进阶用并发回放把OCS压出真实边界OCS上线前我会用并发回放脚本模拟业务网元的请求特征做压测比任何功能测试都能更快暴露问题。核心思路是用一组不同用量的用户会话并发请求OCS观察授权成功率和余额扣减准确性。import threading import time import random users [fuser_{i} for i in range(100)] balances {u: 1000 for u in users} # 每个用户1000个单位余额 def simulate_call(user, duration): # 第一次授权按时长申请配额 grant charge(user, duration) # 模拟使用中按比例消耗 time.sleep(0.01) # 提前释放部分配额模拟中途挂机 refund(user, grant - duration) threads [] for u in users: for _ in range(20): # 每个用户并发20个会话 d random.randint(10, 90) t threading.Thread(targetsimulate_call, args(u, d)) threads.append(t) start time.time() for t in threads: t.start() for t in threads: t.join() print(f并发执行完毕, 耗时: {time.time() - start:.2f}s)脚本里我故意让每个会话只消耗部分配额用来验证返还逻辑是否正确。压测后核对三件事余额总数守恒初始总量减去实际用量等于最终余额、没有负数余额、话单总费用与实际扣费一致。这三个核对都过了基本可以排除扣费逻辑的主要隐患。进阶验证要模拟异常场景断网重发、CCA超时、OCS进程重启。我会在压测中间随机杀掉OCS进程观察业务网元的恢复行为。能扛住这类故障而不产生错账的系统才算真正具备生产条件。另一个值得做的优化是连接池和线程池参数。OCS对外服务节点的线程数一般配置为CPU核心数的2到4倍连接池容量和线程数要匹配。生产环境常见故障是线程池被打满新请求进不了队列直接拒绝。排查时先看线程池活跃线程数曲线如果持续接近上限就该扩容或调参。最后提一个经验OCS系统的参数调整和代码发布一样要走完整的验证流程不能图省事直接在生产上改。配额参数、心跳间隔、有效期这些看似不起眼的数字在峰值流量下会被放大几十倍。我自己吃过一次亏——把心跳间隔调成60秒省那点信令开销结果OCS瞬时故障拖了整整十分钟才被发现比原来多挂了九分钟。后来所有涉及信令频率的参数都严格按压测数据来调不拍脑袋。希望帮到你。本文还有配套的精品资源点击获取
返回列表