
介绍
什么是分布式应用?
简单点说就是一个完整的应用被拆分成了多个应用,每个拆出来的应用都可以单独部署,每个应用负责原来那个完整应用的一部分功能,只有当这些应用全部提供服务时才是完整的。这其实很好理解,举个例子,比如某商城系统被划分成了营销子系统、订单子系统和会员子系统,在这里面每个子系统有其固有职责,对于整个商城系统来说是不可缺少的一部分,通常这种划分也被称为垂直拆分。
什么是微服务应用?
其实所谓微服务应用算是分布式应用的一种特殊形式,它们之间区别不大,相同点很多。唯一比较明显的区分是前者对系统拆分的更加细,更加注重拆分后的子系统(也称为服务)功能复用,职责非常单一,小团队可维护。另外一点是注重DevOps的工作流,让服务的开发、迭代、测试和部署更加自动化,极大提高项目交付效率。
即使是在同一个系统里面,因为依据不同的拆分原则,最终得到的微服务可能不一致,这和站的角度、业务特点以及架构思路有关。实际工作中有些人把握不好原则,容易把服务拆分的过细导致维护非常麻烦,也有拆分的过大而不能复用已有的模块。虽然业界也没有完全统一的结论,不过也有一些原则被总结出来,有兴趣的小伙伴可以在网络上看看相关的文章。
什么是Dubbo?
Apache Dubbo |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。

dubbo最初由阿里内部开源,后来因各种原因停止更新了很长时间,现在终于开始重新维护了,并把这个项目捐赠给了Apache基金会,目前处于孵化阶段。
为什么需要Dubbo?
为什么会诞生dubbo肯定是有原因的,看看下面这张图:

随着互联网的不断发展,各种系统应用的规模不断扩大,常规的单体架构以及垂直应用架构已无法应对,慢慢开始往分布式服务架构以及流动计算架构势演进,这个时候就需要一个治理系统确保架构有条不紊的演进,基于此dubbo就应运而生了。
dubbo的官方文档对这块有详细介绍,可以链接进去看看。
Dubbo的应用场景
从它诞生的背景就可以知道,单体架构等小型系统肯定不适用,过度的架构设计、引入新技术只会把项目做死,因为项目没有到一定规模是没有能力去承担它的副作用的,它带来的是系统结构复杂,对开发人员水平以及硬件性能有高要求。
dubbo是天生为分布式应用提供支持的,不管是所谓SOA的应用,还是现在流行的微服务应用。它是应用框架的基础组件,对你用在什么业务上是不敏感的,也就说基本上没有业务限制。
应用示例
下面通过一个小的电商系统演示如何使用dubbo,为了保证演示效果,项目中的代码是失真的。
需求
- 用户在访问某在线商城,将某件商品添加到购物车,并在购物车中进行结算。
- 商城根据结算请求生成订单,并把结果反馈给用户。
方案设计
服务设计
为了演示简单,我们创建三个服务,每个服务可以单独维护和部署,可以根据访问压力动态扩容(指每个服务可以多节点部署)。

业务设计
调用关系:购物车服务调用会员服务获取用户信息,调用订单服务生成订单。
为了演示简单专注使用dubbo,业务上的东西本次设计的简单些,因为这个并不是重点。
接口设计
根据上面的业务要求我们设计了两个远程接口,分别需要会员和订单服务提供支持。
序号 |
接口名称 |
接口描述 |
请求参数 |
响应参数 |
1 |
genOrder |
生成订单 |
OrderReq |
OrderGenResultResp |
2 |
getUserInfoById |
根据用户编号获取对应信息 |
UserInfoReq |
UserInfoResp |
构建项目
本次演示所用框架:
软件名称 |
版本 |
构建工具 |
gradle5 |
Dubbo |
2.7.2 |
Spring boot |
2.1.6.RELEASE |
项目结构和代码比较简单,下面基本上会贴出所有代码。
构建接口项目
为项目维护所有服务开放接口,有调用需求的服务必须引入本项目。
build.gradle
本项目作为其它服务的依赖jar包,主要用来定义接口、请求和响应对象模型。
1 2 3 4 5 6 7 8 9 10
| plugins { id 'java' }
group 'com.tianmingxing.example' version '1.0-SNAPSHOT'
dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' }
|
OrderService.java
订单服务开放接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.tianmingxing.dubbo.example.api.order;
public interface OrderService {
public OrderGenResultResp genOrder(OrderReq req); }
|
OrderReq.java
接口请求参数封装成对象模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.tianmingxing.dubbo.example.api.order;
import com.tianmingxing.dubbo.example.api.user.UserInfoResp;
import java.io.Serializable;
public class OrderReq implements Serializable {
private UserInfoResp userInfoResp;
private String goodsId;
private Integer qty;
}
|
OrderGenResultResp.java
响应参数模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.tianmingxing.dubbo.example.api.order;
import java.io.Serializable;
public class OrderGenResultResp implements Serializable {
private String orderId;
private boolean isSuccess;
}
|
UserInfoService.java
会员服务开放接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package com.tianmingxing.dubbo.example.api.user;
public interface UserInfoService {
public UserInfoResp getUserInfoById(UserInfoReq req); }
|
UserInfoReq.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.tianmingxing.dubbo.example.api.user;
import java.io.Serializable;
public class UserInfoReq implements Serializable {
private Integer id;
public UserInfoReq(Integer id) { this.id = id; }
}
|
UserInfoResp.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.tianmingxing.dubbo.example.api.user;
import java.io.Serializable;
public class UserInfoResp implements Serializable {
private Integer id;
private String nickName;
private Integer age;
private String email;
}
|
构建会员服务
build.gradle
本服务依赖dubbo、spring boot框架,下面排除了内置tomcat依赖包,这样本服务不会启动web端口,因为没有直接与前端项目对接,只需要开放dubbo的端口供服务之间调用即可。
- 本项目采用dubbo协议,它由netty4提供tcp通信,使用hessian序列化传输对象,dubbo的注册中心、元数据中心均使用zookeeper。
- 如果你想更改上面的配置,记住要添加对应的依赖包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| plugins { id 'java' id 'org.springframework.boot' version '2.1.6.RELEASE' }
group 'com.tianmingxing.example' version '1.0-SNAPSHOT'
dependencies { testCompile( 'org.springframework.boot:spring-boot-starter-test:2.1.6.RELEASE', )
compile( project(':api'), 'org.springframework.boot:spring-boot-starter-web:2.1.6.RELEASE', 'org.apache.curator:curator-framework:4.2.0', 'org.apache.dubbo:dubbo-spring-boot-starter:2.7.1', 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.3', 'org.apache.dubbo:dubbo-configcenter-zookeeper:2.7.3', 'org.apache.dubbo:dubbo-config-spring:2.7.3', 'org.apache.dubbo:dubbo-rpc-dubbo:2.7.3', 'org.apache.dubbo:dubbo-remoting-netty4:2.7.3', 'org.apache.dubbo:dubbo-serialization-hessian2:2.7.3', 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.3', )
configurations { all { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' } } }
|
application.yml
配置RPC接口采用dubbo协议,使用ZK作为注册和元数据中心。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| spring: application: name: example-user dubbo: application: name: ${spring.application.name} protocol: name: dubbo port: 20881 server: netty host: 127.0.0.1 registry: client: curator address: zookeeper://127.0.0.1:2181 metadata-report: address: zookeeper://127.0.0.1:2181
|
Application.java
添加注解 @EnableDubbo
以启用dubbo并初始化框架。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.tianmingxing.dubbo.example.user;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.tianmingxing.dubbo.example.user") @EnableDubbo(scanBasePackages = "com.tianmingxing.dubbo.example.user") public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
UserInfoServiceImpl.java
服务提供者的实现,在api项目中定义了接口,在本服务中进行实现,本服务就是该接口的提供者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.tianmingxing.dubbo.example.user.api;
import com.tianmingxing.dubbo.example.api.user.UserInfoReq; import com.tianmingxing.dubbo.example.api.user.UserInfoResp; import com.tianmingxing.dubbo.example.api.user.UserInfoService; import org.apache.dubbo.config.annotation.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
@Service(version = "1.0.0") public class UserInfoServiceImpl implements UserInfoService {
private final static Logger log = LoggerFactory.getLogger(UserInfoServiceImpl.class);
@Override public UserInfoResp getUserInfoById(UserInfoReq req) { log.info("开始执行获取用户信息的业务。。。"); UserInfoResp userInfoResp = new UserInfoResp(); userInfoResp.setId(1); userInfoResp.setNickName("张三 zhan shan"); userInfoResp.setAge(32); userInfoResp.setEmail("mx.tian@qq.com"); return userInfoResp; } }
|
构建订单服务
build.gradle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| plugins { id 'java' id 'org.springframework.boot' version '2.1.6.RELEASE' }
group 'com.tianmingxing.example' version '1.0-SNAPSHOT'
dependencies { testCompile( 'org.springframework.boot:spring-boot-starter-test:2.1.6.RELEASE', )
compile( project(':api'), 'org.springframework.boot:spring-boot-starter-web:2.1.6.RELEASE', 'org.apache.curator:curator-framework:4.2.0', 'org.apache.dubbo:dubbo-spring-boot-starter:2.7.1', 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.3', 'org.apache.dubbo:dubbo-configcenter-zookeeper:2.7.3', 'org.apache.dubbo:dubbo-config-spring:2.7.3', 'org.apache.dubbo:dubbo-rpc-dubbo:2.7.3', 'org.apache.dubbo:dubbo-remoting-netty4:2.7.3', 'org.apache.dubbo:dubbo-serialization-hessian2:2.7.3', 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.3', )
configurations { all { exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' } } }
|
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| spring: application: name: example-order dubbo: application: name: ${spring.application.name} protocol: name: dubbo port: 20882 server: netty host: 127.0.0.1 registry: client: curator address: zookeeper://127.0.0.1:2181 metadata-report: address: zookeeper://127.0.0.1:2181
|
Application.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.tianmingxing.dubbo.example.order;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.tianmingxing.dubbo.example.order") @EnableDubbo(scanBasePackages = "com.tianmingxing.dubbo.example.order") public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
OrderServiceImpl.java
订单服务接口的具体实现者,也就是服务提供者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.tianmingxing.dubbo.example.order.api;
import com.tianmingxing.dubbo.example.api.order.OrderGenResultResp; import com.tianmingxing.dubbo.example.api.order.OrderReq; import com.tianmingxing.dubbo.example.api.order.OrderService; import org.apache.dubbo.config.annotation.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
@Service(version = "1.0.0") public class OrderServiceImpl implements OrderService {
private final static Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);
@Override public OrderGenResultResp genOrder(OrderReq req) { log.info("开始执行生成订单的业务。。。"); OrderGenResultResp orderGenResultResp = new OrderGenResultResp(); orderGenResultResp.setOrderId("SN2143253"); orderGenResultResp.setSuccess(true); return orderGenResultResp; } }
|
构建购物车服务
build.gradle
本服务没有排除掉内置tomcat的依赖,所以本服务会启动两个端口,分别是web端口用来给前端项目调用,另外dubbo端口用来在服务之间RPC调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| plugins { id 'java' id 'org.springframework.boot' version '2.1.6.RELEASE' }
group 'com.tianmingxing.example' version '1.0-SNAPSHOT'
dependencies { testCompile( 'org.springframework.boot:spring-boot-starter-test:2.1.6.RELEASE', )
compile( project(':api'), 'org.springframework.boot:spring-boot-starter-web:2.1.6.RELEASE', 'org.apache.curator:curator-framework:4.2.0', 'org.apache.dubbo:dubbo-spring-boot-starter:2.7.1', 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.3', 'org.apache.dubbo:dubbo-configcenter-zookeeper:2.7.3', 'org.apache.dubbo:dubbo-config-spring:2.7.3', 'org.apache.dubbo:dubbo-rpc-dubbo:2.7.3', 'org.apache.dubbo:dubbo-remoting-netty4:2.7.3', 'org.apache.dubbo:dubbo-serialization-hessian2:2.7.3', 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.3', 'org.hibernate.validator:hibernate-validator:6.0.17.Final', ) }
|
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| erver: port: 8082 spring: application: name: example-cart dubbo: scan: base-packages: com.tianmingxing.example.dubbo.cart.provider application: name: ${spring.application.name} protocol: name: dubbo port: 20880 server: netty host: 127.0.0.1 transporter: netty registry: client: curator address: zookeeper://127.0.0.1:2181 metadata-report: address: zookeeper://127.0.0.1:2181
|
Application.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.tianmingxing.dubbo.example.cart;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.tianmingxing.dubbo.example.cart") @EnableDubbo(scanBasePackages = "com.tianmingxing.dubbo.example.cart") public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
|
CartController.java
因为本服务需要和前端项目对接,所以提供了HTTP接口出来,下面定义的控制器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package com.tianmingxing.dubbo.example.cart.controller;
import com.tianmingxing.dubbo.example.api.order.OrderGenResultResp; import com.tianmingxing.dubbo.example.cart.model.CommonResp; import com.tianmingxing.dubbo.example.cart.model.SettlementReq; import com.tianmingxing.dubbo.example.cart.service.SettlementService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.validation.Valid;
@RestController @RequestMapping("cart") public class CartController {
private final static Logger log = LoggerFactory.getLogger(CartController.class);
@Resource private SettlementService settlementService;
@PostMapping("settlement") public CommonResp<OrderGenResultResp> settlement(@Valid SettlementReq req) { log.info("开始处理请求:/cart/settlement"); return settlementService.settlement(req); } }
|
SettlementReq.java
请求参数封装成了数据对象模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| package com.tianmingxing.dubbo.example.cart.model;
import javax.validation.constraints.NotNull;
public class SettlementReq {
@NotNull private Integer uid;
private String goodsId;
private Integer qty;
}
|
CommonResp.java
定义了一个统一的接口返回数据结构,用来响应给客户端一个始终统一的格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| package com.tianmingxing.dubbo.example.cart.model;
import java.io.Serializable; import java.time.LocalDateTime; import java.time.ZoneId;
public class CommonResp<T> implements Serializable {
private String timestamp;
private Integer code;
private String message;
private T data;
public CommonResp(T data) { this(0, null, data); }
public CommonResp(Integer code, String message) { this(code, message, null); }
public CommonResp(Integer code, String message, T data) { this.timestamp = LocalDateTime.now(ZoneId.of("Asia/Shanghai")).toString(); this.code = code; this.message = message; this.data = data; }
}
|
SettlementService.java
本类中演示了RPC远程调用其它接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package com.tianmingxing.dubbo.example.cart.service;
import com.tianmingxing.dubbo.example.api.order.OrderGenResultResp; import com.tianmingxing.dubbo.example.api.order.OrderReq; import com.tianmingxing.dubbo.example.api.order.OrderService; import com.tianmingxing.dubbo.example.api.user.UserInfoReq; import com.tianmingxing.dubbo.example.api.user.UserInfoResp; import com.tianmingxing.dubbo.example.api.user.UserInfoService; import com.tianmingxing.dubbo.example.cart.controller.CartController; import com.tianmingxing.dubbo.example.cart.model.CommonResp; import com.tianmingxing.dubbo.example.cart.model.SettlementReq; import org.apache.dubbo.config.annotation.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service;
@Service public class SettlementService {
private final static Logger log = LoggerFactory.getLogger(SettlementService.class);
@Reference(version = "1.0.0") private UserInfoService userInfoService; @Reference(version = "1.0.0") private OrderService orderService;
public CommonResp<OrderGenResultResp> settlement(SettlementReq req) { log.info("开始远程调用UserInfoService"); UserInfoResp userInfoResp = userInfoService.getUserInfoById(new UserInfoReq(req.getUid()));
OrderReq orderReq = new OrderReq(); orderReq.setUserInfoResp(userInfoResp); orderReq.setGoodsId(req.getGoodsId()); orderReq.setQty(req.getQty()); log.info("开始远程调用OrderService"); OrderGenResultResp orderGenResultResp = orderService.genOrder(orderReq);
log.info("cart服务处理结算完成,准备返回。"); return new CommonResp<>(0, "成功", orderGenResultResp); } }
|
测试
分别启动三个服务,使用postman或其它模拟http请求工具,请求购物车链接 http://localhost:8081/cart/settlement
,注意观察响应结果,同时查看控制台日志输出。
- 购物车服务有开启2个端口,分别是http端口以及dubbo的端口。
- 其它两个服务只开启了dubbo端口,在实际项目中根据需求来关闭http端口。
Dubbo Web控制台
介绍
由于服务接口会全部注册到ZK中,所以对接口的管理工具也是基于ZK来操作的,但手动操作是比较复杂和麻烦的,官方提供管理这些接口的WEB控制台,可以让我们非常方便的设置接口权重、负载和路由等功能。

安装
1 2 3 4 5
| git clone https://github.com/apache/dubbo-admin.git cd dubbo-admin mvn clean package cd dubbo-admin-distribution/target java -jar dubbo-admin-0.1.jar
|
使用
具体使用可以见官方文档。