学习 Dubbo 之前我们有必要先来了解一下互联网技术架构的演变过程及通信方式,方便我们搞清楚为什么需要使用基于 RPC 思想的系列框架。
通俗地讲,“单体应用(monolith application)”就是将应用程序的所有功能都打包成一个独立的单元。当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
开发简单:一个 IDE 就可以快速构建单体应用;
便于共享:单个归档文件包含所有功能,便于在团队之间以及不同的部署阶段之间共享;
易于测试:单体应用一旦部署,所有的服务或特性就都可以使用了,这简化了测试过程,因为没有额外的依赖,每项测试都可以在部署完成后立刻开始;
容易部署:整个项目就一个 war 包,Tomcat 安装好之后,应用扔上去就行了。群化部署也很容易,多个 Tomcat + 一个 Nginx 分分钟搞定。
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。
开发成本低,架构简单;
避免单体应用的无限扩大;
系统拆分实现了流量分担,解决了并发问题;
可以针对不同系统进行扩容、优化;
方便水平扩展,负载均衡,容错率提高;
不同的项目可采用不同的技术;
系统间相互独立。
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心。当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。
P.S. 从软件设计的角度上来说,ESB 是一个抽象的间接层,提取了服务调用过程中调用与被调用动态交互中的一些共同的东西,减轻了服务调用者的负担。Java 编程思想里提到:“所有的软件设计的问题都可以通过增加一个抽象的间接层而得到解决或者得到简化!”简单来说 ESB 就是一根管道,用来连接各个服务节点。为了集成不同系统,不同协议的服务,ESB 做了消息的转化解释和路由工作,让不同的服务互联互通。
分享两个小故事,帮助大家更好的理解 SOA 与微服务的区别。
故事一:
很久以前的一天,Martin 在跟好友的交流中悟到了一种很棒的架构设计。他总结了一下,然后告诉了好友,好友说,这不是新鲜东西,早有人总结了,叫做 SOA。
Martin 很高兴,开始在公司内外推广 SOA。结果,不停有人质疑和挑战他。
你这不是 SOA 吧?SOA 这里应该是如此这般的。对,这里我对 SOA 的理解是这样的。你看,这本 SOA 的书说的和你说的有出入。粒度?SOA 没有谈到这个呀,你这不是 SOA。分层跟 SOA 没有关系,你为什么要说这个呢?…
Martin 没办法,心一横,老子就叫它 Martin’s SOA。老子发明的词,老子就是最高权威,有最终解释权。还有谁不服?
同事们一看,这思想本身很不错,值得推广,但叫 Martin’s SOA 太没品了吧?还是要取个好一点的名字,最好还能跟 SOA 有某种暗示传承。干脆就叫 Microservices 好了,又新,又有服务含在其中。
Martin 忿忿地接受了这个建议,心里想着:妈的,明明就是 SOA,一群**非要逼我取个新名字。
后来 Martin 发现每次提一个东西就会收到旧恶傻势力对解释权的挑战,所以每次要提一个什么概念,都去发明一个新词,免得一群人在那一边质疑挑战,一边大谈“我的理解”。
这就是微服务、敏捷、精益企业、持续集成、持续交付背后的故事。
一个名词产生之后,命名者的解释权就会随着时间而弱化(比如 Cooper 发明的 Persona 就被无数设计师乱用)。敏捷已经有点烂了,等微服务也烂了,我们还会发明新词。
实在没辙,都是被逼的啊。
故事二:
话说1979年,又是一个春天,莆田乡下的赤脚医生吴大牛被改革的春风吹的心潮澎湃,说干就干,吴大牛趁着夜色朦胧找大队支书汇报了汇报思想,第二天就承包了村卫生室,开启了自己的在医疗圈的传奇历程。
乡村诊所大家都知道,没什么复杂的东东,房子只有一间,一个大柜台中间隔开,一半是诊疗兼候诊区,一半是药房,看病就直接找医生,如果前面有人就自己找个位子坐下,排队等一会,秩序倒也井然,看完病了医生直接给抓药,然后下一个继续,也不需要护士和药剂师,吴大牛一个人全部包办。
辛辛苦苦忙碌了十年,时间来到了八九年,又是一个春天,昔日的单身汉吴大牛已成为十里八乡的知名人物,媳妇娶上了不说,家里还增加了一对双胞胎儿子,二层的小洋房也甚是气派。可是也有烦心事,尽管乡村诊所扩大到了两间,媳妇还偶尔能去帮帮忙,但是医生还是只有自己一个,天天从早忙到晚挣的都是一份钱,想多挣点怎么办?吴大牛日思夜想,还真给他想出来一招,怎么办,扩大规模,多招几个医生一起干。原来吴大牛只能治头疼脑热和跌打损伤,现在新招了一个医科大学的毕业生刘小明专治感冒发烧,又从邻村请来了老大夫李阿花专治妇科病,现在一个普通的小诊所就变成了有三个独立科室加一个公共药房(吴大牛媳妇负责)的小医院了,吴大牛是外科主任兼院长,收入那可比之前翻了三番。人逢喜事精神爽,大牛院长请县里的书法名家为新医院书写了牌匾–“博爱医院”,挑了一个黄道吉日正式挂了上去。
一晃十年过去了,又是一个春天,吴大牛的博爱医院已经发展到了内科外科妇科五官科骨科生殖科六个科室,每个科室3到5名医生不等,也耗费巨资购进了血液化验B超等先进仪器,大牛院长也早已脱离了医疗一线,成为了专职的管理者,但是医院的大事小事大家都找他,就这三十多号员工搞的他每天是焦头烂额,想再扩大规模实在是有心无力了。要说还是大学生有水平,老部下刘小明给大牛院长献了一计,把各个科室独立出去,让各个科室主任自己管理,大牛院长只管科室之间的协调和医院发展的大事,这样既能调动基层的积极性,又能把大牛院长解放出来扩大生产抓大事谋大事,岂不妙哉?就这样,博爱医院的新一轮改革轰轰烈烈的展开了。
又是一个十年,又是一个春天,大牛院长已成为本地知名的企业家,博爱医院也发展到了二十三个科室数百名员工,发展中也出现了新问题,由于各个科室独立挂号、收费、化验,有的科室整天忙忙碌碌效益好,有的科室就相对平庸些,连分到的各种检查仪器都不能满负荷运行,整个医院养了不少闲人。这时候大牛院长视野也开阔了,请来了管理专家进行了顶层设计,把原来分散到各个科室的非核心服务全部收归集中管理,把原来二十三个挂号窗口整合为十个,二十三个收费窗口整合为八个,集中布设在一楼大厅为全院服务,还把分散在各个科室的检查仪器集中起来成立独立的检验科,也为全院服务,这样人人有活干,整个医院的服务能力又上了一个新台阶,这轮改革后博爱医院通过了各级部门的鉴定成为了远近驰名的三甲医院,吴大牛也摇身一变成为了博爱集团的CEO兼董事长,下一步就准备IPO上市了。
说到这里大家可能有点糊涂,这个跟微服务有嘛关系?大牛诊所的1.0阶段就相当于软件开发的单体结构,一个程序员打天下,从头编到尾,很难做大做强。大牛诊所的2.0阶段就相当于软件开发的垂直结构,各科室按照业务划分,很容易横向扩展。博爱医院的1.0阶段就相当于软件开发的SOA结构,除了药房(数据库)外各个服务独立提供(科室主任负责),但需要大牛院长(ESB总线)来协调。博爱医院的2.0阶段就相当于软件开发的微服务结构,公共服务院内共享,科室主任管理功能弱化(只管医生业务),优点是扩容方便,哪个部门缺人直接加,不用看上下游,资源利用率高,人员和设备效率高。
微服务就是将一个单体架构的应用按业务划分为一个个的独立运行的程序即服务,它们之间通过 HTTP 协议进行通信(也可以采用消息队列来通信,如 RabbitMQ,Kafaka 等),可以采用不同的编程语言,使用不同的存储技术,自动化部署(如 Jenkins)减少人为控制,降低出错概率。服务数量越多,管理起来越复杂,因此采用集中化管理。例如 Eureka,Zookeeper 等都是比较常见的服务集中化管理框架。
**微服务是一种架构风格,架构就是为了解耦,实际使用的是分布式系统开发。**一个大型的复杂软件应用,由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好的完成该任务。
一句话总结:微服务是 SOA 发展出来的产物,它是一种比较现代化的细粒度的 SOA 实现方式。
随着互联网的发展,应用程序从单机走向分布式,通信方式也产生了很多的变化。
都是传输协议,主要区别是 TCP 协议连接需要 3 次握手,断开需要四次挥手,是通过流来传输的,就是确定连接后,一直发送信息,传完后断开。UDP 不需要进行连接,直接把信息封装成多个报文,直接发送。所以 UDP 的速度更快,但是不保证数据的完整性。
一句话总结:最古老且最有效,永不过时,学习成本高。所有通信方式归根结底都是 TCP/UDP。
WebService(SOA,SOAP,WSDL,UDDI,XML)技术, 能使得运行在不同机器上的不同应用无须借助附加的、专门的第三方软件或硬件, 就可相互交换数据或集成。依据 WebService 规范实施的应用之间, 无论它们所使用的语言、 平台或内部协议是什么, 都可以相互交换数据。
WebService 就是一种跨编程语言和跨操作系统平台的远程调用技术。WebService 交互的过程就是遵循 SOAP 协议通过 XML 封装数据,然后由 Http 协议来传输数据。
一句话总结:基于 HTTP + XML 的标准化 Web API。
Representational State Transfer,表现层状态转移。互联网通信协议 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转移"。
客户端用到的手段,只能是 HTTP 协议。具体来说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源(也可以用于更新资源),PUT 用来更新资源,DELETE 用来删除资源。
一句话总结:基于 HTTP + JSON 的标准化 Web API。
Remote Method Invocation,远程方法调用。Java 中实现的分布式通信协议,它大大增强了 Java 开发分布式应用的能力。通过 RMI 技术,某一个本地的 JVM 可以调用存在于另外一个 JVM 中的对象方法,就好像它仅仅是在调用本地 JVM 中某个对象方法一样。
一句话总结:基于 Java 语言的分布式通信协议。
Java Message Service,Java 消息服务应用程序接口,是一个 Java 平台中关于面向消息中间件的 API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。绝大多数 MQ 都对 JMS 提供支持,如 RabbitMQ、ActiveMQ、Kafka、RocketMQ 以及 Redis 等。
一句话总结:JavaEE 消息框架标准。
Remont Proceduce Call,远程过程调用。它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。RPC 只是一个概念,它不是一个协议也不是一个框架。
RPC 的具体实现可以使用 RMI 或 RESTful 等,但一般不用,因为 RMI 不能跨语言,RESTful 效率太低。
RPC 多用于服务器集群内部通信,因此常使用更加高效、短小精悍的传输模式以提高效率。RPC 框架有很多:Apache Thrift、Apache Dubbo、Google Grpc 等。
一句话总结:解决分布式系统中,服务之间的调用问题。远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
主要就是因为在几个进程内(应用分布在不同的机器上),无法共用内存空间,比如不同的系统之间的通讯,甚至不同组织之间的通讯。此外由于机器的横向扩展,需要在多台机器组成的集群上部署应用等等。
比如现在有两台机器:A 机器和 B 机器,并且分别部署了应用 A 和应用 B。假设此时位于 A 机器上的 A 应用想要调用位于 B 机器上的 B 应用提供的函数或是方法,由于 A 应用和 B 应用不在一个内存空间里面,所以不能直接调用,此时就需要通过网络来表达调用的方式和传输调用的数据。也即所谓的远程调用。
一次完整的 RPC 调用流程包含了四个核心部分,分别是 Client
,Server
,Client Stub
以及 Server Stub
,这个 Stub 大家可以理解为存根。分别说说这几个部分:
客户端(Client):服务的调用方。
服务端(Server):服务的提供方。
客户端存根:存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
服务端存根:接收客户端发送过来的消息,将消息解包,并调用本地的方法。
RPC 的目标是要把 2、3、4、5、7、8、9、10 这些步骤都封装起来。
解决通讯的问题,主要是通过在客户端和服务器之间建立 TCP 连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
A 服务器上的应用怎么告诉底层的 RPC 框架,如何连接到 B 服务器(如主机或 IP 地址)以及特定的端口,方法的名称是什么,这样才能完成调用。比如基于 Web 服务协议栈的 RPC,就要提供一个 endpoint URI,或者是从 UDDI(一种目录服务,通过该目录服务进行服务注册与搜索)服务上查找。如果是 RMI 调用的话,还需要一个 RMI Registry 来注册服务的地址。
A 服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如 TCP 传递到 B 服务器,由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,也就是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给 B 服务器。
B 服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后再找到对应的方法(寻址的一部分)进行本地调用(一般是通过生成代理 Proxy 去调用,通常会有 JDK 动态代理、CGLIB 动态代理、Javassist 生成字节码技术等),之后得到调用的返回值。
B 机器进行本地调用(通过代理 Proxy)之后得到了返回值,此时还需要再把返回值发送回 A 机器,同样也需要经过序列化操作,然后再经过网络传输将二进制数据发送回 A 机器,而当 A 机器接收到这些返回值之后,则再次进行反序列化操作,恢复为内存中的表达方式,最后再交给 A 机器上的应用进行相关处理(一般是业务逻辑处理操作)。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>rmi-demoartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<modules>
<module>rmi-apimodule>
<module>rmi-servermodule>
<module>rmi-clientmodule>
modules>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>11maven.compiler.source>
<maven.compiler.target>11maven.compiler.target>
properties>
project>
这个工程主要是存放 client 和 server 都会用到的公共接口。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rmi-demoartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>rmi-apiartifactId>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
dependency>
dependencies>
project>
由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,所以实体类需要实现 Serializable
接口。
package org.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 2159427410483687648L;
private Integer id;
private String username;
}
使用 JavaRMI 对外暴露的服务接口必须继承 java.rmi.Remote.Remote
类,方法必须抛出 java.rmi.RemoteException
异常。
package org.example.service;
import org.example.pojo.User;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* 用户管理服务
*/
public interface UserService extends Remote {
User selectUserById(Integer userId) throws RemoteException;
}
主要提供服务接口的实现以及 RMI 的服务配置。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rmi-demoartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>rmi-serverartifactId>
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>rmi-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
服务接口实现必须继承 java.rmi.server.UnicastRemoteObject
类。
package org.example.service.impl;
import org.example.pojo.User;
import org.example.service.UserService;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
/**
* 用户管理服务
*/
public class UserServiceImpl extends UnicastRemoteObject implements UserService {
public UserServiceImpl() throws RemoteException {
}
@Override
public User selectUserById(Integer userId) throws RemoteException {
System.out.println("用户管理服务接收到客户端请求,请求参数 userId = " + userId);
// 模拟假数据返回
return new User(userId, "张三");
}
}
将服务发布在指定的 IP + 端口上。
package org.example;
import org.example.service.UserService;
import org.example.service.impl.UserServiceImpl;
import java.net.MalformedURLException;
import java.rmi.AlreadyBoundException;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
/**
* 发布服务
*/
public class Publish {
public static void main(String[] args) throws RemoteException {
UserService userService = new UserServiceImpl();
try {
// 对外暴露的服务端口
LocateRegistry.createRegistry(8888);
// 对外暴露的服务地址
Naming.bind("rmi://localhost:8888/userService", userService);
System.out.println("服务发布成功!");
} catch (AlreadyBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
本地 client 如何实现调用远程接口。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rmi-demoartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>rmi-clientartifactId>
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>rmi-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
通过指定的 IP + 端口进行远程服务调用。
package org.example.controller;
import org.example.pojo.User;
import org.example.service.UserService;
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class UserController {
public static void main(String[] args) {
try {
UserService userService = (UserService) Naming.lookup("rmi://localhost:8888/userService");
User user = userService.selectUserById(1);
System.out.println("远程服务调用成功,返回值信息为:" + user);
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
通过测试可以看到 RPC 基于 RMI 的远程服务调用已经完成,接下来我们一起学习一下如何使用 RPC 框架 Dubbo 来完成远程服务调用。
一个典型 RPC 框架使用场景中,包含了服务注册与发现(注册中心)、负载均衡、容错、网络传输、序列化等组件,其中“RPC 相关协议”就指明了程序如何进行网络传输和序列化。RPC 框架有很多:Apache Thrift、Apache Dubbo、Google Grpc 等。下图为完整的 RPC 框架架构图:
官网:http://dubbo.apache.org/zh-cn/
Github:https://github.com/apache/dubbo
2018 年 2 月 15 日,阿里巴巴的服务治理框架 dubbo 通过投票,顺利成为 Apache 基金会孵化项目。
Apache Dubbo 是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用
,智能容错和负载均衡
,以及服务自动注册和发现
。
Dubbo 提供三个核心功能:面向接口的远程方法调用
、智能容错和负载均衡
,以及服务自动注册和发现
。Dubbo 框架广泛的在阿里巴巴内部使用,以及当当、去哪儿、网易考拉、滴滴等都在使用。
节点 | 角色说明 |
---|---|
Provider |
暴露服务的服务提供方 |
Consumer |
调用远程服务的服务消费方 |
Registry |
服务注册与发现的注册中心 |
Monitor |
统计服务的调用次数和调用时间的监控中心 |
Container |
服务运行容器 |
下面我们基于 SpringBoot 环境整合 Dubbo 完成一个入门案例。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.examplegroupId>
<artifactId>dubbo-demoartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<modules>
<module>dubbo-apimodule>
<module>dubbo-providermodule>
<module>dubbo-consumermodule>
modules>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.0.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>2.7.4.1version>
dependency>
dependencies>
project>
这个工程主要是存放 provider 和 consumer 都会用到的公共接口。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubbo-demoartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>dubbo-apiartifactId>
<dependencies>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
dependencies>
project>
由于网络协议是基于二进制的,内存中的参数的值要序列化成二进制的形式,所以实体类需要实现 Serializable
接口。
package org.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 2159427410483687648L;
private Integer id;
private String username;
}
package org.example.service;
import org.example.pojo.User;
/**
* 用户管理服务
*/
public interface UserService {
User selectUserById(Integer userId);
}
主要提供服务接口的实现以及服务提供者的服务配置。
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubbo-demoartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>dubbo-providerartifactId>
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>dubbo-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
配置文件需要配置服务提供者应用信息,配置注册中心及暴露服务的地址,还有采用什么样的协议及协议端口和扫描服务接口的包地址。
server:
port: 7070 # 端口
# Dubbo 配置
dubbo:
# 提供方应用信息,用于计算依赖关系
application:
name: product-service
# 配置注册中心
registry:
address: multicast://224.5.6.7:1234 # 使用 Multicast 注册中心暴露服务地址
# 用 dubbo 协议在 20880 端口暴露服务
protocol:
name: dubbo # 协议名称
port: 20880 # 协议端口
# 扫描需要暴露的服务
scan:
base-packages: org.example.service
package org.example.service.impl;
import org.example.pojo.User;
import org.example.service.UserService;
import org.apache.dubbo.config.annotation.Service;
/**
* 用户管理服务
* timeout 调用该服务的超时时间
* version 为版本号
* group 为分组
* interface、group、version 三者可确定一个服务
* parameters = {"unicast", "false"}
* 建议服务提供者和服务消费者在不同机器上运行,
* 如果在同一机器上,需设置 unicast = false 禁用单播订阅,只有 multicast 注册中心有此问题。
*/
@Service(timeout = 5000, version = "1.0", group = "user-provider", parameters = {
"unicast", "false"})
public class UserServiceImpl implements UserService {
@Override
public User selectUserById(Integer userId) {
System.out.println("用户管理服务接收到客户端请求,请求参数 userId = " + userId);
// 模拟假数据返回
return new User(userId, "张三");
}
}
注意:
parameters = {"unicast", "false"}
:建议服务提供者和服务消费者在不同机器上运行,如果在同一机器上,需设置 unicast = false 禁用单播订阅,只有 multicast 注册中心有此问题。
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 扫描需要暴露的服务,如果配置文件中已声明则无需添加该注解
// @EnableDubbo(scanBasePackages = "org.example.service")
@SpringBootApplication
public class DubboProviderApplication {
public static void main(String[] args) {
SpringApplication.run(DubboProviderApplication.class, args);
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dubbo-demoartifactId>
<groupId>org.examplegroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>dubbo-consumerartifactId>
<dependencies>
<dependency>
<groupId>org.examplegroupId>
<artifactId>dubbo-apiartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
project>
配置文件需要配置服务消费者应用信息,配置注册中心用于发现注册中心暴露的服务。
server:
port: 9090 # 端口
# Dubbo 配置
dubbo:
# 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样
application:
name: dubbo-consumer
# 配置注册中心
registry:
address: multicast://224.5.6.7:1234 # 发现 Multicast 注册中心暴露的服务
package org.example.controller;
import org.apache.dubbo.config.annotation.Reference;
import org.example.pojo.User;
import org.example.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
// dubbo 提供了 @Reference 注解,可替换 @Autowired 注解,用于引入远程服务
// 如果注册服务时设置了版本及分组信息,调用远程服务时也要设置对应的版本及分组信息
@Reference(timeout = 5000, version = "1.0", group = "user-provider", parameters = {
"unicast", "false"})
private UserService userService;
@GetMapping("/{id}")
public User selectUserById(@PathVariable("id") Integer id) {
return userService.selectUserById(id);
}
}
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DubboConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(DubboConsumerApplication.class, args);
}
}
启动服务提供者和服务消费者,访问:http://localhost:9090/consumer/1 结果如下:
dubbo:application
:应用程序名称dubbo:registry
:连接注册中心信息(配置注册中心)dubbo:protocol
:服务提供者注册服务采用的协议
dubbo:service
:声明需要暴露的服务接口dubbo:reference
:配置订阅的服务(生成远程服务代理)更多配置信息请参考:http://dubbo.apache.org/zh-cn/docs/user/references/xml/introduction.html
下一篇我们讲解 Dubbo 支持的注册中心,Dubbo 负载均衡策略和 Dubbo 控制台的安装,记得关注噢~
本文采用 知识共享「署名-非商业性使用-禁止演绎 4.0 国际」许可协议
。
大家可以通过 分类
查看更多关于 Dubbo
的文章。
您的点赞
和转发
是对我最大的支持。
扫码关注 哈喽沃德先生
「文档 + 视频」每篇文章都配有专门视频讲解,学习更轻松噢 ~