Soul网关源码解析(三)代理Dubbo服务
文章目录
- 目标
- 一、使用 soul 代理 dubbo 服务
- 1、dubbo 服务接入网关
- 1.1 springboot 项目接入方式
- 1.2 spring 项目接入方式
- 2、配置 dubbo 插件
- 3、注册 dubbo 服务到网关
- 4、http 方式请求 dubbo 服务
- 二、dubbo 服务如何注册到网关?
- 三、dubbo 插件如何工作的?
- 四、总结
目标
使用 soul 代理 dubbo 服务
dubbo 服务如何注册到网关的?
dubbo 插件是如何工作的?
理清 http --> 网关–> dubbo provider 整条链路经历了什么。
总结
一、使用 soul 代理 dubbo 服务
1、dubbo 服务接入网关
1.1 springboot 项目接入方式
1.1.1 依赖引入
alibaba dubbo 用户
<dependency><groupId>org.dromara</groupId><artifactId>soul-spring-boot-starter-client-alibaba-dubbo</artifactId><version>${last.version}</version></dependency>
apache dubbo 用户
<dependency><groupId>org.dromara</groupId><artifactId>soul-spring-boot-starter-client-apache-dubbo</artifactId><version>${last.version}</version></dependency>
1.1.2 yml 配置
soul:dubbo:adminUrl: http://localhost:9095contextPath: /dubboappName: dubbo
1.2 spring 项目接入方式
1.2.1 依赖引入
alibaba dubbo 用户
<dependency><groupId>org.dromara</groupId><artifactId>soul-client-alibaba-dubbo</artifactId><version>${last.version}</version></dependency>
apache dubbo 用户
<dependency><groupId>org.dromara</groupId><artifactId>soul-client-apache-dubbo</artifactId><version>${last.version}</version></dependency>
1.2.2 xml 配置
alibaba dubbo 用户
<bean id ="alibabaDubboServiceBeanPostProcessor" class ="org.dromara.soul.client.alibaba.dubbo.AlibabaDubboServiceBeanPostProcessor"><constructor-arg ref="dubboConfig"/></bean><bean id="dubboConfig" class="org.dromara.soul.client.dubbo.common.config.DubboConfig"><property name="adminUrl" value="http://localhost:9095"/><property name="contextPath" value="/你的contextPath"/><property name="appName" value="你的名字"/></bean>
apache dubbo 用户
<bean id ="apacheDubboServiceBeanPostProcessor" class ="org.dromara.soul.client.apache.dubbo.ApacheDubboServiceBeanPostProcessor"><constructor-arg ref="dubboConfig"/></bean><bean id="dubboConfig" class="org.dromara.soul.client.dubbo.common.config.DubboConfig"><property name="adminUrl" value="http://localhost:9095"/><property name="contextPath" value="/你的contextPath"/><property name="appName" value="你的名字"/></bean>
2、配置 dubbo 插件
启动 soul-admin
和 soul-bootstrap
,前往后台插件管理页面
启用 dubbo 插件,并配置注册中心 ip + 端口
3、注册 dubbo 服务到网关
dubbo 服务实现类的方法上加上 @SoulDubboClient
注解
@Override@SoulDubboClient(path = "/findById", desc = "Query by Id")public DubboTest findById(final String id) {DubboTest dubboTest = new DubboTest();dubboTest.setId(id);dubboTest.setName("hello world Soul Alibaba Dubbo, findById");return dubboTest;}
启动 dubbo 提供者项目,日志输出 dubbo client register success
注册成功
4、http 方式请求 dubbo 服务
通过 http post 方式请求网关,通过 body 传递 json 格式参数(contextPath=/dubbo)
dubbo 服务访问成功,同时 soul-bootstrap
输出匹配日志
dubbo selector success match , selector name :/dubbo
dubbo selector success match , selector name :/dubbo/findById
二、dubbo 服务如何注册到网关?
alibaba dubbo 与 apache dubbo 差异较小,alibaba dubbo 的注册流程弄明白后,apache dubbo 也自然清楚了。
从控制台日志 dubbo client register success
定位到 RegisterUtils 类,在打印注册日志的地方加上断点
重启项目,可以看到成功进入断点,在此处调用 OkHttpTools.post 进行服务注册。
注意此处是注册到 soul-admin
,再由网关和 soul-admin
的数据同步机制同步到网关内存。
注册的元数据怎么拿到的呢?
从堆栈定位到 doRegister 方法的调用方 AlibabaDubboServiceBeanPostProcessor.handler
private void handler(final ServiceBean<?> serviceBean) {Class<?> clazz = serviceBean.getRef().getClass();// 如果是 cglib 代理,则取带有泛型的父类if (ClassUtils.isCglibProxyClass(clazz)) {String superClassName = clazz.getGenericSuperclass().getTypeName();try {clazz = Class.forName(superClassName);} catch (ClassNotFoundException e) {log.error(String.format("class not found: %s", superClassName));return;}}final Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(clazz);for (Method method : methods) {// 如果方法上有 SoulDubboClient 注解,则调用上面的 doRegister 方法注册元数据信息SoulDubboClient soulDubboClient = method.getAnnotation(SoulDubboClient.class);if (Objects.nonNull(soulDubboClient)) {RegisterUtils.doRegister(buildJsonParams(serviceBean, soulDubboClient, method), url, RpcTypeEnum.DUBBO);}}}
handler 方法通过识别 serviceBean 上的 SoulDubboClient
来判断需要注册哪些服务的元数据。
注意到这里使用 buildJsonParams 来构造元数据,其内容如下:
private String buildJsonParams(final ServiceBean<?> serviceBean, final SoulDubboClient soulDubboClient, final Method method) {// 从 dubboConfig 取 appName,若未配置则使用 applicationNameString appName = dubboConfig.getAppName();if (StringUtils.isEmpty(appName)) {appName = serviceBean.getApplication().getName();}String path = dubboConfig.getContextPath() + soulDubboClient.path();String desc = soulDubboClient.desc();String serviceName = serviceBean.getInterface();String configRuleName = soulDubboClient.ruleName();String ruleName = ("".equals(configRuleName)) ? path : configRuleName;String methodName = method.getName();Class<?>[] parameterTypesClazz = method.getParameterTypes();// 拼装方法参数类型名String parameterTypes = Arrays.stream(parameterTypesClazz).map(Class::getName).collect(Collectors.joining(","));// 构造元数据并序列化为 json 串MetaDataDTO metaDataDTO = MetaDataDTO.builder().appName(appName).serviceName(serviceName).methodName(methodName).contextPath(dubboConfig.getContextPath()).path(path).ruleName(ruleName).pathDesc(desc).parameterTypes(parameterTypes).rpcExt(buildRpcExt(serviceBean)).rpcType("dubbo").enabled(soulDubboClient.enabled()).build();return OkHttpTools.getInstance().getGson().toJson(metaDataDTO);}
继续回到上面的handler,通过堆栈找到其调用方 AlibabaDubboServiceBeanPostProcessor.handler
@Overridepublic void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {if (Objects.nonNull(contextRefreshedEvent.getApplicationContext().getParent())) {return;}// Fix bug(https://github.com/dromara/soul/issues/415), upload dubbo metadata on ContextRefreshedEventMap<String, ServiceBean> serviceBean = contextRefreshedEvent.getApplicationContext().getBeansOfType(ServiceBean.class);for (Map.Entry<String, ServiceBean> entry : serviceBean.entrySet()) {executorService.execute(() -> handler(entry.getValue()));}}
通过实现 ApplicationListener<ContextRefreshedEvent>
接口监听容器刷新事件,容器刷新后取到 ServiceBean 调用 handler 进行处理。
至此,alibaba dubbo 服务注册到网关的流程梳理清楚:
1)AlibabaDubboServiceBeanPostProcessor 监听到容器刷新后,从 spring 上下文取出 ServiceBean 调用 handler 进行处理
2)handler 拿到 ServiceBean 的接口或带泛型父类带 @SoulDubboClient
的方法,调用 RegisterUtils.doRegister
进行注册
3)RegisterUtils.doRegister
使用 OkHttpTools 将元数据 post 到 soul-admin
落库
4)soul-admin
再将元数据同步到网关内存
三、dubbo 插件如何工作的?
在此就不展开插件的统一工作流程了,只聚焦于 dubbo 插件本身的工作内容。
打开 soul-plugin-alibaba-dubbo
插件工程,定位到 AlibabaDubboPlugin.doExecute
@Overrideprotected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {String body = exchange.getAttribute(Constants.DUBBO_PARAMS);SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);assert soulContext != null;MetaData metaData = exchange.getAttribute(Constants.META_DATA);// 元数据检查未通过处理if (!checkMetaData(metaData)) {assert metaData != null;log.error(" path is :{}, meta data have error.... {}", soulContext.getPath(), metaData.toString());exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);Object error = SoulResultWrap.error(SoulResultEnum.META_DATA_ERROR.getCode(), SoulResultEnum.META_DATA_ERROR.getMsg(), null);return WebFluxResultUtils.result(exchange, error);}// 请求体缺失处理if (StringUtils.isNoneBlank(metaData.getParameterTypes()) && StringUtils.isBlank(body)) {exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);Object error = SoulResultWrap.error(SoulResultEnum.DUBBO_HAVE_BODY_PARAM.getCode(), SoulResultEnum.DUBBO_HAVE_BODY_PARAM.getMsg(), null);return WebFluxResultUtils.result(exchange, error);}// dubbo 泛化调用Object result = alibabaDubboProxyService.genericInvoker(body, metaData);if (Objects.nonNull(result)) {exchange.getAttributes().put(Constants.DUBBO_RPC_RESULT, result);} else {exchange.getAttributes().put(Constants.DUBBO_RPC_RESULT, Constants.DUBBO_RPC_RESULT_EMPTY);}exchange.getAttributes().put(Constants.CLIENT_RESPONSE_RESULT_TYPE, ResultEnum.SUCCESS.getName());return chain.execute(exchange);}
可以看到,进入 dubbo 插件处理以后,最核心的是从 exchange 拿到元数据和请求体后进行 dubbo 泛化调用。
进入 AlibabaDubboProxyService.genericInvoker
public Object genericInvoker(final String body, final MetaData metaData) throws SoulException {// 拿到 ReferenceConfigReferenceConfig<GenericService> reference = ApplicationConfigCache.getInstance().get(metaData.getPath());if (Objects.isNull(reference) || StringUtils.isEmpty(reference.getInterface())) {ApplicationConfigCache.getInstance().invalidate(metaData.getPath());reference = ApplicationConfigCache.getInstance().initRef(metaData);}// 组织方法名、参数类型和参数,再交由 genericService 进行泛化调用GenericService genericService = reference.get();try {Pair<String[], Object[]> pair;if (ParamCheckUtils.dubboBodyIsEmpty(body)) {pair = new ImmutablePair<>(new String[]{}, new Object[]{});} else {pair = dubboParamResolveService.buildParameter(body, metaData.getParameterTypes());}return genericService.$invoke(metaData.getMethodName(), pair.getLeft(), pair.getRight());} catch (GenericException e) {log.error("dubbo invoker have exception", e);throw new SoulException(e.getExceptionMessage());}}
可以看到,AlibabaDubboProxyService 只是封装了 alibaba 的 GenericService,先组织方法名、参数类型和参数,再交由 genericService 进行泛化调用。
四、总结
本篇从使用侧先梳理 dubbo 服务接入流程,再从 dubbo 服务注册到网关以及 dubbo 插件的工作内容进行分析,最终将 soul 代理 dubbo 服务的整个脉络串起来。
1)客户端发布 dubbo 服务,同时注册 dubbo 服务元数据到 soul-admin
2) soul-admin
同步元数据到 网关 jvm 内存
3)客户向网关发起 http 请求,访问目标为后端 dubbo 服务
4)网关拿到 requestBody 和内存中的元数据,解析成方法名、参数类型和参数值后发起 dubbo 泛化调用
5)客户端响应 dubbo 泛化调用并回吐响应报文
下一篇,将分析 soul 如何代理 sofa-rpc 服务。
Soul网关源码解析(三)代理Dubbo服务相关推荐
- Soul网关源码解析目录
Soul网关源码解析目录 Soul网关源码解析文章列表 对用Java写的高性能网关:Soul,进行一波学习和研究,下面是相关的文章记录 掘金 了解与初步运行 Soul网关源码解析(一) 概览 ...
- Soul网关源码解析(二)代理Http请求
如何读开源项目:对着文档跑demo,对着demo看代码,懂一点就开始试,有问题了问社区. 文章目录 今日目标: 一.从官方文档开始 1.接入说明: 2.网关需要引入代理插件 3.Http 服务接入网关 ...
- soul 网关源码解析
一.soul网关引入的依赖分析 从上图可以看到我红线划分五个依赖区域 1.soul-common包:这里不是很重要,我们大概看一下他的作用就好了 从上图中可以看出,这个包里主要定义了一个常量,枚举类, ...
- soul网关源码解析-环境搭建
项目功能简介 支持各种语言(http协议),支持 dubbo,springcloud协议. 插件化设计思想,插件热插拔,易扩展. 灵活的流量筛选,能满足各种流量控制. 内置丰富的插件支持,鉴权,限流, ...
- Soul 网关源码阅读(四)Dubbo请求概览
Soul 网关源码阅读(四)Dubbo请求概览 简介 本次启动一个dubbo服务示例,初步探索Soul网关源码的Dubbo请求处理流程 示例运行 环境配置 在Soul源码clone下来 ...
- Soul网关源码阅读(八)路由匹配初探
Soul网关源码阅读(八)路由匹配初探 简介 今日看看路由的匹配相关代码,查看HTTP的DividePlugin匹配 示例运行 使用HTTP的示例,运行Soul-Admin,Sou ...
- Soul网关源码阅读(七)限流插件初探
Soul网关源码阅读(七)限流插件初探 简介 前面的文章中对处理流程探索的差不多了,今天来探索下限流插件:resilience4j 示例运行 环境配置 启动下MySQL和redis d ...
- Soul 网关源码阅读(六)Sofa请求处理概览
Soul 网关源码阅读(六)Sofa请求处理概览 简介 今天来探索一下Sofa请求处理流程,看看和前面的HTTP.Dubbo有什么异同 Sofa示例运行 PS:如果请求加上参数运行不成功,请更 ...
- Soul网关源码阅读(十)自定义简单插件编写
Soul网关源码阅读(十)自定义简单插件编写 简介 综合前面所分析的插件处理流程相关知识,此次我们来编写自定义的插件:统计请求在插件链中的经历时长 编写准备 首先我们先探究一下,一个P ...
最新文章
- pykafka连接重要使用pykafka,kafka-python的api开发kafka生产者和消费者
- heartbeat v2版CRM的高可用web集群的实现
- slam中特征点归一化原因以及方法
- django修改服务器名称,django部署和服务器配置教程
- php程序员笔试题库,2017年初级PHP程序员笔试题
- DSP 28335中GPIO配置
- 轩辕炼妖录java_一个Java对象的回忆录:那些被锁住的日子
- 静态代码块 构造代码块 构造方法的执行顺序
- 写给嵌入式方向的某些同学 - 基于WINCE系统的程序开发[不完整版]
- PHP的类中的常量,静态变量的问题。
- 高通:蓝牙5.0将可同时连接两个设备
- 3使用技巧_盆栽金钱树,平时使用“3个”技巧,叶子稠密、基部冒新芽
- Go语言Revel框架 环境搭建
- 分享线下活动丨物联网走进UIC
- 基因编辑最新研究进展(2022年3月)
- 证监会叫停VR等行业跨界定增,福兮祸兮?
- 使用cordova将Ext JS 6.2的Modern应用程序打包为安卓APP
- suse11 安装 apache 记录
- Mysql的快照读和当前读
- 《乘风破浪》三年,靠王心凌救市?