# dubbo-note
**Repository Path**: weixsun/dubbo-note
## Basic Information
- **Project Name**: dubbo-note
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 2
- **Created**: 2024-02-26
- **Last Updated**: 2024-02-26
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
### DUBBO使用基础篇
> dubbo是一款面向接口代理的高性能 RPC 框架,提供服务注册与发现的特性。随着互联网的发展,网站应用的规模不断扩大,常规的单一架构已无法应对的时候,将单一架构的业务拆分成一个个微服务独立部署,而dubbo就是为了实现不同的微服务之间调用。
#### 架构设计
##### 架构图解析

1. Provider
1. Protocol 负责进行交互,主要是接受Consumer发过来的数据
2. Service 真正的服务端逻辑
3. Container Provider的运行环境,做注册
2. Consumer
1. Protocol 负责进行交互信息,主要发送请求
2. Cluster 负责更新Provider List,感知Provider存在和变化
3. Proxy 代理类,因为是透明的合理就是服务生成bean
3. Registry 注册中心,
4. Monitor 统计
#### 基本配置
##### 简单实现
1. maven配置
```xml
org.apache.dubbo
dubbo
2.7.5
org.apache.dubbo
dubbo-common
2.7.5
org.apache.dubbo
dubbo-registry-zookeeper
2.7.5
org.apache.dubbo
dubbo-registry-nacos
2.7.5
org.apache.dubbo
dubbo-rpc-dubbo
2.7.5
org.apache.dubbo
dubbo-remoting-netty4
2.7.5
org.apache.dubbo
dubbo-serialization-hessian2
2.7.5
```
2. 配置好provider-xml,需要配置的是application信息、注册中心地址、暴露的接口
```xml
```
3. 启动provider
```java
public class SimpleMain {
public static void main(String[] args) throws InterruptedException, IOException {
// spring读取xml
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("META-INF/spring-context.xml");
applicationContext.start();
while(true){
Thread.sleep(Integer.MAX_VALUE);
}
}
}
```
4. 以下是最简单的consumenr.xml配置
```xml
```
5. 启动consumer进行远程调用
```java
public class SimpleConsumer {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext();
classPathXmlApplicationContext.setConfigLocation("spring-context-consumer.xml");
classPathXmlApplicationContext.refresh();// 启动spring容器
System.out.println(classPathXmlApplicationContext.getBean(IHelloService.class).sayHello("jarvis"));
}
}
```
##### 多版本
> 当一个接口,出现了不兼容的升级时,可以用version配置进行引渡,consumer只会选择配置相同version的provider进行远程调用,不同版本是调用不了的
1. 修改配置文件,在consumer的配置文件将对应的reference修改为
```xml
```
2. 再次执行的时候这里由于和provider的版本(默认没有版本号)不同而导致调用不了

3. 修改provider的版本号,将它改成和consumer一样的版本号之后才能调用
##### 分组[示例源码](./dubbo-simple/dubbo-simple-consumer/src/main/java/com/jarvis/dubbo/GroupConsumer.java)
> 当一个接口有多种实现时,可以用 group区分调用哪个实现
1. provider.xml
```xml
```
2. consumer.xml
```xml
```
##### 检查
> consumer启动的时候会默认检查需要的provider时候已经启用,如果provider还没启动的时候consumer启动就会抛出异常,阻止spring初始化完成。此参数为了将检查放到远程调用的时候处理,实现懒加载的功能
>
> 通过在consumer的配置文件中加入check="false"的配置
#### 泛化
> 提供了一套消费者可以不需要知道接口服务接口,也可以进行远程调用服务的机制。常用于在服务网关和跨语言调用
###### 使用方法 [源码](./dubbo-simple/dubbo-simple-consumer/src/main/java/com/jarvis/dubbo/GenericConsumer.java) [xml配置源码](./dubbo-simple/dubbo-simple-consumer/src/main/resources/spring-context-consumer.xml)
```java
public static void main(String[] args) {
// 关于xml的配置,需要接口的配置,具体看源码,以下具体演示的是不需要拥有接口的情况
ApplicationConfig application = new ApplicationConfig();
application.setName("yyy");
// 连接注册中心配置
RegistryConfig registry = new RegistryConfig();
registry.setAddress("zookeeper://127.0.0.1:2181");
ReferenceConfig referenceConfig = new ReferenceConfig<>();
referenceConfig.setGeneric(true);
referenceConfig.setApplication(application);
referenceConfig.setRegistry(registry);
referenceConfig.setInterface("com.jarvis.dubbo.service.IGenericService");// 这里不需要接口对象,直接配置接口的全路径
GenericService configGenericService = referenceConfig.get();
System.out.println(configGenericService.$invoke("genericMethod", new String[]{"java.lang.String"}, new Object[]{"World"}));
}
```
#### 本地存根
> 官网给出的解释是远程服务后,客户端通常只剩下接口,而实现全在服务器端,但提供方有些时候想在客户端也执行部分逻辑,比如:做 ThreadLocal 缓存,提前验证参数,调用失败后伪造容错数据等等。
>
> 而本地存根的作用也是在请求服务之前进行一次拦截,如果没错才进行远程服务调用

###### 演示流程 [源码](./dubbo-simple/dubbo-simple-consumer/src/main/java/com/jarvis/dubbo/StubConsumer.java)
1. 实现调用者的接口
```java
public interface IStubService {
public String echoHello(ValidationModel model);
}
public class StubServiceStub implements IStubService {
Logger logger = Logger.getLogger(this.getClass().getCanonicalName());
private final IStubService stubService;
public StubServiceStub(IStubService stubService) {
this.stubService = stubService;
}
@Override
public String echoHello(ValidationModel model) {
if(StringUtils.isBlank(model.getId())){
logger.warning("id 不能为空");
return "调用错误,id 不能为空";
}
if(StringUtils.isBlank(model.getName())){
logger.warning("名字不能空");
return "调用错误,名字不能为空";
}
return this.stubService.echoHello(model);
}
}
public class StubService implements IStubService {
@Override
public String echoHello(ValidationModel model) {
return "echo Hello";
}
}
```
2. 增加stub的配置
```java
```
3. 运行截图

###### 使用事项
1. 需要实现接口,并且需要有带参数的构造函数
2. 作用在类上,对于类上的所有方法都进行实现
#### 参数验证
> 上一小节讲述了stub的功能可以包括参数验证,这一节讲述的是一种更加优雅的方式进行参数验证,基于jsr303实现,通过注解验证,使用方式类似springweb的@Valiate
###### maven 依赖
```xml
javax.validation
validation-api
1.0.0.GA
org.hibernate
hibernate-validator
4.2.0.Final
```
###### 示例 [消费者源码](./dubbo-simple/dubbo-simple-consumer/src/main/java/com/jarvis/dubbo/ValidationConsumer.java)
1. 在实体类中加入验证注解
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ValidationModel implements Serializable {
private static final long serialVersionUID = -4261785680248286539L;
@NotBlank
private String name;
@NotBlank(groups = IValidationService.Insert.class, message = "名字不能为空")//新增时校验,其他时不校验
private String id;
}
```
2. 在xml中添加验证配置
```xml
```
3. 消费端获取验证失败数据
```java
public class ValidationConsumer {
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("spring-context-consumer.xml");
IValidationService validationService = classPathXmlApplicationContext.getBean(IValidationService.class);
System.out.println(validationService.insert(new ValidationModel("1", "2")));
try{
System.out.println(validationService.update(new ValidationModel(null, "123")));
}catch (ConstraintViolationException e){
Set> violations = e.getConstraintViolations(); // 可以拿到一个验证错误详细信息的集合
System.out.println(String.format("更新错误信息: %s",violations.stream().map(mapper -> mapper.getMessage()).collect(Collectors.toList())));
}
try{
System.out.println(validationService.insert(new ValidationModel()));
}catch (ConstraintViolationException e){
Set> violations = e.getConstraintViolations(); // 可以拿到一个验证错误详细信息的集合
System.out.println(String.format("新增错误信息: %s",violations.stream().map(mapper -> mapper.getMessage()).collect(Collectors.toList())));
}
}
}
```
###### 验证结果

#### 本地伪装(服务降级)
> 当服务压力剧增或者发生异常的时候,为了保护整个系统的运行会当前业务情况进行策略的降级,一般实施方案就是让消费段返回一个默认值(dubbo支持抛出异常或者返回默认值),一般触发降级的条件有服务器调用超时、服务器故障(网络发生异常)、限流等情况
###### 示例
1. 指定伪装类
2. xml指定伪装类
###### 问题
1. 不能像springcloud那样,一般报错不会进入降级
2. 降级的时候不能同时返回异常和默认值
#### SPI
##### Java spi
###### 什么是spi
> SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI的作用就是为这些被扩展的API寻找服务实现。可以理解为java原生做的通过特定的配置帮助我们实现IOC的方法
###### 使用方式:[源码示例](./spi/java-spi/src/main/java/com/jarvis/spi/SPIMain.java)
1. 定义一个接口类/父类
```java
public interface UploadCDN {
public void upload(String url);
}
```
2. 实现接口/继承父类
```java
public class ChinaNetBase extends BaseClass {
@Override
public void update(String url) {
System.out.println(String.format("update to chinaNet cdn url:%s", url));
}
}
public class IqiyiBase extends BaseClass {
@Override
public void update(String url) {
System.out.println(String.format("upload to Iqiyi cdn url:%s", url));
}
}
```
3. 配置,在resources下新建目录为META-INF/services,并在这个目录新建名称为接口全路径名的文件并配置
```text
com.jarvis.spi.impl.ChinaNetCDN
com.jarvis.spi.impl.IqiyiCDN
```
4. 调用
```java
public static void main(String[] args) {
ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();// 通过JMX获取已经装在的class数量
ServiceLoader uploadCDNServiceLoader = ServiceLoader.load(UploadCDN.class);// 这里创建了一个ServiceLoader对象,所以class数量加1,默认是懒加载的形式装载class
Iterator iterator = uploadCDNServiceLoader.iterator();
while(iterator.hasNext()){
UploadCDN uploadCDN = iterator.next(); // 默认是在这里转载class并实例化,
System.out.println(classLoadingMXBean.getLoadedClassCount()); //这里看到每次调用完next方法后数量加1
uploadCDN.upload(uploadCDN.getClass().getCanonicalName());
}
System.out.println(classLoadingMXBean.getLoadedClassCount());
}
```
##### dubbo spi
> dubbo spi 其实就是对dubbo一些功能提供扩展的扩展点,使用和配置和使用方法和java spi类似,具体可配置的spi可以参考[官方文档](http://dubbo.apache.org/zh-cn/docs/dev/impls/protocol.html)查看,一下用路由选择作为演示
###### 示例[源码示例](./spi/dubbo-spi/src/main/java/com/jarvis/dubbo/spi/LoadBalanceSPIDemo.java)
1. 创建自定义路由选择类
```java
public class FirstLoadBalance implements LoadBalance {
Logger logger = Logger.getLogger(this.getClass().getCanonicalName());
@Override
public Invoker select(List> invokers, URL url, Invocation invocation) throws RpcException {
logger.info("执行自定义路由选择...");//输出演示效果
return invokers.get(0);// 每次获取第一个路由
}
}
```
2. 在resources下创建META-INF/dubbo目录并创建文件名为对应扩展点接口名称的配置文件,如下的META-INF\dubbo\org.apache.dubbo.rpc.cluster.LoadBalance,内容格式为xxx名=com....xxxLoadBalance
```text
first=com.jarvis.dubbo.spi.loadBalance.FirstLoadBalance
```
3. 在consumer中配置路由选择策略(注意loadbalance配置项中选择定义好的配置名)
```xml
```
4. 编写测试类测试loadbalance是否生效
```java
public static void main(String[] args) {
ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("META-INF/spring-context.xml");
IHelloService helloService = classPathXmlApplicationContext.getBean(IHelloService.class);
System.out.println(helloService.sayHello("jarvis"));
}
```
5. 结果如图所示

###### 注意
1. 这里loadbalance生效需要至少两个provider才生效,具体请看源码[AbstractClusterInvoker](org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker)
#### 隐式参数
> 在rpc传输过程正可以通过设置隐式参数,将这个参数从consumer传递到provider中,这里用过滤器的例子进行演示
###### 消费端设置隐式参数[示例](./dubbo-simple/dubbo-simple-consumer/src/main/java/com/jarvis/dubbo/filter/TokenConsumerFilter.java)
```java
RpcContext.getContext().setAttachment(隐式参数名, 对象);
```
###### 提供方获取隐式参数[示例](./dubbo-simple/dubbo-simple-provider/src/main/java/com/jarvis/dubbo/filter/TokenProviderFilter.java)
```java
RpcContext.getContext().getAttachment(隐式参数名);
```