目录
前言
设计
问题一
问题二
有个短信需求需要联通开放发送短信的HTTP
调用能力给xxx
短信服务器(简称第三方平台),第三方平台可通过调用接口发送短信给相应用户,如果接收到短信的用户有相应回复,需要通知第三方平台,实现双向互通。
但联通网关目前仅支持SGIP
协议,接入的第三方平台是HTTP
协议,因此需要开发一个中间协议转化服务(简称短信业务服务网关),通过进行协议转换,实现短信发送。
SGIP
协议是SMG
和SP
之间、SMG
和GNS
之间、以及SMG
和SMG
之间的接口协议,简称SGIP
。通过应用
SGIP
协议,SP
可以接入到SMG
,实现SP
应用的一点接入、全网服务;SMG
可以通过SGIP
协议,实现消息在不同SMG
之间的路由和转发。同时SMG
通过该协议也可以和GNS
通信,以实现各SMG
和GNS
之间路由表的同步功能。
总体流程:
业务流程:
下发短信流程:
客户端建立
socket
,先发送一个Bind
请求,解析收到的BindResp
包,正常连接后可以使用submit
命令发送短信,获取解析收到的submitresp
包,发送成功后调用unbind
命令断开连接,解析收到的UnbindResp
,如果命令操作正常就关闭socket
。
bind
的有效期60
秒,有效期内可以一直调用submit
请求下发短信,如果期间不发送unbind
,则bind
在60
秒后自动断开连接。
基于上面流程会存在两个问题:
连接断开时需要重新建立连接
建立连接期间要保证短信不丢失
这里测试的时候发现,bind
有效期内,再次发送bind
,返回失败,即bind
期间不允许再次bind
。
定时任务,周期性重新连接,伪代码:
@Scheduled(fixedRate = 50000, initialDelay = 50000)
public void reconnect() throws Exception {
sgipChannelFuture();
}
public ChannelFuture sgipChannelFuture() {
if (null == channelFuture || !channelFuture.channel().isActive()) {
try {
channelFuture = bootstrap.connect(ip, port).sync();
System.out.println("远程服务已经连接,可以进行数据交换了...");
} catch (Exception e) {
e.printStackTrace();
}
} else {
log.info("开始销毁连接,发送unbind请求");
channelFuture.channel().writeAndFlush("unbind");
try {
channelFuture = bootstrap.connect(ip, port).sync();
System.out.println("远程服务已经连接,可以进行数据交换了...");
} catch (Exception e) {
e.printStackTrace();
}
}
return channelFuture;
}
这里介绍一种更优雅的做法:
@Bean("sgipChannelFuture")
public ChannelFuture sgipChannelFuture() {
ChannelFuture channelFuture = null;
try {
Bootstrap bootstrap = new Bootstrap();
channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
System.out.println("远程服务已经连接,可以进行数据交换了...");
} catch (Exception e) {
e.printStackTrace();
}
return channelFuture;
}
@Scheduled(fixedRate = 50000, initialDelay = 50000)
public void reconnect(){
sgipChannelFuture = (ChannelFuture) applicationContext.getAutowireCapableBeanFactory().getBean("sgipChannelFuture");
if (null == sgipChannelFuture || !sgipChannelFuture.channel().isActive()) {
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) applicationContext.getAutowireCapableBeanFactory();
registry.destroySingleton("sgipChannelFuture");
registry.registerSingleton("sgipChannelFuture", client.sgipChannelFuture());
} else {
log.info("开始销毁连接,发送unbind请求");
sgipChannelFuture.channel().writeAndFlush("unbind");
DefaultSingletonBeanRegistry registry = (DefaultSingletonBeanRegistry) applicationContext.getAutowireCapableBeanFactory();
registry.destroySingleton("sgipChannelFuture");
registry.registerSingleton("sgipChannelFuture", client.sgipChannelFuture());
}
}
这里有必要介绍一下DefaultSingletonBeanRegistry
,部分源码:
// 单例缓存 beanName -> 单例实例
private final Map singletonObjects = new ConcurrentHashMap<>(256);
// 工厂缓存,又称二级缓存,缓存单例工厂
private final Map> singletonFactories = new HashMap<>(16);
// 三级缓存,缓存提前暴露的单例实例
private final Map earlySingletonObjects = new ConcurrentHashMap<>(16);
// 缓存以及注册单例的单例名称(按注册顺序)
private final Set registeredSingletons = new LinkedHashSet<>(256);
// 正在创建的 bean 的 name
private final Set singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
// 是否允许 循环引用
private boolean singletonsCurrentlyInDestruction = false;
//...
定义了大量的缓存属性,用于解决循环依赖,提高性能等
其中 singletonObjects
即我们常说的单例池,所有的单例都在创建完成后缓存其中
DefaultSingletonBeanRegistry
:Spring Framework
中的单例Bean
管理。
在
Spring Framework
中,单例Bean
的管理是非常重要的一部分。Spring
容器负责管理单例Bean
的生命周期,并确保在应用程序中只有一个实例存在。这一管理的核心是DefaultSingletonBeanRegistry
,一个用于注册和解析单例Bean
的默认实现。
DefaultSingletonBeanRegistry
的主要职责是维护一个单例Bean
的注册表,并提供方法来注册、获取和解析单例Bean
。它提供了一个通用的接口,使得开发者能够轻松地自定义单例Bean
的生命周期行为。
首先,让我们来看看如何使用DefaultSingletonBeanRegistry
注册单例Bean
。在Spring
中,我们可以使用registerSingleton
方法将Bean
注册到DefaultSingletonBeanRegistry
中:
DefaultSingletonBeanRegistry registry = new DefaultSingletonBeanRegistry();
registry.registerSingleton("myBean", MyBean.class);
通过 destroySingleton
方法来处理单例对象的销毁:
DefaultSingletonBeanRegistry registry = new DefaultSingletonBeanRegistry();
registry.destroySingleton("myBean");
从发送unbind
到接收到bind
结果期间,短信网关接收到的http
请求缓存到redis
队列中,定时进行补发。
当然也可以使用其他第三方专门的消息队列,如RabblitMQ、RocketMQ