源码地址:https://gitee.com/fighter3/eshop-project.git

持续更新中……

大家好,我是老三,断更了半年,我又滚回来继续写这个系列了,还有人看吗……

在前面的章节中,我们使用Fegin完成了服务间的远程调用,实际上,在更加注重性能的互联网公司中,一般都会使用RPC框架,如Dubbo等,来实现远程调用。

这一节,我们就来把我们的服务间调用从Feign改造成Dubbo。

1.Dubbo简介

Apache Dubbo 是一款微服务开发框架,它提供了 RPC通信与微服务治理两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。

这是Dubbo官网对Dubbo的简介,Dubbo在国内是应用非常广泛的服务治理框架,曾经一度停更,后来又重新维护,并从Apache毕业。

在这一节里,我们主要关注它的RPC通信的能力。

这里再额外提一个老生常谈的问题,Dubbo和我们前面用的Feign的区别:

Dubbo在性能上有优势,Feign使用起来更便捷,接下来,我们来一步步学习Dubbo的使用。

2.Dubbo基本使用

在前面我们使用Feign远程调用实现了一个业务添加商品,接下来,我们把它改造成基于Dubbo远程调用实现。

2.1.服务提供者

我们将原来的eshop-stock拆成两个子module,eshop-stock-apieshop-stock-service,其中eshop-stock-api是主要是RPC接口的定义,eshop-stock-service则是完成库存服务的主要业务。

2.1.1.eshop-stock-api

  • 依赖引入,eshop-stock-api主要是接口和实体类的定义,所以只需要引入对common包的依赖和lombok的依赖
  1. <!--对common的依赖-->
  2. <dependency>
  3. <groupId>cn.fighter3</groupId>
  4. <artifactId>eshop-common</artifactId>
  5. <version>1.0-SNAPSHOT</version>
  6. </dependency>
  7. <!--lombok-->
  8. <dependency>
  9. <groupId>org.projectlombok</groupId>
  10. <artifactId>lombok</artifactId>
  11. <optional>true</optional>
  12. </dependency>
  • 接口和实体定义

StockApiService.java:这个接口定义了两个方法,在哪实现呢?往后看。

  1. /**
  2. * @Author 三分恶
  3. * @Date 2021/11/14
  4. * @Description 对外RPC接口定义
  5. */
  6. public interface StockApiService {
  7. /**
  8. * 添加库存
  9. *
  10. * @param stockAddDTO
  11. * @return
  12. */
  13. Integer addStock(StockAddDTO stockAddDTO);
  14. /**
  15. * 根据商品ID获取库存量
  16. *
  17. * @param goodsId
  18. * @return
  19. */
  20. Integer getAccountById(Integer goodsId);
  21. }

StockAddDTO.java:添加库存实体类

  1. /**
  2. * @Author: 三分恶
  3. * @Date: 2021/5/26
  4. * @Description:
  5. **/
  6. @Data
  7. @Builder
  8. @EqualsAndHashCode(callSuper = false)
  9. public class StockAddDTO implements Serializable {
  10. private static final long serialVersionUID = 1L;
  11. /**
  12. * 商品主键
  13. */
  14. private Integer goodsId;
  15. /**
  16. * 数量
  17. */
  18. private Integer account;
  19. }

2.1.2.eshop-stock-service

我们把原来eshop-stock的相关业务代码都改到了这个module里。

同时,为了实现RPC服务的提供,我们需要:

  • 导入依赖:主要需要导入两个依赖dubbo的依赖,和eshop-stock-api接口声明的依赖,这里的<scope> 设置为compile,这样我们在编译eshop-stock-service的时候,也会编译相应的api依赖。
  1. <!--Dubbo-->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-dubbo</artifactId>
  5. </dependency>
  6. <!--对api的依赖-->
  7. <dependency>
  8. <groupId>cn.fighter3</groupId>
  9. <artifactId>eshop-stock-api</artifactId>
  10. <version>1.0-SNAPSHOT</version>
  11. <scope>compile</scope>
  12. </dependency>
  • StockApiServiceImpl.java:创建一个类,实现api中声明的接口,其中@Service是Dubbo提供的注解,表示当前服务会发布成一个远程服务,不要和Spring提供的搞混。

    1. /**
    2. * @Author 三分恶
    3. * @Date 2021/11/14
    4. * @Description 库存服务提供RPC接口实现类
    5. */
    6. @org.apache.dubbo.config.annotation.Service
    7. @Slf4j
    8. public class StockApiServiceImpl implements StockApiService {
    9. @Autowired
    10. private ShopStockMapper stockMapper;
    11. /**
    12. * 添加库存
    13. *
    14. * @param stockAddDTO
    15. * @return
    16. */
    17. @Override
    18. public Integer addStock(StockAddDTO stockAddDTO) {
    19. ShopStock stock = new ShopStock();
    20. stock.setGoodsId(stockAddDTO.getGoodsId());
    21. stock.setInventory(stockAddDTO.getAccount());
    22. log.info("准备添加库存,参数:{}", stock.toString());
    23. this.stockMapper.insert(stock);
    24. Integer stockId = stock.getStockId();
    25. log.info("添加库存成功,stockId:{}", stockId);
    26. return stockId;
    27. }
    28. /**
    29. * 获取库存数量
    30. *
    31. * @param goodsId
    32. * @return
    33. */
    34. @Override
    35. public Integer getAccountById(Integer goodsId) {
    36. ShopStock stock = this.stockMapper.selectOne(Wrappers.<ShopStock>lambdaQuery().eq(ShopStock::getGoodsId, goodsId));
    37. Integer account = stock.getInventory();
    38. return account;
    39. }
    40. }
    • 远程调用配置:我们需要在applicantion.yml中进行dubbo相关配置,由于在之前,我们已经集成了nacos作为注册中心,所以一些服务名、注册中心之类的就不用配置。完整配置如下:
    1. # 数据源配置
    2. spring:
    3. datasource:
    4. driver-class-name: com.mysql.cj.jdbc.Driver
    5. url: jdbc:mysql://localhost:3306/shop_stock?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8
    6. username: root
    7. password: root
    8. application:
    9. name: stock-service
    10. cloud:
    11. nacos:
    12. discovery:
    13. server-addr: 127.0.0.1:8848
    14. server:
    15. port: 8050
    16. # dubbo相关配置
    17. dubbo:
    18. scan:
    19. # dubbo服务实现类的扫描基准包路径
    20. base-packages: cn.fighter3.serv.service.impl
    21. #Dubbo服务暴露的协议配置
    22. protocol:
    23. name: dubbo
    24. port: 1

2.2.服务消费者

我们的商品服务作为服务的消费者,为了后续开发的考虑,我也类似地把eshop-goods拆成了两个子moudule,服务消费放在了eshop-goods-service里。

  • 引入依赖:引入两个依赖dubboeshop-stock-api,因为在一个工程里,所以对api的依赖同样用了<scope>为compile的方式,在实际的业务开发中,通常会把服务提供者的api打包上传到私服仓库,然后服务消费者依赖api包,这样就可以直接调用api包里定义的方法。

    1. <!--Dubbo相关包-->
    2. <dependency>
    3. <groupId>com.alibaba.cloud</groupId>
    4. <artifactId>spring-cloud-starter-dubbo</artifactId>
    5. </dependency>
    6. <!--对api的依赖-->
    7. <dependency>
    8. <groupId>cn.fighter3</groupId>
    9. <artifactId>eshop-stock-api</artifactId>
    10. <version>1.0-SNAPSHOT</version>
    11. <scope>compile</scope>
    12. </dependency>
  • 远程调用:使用@Reference注入相应的service,就可以像调用本地jar包一样,调用远程服务。

ShopGoodsServiceImpl.java:

  1. @Service
  2. @Slf4j
  3. public class ShopGoodsServiceImpl extends ServiceImpl<ShopGoodsMapper, ShopGoods> implements IShopGoodsService {
  4. @org.apache.dubbo.config.annotation.Reference
  5. StockApiService stockApiService;
  6. /**
  7. * 添加商品
  8. *
  9. * @param goodsAddDTO
  10. * @return
  11. */
  12. public CommonResult addGoods(GoodsAddDTO goodsAddDTO) {
  13. ShopGoods shopGoods = new ShopGoods();
  14. BeanUtils.copyProperties(goodsAddDTO, shopGoods);
  15. this.baseMapper.insert(shopGoods);
  16. log.info("添加商品,商品主键:{}", shopGoods.getGoodsId());
  17. log.info(shopGoods.toString());
  18. StockAddDTO stockAddDTO = StockAddDTO.builder().goodsId(shopGoods.getGoodsId()).account(goodsAddDTO.getAccount()).build();
  19. log.info("准备添加库存,参数:{}", stockAddDTO.toString());
  20. Integer stockId = this.stockApiService.addStock(stockAddDTO);
  21. log.info("添加库存结束,库存主键:{}", stockId);
  22. return CommonResult.ok();
  23. }
  24. /**
  25. * 获取商品
  26. *
  27. * @param goodsId
  28. * @return
  29. */
  30. public CommonResult<GoodsVO> getGoodsById(Integer goodsId) {
  31. GoodsVO goodsVO = new GoodsVO();
  32. //获取商品基本信息
  33. ShopGoods shopGoods = this.baseMapper.selectById(goodsId);
  34. BeanUtils.copyProperties(shopGoods, goodsVO);
  35. //获取商品库存数量
  36. Integer account = this.stockApiService.getAccountById(goodsId);
  37. log.info("商品数量:{}", account);
  38. goodsVO.setAccount(account);
  39. return CommonResult.ok(goodsVO);
  40. }
  41. }
  • 相关配置:需要在applicantion.yml里进行配置,主要配置了要订阅的服务名。完整配置:

    1. # 数据源配置
    2. spring:
    3. datasource:
    4. driver-class-name: com.mysql.cj.jdbc.Driver
    5. url: jdbc:mysql://localhost:3306/shop_goods?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=GMT%2B8
    6. username: root
    7. password: root
    8. application:
    9. name: goods-service
    10. cloud:
    11. nacos:
    12. discovery:
    13. server-addr: 127.0.0.1:8848
    14. server:
    15. port: 8020
    16. #dubbo配置
    17. #要订阅的服务名,多个用,隔开
    18. dubbo:
    19. cloud:
    20. subscribed-services: stock-service

2.3.调试

  • 依次启动Nacos-Server库存服务商品服务,可以看到Nacos服务列表里有两个服务

  • 打开我们商品服务的knife4j接口http://localhost:8020/doc.html,调试添加商品接口

  • 上图可以看到,接口响应成功,查看控制台日志,发现发生了远程调用,查看数据库,发现商品库和库存库都新增了数据

到此,我们一个简单的Dubbo远程调用就完成了。

3.Dubbo进阶使用

在Feign的使用中,它自身集成了Ribbon实现客户端负载均衡,还需要额外继承Hystrix来实现熔断,我们接下来看看类似的一些能力Dubbo是怎么做的。

3.1.集群容错

网络通信中存在很多不可控因素,例如网络延迟、网络中断、服务异常等等,这时候就需要我们的服务消费者在调用服务提供者提供的接口是,对失败的情况进行处理,尽可能保证服务调用成功。

Dubbo默认提供了6中容错模式,默认为Failover 重试[1]。

  • Failover Cluster:失败自动切换,当出现失败,重试集群中的其它服务。可通过 retries="2" 来设置重试次数,但重试会带来更长延迟。一般用于读操作,因为可能会带来数据重复问题。
  • Failfast Cluster:快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。
  • Failsafe Cluster:失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。
  • Failback Cluster:失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。
  • Forking Cluster:并行调用集群中的多个服务,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。
  • Broadcast Cluster:广播调用所有提供者,逐个调用,任意一个服务报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

配置方式很简单,只需要在指定服务的@Service注解上增加一个参数就行了——在@Service注解参数中增加cluster = "failfast"

  1. @org.apache.dubbo.config.annotation.Service(cluster = "failfast")
  2. @Slf4j
  3. public class StockApiServiceImpl implements StockApiService {

在实际应用中,我们可以把读写操作接口分开定义和和实现,读操作接口用默认的Failover Cluster,写操作用Failfast Cluster

3.2.负载均衡

Dubbo中内置了5种负载均衡策略,默认为random。

算法 特性 备注
RandomLoadBalance 加权随机 默认算法,默认权重相同
RoundRobinLoadBalance 加权轮询 借鉴于 Nginx 的平滑加权轮询算法,默认权重相同,
LeastActiveLoadBalance 最少活跃优先 + 加权随机 背后是能者多劳的思想
ShortestResponseLoadBalance 最短响应优先 + 加权随机 更加关注响应速度
ConsistentHashLoadBalance 一致性 Hash 确定的入参,确定的提供者,适用于有状态请求

配置方式也很简单,在@Service注解上增加参数loadbalance = "roundrobin"

  1. @org.apache.dubbo.config.annotation.Service(cluster = "failfast",loadbalance = "roundrobin")

3.3.服务降级

Dubbo提供了一种Mock配置来实现服务降级,也就是说当服务提供方出现网络异常无法访问时,服务调用方不直接抛出异常,而是通过降级配置返回兜底数据。主要步骤如下:

  • eshop-goods-service(服务消费者)中创建MockStockApiServiceImpl,实现StockApiServiceImpl,重写接口方法,返回本地兜底的数据。
  1. /**
  2. * @Author 三分恶
  3. * @Date 2021/11/14
  4. * @Description 库存服务降级兜底类
  5. */
  6. @Slf4j
  7. public class MockStockApiServiceImpl implements StockApiService {
  8. @Override
  9. public Integer addStock(StockAddDTO stockAddDTO) {
  10. log.error("库存服务添加库存接口调用失败!");
  11. return 0;
  12. }
  13. @Override
  14. public Integer getAccountById(Integer goodsId) {
  15. log.error("库存服务获取库存接口调用失败!");
  16. return 0;
  17. }
  18. }
  • 使用也很简单,在ShopGoodsServiceImpl(调用远程服务的类)的@Reference注解,增加mock参数,设置降级类;我们同时设置设置集群容错cluster="failfast"快速失败。
  1. @Service
  2. @Slf4j
  3. public class ShopGoodsServiceImpl extends ServiceImpl<ShopGoodsMapper, ShopGoods> implements IShopGoodsService {
  4. @org.apache.dubbo.config.annotation.Reference(mock = "cn.fighter3.serv.service.impl.MockStockApiServiceImpl",
  5. cluster = "failfast")
  6. StockApiService stockApiService;
  • 不启动服务提供者,我们就可以看到降级数据。

Dubbo实际上还有很多高级的功能,可以满足很多场景的需求,更多内容可以查看官网:https://dubbo.apache.org/zh/docs/advanced/。

4.总结

在本节里,我们把远程调用由Feign改成了Dubbo,学习了Dubbo的一些基础和进阶用法。经过Alibaba的操刀,Dubbo已经能比较快捷地融入SpringCloud的体系中,如果对性能有一定的要求,那妥妥地可以考虑采用Dubbo作为远程调用框架。

实际上,这一节,经过我自己的迁移,Dubbo在应用上确实比Feign稍微麻烦一点点,我原本的计划的是使用Feign作为主要的远程调用组件,但实际上大部分真实电商项目基本都是使用Dubbo,或者自研RPC框架,所以这个项目后面的业务开发,决定改成Dubbo。

系列文章持续更新中,点赞关注不迷路,咱们下期见。


参考:

[1]. Dubbo官方文档

[2]. 《Spring Cloud Alibaba 微服务原理与实战》

[3]. 远程调用 Dubbo 与 Feign 的区别

SpringCloud Alibaba实战(12:引入Dubbo实现RPC调用)的更多相关文章

  1. SpringCloud Alibaba实战(11:引入服务网关Gateway)

    源码地址:https://gitee.com/fighter3/eshop-project.git 持续更新中-- 大家好,我是三分恶. 在前面的章节中,我们已经完成了服务间的调用.统一配置等等,在这 ...

  2. SpringCloud Alibaba实战(3:存储设计与基础架构设计)

    1.存储设计 在上一章中,我们已经完成了基本业务流程的梳理和服务模块的划分,接下来,开始设计数据存储. 虽然在微服务的理论中,没有对数据库定强制性的规范,但一般,服务拆分之后,数据库也会对应的拆分. ...

  3. SpringCloud Alibaba实战(7:nacos注册中心管理微服务)

    源码地址:https://gitee.com/fighter3/eshop-project.git 持续更新中-- 在上一节我们已经完成了Nacos Server的本地部署,这一节我们学习如何将Nac ...

  4. SpringCloud Alibaba实战(8:使用OpenFeign服务调用)

    源码地址:https://gitee.com/fighter3/eshop-project.git 持续更新中-- 在上一个章节,我们已经成功地将服务注册到了Nacos注册中心,实现了服务注册和服务发 ...

  5. SpringCloud Alibaba实战(2:电商系统业务分析)

    选用了很常见的电商业务来进行SpringCloud Alibaba的实战. 当然,因为仅仅是为了学习SpringCloud Alibaba,所以对业务进行了大幅度简化,这里只取一个精简版的用户下单业务 ...

  6. SpringCloud Alibaba实战(6:nacos-server服务搭建)

    源码地址:https://gitee.com/fighter3/eshop-project.git 持续更新中-- 大家好,我是三分恶. 这一节我们来学习SpringCloud Alibaba体系中一 ...

  7. SpringCloud Alibaba实战(9:Hystrix容错保护)

    源码地址:https://gitee.com/fighter3/eshop-project.git 持续更新中-- 在上一节我们已经使用OpenFeign完成了服务间的调用.想一下,假如我们一个服务链 ...

  8. [转载] Dubbo实现RPC调用使用入门

    转载自http://shiyanjun.cn/archives/341.html 使用Dubbo进行远程调用实现服务交互,它支持多种协议,如Hessian.HTTP.RMI.Memcached.Red ...

  9. Dubbo实现RPC调用使用入门

    使用Dubbo进行远程调用实现服务交互,它支持多种协议,如Hessian.HTTP.RMI.Memcached.Redis.Thrift等等.由于Dubbo将这些协议的实现进行了封装了,无论是服务端( ...

随机推荐

  1. DP 优化方法大杂烩 & 做题记录 I.

    标 * 的是推荐阅读的部分 / 做的题目. 1. 动态 DP(DDP)算法简介 动态动态规划. 以 P4719 为例讲一讲 ddp: 1.1. 树剖解法 如果没有修改操作,那么可以设计出 DP 方案 ...

  2. 蛋白质组DIA深度学习之谱图预测

    目录 1. 简介 2. 近几年发表的主要工具 1.DeepRT 2.Prosit 3. DIANN 4.DeepDIA 1. 简介 基于串联质谱的蛋白质组学大部分是依赖于数据库(database se ...

  3. 远程登录Linux系统及上传下载文件

    目录 1. 远程登录Linux系统 1.1 为什么要远程登录 1.2 Xshell6安装 1.3 连接登录 1.3.1 连接前提 1.3.2 Xshell连接配置 2. 远程上传下载文件 2.1 Xf ...

  4. Excel-姓名列中同一个人汇总金额列,得出总金额

    8.姓名列中同一个人求和金额列,得出总金额. 方法一: P2处公式=SUMPRODUCT(($M$2:$M$20=$M2)*($N$2:$N$20)) 解释函数: 引用:https://zhinan. ...

  5. 振鹏同学正式学习java的第一天!

    一.今日收获 1.最棒的莫过于运行Java的HelloWorld! 2.在同学的帮助下历经坎坷困苦安装完成了Eclipse软件并设置好环境变量. 3.最最最开始了解了Java的前世今生,编程语言发展的 ...

  6. 巩固javaweb第十天

    巩固内容: HTML <meta> 元素 meta标签描述了一些基本的元数据. <meta> 标签提供了元数据.元数据也不显示在页面上,但会被浏览器解析. META 元素通常用 ...

  7. 数仓:解读 NameNode 的 edits 和 fsimage 文件内容

    一.edits 文件 一)文件组成 一个edits文件记录了一次写文件的过程,该过程被分解成多个部分进行记录:(每条记录在hdfs中有一个编号) 每一个部分为: '<RECORD>...& ...

  8. day03 Django目录结构与reques对象方法

    day03 Django目录结构与reques对象方法 今日内容概要 django主要目录结构 创建app注意事项(重点) djago小白必会三板斧 静态文件配置(登录功能) requeste对象方法 ...

  9. 大数据学习day19-----spark02-------0 零碎知识点(分区,分区和分区器的区别) 1. RDD的使用(RDD的概念,特点,创建rdd的方式以及常见rdd的算子) 2.Spark中的一些重要概念

    0. 零碎概念 (1) 这个有点疑惑,有可能是错误的. (2) 此处就算地址写错了也不会报错,因为此操作只是读取数据的操作(元数据),表示从此地址读取数据但并没有进行读取数据的操作 (3)分区(有时间 ...

  10. pyqt5 改写函数

    重新改写了keyPressEvent() class TextEdit(QTextEdit): def __init__(self): QtWidgets.QTextEdit.__init__(sel ...