余晟:从软件设计角度看携号转网
“携号转网”的事情已经谈了很久很久了,但看看我们四周,真正成功办理了携号转网的人少之又少。即便办理成功,似乎也有这样那样的问题。
那么,到底有什么问题?
网上有不少文章,看起来云山雾罩,语焉不详,实在难以令人满意。身处 IT 行业,凡事都应该摆事实讲道理,能说得清楚。虽然我没做过移动网络和运营商的相关业务,但查查资料还是能得到不少信息。“携号转网”之所以难办,似乎不只是运营商偷懒,还有技术上的难点。如果从软件设计的角度来看看携号转网这回事,应当会有许多新的发现。
携号转网的问题其实在世界上普遍存在,它有个专门的名称叫 Mobile Number Portability(MNP,移动号码迁移),其中主要涉及三个概念:原运营商(donor)、新运营商(recipient)、转网需求(number portability request,NPR)。对应的,它的技术方案也是现成的,主要有两种技术方案。
第一种是美国、欧洲以及国际通行的方案,叫 recipient-led。用户在转网时,先向新运营商提出申请,然后新运营商会联系原运营商,经过数据校验之后完成资料转移,将原号码“调过来”。
第二种是英国和印度用的方案,叫 donor-led。用户在转网时,先向原运营商提出申请,获得对应代码(英国叫 PAC,porting authorisation code,印度叫 UPC,unique porting code)之后转交给新运营商,新运营商据此完成转网。
第二种方案虽然看起来麻烦,但可以避免欺骗,因为原运营商有机会直接核实号码所有者的身份。但是,这也会导致不公平竞争,因为原运营商可能会借此机会故意拖延,想各种办法挽留用户。
完成了运营商迁移,还只是迁移了移动用户和运营商之间的关系,问题还没完。要知道,移动通讯不只是发生在运营商和用户之间,还发生在用户和用户之间。所以还要让呼入的电话(其他用户)知道,这个号码已经迁移到了新的运营商,这样呼入的电话、发来的短信才能正确抵达新运营商承载的用户。这种抵达的专业叫法是 routing,也就是“路由”。
路由的实现方式同样不止一种。国际和欧洲通行的方案是集中式号码库 CDB(Central Database)。简单说,它就像一张大表,详细记录了每个号码属于哪个运营商。相应的,每次发生携号转网,都必须在 CDB 中新增对应的记录。运营商会维护 CDB 的副本,在外呼电话或者外发短信时先查询它,然后直接联系对应号码的当前运营商。
根据 RFC3482,这个查询叫 ACQ(All Call Query)。合起来的整套方案就叫做 ACQ/CDB routing,美国用的也是这套方案,只是美国的管理机构叫 NPAC(Number Porting Administration Center)。
上面说的只是路由方式之一,英国的携号转网流程不同,路由方式同样独树一帜。英国没有采用 ACQ/CDB,即便用户已经携号转网,呼入的电话或发来的短信仍然会首先抵达原运营商,原运营商再将它转发给新运营商,这就是“间接路由”(indirect routing),它类似 Unix 中的符号链接。
这种方案避免了对集中式号码库的依赖,将携号转网的信息分散给运营商各自维护,问题第一是增加了无谓传输,第二是已经转网的用户仍然无法摆脱对原运营商的依赖——所谓“打断骨头连着筋”,如果原运营商故障或者倒闭,已经转网的用户仍然会受影响,这样用户可能很难理解。
现在来说国内移动运营方案。按照目前国内运营商公布的携号转网流程,用户携号转网时,必须先向当前运营商咨询资格,并获得授权码,然后才能到新运营商处办理转入手续。据此可以猜测出,国内应当采用的是 donor-led 方案。不过,因为国内的手机号可能还有捆绑套餐,转网时需要进行复杂的业务确认。通过询问办理过携号转网的伙伴得知,这个“复杂的业务确认”过程,恰恰是原运营商极力挽留用户的过程。
好玩的是,虽然转网是 donor-led 方案,而且国内之前似乎是没有集中式数据库的。这个事情也不难理解,很长时间里似乎只有三大运营商,各运营商自成一体,工信部更多的是行使管理职能,而没有基础系统的建设和维护。
携号转网对任何一家运营商来说,似乎都是“得不偿失”的。如果用户要转出去,相当于自己流失了用户;如果用户要转进来(按照目前看到的报道,携号转网的用户比例极低),又要额外增加系统建设,其实相当不划算。所以看来看去,还是工信部牵头最合适,也最有可能。
按照我看到的技术文档,现在我国正在采用类似 ACQ/CDB 的方案来完成转网用户的路由。具体来说,工信部会维护统一的中心携号转网数据库(CNPDB),还有管理全国 NP 业务中心 CSMS。联通、电信、移动三家会维护各自的 LNPDB 和 LSMS,数据与 CNPDB 保持一致。
中国联通携号系统架构。来源:张伟强,杜忠岩,李嵩泉,肖禄《移动号码携带核心网部署方案探讨》
用户每次外呼时,运营商先查询自己的 LNPDB,判断外呼号码对应的运营商(进行 NP 查询),然后将外呼信号做对应路由。整套技术方案看起来没有问题,但是之前并没有集中式的数据库,所以 CNPDB 的建设,以及整套流程的理顺都需要时间。
运营商呼叫流程。来源:张伟强,杜忠岩,李嵩泉,肖禄《移动号码携带核心网部署方案探讨》
那么目前,携号转网遇到的最大问题是什么呢?我觉得是短信的路由问题。这一点也被许多携号转网者的经历所证实——客服会告知,转网之后许多短信可能收不到了。为什么会这样呢?
目前大量的短信服务提供商判断用户所属的运营商时,完全是按照线下约定的规则。比如“130 开头是联通的,135-139 开头是移动的,189 开头是电信的”。短信服务商在收到短信数据包之后,会首先按照号段把任务分开,对接到不同的运营商通道进行发送。对于携号转网的用户,会被首先按照号码分配到原有的运营商通道,而该运营商已经不负责该用户了,短信就无法发送——当然反过来看,它也可以屏蔽大部分垃圾短信。
这个问题在充值时也存在。许多充值网站会根据用户输入的手机号来自动选择运营商,它看起来方便,但携号换网的用户也会出现错误。此外,在一些需要判断用户归属运营商的场合,也会有同样问题,如果你输入的手机号“看起来”是联通的,其实已经转到了移动,而系统又是根据号段来判断运营商的,就会报错,无法继续使用。
如果我们暂时放下对运营商的评价,单纯聚焦在携号转网的技术方案,就会发现这其实是开发中很常见的问题:资源迁移的要如何设计?
狭义的迁移很简单,只是 donor(原资源持有方)对 recipient(新资源持有方)做数据传输而已。但是安全的系统必须要解决一个问题:如何判断这种迁移真的可信的?
携号转网的 recipient led 方案中,recipient 可以直接发起资源迁移请求,donor 会信任这种请求,这看起来足够简单直接,但它有一个前提条件,运营商数量不多,成立门槛很高,追责也很方便。如果不具备这个前提条件,资源持有方很多,成立门槛也很低,那么直接由 recipient 向 donor 申请数据迁移就会面临安全问题。
这个问题要怎么解决?我们可以想想如今网上流行的 OAuth 是怎么做的?当 recipient 向 donor 发出申请时,多了一道“donor 与用户确认”的手续,因为有用户的直接参与,就解决了“信任”的问题。
当然办法不止一种,也可以借鉴 donor led 的方案,由用户先向 donor 获得许可及验证码,再完成迁移——实际上,域名迁移正是采用的这种方案,它解决了“众多服务商”环境下建立信任的问题。
但是只做到这一步,并不算资源迁移方案。称职的工程师一定不能只看到眼前的这一点,还必须做完整的方案,保证迁移完成之后,所有相关的业务都保持平稳顺利,不受影响。你看了上面的 ACQ/CDB 方案,大概会觉得“这不是显然的事”嘛,但现实未必如此,这是有无数痛苦教训的。
许多年前我开发过电商的物流系统。有一天,业务的人问:“为了节省成本,同一个收件人的两件货品,是不是可以合并发货?” 负责开发的程序员一听:“这个没问题呀,这个简单,我马上就可以做好”。
没两天真的就开发完成了,拣货、打包、出仓、挂号分配和录入,确实都没有问题,于是顺利上线。刚开始一切正常,他俩正打算为这个”透明“的方案邀功,前方传来大量投诉,相关人员叫苦不迭。
一问才发现,这个工程师根本没考虑异常情况。两件货可以拼单,那么三件货,四件货呢?合并的最小单位到底是货物还是订单?如果用户要发票,到底是开一张票还是两张票?和供应商结算的时候,运费怎么分摊?最麻烦的是逆向流程——如果用户要针对其中某件商品退款或者退货,到底要如何操作?费用又如何计算?
轻率决定的后果就是,一定要踩了大坑才知道,“合并发货”真不是看上去那么简单,远比想象的要麻烦得多。它也不是程序员或者小产品经理能搞定的,还必须加上物流、财务等等一大圈人。程序员想当然“没问题”,造成了很多问题,给所有人都挖了个大坑……
回到数据迁移问题,我见过好些数据迁移方案,完全就是想当然,“我知道这里数据迁走了”,拍拍脑袋就做了,拍拍屁股就迁了。设计者根本不考虑其他人,完全没想过“其他人或业务知不知道数据迁走了”,也不关心其他人或其它业务后来会怎么办。
在“携号转网”的方案里,要解决这个问题,就必须保持数据的同步更新。一种方案是提供集中式记录(ACQ/CDB)方案,这种方案职责清晰,能保持通话路径最短,但是对中心节点的稳定性、响应速度、复杂能力都提出了很高的要求。
另一种 indirect routing 在某种意义上可以称为“分布式”方案,即必须通过原服务商来中转,这时候转网信息碎片其实是由运营商各自维护的。这种方案不需要花大力气建设中心节点,缺点则是职责不清晰,多了不必要的中转,已迁移用户仍然会受原运营商服务质量的影响。
中转还会带来其它问题:如果用户多次迁移就会形成“中转链条”,链条一长,不但影响效率,排查问题也异常麻烦。这还没完,如果设计不当还可能形成环路……
最后我们不妨再深挖一点——所谓“携号转网”,真正跳出来看,核心就是一个函数问题。
函数最简单的方式是 f(x) = y,这大家都知道。对携号转网来说,关键也是根据手机号查询运营商,它可以看作 f(x) = y,其中 x 就是“具体的手机号”,y 就是“运营商”。只要掌握了这个信息,其它都好办。
虽然大家都默认有 f(x) = y 这个函数,但许多人也知道函数f的内部到底是怎么做的,而且这个函数并没有官方版本,所以基本所有人都自己实现了一遍:130~133 号段是联通,135~139 号段是移动,189 号段是电信……
同时我们也知道,软件设计中提倡“暴露接口而不暴露实现”。为什么呢?因为接口是关于抽象行为的定义,比如“输入手机号,得到运营商”就是抽象行为,它包装了 f(x) = y,至于哪个手机号(x)对到哪个运营商(y),规则可能不停变化,甚至有一些特例。不过这都不要紧,因为外部不必知道细节,只要放心调用这个接口既可以,外部原有业务流程照跑。相反,如果暴露的是实现,你就需要在各处不停更新号段规则,如果遇上携号转网这种特例,维护难度更是上了几层楼。
那么“根据手机号查询运营商”的功能,为什么暴露的是实现而不是接口呢?这大概有历史原因,缺乏顶层设计,一开始没有权威公用接口,实现这种接口要承受巨大的负载,技术上有挑战……
所以,早期许多技术问题,确实都是采用“线下共识”来解决的。比如早期不少电商的订单号,上面就承载了很多信息,单纯看订单号就可以识别出下单日期、所属仓库、商品种类等等。
不过现在,随着软件的复杂度越来越高,随时在线变得越来越普遍,这种“线下共识”已经越来越多地被替代了。不信你可以看看各大电商的订单号,早年还可以从中看出下单日期、当日序号等等,但是现在,已经基本看不出任何编号规律了。但是手机号处理起来很麻烦,手机号绑定的线下规则很多,不只有判断运营商,还有归属地……