在上一篇我们介绍了 Dubbo 有什么用 – 分布式背景下的服务间远程通信以及服务治理。那么,本篇我们就来看看如何使用 Dubbo 框架实现服务间通信。
Dubbo 是基于 rpc 协议的服务调用,且一般以 tcp 协议实现,因此在实际开发中只用写完 service 层就能发布服务;若以 http 为基础暴露的服务,则需要写完 controller 层。下面来看一个订单服务调用支付服务
的简单示例…
PS:Dubbo 能进行远程调用的实质封装了 netty,提供方相当于 netty 服务端,而消费方相当于 netty 的客户端。
1.Provider:支付服务
首先就是服务提供方 Provider,负责提供(发布)支付服务。
这里我们创建的是一个聚合工程,其中 pay-api 是要提供的服务,也可以说是对外暴露的接口,即服务。这里把它单独抽象成一个模块也很好理解,因为 Provider 和 Consumer 需要做的事情都是面向接口编程,提供方和调用方都要依赖。
- Provider 实现接口,发布服务(可以有多个 Consumer 去消费)
- Consumer 使用接口,通过 url 远程调用服务
然后,将 pay-api 服务 install 到本地,以便给提供方和调用方依赖。
注:聚合工中,父项目(pay-service)也要 install ,其子模块(pay-api)才能被外调。
下面就到真正需要用到 Dubbo 的核心模块了,pay-provider:
1)引入依赖
<dependencies>
<!-- 服务接口信息-->
<dependency>
<groupId>com.xupt.yzh</groupId>
<artifactId>pay-api</artifactId>
</dependency>
<!-- 通过 Dubbo 实现服务远程通信-->
<!-- 对于 Provider 而言,通过 Dubbo 实现服务发布 -->
<!-- 注:里面还包含了Spring相关jar包 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.2</version>
</dependency>
</dependencies>
注:引入的 dubbo 依赖还包含了 Spring 相关jar包,所以使用 Dubbo 默认都是基于 Spring 的。
2)实现上面的服务(接口)
/**
*服务提供方
*/
public class PayServiceImpl implements IPayService{
@Override
public String pay(String info) {
System.out.println("execute pay: " + info);
return "Hello Dubbo : " + info;
}
}
3)创建配置文件发布服务
注:这里配置文件一定要是 META-INF/spring.application.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://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<!-- xmlns用于声明当前spring的xml的命名空间 -->
<!-- 注:不同类型的xml有不同的文件头-->
<!-- dubbo:application 提供方应用信息,是服务跟踪监控依据 -->
<!-- 该标签下提供了name owner organization等-->
<dubbo:application name="pay-service"/>
<!-- dubbo:registry 注册中心的相关信息-->
<!-- 注:若不配置注册中心直接调用address=N/A-->
<!-- 若用注册中心则要引入其相应客户端jar包(如curator),并且url前要加上zookeeper://-->
<!-- 这里我们先不使用注册中心 -->
<dubbo:registry address="N/A"/>
<!-- dubbo:protocol 以什么协议发布服务-->
<!-- name具体协议(一般采用dubbo协议),port服务发布端口-->
<!-- 注:只有要发布的服务才需要定义协议,即 Consumer 不需要该配置-->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- dubbo:service 服务相关信息 -->
<!-- interface是服务名(接口),ref是具体的服务Bean-->
<!-- 注:只要服务发布方,才需要定义服务名与服务Bean-->
<dubbo:service interface="com.xupt.yzh.IPayService" ref="payServic"/>
<bean id="payServic" class="com.xupt.yzh.PayServiceImpl"/>
</beans>
4)启动服务
/**
*启动入口
*/
public class App {
public static void main(String[] args) throws IOException {
// 加载配置文件,即将相应服务 Bean实例化
ClassPathXmlApplicationContext classPathXmlApplicationContext =
new ClassPathXmlApplicationContext(
new String[]{"META-INF.spring/application.xml"});
classPathXmlApplicationContext.start();
System.in.read(); // 主线程等待,持续服务
}
}
=> 启动后会返回对应服务的url,会将所有配置信息都包含进去:
dubbo://192.168.56.1:20880/com.xupt.yzh.IPayService?anyhost=true // 该服务的url,调用方就通过这个URL去获取服务
url 后面 &
拼接的是一些配置信息(因为 dubbo 是url驱动):
&application=payservice
&bean.name=com.xupt.yzh.IPayService
&bind.ip=192.168.56.1
&bind.port=20880&deprecated=false
&dubbo=2.0.2
&dynamic=true
&generic=false
&interface=com.xupt.yzh.IPayService
&methods=pay
&pid=17924
®ister=true
&release=2.7.2
&side=provider
×tamp=1596016532558, dubbo version: 2.7.2, current host: 192.168.56.1
PS:所谓 url 驱动,简单来说就是服务的具体配置信息都在 URL 中,通过
&
拼接;当 Consumer 要远程调用服务时,可以根据实际情况在 URL 后面拼接(修改)配置参数;当请求到达 Provider 时,会根据 URL 中的配置参数提供服务。-- 原理是 SPI 自适应扩展点…
2.Consumer:订单服务
服务调用方是订单服务,调用支付服务提供结算支持,结构如下:
1) 引入依赖
<dependencies>
<!-- 引入服务接口 -->
<dependency>
<groupId>com.xupt.yzh</groupId>
<artifactId>pay-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 通过 Dubbo 实现服务远程通信-->
<!-- 对于 Consumer 而言,通过 Dubbo 实现服务消费 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.2</version>
</dependency>
</dependencies>
2)创建配置文件引入服务
<?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://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo
http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
<dubbo:application name="order-service"/>
<dubbo:registry address="N/A"/>
<!-- dubbo:reference被引入的服务-->
<!-- interface是服务名,url是服务路径,id是对找到的服务Bean重新赋予的名-->
<!-- 注:当有注册中心时,就不需要url了-->
<dubbo:reference interface="com.xupt.yzh.IPayService" id="PayService"
url="dubbo://192.168.56.1:20880/com.xupt.yzh.IPayService"/>
</beans>
3)调用 Provider 提供的服务
/**
*服务调用方
*/
public class App {
public static void main(String[] args) {
// 加载配置文件,即将相应服务 Bean实例化
ClassPathXmlApplicationContext classPathXmlApplicationContext =
new ClassPathXmlApplicationContext(
new String[]{"META-INF.spring/application.xml"});
// 拿到服务Bean
IPayService payService = (IPayService)classPathXmlApplicationContext.getBean("PayService");
String res = payService.pay("Test");
System.out.println(res);
}
}
同时 Provider 也收了调用信息:
3.DubboMain启动
Dubbo 也可以直接通过 Main.main(args) 启动服务,比如我们通过该方式启动 Provider
public class App {
public static void main(String[] args) {
Main.main(args); // org.springframework.context.support.ClassPathXmlApplicationContext
}
}
结果如下:
1.三种容器
正常情况下,我们会认为服务的发布,需要 tomcat、或者 jetty 这类的容器支持,但是只用 Dubbo 以后,我们并不需要这样重的服务器去支持,同时也会增加复杂性和浪费资源。Dubbo 提供了几种容器让我们去启动和发布服务
- Spring Container:自动加载 META-INF/spring 目录下的所有 Spring 配置。
- logback Container:自动装配 logback 日志
- Log4j Container:自动配置 log4j 的配置
Dubbo 提供了一个 Main.main 快速启动相应的容器,默认情况下,只会启动 spring 容器。
2.原理分析
启动 spring 容器本质上就是加载 IOC 容器,然后启动 一个 netty 服务实现服务的发布,所以并没有特别多的黑科技,下面是 spring容器启动的代码:
// 配置文件默认路径,写死
// 所以,前面我才特别强调配置文件要放在 resource/META-INF/ 目录下
public static final String DEFAULT_SPRING_CONFIG = "classpath*:META-INF/spring/*.xml";
public void start() {
String configPath = ConfigUtils.getProperty(SPRING_CONFIG);
if (StringUtils.isEmpty(configPath)) {
configPath = DEFAULT_SPRING_CONFIG;
}
// 加载Spring容器
context = new ClassPathXmlApplicationContext(configPath.split("[,\\s]+"), false);
context.refresh();
context.start();
}
本篇相关代码我放到 GitHub 上了,有需要参考的同学点击这里跳转…