回顾

大家好,我是老马。

最近 dubbo3.x 在公司内部分享,于是想系统梳理一下。

总体思路是官方文档入门+一些场景的问题思考+源码解析学习。


如何通过 Apache ShenYu 网关代理 Dubbo 服务

本文介绍了如何通过Apache ShenYu网关访问Dubbo服务,主要内容包括从简单示例到核心调用流程分析,并对设计原理进行了总结。

Wednesday, May 04, 2022

img

  1. 介绍

img

Apache ShenYu(Incubating) 是一个异步的,高性能的,跨语言的,响应式的 API 网关。兼容各种主流框架体系,支持热插拔,用户可以定制化开发,满足用户各种场景的现状和未来需求,经历过大规模场景的锤炼。

2021年5月,ShenYu捐献给 Apache 软件基金会,Apache 基金会全票通过,顺利进入孵化器。

Apache Dubbo 是一款微服务开发框架,它提供了 RPC 通信 与 微服务治理 两大关键能力。这意味着,使用 Dubbo 开发的微服务,将具备相互之间的远程发现与通信能力, 同时利用 Dubbo 提供的丰富服务治理能力,可以实现诸如服务发现、负载均衡、流量调度等服务治理诉求。同时 Dubbo 是高度可扩展的,用户几乎可以在任意功能点去定制自己的实现,以改变框架的默认行为来满足自己的业务需求。

  1. Dubbo快速开始

本小节介绍如何将Dubbo服务接入到ShenYu网关,您可以直接在工程下找到本小节的示例代码

2.1 启动shenyu-admin

shenyu-adminApache ShenYu后台管理系统, 启动的方式有多种,本文通过 [本地部署](https://shenyu.apache.org/zh/docs/deployment/deployment-local) 的方式启动。启动成功后,需要在基础配置->插件管理中,把dubbo 插件设置为开启,并设置你的注册地址,请确保注册中心已经开启。

img

2.2 启动shenyu网关

在这里通过 源码 的方式启动,直接运行shenyu-bootstrap中的ShenyuBootstrapApplication

在启动前,请确保网关已经引入相关依赖。如果客户端是apache dubbo,注册中心使用zookeeper,请参考如下配置:

  [xml]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!-- apache shenyu apache dubbo plugin start--> <dependency> <groupId>org.apache.shenyu</groupId> <artifactId>shenyu-spring-boot-starter-plugin-apache-dubbo</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.5</version> </dependency> <!-- Dubbo zookeeper registry dependency start --> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-client</artifactId> <version>4.0.1</version> <exclusions> <exclusion> <artifactId>log4j</artifactId> <groupId>log4j</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.1</version> </dependency> <!-- Dubbo zookeeper registry dependency end --> <!-- apache dubbo plugin end-->

2.3 启动shenyu-examples-dubbo

以官网提供的例子为例 shenyu-examples-dubbo 。 假如dubbo服务定义如下:

  [xml]
1
2
3
4
5
6
7
8
9
<beans /* ...... * /> <dubbo:application name="test-dubbo-service"/> <dubbo:registry address="${dubbo.registry.address}"/> <dubbo:protocol name="dubbo" port="20888"/> <dubbo:service timeout="10000" interface="org.apache.shenyu.examples.dubbo.api.service.DubboTestService" ref="dubboTestService"/> </beans>

声明应用服务名称,注册中心地址,使用dubbo协议,声明服务接口,对应接口实现类:

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** * DubboTestServiceImpl. */ @Service("dubboTestService") public class DubboTestServiceImpl implements DubboTestService { @Override @ShenyuDubboClient(path = "/findById", desc = "Query by Id") public DubboTest findById(final String id) { return new DubboTest(id, "hello world shenyu Apache, findById"); } //...... }

在接口实现类中,使用注解@ShenyuDubboClientshenyu-admin注册服务。

在配置文件application.yml中的配置信息:

  [yaml]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server: port: 8011 address: 0.0.0.0 servlet: context-path: / spring: main: allow-bean-definition-overriding: true dubbo: registry: address: zookeeper://localhost:2181 # dubbo使用的注册中心 shenyu: register: registerType: http #注册方式 serverLists: [http://localhost:9095 ](http://localhost:9095 ) #注册地址 props: username: admin password: 123456 client: dubbo: props: contextPath: /dubbo appName: dubbo

在配置文件中,声明dubbo使用的注册中心地址,dubbo服务向shenyu-admin注册,使用的方式是http,注册地址是http://localhost:9095

关于注册方式的使用,请参考 [应用客户端接入](https://shenyu.apache.org/docs/design/register-center-design/)

2.4 调用dubbo服务

shenyu-examples-dubbo项目成功启动之后会自动把加 @ShenyuDubboClient 注解的接口方法注册到网关。

打开 插件列表 -> Proxy -> dubbo 可以看到插件规则配置列表:

img

注册成功的选择器信息:

img

注册成功的规则信息:

img

选择器和规则是 Apache ShenYu 网关中最灵魂的东西。掌握好它,你可以对任何流量进行管理。对应为选择器与规则里面的匹配条件(conditions),根据不同的流量筛选规则,我们可以处理各种复杂的场景。流量筛选可以从Header, URI, Query, Cookie 等等Http请求获取数据。

然后可以采用 Match=RegexGroovyExclude等匹配方式,匹配出你所预想的数据。多组匹配添加可以使用And/Or的匹配策略。

具体的介绍与使用请看: [选择器与规则管理](https://shenyu.apache.org/zh/docs/user-guide/admin-usage/selector-and-rule)

发起GET请求,通过ShenYu网关调用dubbo服务:

  [http]
1
2
GET [http://localhost:9195/dubbo/findById ](http://localhost:9195/dubbo/findById )?id=100 Accept: application/json

成功响应之后,结果如下:

  [json]
1
2
3
4
{ "name": "hello world shenyu Apache, findById", "id": "100" }

至此,就成功的通过http请求访问dubbo服务了,ShenYu网关通过shenyu-plugin-dubbo模块将http协议转成了dubbo协议。

  1. 深入理解Dubbo插件

在运行上述demo的过程中,是否存在一些疑问:

  • dubbo服务是如何注册到shenyu-admin
  • shenyu-admin是如何将数据同步到ShenYu网关?
  • DubboPlugin是如何将http协议转换到到dubbo协议?

带着这些疑问,来深入理解dubbo插件。

3.1 应用客户端接入

应用客户端接入是指将微服务接入到Apache ShenYu网关,当前支持HttpDubboSpring CloudgRPCMotanSofaTars等协议的接入。

将应用客户端接入到Apache ShenYu网关是通过注册中心来实现的,涉及到客户端注册和服务端同步数据。注册中心支持HttpZookeeperEtcdConsulNacos。默认是通过Http方式注册。

客户端接入的相关配置请参考 [客户端接入配置](https://shenyu.apache.org/zh/docs/user-guide/register-center-access)

3.1.1 客户端注册

img

在你的微服务配置中声明注册中心客户端类型,如HttpZookeeper。 应用程序启动时使用SPI方式加载并初始化对应注册中心客户端,通过实现Spring Bean相关的后置处理器接口,在其中获取需要进行注册的服务接口信息,将获取的信息放入Disruptor中。

注册中心客户端从Disruptor中读取数据,并将接口信息注册到shenyu-adminDisruptor在其中起数据与操作解耦的作用,利于扩展。

3.1.2 服务端注册

img

shenyu-admin配置中声明注册中心服务端类型,如HttpZookeeper。当shenyu-admin启动时,读取配置类型,加载并初始化对应的注册中心服务端,注册中心服务端收到shenyu-client注册的接口信息后,将其放入Disruptor中,然后会触发注册处理逻辑,将服务接口信息更新并发布同步事件。

Disruptor在其中起到数据与操作解耦,利于扩展。如果注册请求过多,导致注册异常,也有数据缓冲作用。

3.2 数据同步原理

数据同步是指在 shenyu-admin 后台操作数据以后,使用何种策略将数据同步到 Apache ShenYu 网关。Apache ShenYu 网关当前支持ZooKeeperWebSocketHttp长轮询NacosEtcdConsul 进行数据同步。默认是通过WebSocket进行数据同步。

数据同步的相关配置请参考 [数据同步配置](https://shenyu.apache.org/zh/docs/user-guide/use-data-sync)

3.2.1 数据同步的意义

网关是流量请求的入口,在微服务架构中承担了非常重要的角色,网关高可用的重要性不言而喻。在使用网关的过程中,为了满足业务诉求,经常需要变更配置,比如流控规则、路由规则等等。因此,网关动态配置是保障网关高可用的重要因素。

当前数据同步特性如下:

  • 所有的配置都缓存在 Apache ShenYu 网关内存中,每次请求都使用本地缓存,速度非常快。
  • 用户可以在 shenyu-admin 后台任意修改数据,并马上同步到网关内存。
  • 支持 Apache ShenYu 的插件、选择器、规则数据、元数据、签名数据等数据同步。
  • 所有插件的选择器,规则都是动态配置,立即生效,不需要重启服务。
  • 数据同步方式支持 ZookeeperHttp 长轮询WebsocketNacosEtcdConsul

3.2.2 数据同步原理分析

下图展示了 Apache ShenYu 数据同步的流程,Apache ShenYu 网关在启动时,会从配置服务同步配置数据,并且支持推拉模式获取配置变更信息,然后更新本地缓存。管理员可以在管理后台(shenyu-admin),变更用户权限、规则、插件、流量配置,通过推拉模式将变更信息同步给 Apache ShenYu 网关,具体是 push 模式,还是 pull 模式取决于使用哪种同步方式。

img

在最初的版本中,配置服务依赖 Zookeeper 实现,管理后台将变更信息 push 给网关。而现在可以支持 WebSocketHttp长轮询ZookeeperNacosEtcdConsul,通过在配置文件中设置 shenyu.sync.${strategy} 指定对应的同步策略,默认使用 weboscket 同步策略,可以做到秒级数据同步。但是,有一点需要注意的是,Apache ShenYu网关 和 shenyu-admin 必须使用相同的同步策略。

如下图所示,shenyu-admin 在用户发生配置变更之后,会通过 EventPublisher 发出配置变更通知,由 EventDispatcher 处理该变更通知,然后根据配置的同步策略(http、weboscket、zookeeper、naocs、etcd、consul),将配置发送给对应的事件处理器。

  • 如果是 websocket 同步策略,则将变更后的数据主动推送给 shenyu-web,并且在网关层,会有对应的 WebsocketDataHandler 处理器来处理 shenyu-admin 的数据推送。
  • 如果是 zookeeper 同步策略,将变更数据更新到 zookeeper,而 ZookeeperSyncCache 会监听到 zookeeper 的数据变更,并予以处理。
  • 如果是 http 同步策略,由网关主动发起长轮询请求,默认有 90s 超时时间,如果 shenyu-admin 没有数据变更,则会阻塞 http 请求,如果有数据发生变更则响应变更的数据信息,如果超过 60s 仍然没有数据变更则响应空数据,网关层接到响应后,继续发起 http 请求,反复同样的请求。

3.3 流程分析

流程分析是从源码的角度,展示服务注册流程,数据同步流程和服务调用流程。

3.3.1 服务注册流程

使用注解@ShenyuDubboClient标记需要注册到网关的dubbo服务。

注解扫描通过ApacheDubboServiceBeanListener完成,它实现了ApplicationListener<ContextRefreshedEvent>接口,在Spring容器启动过程中,发生上下文刷新事件时,开始执行事件处理方法onApplicationEvent()。在重写的方法逻辑中,读取Dubbo服务ServiceBean,构建元数据对象和URI对象,并向shenyu-admin注册。

具体的注册逻辑由注册中心实现,请参考 [客户端接入原理](https://shenyu.apache.org/zh/docs/design/register-center-design/)

客户端通过注册中心注册的元数据和URI数据,在shenyu-admin端进行处理,负责存储到数据库和同步给shenyu网关。Dubbo插件的客户端注册处理逻辑在ShenyuClientRegisterDubboServiceImpl中。继承关系如下:

img

  • ShenyuClientRegisterService:客户端注册服务,顶层接口;
  • FallbackShenyuClientRegisterService:注册失败,提供重试操作;
  • AbstractShenyuClientRegisterServiceImpl:抽象类,实现部分公共注册逻辑;
  • ShenyuClientRegisterDubboServiceImpl:实现Dubbo插件的注册;

注册信息包括选择器,规则和元数据。

整体的dubbo服务注册流程如下:

img

3.3.2 数据同步流程

假设在在后台管理系统中,新增一条选择器数据,请求会进入SelectorController类中的createSelector()方法,它负责数据的校验,添加或更新数据,返回结果信息。

SelectorServiceImpl类中通过createOrUpdate()方法完成数据的转换,保存到数据库,发布事件,更新upstream

Service类完成数据的持久化操作,即保存数据到数据库。发布变更数据通过eventPublisher.publishEvent()完成,这个eventPublisher对象是一个ApplicationEventPublisher类,这个类的全限定名是org.springframework.context.ApplicationEventPublisher,发布数据的功能正是是通过Spring相关的功能来完成的。

当事件发布完成后,会自动进入到DataChangedEventDispatcher类中的onApplicationEvent()方法,根据不同数据类型和数据同步方式进行事件处理。

网关在启动时,根据指定的数据同步方式加载不同的配置类,初始化数据同步相关类。

在接收到数据后,进行反序列化操作,读取数据类型和操作类型。不同的数据类型,有不同的数据处理方式,所以有不同的实现类。但是它们之间也有相同的处理逻辑,所以可以通过模板方法设计模式来实现。相同的逻辑放在抽象类AbstractDataHandler中的handle()方法中,不同逻辑就交给各自的实现类。

新增一条选择器数据,是新增操作,会进入到SelectorDataHandler.doUpdate()具体的数据处理逻辑中。

在通用插件数据中的处理,由 shenyu-plugin-sync-data-${strategy} 负责处理数据同步策略。数据同步策略的抽象类为 AbstractDataHandler ,它定义了解析数据、处理数据模板方法,不同数据类型有不同的实现:

  • PluginDataHandler:插件数据处理器;
  • SelectorDataHandler:选择器数据处理器;
  • RuleDataHandler:规则数据处理器;
  • MetaDataHandler:元数据处理器;
  • AuthDataHandler:权限数据处理器。

Dubbo插件数据变更监听器为 DubboDataHandler,继承自 PluginDataHandler,实现了插件级的处理。当有dubbo插件的数据发生变更时,负责对网关中dubbo插件进行更新:

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DubboDataHandler extends PluginDataHandler { @Override protected void refreshPlugin(final PluginData pluginData) { // 当插件配置变更时,刷新插件配置 BasePluginDataHandler refreshed = Optional.ofNullable(pluginData) .flatMap(data -> Optional.ofNullable(PLUGIN_DATA_MAP.get(data.getName()))) .orElseGet(() -> { DubboPlugin dubboPlugin = new DubboPlugin(); PLUGIN_DATA_MAP.put(dubboPlugin.named(), dubboPlugin); return dubboPlugin; }); refreshed.refreshPlugin(pluginData); } }

数据同步完成后,网关中插件、选择器、规则等数据已更新,后续的流量处理将基于最新配置。

3.3.3 服务调用流程

Apache ShenYu作为网关,核心职责是处理客户端请求,并将其代理到下游服务。Dubbo插件负责将HTTP协议转换为Dubbo协议,整体流程如下:

  1. 请求接入:客户端发起HTTP请求,网关通过Netty接收请求,进入HttpRequestHandler
  2. 插件链执行:请求进入插件链(plugins),依次执行各插件的execute()方法;
  3. Dubbo插件处理
    • 根据请求路径匹配选择器和规则;
    • 通过泛化调用(GenericService)构造Dubbo请求参数;
    • 调用下游Dubbo服务并获取响应;
  4. 响应处理:将Dubbo响应结果转换为网关响应,返回给客户端。

核心代码流程

  1. Dubbo插件的doExecute()方法
  [java]
1
2
3
4
5
6
public Mono<Void> doExecute(final ServerWebExchange exchange, final ShenyuPluginChain chain, final SelectorData selector, final RuleData rule) { // 1. 根据exchange构造Dubbo请求参数 // 2. 通过ApacheDubboProxyService执行泛化调用 // 3. 处理结果并返回 return dubboProxyService.genericInvoker(param, exchange, chain); }
  1. 泛化调用执行
  [java]
1
2
3
4
5
6
7
8
public Mono<Object> genericInvoker(final String param, final ServerWebExchange exchange, final ShenyuPluginChain chain) { // 构造ReferenceConfig ReferenceConfig<GenericService> reference = buildReferenceConfig(metaData); // 发起异步调用 CompletableFuture<Object> future = genericService.$invokeAsync(metaData.getMethodName(), param, RpcContext.getContext()); // 处理响应 return Mono.fromFuture(future.thenApply(ret -> handleResult(ret))); }

关键点

  • 泛化调用:网关通过GenericService接口发起调用,无需依赖服务接口jar包;
  • 参数转换:将HTTP请求参数转换为Dubbo所需的参数类型(如POJO对象);
  • 异步非阻塞:基于CompletableFuture实现异步调用,提升网关吞吐量。

4. 总结

通过本文,我们了解了如何将Dubbo服务接入Apache ShenYu网关,并深入分析了其背后的原理:

  1. 服务注册:通过@ShenyuDubboClient注解和注册中心实现服务自动注册;
  2. 数据同步:支持多策略(如WebSocketZookeeper)实现配置动态更新;
  3. 协议转换:利用Dubbo泛化调用实现HTTPDubbo协议的无缝转换;
  4. 高性能设计:基于Reactor异步模型和Netty实现高吞吐、低延迟的代理能力。

Apache ShenYu的插件化设计和丰富的生态支持,使其成为微服务API网关的优选方案。结合Dubbo强大的服务治理能力,可为企业级应用提供稳定、灵活的服务代理解决方案。

延伸阅读

相关资源

参考资料