在解决应用间通信的问题上,常见的两种方式就是http请求和HSF、dubbo这种RPC框架(以HSF为例)。
直观的感受来讲,规模较大的应用群会选用RPC框架,而很多小规模应用会采用简单的http来维护多应用之间的通信。最开始接触的是http服务,现在项目主体采用的都是HSF服务,部分会用http。
对两者的认识如下:
1.首先,RPC是一种框架,它可以有很多实现方式,可以用tcp协议,也可以用http协议。而http方式就是通过http协议发送请求。所以这两者不是一个层面上的东西。
2.HSF采用长连接方式进行通信。HSF的不采用直接的NIO编程,而是通过Netty框架,保证长连接和心跳。http也可以通过keep-Alive实现长连接,但是这种方式无疑给代码带来了更多的侵入。如果客户端都采用长连接方式,面对海量客户端的请求时,服务端必然要对这些长连接管理,否则服务端肯定崩掉。
3.效率方面,HSF的序列化方式有java、hessian、fastjson、kyro等,默认是hessian2,兼容性和性能都比较好。http也支持各种方式的序列化。HSF可以自定义TCP协议,让请求报文体积更小,http方式如果是HTTP2,可以自己封装一下实现差不多的效果。
4.负载均衡。这个就是HSF这种RPC框架的优势,http如果要做到负载均衡,还需要加nginx来搭配。
5.服务治理。显然RPC就是为此而生的,自己去维护一堆http服务是一件痛苦的事情。
http请求实现是比较简单的,而对于HSF服务,则复杂很多。比如现在注册一个HSF服务,服务启动时都发生了什么?
${hsf.consumer.version}
1.首先,初始化bean的时候调用init方法,实际调用的是HSFApiConsumerBean的init方法:
public void init() throws Exception {
//1.CAS判断,避免多次初始化
if (!this.inited.compareAndSet(false, true)) {
LOGGER.warn("HSF服务:" + this.metadata.getUniqueName() + " 重复初始化!");
} else {
LoggerInit.initHSFLog();
if (this.metadata.getImporters().size() == 0) {
this.metadata.addImporter("default", new Properties());
}
String interfaceName = this.metadata.getInterfaceName();
Class interfaceClass = null;
try {
interfaceClass = Class.forName(this.metadata.getInterfaceName());
} catch (ClassNotFoundException var8) {
if (!HSFServiceTargetUtil.isGeneric(this.metadata.getGeneric())) {
StringBuilder errorMsg = new StringBuilder();
errorMsg.append("ConsumerBean中指定的接口类不存在[");
errorMsg.append(interfaceName).append("].");
throw new IllegalArgumentException(errorMsg.toString());
}
interfaceClass = GenericService.class;
}
//2.设置metadata的ifclazz值
this.metadata.setIfClazz(interfaceClass);
//3.解析异步调用方法
if (this.asyncallMethods != null) {
Iterator i$ = this.asyncallMethods.iterator();
while(i$.hasNext()) {
String desc = (String)i$.next();
this.parseAsyncFunc(desc);
}
}
this.metadata.initUniqueName();
//4.缓存metadata对象
ServiceMetadataManager.allServiceMetadata.put(this.metadata.getUniqueName(), this.metadata);
//5.生成一个processService实例
ProcessService processService = (ProcessService)HSFServiceContainer.getInstance(ProcessService.class);
try {
//6.关键步骤:构建RPC的代理对象
this.metadata.setTarget(processService.consume(this.metadata));
LOGGER.warn("成功生成对接口为[" + this.metadata.getInterfaceName() + "]版本为[" + this.metadata.getVersion() + "]的HSF服务调用的代理!");
} catch (Exception var7) {
LOGGER.error("生成对接口为[" + this.metadata.getInterfaceName() + "]版本为[" + this.metadata.getVersion() + "]的HSF服务调用的代理失败", var7);
throw var7;
}
int waitTime = this.metadata.getMaxWaitTimeForCsAddress();
if (waitTime > 0) {
try {
this.metadata.getCsAddressCountDownLatch().await((long)waitTime, TimeUnit.MILLISECONDS);
} catch (InterruptedException var6) {
;
}
}
}
}
public Object consume(ServiceMetadata metadata) throws HSFException {
if (this.cachedServices.containsKey(metadata.getUniqueName())) {
return this.cachedServices.get(metadata.getUniqueName());
} else {
Iterator proxyObj = this.hookServices.iterator();
while(proxyObj.hasNext()) {
ProcessHookService hookService = (ProcessHookService)proxyObj.next();
hookService.preConsume(metadata);
}
proxyObj = null;
List> interfaces = new ArrayList(3);
...
Class>[] interfacesArray = new Class[interfaces.size()];
interfaces.toArray(interfacesArray);
Object proxyObj;
if ("javassist".equalsIgnoreCase(metadata.getProxyStyle())) {
proxyObj = this.createJavassistBytecodeDynamicProxy(metadata, interfacesArray);
} else {
proxyObj = this.createJdkDynamicProxy(metadata, interfacesArray);
}
this.metadataService.subscribe(metadata);
Iterator i$ = this.hookServices.iterator();
while(i$.hasNext()) {
ProcessHookService hookService = (ProcessHookService)i$.next();
hookService.afterConsume(metadata);
}
this.cachedServices.putIfAbsent(metadata.getUniqueName(), proxyObj);
return proxyObj;
}
}
(1)先尝试从cachedServices缓存中取代理对象
(2)没有则准备生成代理,有两种方式,javassist和jdk的代理。
(3)然后从HSF治理中心订阅服务信息。这样就可以找到可以调用的地址。
总结:
1.实际上框架这种东西,就是整合现有的优秀资源,实现用户的感知透明,减少代码侵入,不用再去自己编写一套已有的东西。同时方便管理。
2.在选用上,根据具体的情况去分析,并不是http的损耗就一定比RPC要差。如果在应用很少,服务端相对固定的场景下,完全可以自己搭一套http服务。当然在应用较多的情况下,还是选用优秀的RPC框架,尽量减少http的请求。