前言
公司最近要做一个新的项目,打算使用分布式的架构,目前最火的大概是dubbo和springcloud两家,最后使用了dubbo作为分布式的框架,然而同事一直以来都是使用springboot,所以看了一下dubbo的官方文档,又在网上查了一下资料,把整个过程记录一下。
开发环境
操作系统:win10
IDE:IntelliJ IDEA Ultimate
必要工具:务必保证安装了jdk8或以上、maven
一、注册中心
Dubbo作为一个RPC框架,它能把各个微服务集中起来,靠的就是注册中心——即服务提供者(被调用)把自己的服务注册到注册中心,服务消费者(主动调用)去注册中心订阅。Dubbo支持的注册中心有很多:Multicast、Zookeeper、Nacos、Redis、Simple,官方推荐使用Zookeeper。
zookeeper下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.14/
下载成功后直接解压,到conf目录下把zoo_sample.cfg重命名为zoo.cfg,并且修改里面的内容dataDir的属性,这个属性是用来指定zookeeper的数据存放位置的。然后到bin目录下双击zkServer.cmd文件启动zookeeper服务,这时可以看到zookeeper服务启动成功,端口号是2181。
顺便说一下,因为我们还处于开发的阶段,所以注册中心暂时也不需要搭建成集群。但是注册中心作为dubbo中一个最重要的组件,为了保证服务的高可用,在生产环境必须要做集群,这里就不关注zookeeper集群搭建了,可以自行查阅相关资料。
二、场景模拟
为了更易于理解,我在这里模拟一个场景,比如说我们的电商系统,用户下单成功后想查看自己的订单,这个时候我们除了给用户返回订单的基本信息(比如订单号、下单时间、商品id,商品数量)之外,我们还应该返回商品的一些简略的信息,比如商品名,商品缩略图。其中商品名和商品缩略图的数据应该是属于商品详情服务的,与订单相关的数据才是订单服务的。那种在这个场景下,订单服务是服务消费者(主动调用),商品服务是服务提供者(被调用)。
有了这个场景,我们就可以开始了。说明一下,由于本文只是为了整合,所以就不连接数据库了,直接模拟一些数据即可。
三、创建项目
(1)创建空项目
在IDEA中新建项目,选择左侧最下面的Empty Project,填好项目名。创建好的project如下,什么也没有。
(2)创建api项目
在dubbo文档的最佳实践中,官方建议我们分包:
建议将服务接口、服务模型、服务异常等均放在 API 包中,因为服务模型和异常也是 API 的一部分,这样做也符合分包原则:重用发布等价原则(REP),共同重用原则(CRP)。
我们先创建一个api的module:File -> New -> Module...
选择左侧的maven,勾选Create from aom archetype,选中下面的org.apache.maven.archetypes:maven-archetype-quickstart
点击Next后填好GroupId和ArtifactId然后一直Next到完成,模块创建好之后把pom文件的<build>节点的内容删掉。ok,到此接口的项目就创建完成。
接下来是创建对象和接口。创建pojo包和service包,pojo下放一个Spu类,service放一个SpuService接口
Spu.java的代码如下:
public class Spu implements Serializable {
private String spuId;
private String spuName;
private String spuImage;
// 省略getter和setter方法
}
(记得一定要implements Serializable接口,不然会因为对象不能序列化而报错)
SpuService.java的代码如下:
public interface SpuService {
/**
* 根据商品id获取详情信息
* */
List<Spu> getSpuDetails(List<String> spuIds);
}
项目结构如下:
到此,api模块就完成了。
(2)创建spu-service-provider项目
同样,新建一个module,但是这次选择的是spring initializr,因为我们的服务提供者是一个springboot项目。然后我们需要在pom文件中添加两个依赖,第一个是我们的apimodule的依赖,另一个是dubbo的依赖,添加好之后的pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.spareyaya.mall</groupId>
<artifactId>spu-service-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spu-service-provider</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.spareyaya.mall</groupId>
<artifactId>mall-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>0.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
接下来需要实现接口
创建service.impl包,在该包下创建一个SpuServiceImpl类,代码如下:
package com.spareyaya.mall.spuserviceprovider.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.spareyaya.mall.pojo.Spu;
import com.spareyaya.mall.service.SpuService;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
@Service
public class SpuServiceImpl implements SpuService {
@Override
public List<Spu> getSpuDetails(List<String> spuIds) {
// 实际这里应该会根据spuIds的值去缓存或者数据库中查出相关商品的信息,
// 再封装好返回,这里只是为了演示,就直接返回两条假数据
List<Spu> spus = new ArrayList<>(2);
Spu spu = new Spu();
spu.setSpuId("1000");
spu.setSpuName("我是商品1000的商品名");
spu.setSpuImage("我是商品1000的商品缩略图的链接");
spus.add(spu);
spu = new Spu();
spu.setSpuId("1001");
spu.setSpuName("我是商品1001的商品名");
spu.setSpuImage("我是商品1001的商品缩略图的链接");
spus.add(spu);
return spus;
}
}
这里值得注意的是,类上的注解@Component是spring的注解,表示这是一个bean,而@Service是dubbo的注解,表示这是作为服务提供者对外暴露的服务接口。
最后在启动类加上@EnableDubbo注解开启dubbo
最后的最后,需要加上配置,在resources/application.properties中(如果习惯用yml文件的就是application.yml)加上相关配置:
dubbo.application.name=user-service-provider
dubbo.registry.protocol=zookeeper
dubbo.registry.address=127.0.0.1:2181
dubbo.protocol.name=dubbo
dubbo.protocol.port=20880
到此,服务提供者的项目也完成了。
(3)创建order-consumer项目
创建方法和spu-service-provider基本一致,不过因为我们还需要在服务消费者中接收请求,所以在创建项目的时候需要加上Spring Web的依赖,同样需要添加mall-api这个module和dubbo的依赖。在resources/application.properties中添加配置:
dubbo.application.name=order-consumer
dubbo.registry.protocol=zookeeper
dubbo.registry.address=127.0.0.1:2181
同样也需要在启动类添加@EnableDubbo注解。
接下来就可以调用提供者里的方法了。
创建pojo包和controller包,并在pojo包下创建Order类和OrderDetail类,在controller包下创建OrderController类,完成后项目结构和这三个类的代码如下:
Order.java
public class Order {
private String orderId;
private List<OrderDetail> orderDetail;
// 省略getter和setter方法
}
OrderDetail.java
public class OrderDetail {
private String orderId;
private String spuId;
private String spuName;
private String spuUrl;
private int price;
private int amount;
// 省略了getter和setter方法
}
OrderController.java
@RestController
public class OrderController {
@Reference
private SpuService spuService;
@RequestMapping("/getOrderDetails")
public Order getOrderDetails(@RequestParam("orderId") String orderId) {
// 正常情况下,需要通过用户的登录token认证和上传的订单号,现在订单服务中查询订单的相关信息,
// 再把通过调用商品服务中的方法把商品的信息查询回来,最后把数据合并、返回,这里为了方便就只
// 演示调用的过程,忽略实际业务操作
List<Spu> spus = spuService.getSpuDetails(new ArrayList<>());
Order order = new Order();
order.setOrderId(orderId);
List<OrderDetail> orderDetails = new ArrayList<>();
for (Spu spu : spus) {
OrderDetail orderDetail = new OrderDetail();
orderDetail.setSpuId(spu.getSpuId());
orderDetail.setSpuName(spu.getSpuName());
orderDetail.setSpuImage(spu.getSpuImage());
orderDetail.setPrice(12.99F);
orderDetail.setAmount(3);
orderDetails.add(orderDetail);
}
order.setOrderDetail(orderDetails);
return order;
}
}
这里的@Reference是dubbo包下的,表示这里需要使用服务提供者的服务。
至此,服务消费者也完成。
四、启动&测试
(1)启动zookeeper,如果在第一步的注册中心介绍还没有启动zookeeper,可以参考第一步
(2)启动服务提供者,找到启动类的main方法,直接运行(就是启动一个Spring Boot项目)
(3)启动服务消费者,启动方法同上
提供者和消费者都启动成功后就可以测试了,在浏览器访问http://127.0.0.1:8080/getOrderDetails?orderId=w123628可以看到返回结果:
五、题外话
(1)如果在windows下开发的觉得每次都要手动去启动zookeeper很麻烦,可以把zookeeper做成windows服务并设置开机启动,这样就不用每次启动而且出现一个黑黑的控制台窗口了.
(2)本文只是讲述了Spring Boot集成Dubbo的过程,而无论是服务提供者还是服务消费者还是spring boot项目,所以如果需要用spring boot的功能还是一样的用法,比如集成mybatis,通过注解的方法使用mybatis等。
(3)在实际开发中,一个模块可能既是服务提供者,也是服务消费者。
(4)本文并没有提供dubbo后台的部署过程,实际上,可视化服务治理是dubbo一个很重要的功能,可以在后台查看各个微服务的运行状态,可以对服务进行断开、降级、监控各个服务并发量等等。