最近在对系统做整合优化,记录一下其中一个优化的小思路。
逻辑是这样玩的,系统有坐席发起打电话,打电话的过程当中会产生许多的事件,比如振铃事件,用户接听事件,用户挂断事件,老系统的实现方式是每产生一个事件就从缓存中取出之前的值然后加工后覆盖更新,这样做最直观存在的问题就是如果某两个事件刚好同时到达同时取出了缓存中的数据进行加工后更新的会覆盖先更新的,笔者之前的从业经验是将事件异步发往消息队列交给Spark等大数据组件实时计算做聚合处理,但是团队也在推系统多租户,做多租户就必然涉及到后续的私有化部署,引入大数据组件相对会增加系统复杂度和私有化部署复难度。
优化思路是仍然使用缓存的方案实现,但是缓存的数据结构改为一个列表,每次收到一个事件,比如振铃事件,用户接听事件,用户挂断事件就将相关的元数据进行加单加工,然后将事件放到这通通话相关的列表当中。这样做还有一个好处,比如在呼叫中心行业当中有一个早期媒体识别的概念,简单解释就是你打电话给别人的时候你会听到例如嘟嘟嘟,你拨打的电话正在通话中这些声音,一般系统会对这些声音进行识别,然后如果系统检测到你拨打的电话已停机这种媒体就可以主动提前挂机,但是识别本身不是准确的,可能会产生多次识别,这样你只需要将多个事件推入这个列表中,最后根据业务需求做任意的扩展实现,数据结构上相对优雅。
下面是事件的代码例子
public class ManualCallDialingEvent extends CallInfoEvent<ManualCallDialingEvent.EventData> {
public ManualCallDialingEvent(String callId) {
super("ManualCallDialingEvent", callId, null);
}
@Builder
@Data
public static class EventData {
private String fsHost;
private String gateway;
private Integer seatAnswerStatus;
private Long seatAnswerTime;
}
}
下面是对事件进行简单加工的一个例子
protected void handleManualCallDialingEvent(CallEvent callEvent) {
ManualCallDialingEvent.EventData eventData = ManualCallDialingEvent.EventData
.builder()
.seatAnswerStatus(AnswerStatusEnum.CONNECTED.getCode())
.seatAnswerTime(System.currentTimeMillis())
.fsHost(callEvent.getData().getFsIp())
.gateway(callEvent.getData().getGateway())
.build();
ManualCallDialingEvent manualCallDialingEvent = new ManualCallDialingEvent(callEvent.getData().getCallId());
manualCallDialingEvent.setEventData(eventData);
callInfoEventProcessor.handleManualCallDialingEvent(manualCallDialingEvent);
}
上面这个方法中最后一行代码负责将事件推入列表。
下面是结束节点事件聚合的简单示例
List<String> caches = jedis.lrange(CALL_INFO_EVENT_PROCESSOR_CACHE_PREFIX + contactHangupEvent.getCallId(), 0, -1);
JSONObject finalJsonObject = new JSONObject();
finalJsonObject.putAll(JSON.parseObject(JSON.toJSONString(contactHangupEvent.getEventData())));
if (CollectionUtils.isNotEmpty(caches)) {
for (String cache : caches) {
JSONObject jsonObject = JSON.parseObject(cache);
String eventData = jsonObject.getString("eventData");
if (Objects.nonNull(eventData)) {
finalJsonObject.putAll(JSON.parseObject(eventData));
}
}
}
// 后续将finalJsonObject再转换成业务对象
顺便提一下,这里主要的就是如何判断结束节点触发聚合,还是以打电话为例,就是挂机事件,挂机之后这通通话就结束了。既然是事件就要考虑事件丢失,比如挂断事件不来怎么处理,那就是加入超时检测,如果某通通话长时间没有结束事件就直接超时聚合处理。