简介:这份文档面向电信运营商业务支撑系统的运营管理与设计人员,以及希望理解在线计费机制的技术学习者,系统讲解OCS(在线计费系统)的计费原理与实现路径,帮助读者厘清实时计费从概念到落地的关键环节。资源包内共1个doc文件,约1.87MB,内容按序篇、扫盲篇、计费流程等模块组织,涵盖计费系统从离线批处理向实时处理演进的背景,TAG、业务用量、费用项、产品、订购、入帐关系、模板、策略、事件与子事件等名词解释,以及预处理、批价等流程拆解,并延伸至计费引擎、数据库、消息中间件等实现组件与安全机制。已有297人学习,适合作为理解OCS工作流程、优化计费流程与系统设计的参考材料。
1. 一次通话还没挂断,钱就已经扣完了:OCS 到底在背后做了什么
你打完一通电话,挂机短信还没到,余额已经变了。这个“实时”背后并不是简单的“通话结束扣钱”,而是一套在线计费系统在通话进行中反复做预算、授权、再预算的循环。OCS(Online Charging System,在线计费系统)要解决的核心问题只有一个:在用户还没消费完之前,就判断他还能消费多少,并且保证最终扣的钱和实际用量对得上。这件事听起来简单,做起来却牵扯到业务用量怎么抽象、费用项怎么组织、定价计划怎么复用、预付费和后付费怎么统一。搞懂 OCS 的计费原理,本质上是在搞懂一套“先授权后结算”的信用控制机制为什么能在线跑起来,以及它的抽象层次是怎么一步步演进出来的。这份资料把计费系统从两张表到 OCS 的演进拆得很细,下面按机制层次往下推。
2. 计费系统的四次演进:为什么费率表最后一定会失控
2.1 从两张表到号码分析:维度爆炸是怎么发生的
最早的计费系统只有两张表:用户表存余额,费率表根据主被叫归属地、主被叫拜访地四个号码头查费率。这个设计在只有一个号段、一个地区的时候完全够用。问题出在号段增长之后:一个地区会有多个号段,四个维度组合在一起,费率表的记录数不是线性增长,而是按维度乘积膨胀。更麻烦的是局方调整费率时,需要人工分析每个受影响地区的每一条记录,改错一条就是计费事故。
常见的实现思路是把“查费率”拆成三步。第一步号码分析,把主被叫的归属地、拜访地号码头归一成地区号,一个地区的多个号段在号码分析表里收敛成一条记录。第二步计费矩阵,把一次呼叫拆成基本费、漫游费、长途费三种消费类别,每个类别对应一个费率索引,也就是 ChargeClass,根据地区号查出该用哪组费率。第三步才是费率表,只描述费率本身,被计费矩阵引用。这样改费率只动费率表几条记录,加号段只动号码分析表,可维护性从“改一处牵全身”变成“各管各的”。
这个拆分的关键在于把“维度”和“价格”解耦。号码分析负责维度归一,计费矩阵负责消费分类,费率表负责价格数值。三层各司其职,任何一层的变更不会污染另外两层。我一般会这么理解:这不是简单的表拆分,而是把计费查询从“一次多维匹配”改成了“三次单维查找”,用查询次数换维护性。
2.2 帐户抽象与消费类别扩展:专款专销为什么需要独立帐户
用户数增长之后,运营商发现单纯降价拉不动消费,于是开始做套餐:50 元买 500 分钟长途、10 元买 200 分钟市话,长途时长只能打长途,市话时长只能打市话,用完再走基本余额。这就引出一个新问题:余额不再是单一数字,而是分类型、分用途的多个池子。
实现上的做法是把余额从用户表里抽出来,单独建帐户表,一个用户可以挂多个帐户,每个帐户有类型和余额。同时把 ChargeClass 从纯粹的费率索引扩展成“消费类别”,额外定义这个类别只能在哪些帐户上扣、扣减优先级是什么、费率是多少。比如长途呼叫产生的消费类别,只能从长途免费时长、赠送金额、基本金额里按优先级依次扣。
这里的设计动机是:消费类别是“怎么算钱”,帐户是“从哪扣钱”,两者通过入帐关系连接。入帐关系是一张映射表,说明某类费用项应该落到哪个帐户。把“算”和“扣”分开之后,专款专销不需要改计费逻辑,只需要配一条入帐关系。费率表也在这个阶段从单一费率扩展成分档费率,头 3 分钟一个价、后 10 分钟一个价,越打越便宜就是分档费率加累计用量判断。
2.3 OCS 时代的五个抽象:业务用量、计费事件、模板、定价计划、订购
到了多业务场景,前面那套开始吃力。一次计费请求可能不是“通话 5 分钟”,而是“短信 3 条”“流量 200M”“直接扣款 2 元”,用 ChargeClass 加时长已经表达不了。套餐推出频率越来越快,改 ChargeClass 既不直观也容易影响存量业务。预付费和后付费的计费逻辑被拆在两套系统里,同一个优惠活动要配两遍。
OCS 的解法是五个抽象层层叠加。业务用量替代 ChargeClass,表达“消费的是什么业务、量是多少”,比如 20 分钟长途费、2M 流量,新增消费类型只是新增一个用量定义。计费事件把每种计费请求抽象成事件,事件包含若干计费要素,每个要素有自己的求解过程,新增请求种类是加代码而不是改老代码,修改是线性的。模板把可复用的计费能力封装成模块,赠送模板只管赠送内容参数,呼叫计费模板只管费率和适配条件。定价计划是一套完整的费用计算体系包,通过调用多个模板组合出套餐,比如 20 元套餐同时调赠送模板和呼叫计费模板。订购决定用户拥有哪些定价计划,只有订购了才享受对应费率。
这五层的关系是:事件触发计费,事件产生业务用量,定价计划里的模板对业务用量做批价,批价输出费用项,费用项按入帐关系落到帐户。新增一个优惠活动,只需要新增一个定价计划并让测试用户订购,不动存量数据和代码,可测试性和兼容性都保住了。
2.4 预付费与后付费的统一:请求用量、授权用量、已使用用量
预付费和后付费的本质差别在风险控制时机:预付费先扣钱后消费,没有欠费风险;后付费先消费后出账,需要账单、邮寄、催缴。OCS 把计费动作统一,把差异推到用量类型上。
预付费用户使用前,业务控制系统发起计费请求,传入“请求业务用量”,系统根据帐户余额算出“授权业务用量”返回,业务侧按授权量控制消费。后付费用户使用完成后,话单里的实际用量作为“已使用业务用量”送进系统批价入帐。批价入帐这段逻辑对两者完全一致,所以定价计划对预付费和后付费都适用。预付费批价后出话单即可,后付费再走账单和催缴那套原有流程。
这个统一还顺带解决了一卡多用。预付费用户有 10 元,A 业务请求 5 分钟需要 1 元,系统授权后还剩 9 元可用,B 业务同时请求 200M 流量也能被响应,不需要等 A 业务结束。因为授权是在请求时刻基于当前余额算的,多个业务可以并发占用同一份余额的授权额度,只要总额不超。
3. 批价流程拆解:从事件到费用项链表到底经过了几层
3.1 预处理、批价、入帐、后处理四段的分工
一次完整的计费流程分四段。预处理负责把原始计费请求翻译成系统内部能处理的形态,包括解析事件类型、提取计费要素、做必要的格式转换和校验。批价是核心,根据业务用量和定价计划算出费用项。入帐根据入帐关系把费用项落到具体帐户上完成扣减。后处理做收尾,比如生成话单、触发通知、更新累计项。
这四段里,预处理和后处理对初学者是黑匣子,资料里也明确说封装细节,只需要知道输入输出。真正要理解的是批价,因为费率、优惠、套餐的所有逻辑都压在这一段。批价的输入是业务用量集合和用户订购的定价计划,输出是费用项链表。费用项链表这个数据结构很关键:每条记录包含业务用量标识、用量数值、金额、费用项类型,链表顺序对应消费明细的顺序。
3.2 业务识别:一次呼叫怎么被拆成多个业务用量
批价第一步是业务识别,判断这次计费请求涉及哪些业务用量。以主叫漫游拨打长途 5 分钟为例,系统要判断主叫是否漫游、被叫是否长途,然后生成基本费时长、长途费时长、漫游费时长三个业务用量,各 300 秒。如果主叫没有漫游,漫游费时长为 0。这些用量名称和 TAG 是设计时规划好的,比如基本费时长对应 TAG 2003,长途费时长对应 2005,漫游费时长对应 2004。
业务识别的本质是把“一次呼叫”这个复合事件拆成“多个可独立计价的用量”。为什么要拆?因为基本费、长途费、漫游费可能走不同的费率、落不同的帐户、享受不同的优惠。拆成独立用量之后,每个用量可以单独匹配定价计划里的模板,互不干扰。这跟前面说的消费类别扩展是一脉相承的,只是粒度更细、表达更通用。
3.3 批价处理与费用项生成:费率匹配和金额计算怎么落地
业务用量确定后,批价按用量逐个匹配定价计划。匹配的依据是定价计划里模板的适配条件,比如“省内漫游”这个条件只对漫游费用量生效。匹配上之后,模板用传入的费率参数对用量做计算。假设市话费率 0.1 元/分钟、长途 0.2 元/分钟、漫游 0.3 元/分钟,5 分钟就是 300 秒,三个用量分别算出 50 分、100 分、150 分,生成三条费用项:本地通话费用项、长途通话费用项、漫游通话费用项。
费用项链表的结构可以粗略表示为下面这样,字段名按资料里的描述归纳:
# 费用项节点:批价输出的消费明细单元 class ChargeItem: def __init__(self, usage_tag, usage_value, amount, item_type): self.usage_tag = usage_tag # 业务用量标识,如 2003 基本费时长 self.usage_value = usage_value # 用量数值,如 300 秒 self.amount = amount # 金额,单位分 self.item_type = item_type # 费用项类型,如 1 本地通话 self.next = None # 链表指针,串起多条消费明细 # 批价主循环的简化逻辑 def rating(usages, pricing_plan): head = None for usage in usages: template = pricing_plan.match(usage) # 按适配条件匹配模板 if template is None: continue # 无匹配则不产生费用项 amount = template.calculate(usage) # 模板内按费率算金额 node = ChargeItem(usage.tag, usage.value, amount, template.item_type) node.next = head # 头插或尾插,形成消费清单 head = node return head这段逻辑里,match负责适配条件判断,calculate负责费率计算,两者都在模板内部,批价主循环只做遍历和组装。这样新增一种计费能力只需要新增模板,主循环不动。usage_tag和item_type的分离也值得注意:用量标识说明消费了什么,费用项类型说明这笔钱归到哪类账目,入帐时看的是后者。
3.4 入帐关系:费用项怎么找到该扣的帐户
批价输出费用项之后,入帐按入帐关系把每条费用项映射到帐户。入帐关系是一张配置,说明某类费用项按什么优先级从哪些帐户扣。比如长途通话费用项,先扣长途免费时长帐户,扣完再扣赠送金额帐户,最后扣基本余额帐户。每个帐户扣减时还要判断余额是否足够,不足的部分按优先级继续往下找。
这里有个容易忽略的点:入帐不是简单扣一个总数,而是按费用项逐条处理,每条费用项独立走自己的入帐关系。这意味着一次呼叫产生的本地、长途、漫游三条费用项,可能分别落到三个不同的帐户组合上。这种设计让专款专销和分类型余额能够精确执行,代价是入帐阶段要做多次帐户查找和扣减,对性能有要求。常见的优化是把同一用户的帐户和入帐关系做缓存,批价和入帐之间尽量少查库。
4. 充值、月结与免费资源:那些不在呼叫主流程里的计费能力
4.1 充值流程的预处理与模板复用
充值看起来是独立流程,但它复用了和呼叫计费同一套模板机制。充值请求进来先做预处理,解析充值金额、充值类型、用户标识,然后匹配充值相关的定价计划。充 100 送 40 分几个月赠送这种活动,本质是充值模板加赠送模板的组合:充值模板处理本金入帐,赠送模板按分期规则生成后续的赠送计划。
模板复用的价值在这里体现得很明显。赠送能力被封装成模板之后,呼叫套餐里的赠送、充值活动的赠送、月结的回赠,调的都是同一个模板,只是参数不同。参数包括赠送内容、赠送时机、分期方式。这样新增一种赠送活动不需要写新代码,配参数就行。资料里把模板和策略并列解释,我理解模板是“计费能力的实现”,策略是“模板被调用时的参数和条件组合”,两者配合才能表达一个完整的优惠规则。
4.2 月结与回赠:定期事务怎么和实时计费共用一套帐户
月结是定期事务,每天后台跑,处理月租扣取、套餐定期赠送、充值优惠分期到账。它和实时计费共用帐户表和入帐关系,区别在于触发时机和用量来源。实时计费的用量来自呼叫事件,月结的用量来自时间周期和预配的赠送计划。
月结流程里,系统扫描到期的赠送计划,生成对应的业务用量,走批价和入帐,把赠送金额或时长落到用户帐户。回赠是月结的一个子场景,比如充值送的分月到账,每个月结日把当月份额入帐。因为走的是同一套批价入帐逻辑,月结赠送和呼叫计费在帐户层面完全一致,不会出现两套余额对不上的情况。这也是 OCS 把 iCharge 和 iUser-bill 融合之后的好处:定期事务和实时计费不再是两个系统,而是同一套计费能力的不同触发方式。
4.3 免费资源与累计项:套餐里的“先用免费额度”怎么实现
免费资源处理是套餐计费里的常见需求:用户订购了含 300 分钟市话的套餐,打电话时先扣免费时长,用完再按费率扣钱。实现上,免费时长存在特定类型的帐户里,批价时业务用量先匹配免费资源模板,模板判断对应帐户是否有余额,有则生成金额为 0 的费用项并扣减免费时长,没有则走正常费率模板。
累计项处理解决的是“越打越便宜”和“消费累计”这类跨呼叫的状态问题。累计项记录用户在某段时间内的累计用量或累计消费,批价时读取累计项判断当前处于哪个分档。比如头 3 分钟 0.4 元/分钟、后 10 分钟 0.2 元/分钟,批价时要先查累计通话时长,确定本次用量落在哪一档,再按对应费率计算。累计项的更新发生在批价或后处理阶段,要保证和费用项生成的一致性,否则会出现费率跳档错误。常见的坑是累计项更新和批价不在同一个事务里,并发呼叫时读到旧值,这个在排查章节会展开。
5. 避坑与排查:OCS 计费里那些容易翻车的地方
5.1 费用项金额对但帐户扣错:入帐关系配反了
现象是批价输出的费用项金额正确,但用户的基本余额没动,反而赠送帐户被多扣了。原因通常是入帐关系里费用项类型和帐户类型的映射配反,或者扣减优先级顺序写错。排查时先看批价输出的费用项链表,确认 item_type 正确,再查入帐关系表里该 item_type 对应的帐户列表和优先级,逐条比对扣减记录。解决方法是修正入帐关系配置,并对已受影响的用户做余额冲正。
5.2 免费时长没用完就开始扣钱:免费资源模板的适配条件没命中
用户套餐里有 300 分钟市话,但第一通电话就扣了基本余额。原因多半是免费资源模板的适配条件没匹配上,比如业务用量标识和模板条件里的用量类型不一致,或者免费帐户的入帐关系优先级低于基本余额。排查时看批价日志里该次呼叫匹配到了哪些模板,如果免费资源模板没出现在匹配列表里,就是适配条件问题;如果匹配了但没扣免费帐户,就是入帐关系优先级问题。解决方法是修正模板适配条件或调整入帐优先级。
5.3 并发呼叫下余额被扣穿:授权和扣减之间的时间窗
预付费用户同时发起两路呼叫,每路都请求授权,系统基于同一份余额分别算出授权量,结果两路都授权成功,实际消费超过余额。这是授权阶段的经典并发问题。原因在于授权时读余额和扣减时读余额之间有时间窗,两个请求都读到了扣减前的余额。常见的实现思路是在授权时做额度预占,把授权量对应的金额先冻结,扣减时用冻结额度结算,释放多余部分。排查时看授权记录和扣减记录的时间戳,确认是否存在同一余额被多次授权。解决方法是引入预占机制或对用户级余额加锁。
5.4 月结赠送没到账:赠送计划的到期判断用了错误时区
月结跑完之后,部分用户的充值赠送没有入帐。原因可能是赠送计划的到期时间按 UTC 存储,月结任务按本地时间判断到期,跨时区时差导致部分计划被跳过。排查时对比赠送计划的到期时间字段和月结任务的判断条件,看时间基准是否一致。解决方法是统一时间基准,或者在月结任务里显式做时区转换。这类问题在跨地区部署时特别容易翻车,血泪经验是时间字段一定要标注时区。
5.5 累计项跳档错误:批价和累计更新不在同一事务
越打越便宜的套餐,用户反映费率跳档不对,有时提前跳档有时延迟跳档。原因是累计项更新和批价计算不在同一个事务里,并发呼叫时批价读到的累计值是旧的,算完费率后另一个呼叫又更新了累计项,导致分档判断错位。排查时看同一用户短时间内的多次批价记录,对比每次读到的累计值和实际累计值。解决方法是将累计项读取、批价、累计项更新放在同一事务或同一把用户级锁内,保证读到的累计值和更新之间没有其他写入。
6. 抽象层次的取舍:为什么 OCS 选择用模板和定价计划换灵活性
回头看这四次演进,每一次都是在解决上一阶段的抽象泄漏。两张表阶段,费率和维度混在一起,加号段就爆炸;号码分析加计费矩阵阶段,维度和价格分开了,但消费类型和帐户还是硬编码;帐户抽象阶段,专款专销能做了,但多业务和多计费单位表达不了;OCS 阶段用业务用量、计费事件、模板、定价计划、订购五层抽象,把“算什么”“怎么算”“从哪扣”“谁能享受”彻底拆开。
这个拆分的代价是抽象层数变多,初学者容易在模板和定价计划之间迷路。资料里也承认 OCS 概念抽象、细节暴露多、版本里揉成一团。但从设计取舍看,这是用前期理解成本换后期的扩展性:新增业务类型加事件,新增计费能力加模板,新增优惠活动加定价计划,新增用户群加订购,每一层的变化都被限制在自己的边界内。对比智能网时代改 ChargeClass 牵一发动全身,OCS 的线性修改特性是它能在多业务场景下站住脚的根本原因。
验证这套机制是否理解到位,有个简单方法:拿一个具体套餐,比如“20 元套餐赠送 300 分钟市话、200 条网内短信、省内漫游 0.1 元/分钟”,从订购开始往下推,看它调了哪些模板、生成了哪些业务用量、批价输出哪些费用项、按什么入帐关系落到哪些帐户。能把这整条链路推通,说明抽象层次和实现细节都对上了。我一般排查计费问题时也强制走一遍这个链路,从费用项反推到业务用量再反推到事件和定价计划,比直接看代码快得多。这份资料把扫盲、入门、提高三篇的层次铺得很清楚,配合实际配置数据看,能把 OCS 的计费原理从概念落到可操作的细节上。希望帮到你。
本文还有配套的精品资源,点击获取