# 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就是为了实现不同的微服务之间调用。 #### 架构设计 ##### 架构图解析 ![架构图](./static/dubb-structure.jpg) 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的版本(默认没有版本号)不同而导致调用不了 ![vesion导致调用失败](./static/version-error.png) 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 缓存,提前验证参数,调用失败后伪造容错数据等等。 > > 而本地存根的作用也是在请求服务之前进行一次拦截,如果没错才进行远程服务调用 ![官网调用示意图](./static/dubbo-stub.jpg) ###### 演示流程 [源码](./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. 运行截图 ![演示调用](./static/stub-result.png) ###### 使用事项 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()))); } } } ``` ###### 验证结果 ![验证结果](./static/validation-result.png) #### 本地伪装(服务降级) > 当服务压力剧增或者发生异常的时候,为了保护整个系统的运行会当前业务情况进行策略的降级,一般实施方案就是让消费段返回一个默认值(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. 结果如图所示 ![dubbo-sp](./static/dubbo-spi-result.png) ###### 注意 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(隐式参数名); ```