标签:color pool lan password .com ace add 中间 大文件
RPC 的主要功能目标是让构建分布式应用更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。
为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用。
RPC 调用分以下两种:异步和同步的区分在于是否等待服务端执行完成并返回结果。
如下图所示。
RPC 服务方通过 RpcServer
去导出(export)远程接口方法,而客户方通过 RpcClient
去引入(import)远程接口方法。 客户方像调用本地方法一样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理 RpcProxy
。 代理封装调用信息并将调用转交给 RpcInvoker
去实际执行。 在客户端的 RpcInvoker
通过连接器 RpcConnector
去维持与服务端的通道 RpcChannel
, 并使用 RpcProtocol
执行协议编码(encode)并将编码后的请求消息通过通道发送给服务方。
RPC 服务端接收器 RpcAcceptor
接收客户端的调用请求,同样使用 RpcProtocol
执行协议解码(decode)。 解码后的调用信息传递给 RpcProcessor
去控制处理调用过程,最后再委托调用给 RpcInvoker
去实际执行并返回调用结果。
上面我们进一步拆解了 RPC 实现结构的各个组件组成部分,下面我们详细说明下每个组件的职责划分。
RpcServer
RpcClient
RpcProxy
RpcInvoker
RpcProtocol
RpcConnector
RpcAcceptor
RpcProcessor
RpcChannel
在进一步拆解了组件并划分了职责之后,这里以在 java 平台实现该 RPC 框架概念模型为例,详细分析下实现中需要考虑的因素。
导出远程接口的意思是指只有导出的接口可以供远程调用,而未导出的接口则不能。 在 java 中导出接口的代码片段可能如下:
DemoService demo = new ...; RpcServer server = new ...; server.export(DemoService. class , demo, options); |
我们可以导出整个接口,也可以更细粒度一点只导出接口中的某些方法,如:
// 只导出 DemoService 中签名为 hi(String s) 的方法 server.export(DemoService. class , demo, "hi" , new Class<?>[] { String. class }, options); |
java 中还有一种比较特殊的调用就是多态,也就是一个接口可能有多个实现,那么远程调用时到底调用哪个? 这个本地调用的语义是通过 jvm 提供的引用多态性隐式实现的,那么对于 RPC 来说跨进程的调用就没法隐式实现了。 如果前面 DemoService 接口有 2 个实现,那么在导出接口时就需要特殊标记不同的实现,如:
1
2
3
4
5
|
DemoService demo = new ...; DemoService demo2 = new ...; RpcServer server = new ...; server.export(DemoService. class , demo, options); server.export( "demo2" , DemoService. class , demo2, options); |
上面 demo2 是另一个实现,我们标记为 demo2 来导出, 那么远程调用时也需要传递该标记才能调用到正确的实现类,这样就解决了多态调用的语义。
导入相对于导出远程接口,客户端代码为了能够发起调用必须要获得远程接口的方法或过程定义。 目前,大部分跨语言平台 RPC 框架采用根据 IDL 定义通过 code generator 去生成 stub 代码, 这种方式下实际导入的过程就是通过代码生成器在编译期完成的。 我所使用过的一些跨语言平台 RPC 框架如 CORBAR、WebService、ICE、Thrift 均是此类方式。
代码生成的方式对跨语言平台 RPC 框架而言是必然的选择,而对于同一语言平台的 RPC 则可以通过共享接口定义来实现。 在 java 中导入接口的代码片段可能如下:
RpcClient client = new ...; DemoService demo = client.refer(DemoService. class ); demo.hi( "how are you?" ); |
在 java 中 import
是关键字,所以代码片段中我们用 refer 来表达导入接口的意思。 这里的导入方式本质也是一种代码生成技术,只不过是在运行时生成,比静态编译期的代码生成看起来更简洁些。 java 里至少提供了两种技术来提供动态代码生成,一种是 jdk 动态代理,另外一种是字节码生成。 动态代理相比字节码生成使用起来更方便,但动态代理方式在性能上是要逊色于直接的字节码生成的,而字节码生成在代码可读性上要差很多。 两者权衡起来,个人认为牺牲一些性能来获得代码可读性和可维护性显得更重要。
客户端代理在发起调用前需要对调用信息进行编码,这就要考虑需要编码些什么信息并以什么格式传输到服务端才能让服务端完成调用。 出于效率考虑,编码的信息越少越好(传输数据少),编码的规则越简单越好(执行效率高)。 我们先看下需要编码些什么信息:
除了以上这些必须的调用信息,我们可能还需要一些元信息以方便程序编解码以及未来可能的扩展。 这样我们的编码消息里面就分成了两部分,一部分是元信息、另一部分是调用的必要信息。 如果设计一种 RPC 协议消息的话,元信息我们把它放在协议消息头中,而必要信息放在协议消息体中。 下面给出一种概念上的 RPC 协议消息设计格式:
协议编码之后,自然就是需要将编码后的 RPC 请求消息传输到服务方,服务方执行后返回结果消息或确认消息给客户方。 RPC 的应用场景实质是一种可靠的请求应答消息流,和 HTTP 类似。 因此选择长连接方式的 TCP 协议会更高效,与 HTTP 不同的是在协议层面我们定义了每个消息的唯一 id,因此可以更容易的复用连接。
既然使用长连接,那么第一个问题是到底 client 和 server 之间需要多少根连接? 实际上单连接和多连接在使用上没有区别,对于数据传输量较小的应用类型,单连接基本足够。 单连接和多连接最大的区别在于,每根连接都有自己私有的发送和接收缓冲区, 因此大数据量传输时分散在不同的连接缓冲区会得到更好的吞吐效率。 所以,如果你的数据传输量不足以让单连接的缓冲区一直处于饱和状态的话,那么使用多连接并不会产生任何明显的提升, 反而会增加连接管理的开销。
连接是由 client 端发起建立并维持。 如果 client 和 server 之间是直连的,那么连接一般不会中断(当然物理链路故障除外)。 如果 client 和 server 连接经过一些负载中转设备,有可能连接一段时间不活跃时会被这些中间设备中断。 为了保持连接有必要定时为每个连接发送心跳数据以维持连接不中断。 心跳消息是 RPC 框架库使用的内部消息,在前文协议头结构中也有一个专门的心跳位, 就是用来标记心跳消息的,它对业务应用透明。
client stub 所做的事情仅仅是编码消息并传输给服务方,而真正调用过程发生在服务方。 server stub 从前文的结构拆解中我们细分了 RpcProcessor
和 RpcInvoker
两个组件, 一个负责控制调用过程,一个负责真正调用。 这里我们还是以 java 中实现这两个组件为例来分析下它们到底需要做什么?
java 中实现代码的动态接口调用目前一般通过反射调用。 除了原生的 jdk 自带的反射,一些第三方库也提供了性能更优的反射调用, 因此 RpcInvoker 就是封装了反射调用的实现细节。
调用过程的控制需要考虑哪些因素,RpcProcessor 需要提供什么样地调用控制服务呢? 下面提出几点以启发思考:
无论 RPC 怎样努力把远程调用伪装的像本地调用,但它们依然有很大的不同点,而且有一些异常情况是在本地调用时绝对不会碰到的。 在说异常处理之前,我们先比较下本地调用和 RPC 调用的一些差异:
正是这些区别决定了使用 RPC 时需要更多考量。 当调用远程接口抛出异常时,异常可能是一个业务异常, 也可能是 RPC 框架抛出的运行时异常(如:网络中断等)。 业务异常表明服务方已经执行了调用,可能因为某些原因导致未能正常执行, 而 RPC 运行时异常则有可能服务方根本没有执行,对调用方而言的异常处理策略自然需要区分。
由于 RPC 固有的消耗相对本地调用高出几个数量级,本地调用的固有消耗是纳秒级,而 RPC 的固有消耗是在毫秒级。 那么对于过于轻量的计算任务就并不合适导出远程接口由独立的进程提供服务, 只有花在计算任务上时间远远高于 RPC 的固有消耗才值得导出为远程接口提供服务。
***************************************************************************基于 RPC 机制的工具 -- Dubbo *********************************************************************************
Dubbo是什么
Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。其核心部分包括:
Dubbo能做什么
Dubbo功能强大,总结下来,它大致可以做以下几件事:
Dubbo架构图
这是最重要的,理解Dubbo的架构图是理解Dubbo的第一步,我从Dubbo官网手册上截了一下Dubbo架构图:
在接下来的讲解之前,说明一个概念:所谓SOA也好,分布式服务框架也好,不是服务消费者从中间件(一般都是Zookeeper)上去拿数据,而是服务消费者从中间件上拿到可用的服务生产者的集群地址,再从集群地址中选出一个进行直连。
接下来认识一下图中的结点:
图中已经有了调用步骤了,接着对步骤进行说明:
Dubbo用法
Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入(这句话的意思是应用程序不会、不需要手动调用Dubbo的任何类和任何接口),只需用Spring加载Dubbo的配置即可(意思是对Dubbo的使用只需要写Spring配置文件或注解),Dubbo基于Spring的Schema扩展进行加载。
如果不想使用Spring配置,而希望通过API的方式进行调用,可以自己看一下官方手册Dubbo API配置,但是,这种做法十分不推荐。
下面简单说明一下Dubbo的用法,首先要定义服务生产者的接口及其实现,那么定义一个接口(该接口需要单独打包,在生产者和消费者之间共享):
package com.xrq.dubbo.demo; public interface DemoService { String sayHello(String name); }
在服务生产者处实现接口(对服务消费者隐藏接口实现细节):
package com.xrq.dubbo.demo.provider; import com.xrq.dubbo.demo.DemoService; public class DemoServiceImpl implements DemoService { public String sayHello(String name) { return "Hello " + name; } }
写一个provider.xml,在服务生产者使用Spring暴露服务:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 提供方应用信息,用于计算依赖关系 --> <dubbo:application name="hello-world-app" /> <!-- 使用multicast广播注册中心暴露服务地址 --> <dubbo:registry address="multicast://224.5.6.7:1234" /> <!-- 用dubbo协议在20880端口暴露服务 --> <dubbo:protocol name="dubbo" port="20880" /> <!-- 声明需要暴露的服务接口 --> <dubbo:service interface="com.xrq.dubbo.demo.DemoService" ref="demoService" /> <!-- 和本地bean一样实现服务 --> <bean id="demoService" class="com.xrq.dubbo.demo.provider.DemoServiceImpl" /> </beans>
在服务消费者处写一个consumer.xml引用远程服务:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd"> <!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 --> <dubbo:application name="consumer-of-helloworld-app" /> <!-- 使用multicast广播注册中心暴露发现服务地址 --> <dubbo:registry address="multicast://224.5.6.7:1234" /> <!-- 生成远程服务代理,可以和本地bean一样使用demoService --> <dubbo:reference id="demoService" interface="com.alibaba.dubbo.demo.DemoService" /> </beans>
服务消费者处只要通过Spring拿到demoService,即可像使用本地接口一样使用DemoService这个接口里面的方法:
import org.springframework.context.support.ClassPathXmlApplicationContext; import com.xrq.dubbo.demo.DemoService; public class Consumer { public static void main(String[] args) throws Exception { ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"http://aaa.bbb.ccc:dddd/eee/fff/consumer.xml"}); context.start(); DemoService demoService = (DemoService)context.getBean("demoService"); // 获取远程服务代理 String hello = demoService.sayHello("world"); // 执行远程方法 System.out.println(hello); // 显示调用结果 } }
看到整个过程中:
1、没有任何Dubbo的代码,使用Dubbo的时候全部都是Spring配置,这就是前面提到的Dubbo对应用没有任何API侵入
2、不需要考虑底层线程模型、序列化、反序列化、url解析等问题,这些都是Dubbo底层做好的
Dubbo常用标签
首先说一个优先级的问题,在dubbo中比如timeout、retries、loadbalance等参数可以在多个标签内同时配置,它们之间的优先级,dubbo手册上是这么说的:
OK,下面罗列一些Dubbo常用的,也就是说每个项目的Dubbo的xml文件中基本都会出现的标签,并以表格形式列举标签中常见的可用属性。
1、<dubbo:service /> 用于服务生产者暴露服务配置
属 性 | 类 型 | 是否必填 | 缺省值 | 描 述 |
interface | class | 必填 | 无 | 服务接口全路径 |
ref | object | 必填 | 无 | 服务对象实现引用 |
version | string | 可选 | 0.0.0 | 服务版本,建议使用两位数字版本如1.0,通常在接口不兼容时版本号才需要升级 |
timeout | int | 可选 | 1000 | 远程服务调用超时时间(毫秒) |
retries | int | 可选 | 2 | 远程服务调用重试次数,不包括第一次调用,不需要重试请设为0 |
connections | int | 可选 | 100 | 每个生产者的最大连接数,短连接协议如rmi,表示限制连接数;长连接协议如dubbo表示建立的长连接个数 |
loadbalance | string | 可选 | random | 负载均衡策略,可选值为:random(随机)、roundrobin(轮询)、leastactive(最少活跃调用) |
async | boolean | 可选 | false | 是否缺省异步执行,不可靠的异步,只是忽略返回值,不阻塞执行线程 |
register | boolean | 可选 | true | 该协议的服务是否注册到注册中心 |
2、<dubbo:reference /> 用于服务消费者引用服务配置
属 性 | 类 型 | 是否必填 | 缺省值 | 描 述 |
id | string | 必填 | 无 | 服务引用beanId |
interface | class | 必填 | 无 | 服务接口全路径 |
version | string | 可选 | 无 | 服务版本,与服务生产者的版本一致 |
timeout | long | 可选 | 使用<dubbo:consumer>的timeout | 服务方法调用超时时间(毫秒) |
retries | int | 可选 | 使用<dubbo:consumer>的retries | 远程服务调用重试次数,不包括第一次调用,不需要重试请设为0 |
connections | int | 可选 | 使用<dubbo:consumer>的connections | 每个生产者的最大连接数,短连接协议如rmi,表示限制连接数;长连接协议如dubbo表示建立的长连接个数 |
loadbalance | string | 可选 | 使用<dubbo:consumer>的loadbalance | 负载均衡策略,可选值为:random(随机)、roundrobin(轮询)、leastactive(最少活跃调用) |
async | boolean | 可选 | 使用<dubbo:consumer>的async | 是否缺省异步执行,不可靠的异步,只是忽略返回值,不阻塞执行线程 |
check | boolean | 可选 | 使用<dubbo:consumer>的check | 启动时检查服务生产者是否存在,true则报错,false则忽略 |
url | string | 可选 | 无 | 点对点直连服务提供者地址,将绕过注册中心,比如"dubbo://localhost:20890",这个比较多的使用在测试中 |
protocol | string | 可选 | 无 | 只调用指定协议的服务生产者,其他协议忽略 |
3、<dubbo:protocol /> 用于服务生产者协议配置(如果需要支持多协议,可以声明多个此标签,并在<dubbo:service />通过protocol属性指定使用的协议)
属 性 | 类 型 | 是否必填 | 缺省值 | 描 述 |
id | string | 可选 | dubbo | 协议beanId,<dubbo service />中的protocol引用此ID,如果不填缺省和name属性值一样 |
name | sring | 必填 | dubbo | 协议名称 |
port | int | 可选 |
dubbo->20800,rmi->1099,http->80,hessian->80 如果配置为-1或未配置,则会分配一个没有被占用的端口 |
服务端口 |
host | string | 可选 | 自动查找本机ip |
为空则自动查找本机ip,建议不配置让Dubbo自动获取本机ip |
threadpool | string | 可选 | fixed | 线程池类型,可选fixed/cached |
threads | int | 可选 | 100 | 服务线程池大小(固定大小) |
serialization | string | 可选 | dubbo->hession2,rmi->java,http->json | 协议序列化方式,当协议支持多种序列化方式时使用 |
register | boolean | 可选 | true | 该协议的服务是否注册到注册中心 |
4、<dubbo:registry /> 用于注册中心配置(如果有多个不同的注册中心可以声明多个标签并且<dubbo:service />或<dubbo:reference />中使用registry属性指定)
属 性 | 类 型 | 是否必填 | 缺省值 | 描 述 |
id | string | 可选 | 无 | 注册中心引用beanId,可在<dubbo:service />或<dubbo:reference />中引用此ID |
address | string | 必填 | 无 |
注册中心服务地址,如果地址没有端口缺省为9090,同一个集群内的多个地址用逗号分隔,如:ip:port,ip:port,不同的 集群注册中心请配置多个<dubbo:registry />标签 |
protocol | string | 可选 | dubbo | 注册中心地址协议,支持dubbo、http、local三种协议,分别表示dubbo地址、http地址和本地注册中心 |
port | int | 可选 | 9090 | 注册中心缺省端口,当address没有带端口时使用此端口作为缺省值 |
username | string | 可选 | 无 | 登陆注册中心用户名,如果注册中心不需要验证可不填 |
password | string | 可选 | 无 | 登陆注册中心密码,如果注册中心不需要验证可不填 |
transport | string | 可选 | netty | 网络传输方式,可选mina、netty |
timeout | int | 可选 | 5000 | 注册中心请求超时时间(毫秒) |
file | string | 可选 | 无 | 使用文件缓存注册中心地址列表以及服务提供者列表,应用重启时将基于此文件恢复,注意两个注册中心不能使用同一文件存储 |
check | boolean | 可选 | true | 注册中心不存在时,是否报错 |
register | boolean | 可选 | true | 是否向此注册中心注册服务,如果设为false,将只订阅,不注册 |
subscribe | boolean | 可选 | true | 是否向此注册中心订阅服务,如果设为false,将只注册,不订阅 |
5、<dubbo:method />用于方法级配置(该标签为<dubbo:service/>或<dubbo:reference/>的子标签,用于控制到方法级)
属 性 | 类 型 | 是否必填 | 缺省值 | 描 述 |
method | string | 必填 | 无 | 方法名 |
timeout | int | 可选 | 缺省为<dubbo:reference/>的timeout | 方法调用超时时间(毫秒) |
retires | int | 可选 | 缺省为<dubbo:reference/>的retries | 远程服务调用重试次数,不包括第一次调用,不需要重试请设为0 |
loadbalance | string | 可选 | 缺省为<dubbo:reference/>的loadbalance | 负载均衡策略,可选值为:random(随机)、roundrobin(轮询)、leastactive(最少活跃调用) |
async | boolean | 可选 | 缺省为<dubbo:reference/>的async | 是否异步执行,不可靠异步,只是忽略返回值,不阻塞执行线程 |
actives | int | 可选 | 0 | 每服务消费者最大并发调用限制 |
executes | int | 可选 | 0 | 每服务每方法最大使用线程数限制,此属性只在<dubbo:method/>作为<dubbo:service/>子标签时有效 |
另外,还有<dubbo:provider/>和<dubbo:consumer/>分别表示服务提供者(生产者)和服务消费者的缺省值配置,就不列举了。
Dubbo协议dubbo://
前面有说到一个Dubbo协议,下面来看一下Dubbo协议。
Dubbo协议Dubbo的缺省协议,使用基于mina1.1.7+hessian3.2.1的tbremoting交互。
从上面的适用范围总结,dubbo适合小数据量大并发的服务调用,以及消费者机器远大于生产者机器数的情况,不适合传输大数据量的服务比如文件、视频等,除非请求量很低。
另外,Dubbo手册还给开发者列出了Dubbo协议使用的约束:
标签:color pool lan password .com ace add 中间 大文件
原文地址:https://www.cnblogs.com/gxyandwmm/p/9651780.html