简介这份文档面向电信运营商业务支撑系统的运营管理与设计人员以及希望理解在线计费机制的技术学习者系统讲解OCS在线计费系统的计费原理与实现路径帮助读者厘清实时计费从概念到落地的关键环节。资源包内共1个doc文件约1.87MB内容按序篇、扫盲篇、计费流程等模块组织涵盖计费系统从离线批处理向实时处理演进的背景TAG、业务用量、费用项、产品、订购、入帐关系、模板、策略、事件与子事件等名词解释以及预处理、批价等流程拆解并延伸至计费引擎、数据库、消息中间件等实现组件与安全机制。已有297人学习适合作为理解OCS工作流程、优化计费流程与系统设计的参考材料。1. 一次通话还没挂断钱就已经扣完了OCS 到底在背后做了什么你打完一通电话挂机短信还没到余额已经变了。这个“实时”背后并不是简单的“通话结束扣钱”而是一套在线计费系统在通话进行中反复做预算、授权、再预算的循环。OCSOnline 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 的计费原理从概念落到可操作的细节上。希望帮到你。本文还有配套的精品资源点击获取