共计 8636 个字符,预计需要花费 22 分钟才能阅读完成。
其实上周是打算写 Dubbo 的,然而发现 Dubbo 须要一个注册核心,因为也有学习 Dubbo 的打算,所以将 Zookeeper 和 Dubbo 放在一起介绍。
是啥?
我记得上一次看 Dubbo 的官网,Dubbo 将本人定义为一款 RPC 框架,到 Dubbo3 就变成了:
Apache Dubbo 是一款微服务开发框架,它提供了 RPC 通信 与 微服务治理 两大要害能力。这意味着,应用 Dubbo 开发的微服务,将具备相互之间的近程发现与通信能力,同时利用 Dubbo 提供的丰盛服务治理能力,能够实现诸如服务发现、负载平衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩大的,用户简直能够在任意性能点去定制本人的实现,以扭转框架的默认行为来满足本人的业务需要。
这里再讨论一下什么是 RPC(这一点我在 RPC 学习笔记初遇篇(一) 探讨的曾经很齐备了),不少介绍 RPC 的文章都会先从一个利用想要调用另一个利用的函数动手,但这不如维基百科直观:
分布式计算中,近程过程调用(英语:Remote Procedure Call,RPC)是一个计算机通信协议。该协定容许运行于一台计算机的程序调用另一个地址空间(通常为一个凋谢网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额定地为这个交互作用编程(无需关注细节).
那为什么都从函数上动手,这是一种形象和封装,两个过程须要进行通信,须要在 TCP 之上制订规范,也就是制订应用层的协定,能够抉择 HTTP(跨语言),也能够基于 TCP,自定义应用层的协定。咱们能够在 Dubbo3 概念架构一节的协定印证咱们的观点:
Dubbo3 提供了 Triple(Dubbo3)、Dubbo2 协定,这是 Dubbo 框架的原生协定。除此之外,Dubbo3 也对泛滥第三方协定进行了集成,并将它们纳入 Dubbo 的编程与服务治理体系,包含 gRPC、Thrift、JsonRPC、Hessian2、REST 等。以下重点介绍 Triple 与 Dubbo2 协定。
最终咱们抉择了兼容 gRPC,以 HTTP2 作为传输层构建新的协定,也就是 Triple。
也就是咱们能够认为 HTTP 协定是 RPC 的一种。至于微服务治理,这里不再反复的进行的探讨,参考我掘金的文章:《写给小白看的 Spring Cloud 入门教程》。那既然你说 HTTP 协定是 RPC 的一种,那 Dubbo 的意义又何在呢,我集体认为是对 HTTP 协定进行革新吧,HTTP 2.0 之前都是文本模式的,采取二进制字节流在网络中传输更快,除此之外应用 HTTP 协定传送数据,还须要本人入手将数据进行序列化,如果须要跨语言通信,定义的规定就更多了,Dubbo 帮咱们做好了这所有,甚至做的更多。这也就是咱们学习 Dubbo 的意义所在。
梳理一下,RPC 是一个计算机通信协议,那为什么都结构成了函数调用这一模式,这是因为从形象来说是最正当的,咱们能够大抵推断演进一下:
- 首先是两个过程须要进行替换信息, 抉择了 TCP 作为传输层的协定, 有的人抉择了 HTTP 协定,因为这更简略一些, 当替换的信息比较简单,各个高级语言的 Socket API 是能够满足其需要的。
- 如果咱们期待这种替换的信息要更简单一点呢,如果说咱们抉择 TCP 或 HTTP 作为利用间通信的模式,那么就有一些重复性的编码工作,比方取值,序列化为对象,如果是 TCP 协定还要思考拆包等等,这无疑减轻了程序员们编码的累赘,那么能不能简化这个过程呢,屏蔽掉网络编程中波及的简单细节,形象进去一个简略的模型呢,高级语言都内置有函数,那不如就从函数动手,让过程间替换信息就像是调用两个利用一样,这也就是很多 RPC 教程都从函数动手的起因,我感觉是由过程通信的过程中,为了屏蔽掉网络编程的简单细节,抉择从函数动手,这样让人容易了解一些,而不是一开始就是函数调用的模式。换句话说,少数程序员可能没理解过 Socket 编程中的拆包之类的概念,然而肯定了解函数这个概念,这是一种封装。
而 Dubbo 尽管在官网将本人申明为是一款微服务开发框架,然而在理论利用场景中,Apache Dubbo 个别会作为后端系统间 RPC 调用的实现框架,咱们能够将其类比为 HTTP 协定对应的诸多 HTTP Client。Dubbo 提供了多语言反对,目前只反对 Java、Go、Erlang 这三种语言,那么咱们天然提出一个问题,不同语言内置的数据类型、办法模式是不一样的,那作为 RPC 的实现者,它是如何做到跨语言的。
当然是引入一个中间层 -IDL
为了和计算机进行通信,咱们引入了编程语言,编程语言就是一个中间层,那么为了让不同的高级语言进行通信,Dubbo 引入了 IDL,Dubbo 中举荐应用 IDL 定义跨语言服务,那什么是 IDL,Dubbo 官网并没有解释,于是我去了维基百科:
An interface description language or interface definition language (IDL), is a generic term for a language that lets a program or object written in one language communicate with another program written in an unknown language. IDLs describe an interface in a language-independent way, enabling communication between software components that do not share one language, for example, between those written in C++ and those written in Java.
接口定义语言或者接口描述语言,是一种两种不同的语言进行通信的一种语言,IDL 以独立于语言的任何模式形容接口,反对不同的高级语言进行通信。例如 C ++ 写的利用和 Java 写的利用
IDLs are commonly used in remote procedure call software. In these cases the machines at either end of the link may be using different operating systems and computer languages. IDLs offer a bridge between the two different systems.
IDL 通常在利用 RPC,在 RPC 中通信的单方,链路的两端通常是不同的操作系统和编程语言。IDL 为两个不同的零碎提供了桥梁。
为什么又把英文贴出来了,维基百科不是也有中文吗?上面是维基百科中 IDL 的中文解释:
接口描述语言(Interface description language,缩写IDL),是用来形容软件组件介面的一种计算机语言。IDL 通过一种独立于编程语言的形式来形容接口,使得在不同平台上运行的对象和用不同语言编写的程序能够互相通信交换;比方,一个组件用 C ++ 写成,另一个组件用写成。
看到这个介面我懵了一下,我预计是对 interface 的翻译,interface 的中文有界面的意思。那既然是一种计算机语言,咱们合情推理,那就有语法,在 Dubbo 中提供了 IDL 的示例:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "org.apache.dubbo.demo";
option java_outer_classname = "DemoServiceProto";
option objc_class_prefix = "DEMOSRV";
package demoservice;
// The demo service definition.
service DemoService {rpc SayHello (HelloRequest) returns (HelloReply) {}}
// The request message containing the user's name.
message HelloRequest {string name = 1;}
// The response message containing the greetings
message HelloReply {string message = 1;}
Dubbo 是如是形容的:
以上是应用 IDL 定义服务的一个简略示例,咱们能够把它命名为
DemoService.proto
,proto 文件中定义了 RPC 服务名称DemoService
与办法签名SayHello (HelloRequest) returns (HelloReply) {}
,同时还定义了办法的入参构造体、出参构造体HelloRequest
与HelloReply
。IDL 格局的服务依赖 Protobuf 编译器,用来生成能够被用户调用的客户端与服务端编程 API,Dubbo 在原生 Protobuf Compiler 的根底上提供了适配多种语言的特有插件,用于适配 Dubbo 框架特有的 API 与编程模型。
又呈现了一个新名词: Protobuf, Protobuf 是啥?Apache Dubbo 没有解释,我只好再诉诸于搜索引擎:
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages. –《Protocol Buffers》官网
Protocol Buffers 是 Google 为序列化构造数据设计的一种独立于语言、平台的一种可扩大机制, 相似于 XML, 然而更小、更简略、更快。你只须要定义数据如何被结构化,而后用生成的源码,就可能在不同的语言中读取和写入你的结构化数据。
XML 是一种形容数据的一种模式,那既然是相似于 XML,那又是一种形容数据的一种模式,联合下面的跨语言语境,也就是说咱们借助对应的 Protobuf 编译器用 proto 来生成调用客户端与服务端编程 API。
Protobuf 简略入门
既然是形容数据,那么就会有数据类型,Protobuf 为了跨语言,申明了一些数据类型, 与各个语言的数据类型有对应的映射关系 , 这里简略列出一一下和 java 数据类型的映射关系:
- double ==> java double
- float ==> java float
- int64 ==> java long
- uint32 ==> java int
- bool ==> java bool
- String ==> java String
下面的示例中 HelloRequest、HelloReply 的字段每个都进行了赋值,但这并不是默认值, 而是字段编号,这些字段编号用于标识二进制模式的字段。到目前为止咱们就只剩下面的几个 optional 看不懂了:
- java_multiple_files
如果为 true, 每个 message 和 service 都会被生成为一个类。如果是 false,则所有的 message 和 service 都会被生成到一个类中。
- java_package
生产的代码所处的地位,如果没有则会产生在 package 前面申明的包。
- java_outer_classname
生产服务的名称。
- objc_class_prefix
很奇怪官网的示例为什么会把这个放进去,我查了很多材料,这个语法为 objective- c 所提供,用于为指定的类生成前缀。
Dubbo 还说:
应用 Dubbo3 IDL 定义的服务只容许一个入参加出参,这种模式的服务签名有两个劣势,一是对多语言实现更敌对,二是能够保障服务的向后兼容性,依赖于 Protobuf 序列化的兼容性,咱们能够很容易的调整传输的数据结构如增、删字段等,齐全不必放心接口的兼容性
到当初为止咱们曾经看懂了官网的示例,当初咱们就要用起来。
根本应用示例
Dubbo 官网举荐应用 IDL,那咱们还是应用官网的示例,来定义服务。官网提供了示例:
我这里贴下指令:
git clone -b master https://github.com/apache/dubbo-samples.git
cd dubbo-samples/dubbo-samples-protobuf
# 要求配置 maven 的环境变量
mvn clean package
# 运行 Provider
java -jar ./protobuf-provider/target/protobuf-provider-1.0-SNAPSHOT.jar
# 运行 consumer
java -jar ./protobuf-consumer/target/protobuf-consumer-1.0-SNAPSHOT.jar
而后你会发现跑不起来, 我跑是这样:
Zookeeper 连贯不上,这里批评一下 Apache Dubbo 的官网示例文档,齐全跑不起来,真的是在用心写文档吗!这个 Zookeeper 咱们在《Zookeeper 学习笔记 (一) 基本概念和简略应用》曾经介绍过了,一个分布式协调服务,提供命名服务。那 Dubbo 这个示例中为什么要求连贯 Zookeeper 呢,为理解耦合,咱们如果间接通过 IP+ 端口的形式去调服务提供者的服务的话,这样就耦合在一起了,假如生产上换台机器咱们还得改代码,再有就是集群的状况下,我晓得服务名就好,不须要晓得特定 ip 的,这也就是注册核心的概念,服务提供者将服务注册到注册核心,消费者提供服务名和腰生产的服务即可。Dubbo 服务常见的架构:
Monitor 是监控,监控服务调用,这里咱们不做介绍。其实在 Dubbo 提供的源码中也默认连贯了 Zookeeper 这个注册核心:
还好咱们曾经装过了 Zookeeper,咱们将地址改掉就行。留神哈,高版本的 JDK 改变了很多货色,Dubbo 官网提供的示例,在 JDK 17 下可能跑不起来,如果到时候编译报错,将环境调整到 JDK8 就行,我本人测试的话,JDK 11 也能够的, 然而有的时候会报 Zookeeper 连贯不上的谬误。我用 IDEA 启动一下:
而后启动消费者:
发现是没问题的,我剖析了一下为啥在我的 windows power shell 中呈现 Zookeeper 连贯不上的起因可能是我配置的 JDK 环境变量是 JDK 11 的,在 IDEA 中可能胜利跑起来的起因是 IDEA 用的是 JDK8.
颇有种你发任你发,我接着用 JDK8 的感觉。
从示例中剖析
pom 外面有 Protobuf 插件:
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
<!-- 将 protobuf 文件输入到这个目录 -->
<outputDirectory>build/generated/source/proto/main/java</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
<protocPlugins>
<protocPlugin>
<id>dubbo</id>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-compiler</artifactId>
<version>${compiler.version}</version>
<mainClass>org.apache.dubbo.gen.dubbo.Dubbo3Generator</mainClass>
</protocPlugin>
</protocPlugins>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
public class ConsumerApplication {public static void main(String[] args) throws Exception {
// 加载 Spring 的上下文文件
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
// 从容器中获取 demoService
DemoService demoService = context.getBean("demoService", DemoService.class);
// 构建入参
HelloRequest request = HelloRequest.newBuilder().setName("Hello").build();
// 实现 RPC
HelloReply reply = demoService.sayHello(request);
System.out.println("result:" + reply.getMessage());
System.in.read();}
}
public class Application {public static void main(String[] args) throws Exception {
// 加载 bean
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-provider.xml");
context.start();
System.out.println("dubbo service started");
// 防止利用敞开
new CountDownLatch(1).await();}
}
/**
* 真正的实现类
*/
public class DemoServiceImpl implements DemoService {private static final Logger logger = LoggerFactory.getLogger(DemoServiceImpl.class);
@Override
public HelloReply sayHello(HelloRequest request) {logger.info("Hello" + request.getName() + ", request from consumer:" + RpcContext.getContext().getRemoteAddress());
return HelloReply.newBuilder()
.setMessage("Hello" + request.getName() + ", response from provider:"
+ RpcContext.getContext().getLocalAddress())
.build();}
@Override
public CompletableFuture<HelloReply> sayHelloAsync(HelloRequest request) {return CompletableFuture.completedFuture(sayHello(request));
}
}
总结一下
过程间的通信能够间接应用应用层的协定如 HTTP、也能够基于 TCP 自定义应用层的协定,然而对于面向对象的高级语言来说,数据接过来说还要有一个序列化过程,如果说是基于 TCP 的话,咱们还要思考拆包的问题,咱们都喜爱的货色,咱们是否屏蔽两头的通信细节呢,两个过程的通信就像是调用各自的函数一样,这也就是 RPC,然而如果两个过程是用不同的语言编写的呢,为了语言中立,咱们引入 IDL,跨语言,然而通信还是要抉择应用层的协定,要么本人基于 TCP,要么基于已有的应用层协定,比如说 HTTP,然而当初曾经有高度成熟的 RPC 框架了,你不须要关怀那么多 HTTP 协定的通信细节、以及序列过程,在肯定的配置下,你能够实现像调本地函数一样,调另一个过程的函数,高度的封装。RPC 是在演进的过程中抉择了函数作为载体,这是为了屏蔽掉通信和序列化的细节,而不是一开始就是就是以函数的模式呈现,实质上是一种通信协议。
参考资料
- 从原理到操作,让你在 Apache APISIX 中代理 Dubbo 服务更便捷 https://dubbo.apache.org/zh/b…
- gRPC 官网文档中文版 https://doc.oschina.net/grpc?…