1.介绍
虚拟号是一种基于互联网技术的电话号码服务。它通过将电话号码与用户的手机或其他通信设备绑定,实现了在不同设备上接听和拨打电话的便利,保护用户号码隐私,记录通话中内容及信息等,虚拟号的主要特点包括以下几个方面:
- 隐私保护:虚拟号可以作为一个中间号码,将用户的真实电话号码隐藏起来。当用户需要提供电话号码时,可以使用虚拟号码代替,从而保护个人隐私。
- 灵活性:虚拟号可以根据用户的需求进行设置和调整。用户可以选择不同的地区号码,甚至可以选择国际号码,使得自己的电话号码更具灵活性和适应性。
- 多功能性:虚拟号不仅可以用于接听和拨打电话,还可以提供一系列增值服务。例如,用户可以设置来电转接、语音信箱、短信转发等功能,满足不同的通信需求。
- 企业应用:虚拟号在企业通信中有着广泛的应用。企业可以通过虚拟号为客户提供更好的服务体验,例如设置客服热线、呼叫中心等,提高客户满意度和业务效率。
总之,虚拟号作为一种创新的电话号码服务,为用户提供了更加灵活、安全和便捷的通信方式。它在个人和企业通信中都有着广泛的应用场景。
2.实践与应用
在门店业务的场景下,需要店员/用户拨打号码进行售卖/购买等信息咨询,通过第三方服务提供的虚拟号的方式对双方手机号绑定,通过虚拟号来实现接听/拨打,有效解决了用户号码隐私保护的问题,以及帮助门店进行管理店员话术监控与管理数据分析等。
二手交易虚拟号绑定图
2.1 名称解释
- A:用户手机号
- X:虚拟号/中间户
- B:店员手机号
- 第三方:虚拟号提供服务方
- 运营商:电信、联通、移动各大平台
2.2 虚拟号的 XB 模式
XB 模式:指可以将 B(店员手机号)与 X(虚拟号)进行绑定,后续用户直接拨打 X(虚拟号)都是由当前店员 B 手机号进行接听。
2.3 虚拟号的 AXB 模式
AXB 模式:指用户手机号 A 与店员手机号 B,同时绑定虚拟号码 X,后续 A 拨打 X号码,B 会看到 X 号码打过来;同理 B 拨打 X号码,A 会看到 X 号码打过来。
2.4 虚拟号呼叫流程图
呼叫流程图
3. 与第三方交互的版本演进
与第三方进行交互的过程中,随着时间的推移,业务不断的发展,服务方与第三方的交互流程也会不断的改版。下面会说明历史版本与第三方交互中的一些问题。
3.1 初始版本绑定流程
图片
在最初的版本中,交互流程相对简单,只包含基本的绑定和解绑操作。在正常情况下,这种简单的交互是没有问题的。然而,在线上出现最多的情况就是超时、超时、还是超时,因为和第三方交互都是基于HTTP方式进行交互,对于第三方的响应时间是不可控的。一旦响应超过了APP的请求时间,就会熔断请求,导致请求失败。
3.2 最新版本交互流程
为了保证服务的可靠性,在最新版本中做了以下优化措施:
图片
优化概念模型图
- 超时处理机制:提供一个查询绑定状态的接口,APP 不断轮询获取绑定状态。前置校验代码同步,将与第三方绑定/解绑接口进行异步化处理。
//开启异步线程,执行重试方法,进行与第三方接口绑定。
ThreadUtil.executor.submit(() -> {
try {
log.info("act=AppOperationHandler type=retryBindXb_retryBindAxb_start");
pstnRetryService.retryBindXb(appOpnParam);
} catch (Exception e) {
log.error("act=AppOperationHandler type=retryBindXb_retryBindAxb_fail", e);
}
});
- 并发处理机制:基于乐观锁的方式加锁实现避免并发问题。将绑定状态添加了一个操作中的中间态。
NOT_BIND(1, "未绑定"),
BIND(2, "已绑定"),
OPERATION(3, "操作中");
- 异常处理机制:在绑定过程中,及时捕获并处理异常情况,例如调用第三方接口超时、服务端错误异常等等,通过重试方式来保证操作的完整性和一致性。重试基于注解AOP拦截,通过MQ消息的方式实现重试。
@Override
@ZZMQRetry(errorHandler = "onErrorMsgPcBindXb", firstSyncCall = true)
public void retryBindXb(PstnBindParam pstnBindParam) {
//调用第三方接口进行绑定
String bindId = bindVirtualNumber(pstnBindParam);
Boolean executeResult = transactionTemplate.execute(status -> {
try {
//创建绑定记录
pstnBindRecordService.insertBindRecord(bindId, pstnBindParam);
//状态流转至已绑定
return this.xbOpnStateToBind(pstnBindParam.getAssignVirtualNum());
} catch (Exception e) {
status.setRollbackOnly();
return Boolean.FALSE;
}
});
if (Objects.isNull(executeResult) || !executeResult) {
throw new BusinessException("修改状态异常进行重试");
}
}
- 告警处理机制:在发生严重异常、或者重试到一定次数之后任然没有成功,发送告警进行通知。
public void onErrorMsgPcBindXb(PstnBindParam param) {
int retryCount = RetryContext.getRetryCount();
log.info("act=PcOperationHandler type=onErrorMsgPcBindXb retryCount={} ", retryCount);
if (retryCount >= RETRY_MAX_NUM) {
WxMsgUtil.sendMsg(GROUP, "绑定XB重试"+ retryCount + "次,仍未成功请排查,虚拟号:"+ param.getVirtualNum());
}
}
通过以上优化措施,可以提高交互流程的稳定性和可靠性,减少业务方与服务方的异常、超时以及绑定状态不一致的情况,从而确保虚拟号的正常使用,提升系统的稳定性和店员使用体验。
axb绑定流程
以上优化后的店员拨打电话AXB绑定的流程,在与第三方交互绑定时,开启了一个新的线程进行处理任务,并且在这个线程中有出现错误或异常时就会重新回到线程入口重新执行。通过异步和循环查询状态的方式解决了与第三方交互耗时高的问题,有效提高了流程以及使用体验。同时,通过重试的方式保证了流程的一致性和完整性,避免了因网络波动或其他异常情况导致的状态数据不一致问题。此外,通过监控告警可以实时监控系统应用的运行状态,并在出现异常或故障时及时发出警报通知,帮助店员及时处理问题,提高整个流程的可靠性和稳定性。
这个流程只是业务中的其中一环,在线上使用还有更复杂的场景比如同时绑定xb、axb两个场景,怎么能保证都能成功、怎么能保证不超时、怎么能保证状态都一致?
确保流程成功、避免超时和保持状态一致,可以通过以下步骤来优化:
- 增加状态值:在库表中添加两个状态值,一个用于XB的绑定状态,另一个用于AXB的绑定状态。
- 使用乐观锁:在业务检查完成后,以乐观锁的方式同时更新这两个状态值。
- 创建线程:创建两个线程来处理XB和AXB的绑定流程,这样可以保证两个流程相互不影响。
- 状态流转:在流程结束后,将这两个状态值流转至已绑定的状态。
- 轮询接口:提供一个单独获取绑定状态的接口,供端轮询查询使用,优化店员使用体验。
通过以上步骤,可以有效地优化流程,确保其成功、避免超时并保持状态一致。
4.上线方案
在进行版本优化时,尽量保持入口不变,底层改变。这样可以使得灰度测试更加容易进行。通过保持入口不变,可以确保用户在升级版本时不会遇到任何新的障碍或问题。
/**
* 是否为灰度虚拟号码
*
* @param grayVirtualNumber
* @return
*/
public boolean isGrayVirtualNumber(String grayVirtualNumber) {
return flag || grayVirtualNumberList.contains(grayVirtualNumber);
}
根据阿波罗配置,我们将某个城市的虚拟号码设置为线上灰度测试。其他虚拟号码仍将使用老版本的服务接口。在测试没有问题后,将直接开启全量即可。
5.重试组件
我们对添加了@ZZMQRetry的函数增加了AOP拦截,请求将不会真正同步执行重试函数,而是发送一条MQ消息;同时会自动start一个消费组,消费函数为@ZZMQRetry函数。
图片
6.总结
在与第三方服务的接口交互中,不可控因素是不可避免的。为了确保系统的稳定性和可靠性,重要的是采取预防措施。总体来说,就是要设计并实施异常处理、超时设置、重试机制、断路器以及监控与报警等措施。这些措施可以有效地保护系统,避免因不可控因素导致的故障和不稳定。
关于作者
徐鑫辉,转转门店技术部后端研发工程师