coldsmog开发笔记
JS 事件笔记
Ckeditor 上传WPS图片失败问题
Springboot
SpringBoot 统一异常处理
Springboot 引入外部jar包
Springboot 打成war包
Springboot 多环境配置
SpringBoot @Scope注解学习
Springboot 快速生成项目API文档
SpringCache 缓存
Spring jetcache 二级缓存
Springboot 按条件装配类
FastJson的JsonPath语法
正则表达式语法
Spring 路径匹配
Feign 基础数据操作
监控Feign调用metrics
Springboot feign的性能优化
Jackson 设置序列化属性
SpringBoot 集成 Spring Data Mongodb 操作 MongoDB
MongoDB 的一些注意事项
MongoDB 指令对比
Jackson 解析XML
Springboot Redis注册
SpringBoot RedisTemplate批量插入
Springboot 指标监控Micrometer
springboot validation 注解校验
springboot 引入配置
Springboot 静态文件处理
Springboot 导出csv文件
Springboot 事件驱动(发布/订阅模式)
Springboot 启动过程和扩展点
Springboot 优化停机上下线
Spring自动装配 - 干饭角度学习
Springboot ShardingJDBC
Springboot的重试
springboot 动态修改端口
Oracle
Oracle 中实现自增ID
Oracle 定时任务
Oracle 解锁临时表
Oracle 检查连接数
Oracle 表空间
Oracle 解释执行SQL
markdown作图(适用typora)
服务器压测
业务对象层和数据层
并发限流处理
中间件
Yarn的使用
Dubbo学习笔记-RPC扩展和本地Mock
Dubbo学习笔记-泛化实现进行mock
Redis缓存穿透,缓存击穿,缓存雪崩
Galera 集群说明
Pip 镜像
pip 使用
MySQL命令行
数据库缓存双写方案
Git相关操作
Redis 操作时间复杂度一览
nacos 杂记
mybatis 散记
shardingjdbc
一次线上事故排查发现的Caffeine缓存死锁问题
设计模式
重新讲讲单例模式和几种实现
更优雅地实现策略模式
Http-headers
Prometheus 杂散笔记
JAVA 散记
CompletableFuture
Gson、FastJson、Jackson、json-lib对比总结
jackson 时间的序列化踩坑
JVM
自定义注解
mysql类型和java类型 转换一览表
枚举维护一个Map<value, Enum>的映射
Java中String +、concat、StringBuilder、StringBuffer 性能对比
TraceId 使用
MySQL 多数据源处理
Mybatis-plus 流式查询
JAVA发送win 桌面通知
idea 启动项目失败非代码问题杂记
Lambda 简述
Arthas 使用笔记
一种链式更新数据的数据模式
Skywalking 新增中间件插件
Redission 使用
数据导出为图片
IDEA 的热重启
Netty 工具类
maven 插件
本文档使用 MrDoc 发布
-
+
首页
Dubbo学习笔记-泛化实现进行mock
## 1. 什么是Dubbo,我们正常是怎么使用的? Apache Dubbo™ 是一款高性能Java RPC框架.其中与Alibaba Dubbo的区别主要在于阿里开发的2.6.X且不再维护,Apache开发的2.7.X新增了元数据中心 **MetaData** 和配置中心 **Conf-center** 这两个功能。元数据信息包括服务接口,及接口的方法信息。这些信息将被用于服务mock,服务测试。 我们平常是在实例上增加注解@Service暴露服务和使用@Reference来完成自动引用实例,样例查看[http://dubbo.apache.org/zh-cn/docs/user/configuration/annotation.html](http://dubbo.apache.org/zh-cn/docs/user/configuration/annotation.html) ## 2. 注册一个RPC服务需要什么参数 官方给出的快速启动样例是这样的: [http://dubbo.apache.org/zh-cn/docs/user/quick-start.html](http://dubbo.apache.org/zh-cn/docs/user/quick-start.html) 这个样例是使用XML,我们并不常用,但可以比较清楚地观察到注册一个RPC服务需要什么参数。注意下面这个 **provider.xml**,我增加了一些改动用以说明 ``` <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!-- 提供方应用信息,用于计算依赖关系 application.name为应用名,一个接口下可以有多个应用来提供 --> <dubbo:application name="hello-world-app" /> <!-- 使用multicast广播注册中心暴露服务地址 ip:port地址可以为多个,用,分割即可。 zookeeper://101.201.232.80:2181?backup=10.20.153.11:2181,10.20.153.12:2181"--> <dubbo:registry address="zookeeper://101.201.232.80:2181" /> <!-- 用dubbo协议在20880端口暴露服务 --> <dubbo:protocol name="dubbo" port="20880" /> <!-- 声明需要暴露的服务接口 interface为接口全类名, ref服务对象实现引用,与下面的bean.id对应 version=服务版本,升级接口如果不兼容之前接口,就需要升级版本号 group=服务分组,当一个接口有多个实现,可以用分组区分 --> <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService" version="1.0.0" group="dubbo"/> <!-- 声明实现类,使远程RPC像本地bean一样实现服务 id=service.ref class=实现类的全类名 --> <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl" /> </beans> ``` 其实区别定位一个RPC服务需要的条件一共有四个: 1. application.name 2. service.interface。注意:如果这个接口不存在,就无法注册并暴露服务,而我们mock的目标就是个假的服务出来。 3. service.version 4. service.group **此处注意:服务的元数据是根据service.interface反射得到的,我们为了和2.7.X兼容,就必须自己造个假的元数据上去** ## 3. 我们的Mock实现思路 Dubbo与mock相关的提供了两种api:[本地伪装Mock](http://dubbo.apache.org/zh-cn/docs/user/demos/local-mock.html)和[泛化实现](http://dubbo.apache.org/zh-cn/docs/user/demos/generic-service.html)。 ![](https://dubbo.apache.org/imgs/blog/dubbo-mock-stub-flow.png) 本地伪装Mock,XML对应为<dubbo:reference interface="com.foo.BarService" mock="true" .../>用于服务降级,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。 大白话就是参考AOP的after-throw,原服务抛出异常后,触发本地mock进行服务降级。```try {//原服务} catch (Exception e) {//执行mock实例}```。这个坑了我很多时间,因为mock="true"太具有迷惑性,我原本使用这个mock约定实现mock平台功能,使用force模式强制调用mock平台上的服务,但是一来会覆盖原先的本地mock;二来不符合无侵入原则。最终放弃。贴出参考用法如下 ``` <dubbo:reference id="demoService" check="false" interface="com.foo.BarService"> <!-- force表示强制走mock; return fake是mock执行的语句 --> <dubbo:parameter key="sayHello.mock" value="force:return fake"/> </dubbo:reference> ``` ------------ 泛化实现,XML对应为<dubbo:reference interface="com.foo.BarService" generic="true" .../>,用于服务器端没有API接口及模型类元的情况,参数及返回值中的所有POJO均用Map表示,通常用于框架集成,比如:实现一个通用的远程服务Mock框架,可通过实现GenericService接口处理所有服务请求。 这个就是我们最终采用的实现思路。通过指定一个通用服务接口GenericService的实现,完成服务的暴露。 ``` // 这个就是 提供者dubbo 和标签一一对应,该类很重,因为包括了与注册中心的链接等等,需要自行缓存处理 ServiceConfig<GenericService> service = new ServiceConfig<GenericService>; // 配置service里注册中心的属性,需要指定address,例"zookeeper://101.201.232.80:2181"。 service.setRegistry(new RegistryConfig("zookeeper://101.201.232.80:2181")); // 配置应用信息,这里我们将应用名统一表示为luckymock。 ApplicationConfig application = new ApplicationConfig(); application.setDefault(true); application.setName("luckymock"); service.setApplication(application); // 指定通信协议和本地通讯端口 ProtocolConfig protocol = new ProtocolConfig(); protocol.setName("dubbo"); protocol.setPort(20880); service.setProtocol(protocol); // 说明是否是通用服务 并绑定通用服务接口的实现:GenericServiceImpl service.setGeneric("true"); GenericService genericService = new GenericServiceImpl(); service.setRef(genericService); // 配置这个服务特有的的东西 service.setGroup("dubbo"); service.setVersion("1.0.0"); service.setInterface("test") // 对外暴露这个服务 service.export(); // 由于上面提到的元数据问题,我们手动去提交了一遍元数据。 // 这方面资料极少,我是通过源码ZookeeperMetadataReport的storeMetadata方法追溯到MetadataReportService的publishProviderf方法,所有用法都参考这个方法 // 使用下面这个函数提交服务提供者的元数据,不一定是zookeeper作注册中心 metadataReport.storeProviderMetadata(metadataIdentifier, fullServiceDefinition); ``` 关于元数据,MetadataIdentifier 该类主要是用于配置zookeeper上元数据节点路径和节点名称 > 例:/dubbo/metadata/服务名(接口全类名)/1.0.0(版本)/dubbo(分组)/provide(side)/demo-provide(应用名) FullServiceDefinition 该对象进行gson转换后的json串,即为元数据节点内容 - parameters:服务的属性,包括version,group等等 - canonicalName:接口全类名 - codeSource:源代码 - methods:方法 - types: 所有方法入参出参的类型 ## 样例 GenericServiceImpl 这个impl就是xml中service.ref所需要的类 ``` import org.apache.dubbo.rpc.service.GenericException; import org.apache.dubbo.rpc.service.GenericService; /** * @Description: 服务端实现 GenericService * @Author: quanhuan.huang@luckincoffee.com * @Date: 2019/12/19 15:01 */ public class GenericServiceImpl implements GenericService { @Override public Object $invoke(String method, String[] parameterTypes, Object[] args) throws GenericException { // 这里只有method方法名可以进行区别,所以该类可能需要动态编译 if (method.equals("hi")) { return "hi, " + args[0]; } else if (method.equals("hello")) { return "hello, " + args[0]; } else if (method.equals("sayHello")) { return "say:hello, " + args[0]; } return "未找到该方法,无法mock"; } } ``` 数据结构:DubboRpcDTO ``` public class DubboRpcDTO { /** * 服务接口的全类名 */ private String interfaceName; /** * 分组 */ private String group; /** * 版本 */ private String version; /** * 方法列表 */ private List<DubboMethodDTO> methodList; } ``` 数据结构:DubboMethodDTO ``` public class DubboMethodDTO { /** * 方法名 */ private String methodName; /** * 方法参数 */ private String[] arguments; /** * 返回类型 */ private String returnType; } ``` 服务实现 ``` @Service public class DubboProvideService { private static Logger logger = LoggerFactory.getLogger(DubboProvideService.class); private MetadataReportFactory metadataReportFactory = ExtensionLoader.getExtensionLoader(MetadataReportFactory.class).getAdaptiveExtension(); /** * 该类很重 自行缓存 */ private static ServiceConfig<GenericService> service; /** * 提供rpc服务 * * @return 是否完成 */ Boolean rpcMockProvide(DubboRpcDTO rpcDTO) throws ClassNotFoundException { // 注册并对外暴露服务 ServiceConfig<GenericService> service = getService(); service.setGroup(rpcDTO.getGroup()); service.setVersion(rpcDTO.getVersion()); service.setInterface(rpcDTO.getInterfaceName()); // 指向自己实现的通用类实例,需要动态化 GenericService genericService = new GenericServiceImpl(); service.setGeneric("true"); service.setRef(genericService); service.export(); // 注册数据源 FullServiceDefinition fullServiceDefinition = new FullServiceDefinition(); TypeDefinitionBuilder builder = new TypeDefinitionBuilder(); List<TypeDefinition> typeDefinitions = new LinkedList<>(); List<MethodDefinition> methodDefinitions = new LinkedList<>(); for (DubboMethodDTO method : rpcDTO.getMethodList()) { // 记录方法 MethodDefinition methodDefinition = new MethodDefinition(); methodDefinition.setName(method.getMethodName()); methodDefinition.setParameterTypes(method.getArguments()); methodDefinition.setReturnType(method.getReturnType()); methodDefinitions.add(methodDefinition); // 记录所有入参的type for (String argument : method.getArguments()) { TypeDefinition td = builder.build(Class.forName(argument), Class.forName(argument)); typeDefinitions.add(td); } // 返回值的type也需要记录 typeDefinitions.add(builder.build(Class.forName(method.getReturnType()), Class.forName(method.getReturnType()))); } // 拼接服务内容 Map<String, String> parameters = new HashMap<>(16); parameters.put("side", "provider"); parameters.put("release", "2.7.3"); parameters.put("methods", "*"); parameters.put("deprecated", "false"); parameters.put("dubbo", "2.0.2"); parameters.put("interface", rpcDTO.getInterfaceName()); parameters.put("version", rpcDTO.getVersion()); parameters.put("generic", "true"); parameters.put("application", "luckymock"); parameters.put("dynamic", "true"); parameters.put("register", "true"); parameters.put("group", rpcDTO.getGroup()); parameters.put("anyhost", "true"); fullServiceDefinition.setParameters(parameters); fullServiceDefinition.setCodeSource(ClassUtils.getCodeSource(this.getClass())); fullServiceDefinition.setCanonicalName(rpcDTO.getInterfaceName()); fullServiceDefinition.setTypes(typeDefinitions); fullServiceDefinition.setMethods(methodDefinitions); // 拼接服务描述 MetadataIdentifier metadataIdentifier = new MetadataIdentifier(); metadataIdentifier.setServiceInterface(rpcDTO.getInterfaceName()); metadataIdentifier.setVersion(rpcDTO.getVersion()); metadataIdentifier.setGroup(rpcDTO.getGroup()); metadataIdentifier.setSide("provider"); // 应用名统一为luckymock metadataIdentifier.setApplication("luckyMock"); // 写元数据中心 MetadataReport metadataReport = metadataReportFactory.getMetadataReport(URL.valueOf("zookeeper://101.201.232.80:2181")); metadataReport.storeProviderMetadata(metadataIdentifier, fullServiceDefinition); logger.info("注册RPC服务成功:{}", rpcDTO.getInterfaceName()); return true; } /** * 该类很重,缓存 * * @return @Service的信息 */ private static ServiceConfig<GenericService> getService() { if (null == service) { service = new ServiceConfig<>(); // 注册中心配置 RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://101.201.232.80:2181"); service.setRegistry(registryConfig); // 应用配置 ApplicationConfig application = new ApplicationConfig(); application.setName("luckymock"); service.setApplication(application); // 协议配置 ProtocolConfig protocol = new ProtocolConfig(); protocol.setName("dubbo"); protocol.setPort(20880); service.setProtocol(protocol); } return service; } } ``` 经测试,下面这个test可以完成provider.xml一样的功能。 ``` @Test public void demoService() { // 现在用泛化实现,实现com.lucky.demo.api.DemoService // 如果想要保证消费者正常调用,消费者处 InterfaceName这个类必须存在。当然,我们作为服务提供者不需要真的有这个类 // 什么情况消费者也没有这个接口呢?rpc测试平台,dubbo-admin已经做了。 DubboRpcDTO dubboRpcDTO = new DubboRpcDTO(); dubboRpcDTO.setGroup("dubbo"); dubboRpcDTO.setVersion("1.0.0"); dubboRpcDTO.setInterfaceName("com.lucky.demo.api.DemoService"); List<DubboMethodDTO> methodList = new LinkedList<>(); DubboMethodDTO dubboMethodDTO = new DubboMethodDTO(); dubboMethodDTO.setMethodName("sayHello"); dubboMethodDTO.setReturnType("java.lang.String"); dubboMethodDTO.setArguments(new String[]{"java.lang.String"}); methodList.add(dubboMethodDTO); dubboRpcDTO.setMethodList(methodList); try { dubboProvideService.rpcMockProvide(dubboRpcDTO); System.out.println("dubbo service started,enter any keys stop"); Scanner scanner = new Scanner(System.in); scanner.next(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } ```
寒烟濡雨
2022年3月9日 09:54
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码