当前位置:   article > 正文

利用springboot+dubbo,构建分布式微服务,全程注解开发

dubbo在分布式微服务项目的运用

每天早上七点三十,准时推送干货

随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。

一、先来一张图

说起 Dubbo,相信大家都不会陌生!阿里巴巴公司开源的一个高性能优秀的服务框架,可以使得应用可通过高性能的 RPC 实现服务的输出和输入功能,同时可以和 Spring 框架无缝集成。

Dubbo 架构图

节点角色说明:

  • Provider:暴露服务的服务提供方

  • Consumer:调用远程服务的服务消费方

  • Registry:服务注册与发现的注册中心

  • Monitor:统计服务的调用次数和调用时间的监控中心

  • Container:服务运行容器

二、实现思路

今天,我们以一个用户选择商品下订单这个流程,将其拆分成3个业务服务:用户中心、商品中心、订单中心,使用 Springboot + Dubbo 来实现一个小 Demo!

服务交互流程如下:

本文主要是介绍 Springboot 与 Dubbo 的框架整合以及开发实践,而真实的业务服务拆分是一个非常复杂的过程,比我们介绍的这个要复杂的多,上文提到的三个服务只是为了项目演示,不必过于纠结为什么要这样拆分

好了,废话也不多说了,下面我们开撸!

  • 1.在虚拟机创建 4 台 centos7,任意选择一台安装 zookeeper

  • 2.构建微服务项目并编写代码

  • 3.在 centos7 上部署微服务

  • 4.远程服务调用测试

三、zookeeper安装

在使用 Dubbo 之前,我们需要一个注册中心,目前 Dubbo 可以选择的注册中心有 zookeeper、Nacos 等,一般建议使用 zookeeper!

首先在安装 Zookeeper 之前,需要安装并配置好 JDK,本机采用的是Oracle Java8 SE。

  • 安装JDK(已经安装可以忽略)

yum -y install java-1.8.0-openjdk
  • 查看java安装情况

java -version
  • JDK安装完成之后,下载安装Zookeeper

  1. #创建一个zookeeper文件夹
  2. cd /usr
  3. mkdir zookeeper
  4. #下载zookeeper-3.4.14版本
  5. wget http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
  6. #解压
  7. tar -zxvf zookeeper-3.4.14.tar.gz
  • 创建数据、日志目录

  1. #创建数据和日志存放目录
  2. cd /usr/zookeeper/
  3. mkdir data
  4. mkdir log
  5. #把conf下的zoo_sample.cfg备份一份,然后重命名为zoo.cfg
  6. cd conf/
  7. cp zoo_sample.cfg zoo.cfg
  • 配置zookeeper

  1. #编辑zoo.cfg文件
  2. vim zoo.cfg

  • 启动Zookeeper

  1. #进入Zookeeper的bin目录
  2. cd zookeeper/zookeeper-3.4.14/bin
  3. #启动Zookeeper
  4. ./zkServer.sh start
  5. #查询Zookeeper状态
  6. ./zkServer.sh status
  7. #关闭Zookeeper状态
  8. ./zkServer.sh stop

出现如下信息,表示启动成功!

四、项目介绍

  • springboot版本:2.1.1.RELEASE

  • zookeeper版本:3.4.14

  • dubbo版本:2.7.3

  • mybtais-plus版本:3.0.6

  • 数据库:mysql-8

  • 构建工具:maven

  • 服务模块:用户中心、商品中心、订单中心

五、代码实践

5.1、初始化数据库

首先在 mysql 客户端,创建3个数据库,分别是:dianshang-userdianshang-platformdianshang-business

  • 在 dianshang-user 数据库中,创建用户表 tb_user,并初始化数据

  • 在 dianshang-platform 数据库中,创建商品表 tb_product,并初始化数据

  • 在 dianshang-platform 数据库中,创建订单表 tb_order、订单详情表 tb_order_detail

5.2、创建工程

数据库表设计完成之后,在 IDEA 下创建一个名称为dianshangSpringboot工程。

最终的目录如下图:

目录结构说明:

  • dianshang-common:主要存放一些公共工具库,所有的服务都可以依赖使用

  • dianshang-business:订单中心,其中api模块主要是提供dubbo服务暴露接口,provider模块是一个springboot项目,提供服务处理操作

  • dianshang-user:用户中心,其中api模块和provider模块,设计与之类似

  • dianshang-platform:商品中心,其中api模块和provider模块,设计与之类似

在父类pom文件中加入dubbozookeeper客户端,所有依赖的项目都可以使用。

  1. <!-- lombok -->
  2. <dependency>
  3. <groupId>org.projectlombok</groupId>
  4. <artifactId>lombok</artifactId>
  5. <version>1.18.4</version>
  6. <scope>provided</scope>
  7. </dependency>
  8. <!-- Dubbo Spring Boot Starter -->
  9. <dependency>
  10. <groupId>org.apache.dubbo</groupId>
  11. <artifactId>dubbo-spring-boot-starter</artifactId>
  12. <version>2.7.3</version>
  13. </dependency>
  14. <!-- 因为使用的是 zookeeper 作为注册中心,所以要添加 zookeeper 依赖 -->
  15. <dependency>
  16. <groupId>org.apache.zookeeper</groupId>
  17. <artifactId>zookeeper</artifactId>
  18. <version>3.4.13</version>
  19. <exclusions>
  20. <exclusion>
  21. <groupId>org.slf4j</groupId>
  22. <artifactId>slf4j-api</artifactId>
  23. </exclusion>
  24. <exclusion>
  25. <groupId>org.slf4j</groupId>
  26. <artifactId>slf4j-log4j12</artifactId>
  27. </exclusion>
  28. <exclusion>
  29. <groupId>log4j</groupId>
  30. <artifactId>log4j</artifactId>
  31. </exclusion>
  32. </exclusions>
  33. </dependency>
  34. <!--使用curator 作为zookeeper客户端-->
  35. <dependency>
  36. <groupId>org.apache.curator</groupId>
  37. <artifactId>curator-framework</artifactId>
  38. <version>4.2.0</version>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.apache.curator</groupId>
  42. <artifactId>curator-recipes</artifactId>
  43. <version>4.2.0</version>
  44. </dependency>

温馨提示:小编在搭建环境的时候,发现一个坑,工程中依赖的zookeeper版本与服务器的版本,需要尽量一致,例如,本例中zookeeper服务器的版本是3.4.14,那么在依赖zookeeper文件库的时候,也尽量保持一致,如果依赖3.5.x版本的zookeeper,项目在启动的时候会各种妖魔鬼怪的报错!

5.3、创建用户中心项目

在 IDEA 中,创建dianshang-user子模块,并依赖dianshang-common模块

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.project.demo</groupId>
  4. <artifactId>dianshang-common</artifactId>
  5. <version>1.0.0</version>
  6. </dependency>
  7. </dependencies>

同时,创建dianshang-user-providerdianshang-user-api模块。

  • dianshang-user-api:主要对其他服务提供接口暴露

  • dianshang-user-provider:类似一个web工程,主要负责基础业务的crud,同时依赖dianshang-user-api模块

5.3.1、配置dubbo服务

dianshang-user-providerapplication.yml文件中配置dubbo服务,如下:

  1. #用户中心服务端口
  2. server:
  3. port: 8080
  4. #数据源配置
  5. spring:
  6. datasource:
  7. druid:
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. url: "jdbc:mysql://localhost:3306/dianshang-user"
  10. username: root
  11. password: 111111
  12. #dubbo配置
  13. dubbo:
  14. scan:
  15. # 包名根据自己的实际情况写
  16. base-packages: org.project.dianshang.user
  17. protocol:
  18. port: 20880
  19. name: dubbo
  20. registry:
  21. #zookeeper注册中心地址
  22. address: zookeeper://192.168.0.107:2181
5.3.2、编写服务暴露接口以及实现类

dianshang-user-api模块中,创建一个UserApi接口,以及返回参数对象UserVo

  1. public interface UserApi {
  2. /**
  3. * 查询用户信息
  4. * @param userId
  5. * @return
  6. */
  7. UserVo findUserById(String userId);
  8. }

其中UserVo,需要实现序列化,如下:

  1. @Data
  2. @EqualsAndHashCode(callSuper = false)
  3. @Accessors(chain = true)
  4. public class UserVo implements Serializable {
  5. private static final long serialVersionUID = 1L;
  6. /**
  7. * 用户ID
  8. */
  9. private String userId;
  10. /**
  11. * 用户中文名
  12. */
  13. private String userName;
  14. }

dianshang-user-provider模块中,编写UserApi接口实现类,如下:

  1. @Service(interfaceClass =UserApi.class)
  2. @Component
  3. public class UserProvider implements UserApi {
  4. @Autowired
  5. private UserService userService;
  6. @Override
  7. public UserVo findUserById(String userId) {
  8. QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
  9. queryWrapper.eq("user_id",userId);
  10. User source = userService.getOne(queryWrapper);
  11. if(source != null){
  12. UserVo vo = new UserVo();
  13. BeanUtils.copyProperties(source,vo);
  14. return vo;
  15. }
  16. return null;
  17. }
  18. }

其中的注解@Service指的是org.apache.dubbo.config.annotation.Service下的注解,而不是Spring下的注解哦

接着,我们继续创建商品中心项目!

5.4、创建商品中心项目

与用户中心项目类似,在 IDEA 中,创建dianshang-platform子模块,并依赖dianshang-common模块

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.project.demo</groupId>
  4. <artifactId>dianshang-common</artifactId>
  5. <version>1.0.0</version>
  6. </dependency>
  7. </dependencies>

同时,创建dianshang-platform-providerdianshang-platform-api模块。

  • dianshang-platform-api:主要对其他服务提供接口暴露

  • dianshang-platform-provider:类似一个web工程,主要负责基础业务的crud,同时依赖dianshang-platform-api模块

5.4.1、配置dubbo服务

dianshang-platform-providerapplication.yml文件中配置dubbo服务,如下:

  1. #用户中心服务端口
  2. server:
  3. port: 8081
  4. #数据源配置
  5. spring:
  6. datasource:
  7. druid:
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. url: "jdbc:mysql://localhost:3306/dianshang-platform"
  10. username: root
  11. password: 111111
  12. #dubbo配置
  13. dubbo:
  14. scan:
  15. # 包名根据自己的实际情况写
  16. base-packages: org.project.dianshang.platform
  17. protocol:
  18. port: 20881
  19. name: dubbo
  20. registry:
  21. #zookeeper注册中心地址
  22. address: zookeeper://192.168.0.107:2181
5.4.2、编写服务暴露接口以及实现类

dianshang-platform-api模块中,创建一个ProductApi接口,以及返回参数对象ProductVo

  1. public interface ProductApi {
  2. /**
  3. * 通过商品ID,查询商品信息
  4. * @param productId
  5. * @return
  6. */
  7. ProductVo queryProductInfoById(String productId);
  8. }

其中ProductVo,需要实现序列化,如下:

  1. @Data
  2. @EqualsAndHashCode(callSuper = false)
  3. @Accessors(chain = true)
  4. public class ProductVo implements Serializable {
  5. private static final long serialVersionUID = 1L;
  6. /**商品ID*/
  7. private String productId;
  8. /**商品名称*/
  9. private String productName;
  10. /**商品价格*/
  11. private BigDecimal productPrice;
  12. }

dianshang-platform-provider模块中,编写ProductApi接口实现类,如下:

  1. @Service(interfaceClass = ProductApi.class)
  2. @Component
  3. public class ProductProvider implements ProductApi {
  4. @Autowired
  5. private ProductService productService;
  6. @Override
  7. public ProductVo queryProductInfoById(String productId) {
  8. //通过商品ID查询信息
  9. Product source = productService.getById(productId);
  10. if(source != null){
  11. ProductVo vo = new ProductVo();
  12. BeanUtils.copyProperties(source,vo);
  13. return vo;
  14. }
  15. return null;
  16. }
  17. }

接着,我们继续创建订单中心项目!

5.5、创建订单中心项目

与商品中心项目类似,在 IDEA 中,创建dianshang-business子模块,并依赖dianshang-common模块

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.project.demo</groupId>
  4. <artifactId>dianshang-common</artifactId>
  5. <version>1.0.0</version>
  6. </dependency>
  7. </dependencies>

同时,创建dianshang-business-providerdianshang-business-api模块。

  • dianshang-business-api:主要对其他服务提供接口暴露

  • dianshang-business-provider:类似一个web工程,主要负责基础业务的crud,同时依赖dianshang-business-api模块

5.5.1、配置dubbo服务

dianshang-business-providerapplication.yml文件中配置dubbo服务,如下:

  1. #用户中心服务端口
  2. server:
  3. port: 8082
  4. #数据源配置
  5. spring:
  6. datasource:
  7. druid:
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. url: "jdbc:mysql://localhost:3306/dianshang-business"
  10. username: root
  11. password: 111111
  12. #dubbo配置
  13. dubbo:
  14. scan:
  15. # 包名根据自己的实际情况写
  16. base-packages: org.project.dianshang.business
  17. protocol:
  18. port: 20882
  19. name: dubbo
  20. registry:
  21. #zookeeper注册中心地址
  22. address: zookeeper://192.168.0.107:2181
5.5.2、编写服务暴露接口以及实现类

dianshang-business-api模块中,创建一个OrderApi接口,以及返回参数对象OrderVo

  1. public interface OrderApi {
  2. /**
  3. * 通过用户ID,查询用户订单信息
  4. * @param userId
  5. * @return
  6. */
  7. List<OrderVo> queryOrderByUserId(String userId);
  8. }

其中OrderVo,需要实现序列化,如下:

  1. @Data
  2. @EqualsAndHashCode(callSuper = false)
  3. @Accessors(chain = true)
  4. public class OrderVo implements Serializable {
  5. private static final long serialVersionUID = 1L;
  6. /**订单ID*/
  7. private String orderId;
  8. /**订单编号*/
  9. private String orderNo;
  10. /**订单金额*/
  11. private BigDecimal orderPrice;
  12. /**下单时间*/
  13. private Date orderTime;
  14. }

dianshang-business-provider模块中,编写OrderApi接口实现类,如下:

  1. @Service(interfaceClass = OrderApi.class)
  2. @Component
  3. public class OrderProvider implements OrderApi {
  4. @Autowired
  5. private OrderService orderService;
  6. @Override
  7. public List<OrderVo> queryOrderByUserId(String userId) {
  8. QueryWrapper<Order> queryWrapper = new QueryWrapper<Order>();
  9. queryWrapper.eq("user_id",userId);
  10. List<Order> sourceList = orderService.list(queryWrapper);
  11. if(!CollectionUtils.isEmpty(sourceList)){
  12. List<OrderVo> voList = new ArrayList<>();
  13. for (Order order : sourceList) {
  14. OrderVo vo = new OrderVo();
  15. BeanUtils.copyProperties(order, vo);
  16. voList.add(vo);
  17. }
  18. return voList;
  19. }
  20. return null;
  21. }
  22. }

至此,3个项目的服务暴露接口已经开发完成!接下来我们来编写怎么进行远程调用!

5.6、远程调用
5.6.1、编写创建订单服务

dianshang-business-provider模块中,编写创建订单接口之前,先依赖dianshang-business-apidianshang-user-api,如下:

  1. <!--商品服务接口暴露 api-->
  2. <dependency>
  3. <groupId>org.project.demo</groupId>
  4. <artifactId>dianshang-platform-api</artifactId>
  5. <version>1.0.0</version>
  6. </dependency>
  7. <!--用户服务接口暴露 api-->
  8. <dependency>
  9. <groupId>org.project.demo</groupId>
  10. <artifactId>dianshang-user-api</artifactId>
  11. <version>1.0.0</version>
  12. </dependency>

dianshang-business-provider模块中,编写创建订单服务,如下:

  1. @RestController
  2. @RequestMapping("/order")
  3. public class OrderController {
  4. @Autowired
  5. private OrderService orderService;
  6. @Autowired
  7. private OrderDetailService orderDetailService;
  8. @Reference(check =false)
  9. private ProductApi productApi;
  10. @Reference(check =false)
  11. private UserApi userApi;
  12. /**
  13. * 新增
  14. */
  15. @JwtIgnore
  16. @RequestMapping(value = "/add")
  17. public boolean add(String productId,String userId){
  18. LocalAssert.isStringEmpty(productId,"产品Id不能为空");
  19. LocalAssert.isStringEmpty(userId,"用户Id不能为空");
  20. ProductVo productVo = productApi.queryProductInfoById(productId);
  21. LocalAssert.isObjectEmpty(productVo,"未查询到产品信息");
  22. UserVo userVo = userApi.findUserById(userId);
  23. LocalAssert.isObjectEmpty(userVo,"未查询到用户信息");
  24. Order order = new Order();
  25. order.setOrderId(IdGenerator.uuid());
  26. order.setOrderNo(System.currentTimeMillis() + "");
  27. order.setOrderPrice(productVo.getProductPrice());
  28. order.setUserId(userId);
  29. order.setOrderTime(new Date());
  30. orderService.save(order);
  31. OrderDetail orderDetail = new OrderDetail();
  32. orderDetail.setOrderDetailId(IdGenerator.uuid());
  33. orderDetail.setOrderId(order.getOrderId());
  34. orderDetail.setProductId(productId);
  35. orderDetail.setSort(1);
  36. orderDetailService.save(orderDetail);
  37. return true;
  38. }
  39. }

其中的@Reference注解,是属于org.apache.dubbo.config.annotation.Reference下的注解,表示远程依赖服务。

参数check =false表示启动服务时,不做远程服务状态检查,这样设置的目的就是为了防止当前服务启动不了,例如用户中心项目没有启动成功,但是订单中心又依赖了用户中心,如果check=true,此时订单中心启动会报错!

5.6.2、编写用户查询自己的订单信息

同样的,在dianshang-user-provider模块中,编写用户查询自己的订单信息接口之前,先依赖dianshang-business-apidianshang-user-api,如下:

  1. <dependency>
  2. <groupId>org.project.demo</groupId>
  3. <artifactId>dianshang-business-api</artifactId>
  4. <version>1.0.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.project.demo</groupId>
  8. <artifactId>dianshang-user-api</artifactId>
  9. <version>1.0.0</version>
  10. </dependency>

dianshang-user-provider模块中,编写用户查询自己的订单信息接口,如下:

  1. @RestController
  2. @RequestMapping("/user")
  3. public class UserController {
  4. @Reference(check =false)
  5. private OrderApi orderApi;
  6. /**
  7. * 通过用户ID,查询订单信息
  8. * @param userId
  9. * @return
  10. */
  11. @RequestMapping("/list")
  12. public List<OrderVo> queryOrderByUserId(String userId){
  13. return orderApi.queryOrderByUserId(userId);
  14. }
  15. }

至此,远程服务调用,编写完成!

六、服务测试

在将项目部署在服务器之前,咱们先本地测试一下,看服务是否都可以跑通?

  • 启动用户中心dianshang-user-provider

  • 继续启动商品中心dianshang-platform-provider

  • 接着启动订单中心dianshang-business-provider

最后,我们来测试一下服务接口是否为我们预期的结果?

打开浏览器,输入http://127.0.0.1:8082/order/add?productId=1&userId=1测试创建订单接口,页面运行结果显示正常!

我们再来看看数据库,订单是否生成?

ok!很清晰的看到,数据已经进去了,没啥问题!

我们再来测试一下在用户中心订单查询接口,输入http://127.0.0.1:8080/user/list?userId=1,页面运行结果如下!

到此,本地服务测试基本通过

七、服务器部署

在上文中,我们介绍了服务的构建、开发和测试,那如何在服务器端部署呢

首先,修改各个项目的application.yml文件,将其中的数据源地址、dubbo注册中心地址修改为线上能联通的地址,然后在dianshang目录下使用maven工具对整个工程执行如下命令进行打包!

mvn clean install

也可以在 IDEA 环境下,通过maven配置clean install命令执行打包。

将各个项目target目录下的dianshang-user-provider.jardianshang-platform-provider.jardianshang-business-provider.jar拷贝出来。

分别上传到对应的服务器目录,本服务器采用的是 CentOS7,总共4台服务器,其中一台部署zookeeper,另外三台部署三个微服务项目。

登录服务器,输入如下命令,确保JDK已经安装完成!

java -version

关闭所有服务器的防火墙,放行端口访问!

  1. #关闭防火墙
  2. systemctl stop firewalld.service
  3. #禁止开机启动
  4. systemctl disable firewalld.service
  • 启动用户中心服务,日志信息输出到service.log(虚拟机ip:192.168.0.108)

nohup java -jar  dianshang-user-provider.jar > service.log 2>&1 &
  • 启动商品中心服务,日志信息输出到service.log(虚拟机ip:192.168.0.107)

nohup java -jar  dianshang-platform-provider.jar > service.log 2>&1 &
  • 启动订单中心服务,日志信息输出到service.log(虚拟机ip:192.168.0.109)

nohup java -jar  dianshang-business-provider.jar > service.log 2>&1 &

打开浏览器,输入http://192.168.0.109:8082/order/add?productId=1&userId=1测试创建订单接口,页面运行结果显示正常!

我们再来测试一下在用户中心订单查询接口,输入输入http://192.168.0.108:8080/user/list?userId=1,页面运行结果如下!

很清晰的看到,输出了2条信息,第二条订单是在测试环境服务器生成的,第一条是本地开发环境生成的

到此,服务器部署基本已经完成!

如果是生产环境,可能就需要多台zookeeper来保证高可用,至少2台服务器来部署业务服务,通过负载均衡来路由!

八、总结

整片文章比较长,主要是围绕 springboot + dubbo 的整合,通过注解开发实现远程服务调用像传统的 springmvc 开发一样轻松,当然还可以通过xml配置方式实现dubbo服务的调用,这个会在后期去介绍。

同时也介绍了服务器的部署,从中可以看出,开发虽然简单,但是由于分布式部署,如何保证服务高可用成了开发人员头等工作任务,所以,分布式微服务开发虽然开发简单,但是如何确保部署的服务高可用?运维方面会带来不少的挑战!

九、参考

1、apache - dubbo - 官方文档

< END >

如果大家喜欢我们的文章,欢迎大家转发,点击在看让更多的人看到。也欢迎大家热爱技术和学习的朋友加入的我们的知识星球当中,我们共同成长,进步。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/166016
推荐阅读
相关标签
  

闽ICP备14008679号