Http和RPC的选择

  在解决应用间通信的问题上,常见的两种方式就是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) {
                    ;
                }
            }

        }
    }
  • 关键的动态代理部分,是在processService中生成的,waitTime是设定的等待服务信息推送的时间
  • processService.consume()负责了生成代理的任务
 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的请求。

 

你可能感兴趣的:(java)