Dubbo2
一、什么是Dubbo?
早期Dubbo是基于Java的高性能、轻量级的RPC框架, 是SOA方面的技术. SOA架构, Service Oriented Architecture 面向服务结构. SOA = RPC + 服务治理
现在是一款易用的、高性能的Web和RPC框架. 同时为构建企业级微服务提供服务发现,流量治理、可观测、认证鉴权等能力.全面面向微服务, Dubbo已经在阿里巴巴内部微服务集群全面落地. 依托Dubbo3, 阿里提出了自己的微服务解决方案DNS, D-Dubbo N-Nacos S-Sentinel
二、架构发展历程
1.架构演变
大致分为6个阶段: 单体架构 -〉水平扩展 -〉垂直扩展 -〉RPC -〉soa -〉微服务
时至今日, SOA架构和微服务结构基本可以等同视之了, 下面将对不同架构作简要概述
1.1) 单体项目
就是把项目中所涉及的模块( 门户系统、后台系统 )等统一打成一个jar包或者war包, 部署在一个tomcat实例里
出现问题:
1, 热点问题, 即某个子系统访问量大, 可能会导致其他子系统的访问出现问题
2, 模块与模块之间的耦合度高, 相互影响( 修改某一个子系统的代码, 产生了问题,影响其他的子系统 )
3, 维护部署成本高 ( 某次发布, 只更新了后台管理的模块, 但是因为是单体架构, 所以会发布整个系统 )
4, 技术栈受限, 必须使用相同的编程语言开发不用的子系统
1.2) 水平扩展
集群构建, 就是部署多个单体架构, 形成一个集群
1, 提高了系统的稳定性和并发能力
2, 一台节点出现了问题, 不会影响整个系统
但是根本上没有解决单体架构的问题
1.3) 垂直结构
把一个单体应用, 根据业务的边界进行了划分, 每个业务系统都独立部署在自己的tomcat中, 多个业务共享数据库等存储资源
如把门户系统和后台系统进行拆分, 解决了部分单体架构的问题
解决了:
1, 子系统之间的热点问题解决了, 但是模块的热点问题没有解决
2,子系统之间耦合度降低, 独立更新和发版本
3, 技术栈也不要求统一语言
1.4) RPC
RPC : Remote Procedure Call 远程过程调用
由垂直结构演变而来, 解决的是子系统间模块功能的调用
如下门户系统中需要调用后台中系统中的订单服务,这种跨进程间该如何调用呢? 答案RPC
RPC的好处:
复用其他系统的功能
问题:
1, 不能像调用本地方法那样进行调用
如在Web门户系统内部增加一个订单模块,那么可以像调用本地方法那样调用OrderService.getOrderByOrderNo(), 这样的话就是重复造轮子, 那么现在需要调用另外一台服务器当中的订单模块, 这是跨虚拟机的, 就是跨进程的, 跨进程之间的调用是没有办法像调用本地方法那样的, 跨进程之间的调用必须通过网络来调用. ( 不管是一台机器还是多台机器 )即从web门户将请求参数通过网络传递给后台管理的订单模块, 订单模块处理完成再通过网络将处理结果返回给web门户
2, 走网络调用,那么 1,通信方式 2, 协议 3, 序列化
1.5) SOA架构 Service Oriented Architecture 面向服务架构
SOA是RPC架构的演化. 代表框架 Dubbo
RPC存在的问题:
如上用户自服务模块调用订单模块
1, 如果后台系统的订单模块出现了问题怎么办?
2, 订单模块访问量特别大怎么办?
解决方式: 对订单模块进行水平扩展, 集群. 但是按照目前RPC的结构,无法完成针对订单模块的扩展,如果扩展只能子系统内全扩展
SOA解决:
把订单模块从后台系统中抽取出来, 成为一个服务
即: 把订单模块的代码独立成一个新的module, 打成一个jar包, 部署在一个tomcat中. 可以分别对门户和后台系统共同提供服务
避免单节点故障, 可以水平扩展, 部署多个订单服务形成一个集群
如下已经将功能模块抽取出来并形成一个集群, 那么谁来管理这个集群呢? 所以服务治理应运而生
服务治理
注册中心: 提供服务注册功能, 对服务集群进行管理, 监控集群每一个服务是否健康等
负载均衡: 将调用订单服务的请求按照一定的算法调度给集群中的机器
容错: 当一次请求走到了2号机器进行处理请求,此时2号机器出问题无法提供服务, 就会有容错机制切换到其他机器进行处理请求
配置中心: 对集群中的服务的配置进行统一配置管理, 改一处集群服务均生效
限流: 对集群的请求流量进行流量限制, 保证集群的稳定
所以: SOA = RPC + 服务治理 如果有异构系统, 再加个ESB
1.6) 企业服务总线 ESB Enterpraise-Service-Bus ( SOA架构的变种 )
如下, 订单模块抽取服务后, 门户系统和后台系统都可以对其发起调用, 当然也有可能存在其他语言开发的系统(Go/PHP/Python)等开发的系统需要对其发起调用. 所以就会存在python调java的代码, 这里java是不认识python的,所以对于这种异构系统之间如何完成RPC的调用呢?
解决: RPC是跨进程建的调用, 我们需要将数据在网络传输过程中, 把请求数据和返回数据转换成一种大家都认可的中间语言类型. 中间语言类型: GRpc Probuf 将数据类型都转换为 Probuf.Thrift IDL (java版) IDL也是支持不同语言的, 这种中间层即为ESB-企业服务总线, 可以把不同语言的数据类型转换成一种都认识的中间状态( 1, Probuf 2, IDL), 对于相同语言的, 如java也要同通过ESB. 即所有语言不论相同还是不同都要经过ESB
RPC的调用, 如果是同步的调用, 可以通过ESB进行处理, 异步的调用可以使用MQ进行处理.
1.7) 微服务架构
微服务架构是SOA结构的升级, 在微服务架构系统中, 没有子系统了, 全部都拆解为服务化功能.
微服务架构代表框架: Springcloud体系 ( Alibaba), 以Bubbo为代表的DNS体系
2.Dubbo环境搭建
基于Springboot2 + Dubbo2.7.3 提前安装好zookeeper注册中心
springboot中使用dubbo也是一样, 需要建立接口层、服务提供者、服务消费者
接口层
public interface SiteService {
String getName(String name);
}
服务提供者
pom.xml
<!--接口层依赖-->
<dependency>
<groupId>org.yuchi</groupId>
<artifactId>myboot-dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--dubbo依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!--zookeeper依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>2.7.3</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
实现接口层实现类
@Service是apache.dubbo的, 2.7.4以后为@DubboService
功能:将以下实现类发布成RPC服务,暴漏SiteService接口,并将实现类SiteServiceImpl注入iOC容器中
import org.apache.dubbo.config.annotation.Service;
@Service
public class SiteServiceImpl implements SiteService {
@Override
public String getName(String name) {
return "name: " + name;
}
}
application.yml
server:
port: 9001
dubbo:
application:
name: myboot-dubbo-provider // 服务名称
registry:
address: zookeeper://localhost:2181 // zookeeper注册中心地址
protocol:
name: dubbo // dubbo协议
port: 20882 // dubbo端口
启动类开启dubbo @EnableDubbo
@SpringBootApplication
@EnableDubbo
public class MybootDubboProviderApplication {
public static void main(String[] args) {
SpringApplication.run(MybootDubboProviderApplication.class, args);
}
}
开启成功后,在注册中心可以发现存在了dubbo节点,且已经注册了com.yuchi.service.SiteService服务
服务消费者
pom依赖
<!--接口层依赖-->
<dependency>
<groupId>org.yuchi</groupId>
<artifactId>myboot-dubbo-interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--dubbo依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!--zookeeper依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>2.7.3</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
application.yml
server:
port: 9002
dubbo:
application:
name: myboot-dubbo-consumer
registry:
address: zookeeper://localhost:2181 注册中心地址
消费提供者接口
@RestController
@RequestMapping("site")
public class SiteController {
// @Reference完成两件事:1,服务启动时向注册中心订阅siteService服务地址列表
// 2, 生成siteService服务代理对象
@Reference // dubbo的Reference 2.7.4以后为@DubboReference
private SiteService siteService;
@GetMapping("getName")
public String getName(String name){
return siteService.getName("aaa");
}
}
启动类开启dubbo
@SpringBootApplication
@EnableDubbo
public class MybootDubboConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(MybootDubboConsumerApplication.class, args);
}
}
3.服务治理
3.1) 服务超时
服务提供者和服务消费者都可以配置服务超时时间(在dubbo框架中默认是1s)
服务提供者超时:执行暴漏的服务的超时时间。如果超时,则会打印超时日志(warn),但服务会正常执行完
服务消费者超时:从发起服务调用到收到服务响应的整个过程的时间。如果超时,则进行重试,重试失败则抛出异常。
场景一: 服务消费者或服务提供者只有一方配置了超时时间,则默认是对双方都生效
如下是对服务提供者配置了超时时间:6秒,默认消费者也是6秒, 服务执行5秒,所以正常调用
消费者配置
@Service(version = "sync", timeout = 6000)
public class AsyncSiteServiceImpl implements SiteService {
@Override
public String getName(String name) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "sync" + name;
}
}
场景二: 服务消费者或服务提供者都配置了超时时间
如下消费者配置3秒,上提供者配置6秒,虽然提供者没有超时,但消费者超时了, 提供者超时时间配置4s,程序运行5s
@Service(version = "sync", timeout = 4000)
public class AsyncSiteServiceImpl implements SiteService {
@Override
public String getName(String name) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("aaa");
return "sync" + name;
}
}
消费者配置3s
@RestController
@RequestMapping("site")
public class SiteController {
@Reference(version = "sync", timeout = 3000)
private SiteService siteService;
@GetMapping("/getName")
public String getName(String name){
return siteService.getName("aaa");
}
}
结果:
1,提供者打印了warn日志,程序执行了3次 (消费者重试了三次),没有报错
a)也就是说提供者即使是服务超时了,程序也是会执行完的
b)提供者配置的超时时间是预期执行时间,即使超过了,程序仍然可以继续运行,会打印warn日志
2, 消费者根据自己配置超时时间判断,超时重试2次后仍然超时,则报错
dubbo请求超时后会,默认会重试2次,一共执行三次
3.2) 集群容错
集群容错: 指的是在集群调用失败时, Dubbo提供的容错方案
1、默认使用的是: failover 失败自动切换, 重试其他服务器 ( 不是重试自己 ). 通常用于读操作( 读是幂等的 ), 但重试会带来更长延迟. 可通过 retries=“2” 来设置重试次数
failover: 当对集群调用失败时, 会进行重试, 默认重试2次,一共三次调用. 会出现幂等性问题
虽然出现幂等性问题,但是依然推荐使用这种容错机制, 需要在业务层面解决幂等问题
方案1: 把数据的业务id作为数据库的联合主键, 此时业务id不能重复
方案2( 推荐 ): 使用分布式锁来解决重复消费问题.
2、failfast: 快速失败. 只发起一次调用, 失败立即报错. 通常用于非幂等性的写操作, 比如新增数据等功能
3、failsafe: 失败安全, 出现异常, 直接忽略. 常用语日志记录等操作
4、failback: 失败自动恢复, 后台记录失败请求记录, 定时重发. 常用于消息通知操作
5、forking: 并行调用多个服务器, 只要一个成功即返回. 通常用于实时性要求较高的读操作. 但需要浪费更多服务资源. 可通过forks=”2“来设 置最大并行数
3.3) 服务降级
比如一个电商系统, 核心业务是登陆、搜索、加入购物车、下单、支付等, 非核心业务有评论、个性化推荐等. 那么在双11流量洪峰过来时, 应该把服务器的资源留给核心业务, 非核心业务就可以通过降级处理.
作用域: 消费者
@RestController
@RequestMapping("site")
public class SiteController {
@Reference(version = "sync", timeout = 3000, mock = "force:return timeoutaaa")
private SiteService siteService;
@GetMapping("/getName")
public String getName(String name){
return siteService.getName("aaa");
}
}
降级方式:
1、mock = “fail:return timeoutaaa”), 是消费者对提供者发起远程调用失败后的处理结果, 返回"timeoutaaa", 不抛异常, 用来容忍不重要服务不稳定时对调用方的影响
2、mock = “force:return timeoutaaa”, 是消费者只要调用该方法,不发起远程调用直接强制返回: timeoutaaa, 用来屏蔽不重要服务不可用时对调用方的影响