From 303c6a107a111656d62fb371c1666ce1609bad74 Mon Sep 17 00:00:00 2001 From: hexiaofeng Date: Tue, 28 May 2024 10:10:58 +0800 Subject: [PATCH] Add plugin doc --- docs/architect.md | 4 + docs/cn/architect.md | 8 +- docs/cn/plugin.md | 237 +++++++++++++++++++++++++++++++++++++++++++ docs/plugin.md | 236 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 483 insertions(+), 2 deletions(-) create mode 100644 docs/cn/plugin.md create mode 100644 docs/plugin.md diff --git a/docs/architect.md b/docs/architect.md index 56402387b..d9bb243b9 100644 --- a/docs/architect.md +++ b/docs/architect.md @@ -8,3 +8,7 @@ See [Classloader](classloader.md) ## 2. Extension System See [Extension System](extension.md) + +## 3. Plugin System + +See [Plugin System](plugin.md) diff --git a/docs/cn/architect.md b/docs/cn/architect.md index caf6fb20e..2a7358d58 100644 --- a/docs/cn/architect.md +++ b/docs/cn/architect.md @@ -3,8 +3,12 @@ ## 1. 类加载器隔离 -查看[类加载器](./docs/cn/classloader.md) +查看[类加载器](classloader.md) ## 2. 扩展体系 -查看[扩展体系](./docs/cn/extension.md) +查看[扩展体系](extension.md) + +## 3. 插件体系 + +查看[插件体系](plugin.md) diff --git a/docs/cn/plugin.md b/docs/cn/plugin.md new file mode 100644 index 000000000..401f92878 --- /dev/null +++ b/docs/cn/plugin.md @@ -0,0 +1,237 @@ +插件体系 +=== + +插件是基于扩展实现的,有多个扩展组成,对某个框架进行特定增强,实现了多活流量治理等等业务逻辑。 + +一个插件打包成一个目录,如下图所示: + +``` +. +└── plugin + ├── dubbo + │   ├── joylive-registry-dubbo2.6-1.0.0-SNAPSHOT.jar + │   ├── joylive-registry-dubbo2.7-1.0.0-SNAPSHOT.jar + │   ├── joylive-registry-dubbo3-1.0.0-SNAPSHOT.jar + │   ├── joylive-router-dubbo2.6-1.0.0-SNAPSHOT.jar + │   ├── joylive-router-dubbo2.7-1.0.0-SNAPSHOT.jar + │   ├── joylive-router-dubbo3-1.0.0-SNAPSHOT.jar + │   ├── joylive-transmission-dubbo2.6-1.0.0-SNAPSHOT.jar + │   ├── joylive-transmission-dubbo2.7-1.0.0-SNAPSHOT.jar + │   └── joylive-transmission-dubbo3-1.0.0-SNAPSHOT.jar +``` +该dubbo插件,支持了3个版本,增强了注册中心,路由和链路透传的能力。 + +## 1. 插件扩展接口 + +### 1.1 插件定义接口 + +该接口描述了匹配的类型和提供了方法拦截器定义 + +```java +@Extensible("PluginDefinition") +public interface PluginDefinition { + + ElementMatcher getMatcher(); + + InterceptorDefinition[] getInterceptors(); +} +``` + +### 1.2 拦截器定义接口 + +该接口描述了匹配的方法及拦截器 + +```java +public interface InterceptorDefinition { + + ElementMatcher getMatcher(); + + Interceptor getInterceptor(); +} +``` + +### 1.2 拦截器接口 + +该接口描述了方法的拦截点 + +```java +public interface Interceptor { + + void onEnter(ExecutableContext ctx); + + void onSuccess(ExecutableContext ctx); + + void onError(ExecutableContext ctx); + + void onExit(ExecutableContext ctx); +} +``` + +### 1.2 上下文对象 + +`ExecutableContext`在拦截成员方法和构造函数的时候,分别有不同的实现`MethodContext`和`ConstructorContext` + +```mermaid +classDiagram +direction BT +class ConstructorContext { + + getConstructor() Constructor~?~ +} +class ExecutableContext { + + getId() long + + getType() Class~?~ + + getArguments() Object[] + + getDescription() String + + getTarget() Object + + getThrowable() Throwable + + setTarget(Object) void + + setThrowable(Throwable) void + + isSuccess() boolean + + isSkip() boolean +} +class MethodContext { + + getMethod() Method + + getResult() Object + + setResult(Object) void + + setSkip(boolean) void + + success(Object) void + + isSkip() boolean + + toString() String + + invoke() Object +} + +ConstructorContext --> ExecutableContext +MethodContext --> ExecutableContext +``` + +## 2. 插件实现 + +以Dubbo3为例来看如何实现一个业务插件 + +### 2.1 插件的目录结构如下 + +``` +. +├── com +│   └── jd +│   └── live +│   └── agent +│   └── plugin +│   └── router +│   └── dubbo +│   └── v3 +│   ├── definition +│   │   ├── ClassLoaderFilterDefinition.java +│   │   ├── ClusterDefinition.java +│   │   └── LoadBalanceDefinition.java +│   ├── instance +│   │   └── DubboEndpoint.java +│   ├── interceptor +│   │   ├── ClassLoaderFilterInterceptor.java +│   │   ├── ClusterInterceptor.java +│   │   └── LoadBalanceInterceptor.java +│   ├── request +│   │   ├── DubboRequest.java +│   │   └── invoke +│   │   └── DubboInvocation.java +│   └── response +│   └── DubboResponse.java +└── org + └── apache + └── dubbo + └── rpc + └── cluster + └── support + └── DubboCluster3.java +``` + +1. `definition`用于存放插件定义 +2. `interceptor`用于存放拦截器 +3. `request`用于存放请求对象 +4. `response`用于存放应答对象 +5. `invoke`用于存放调用对象 +6. `instance`用于存放后端实例对象 +7. `DubboCluster3`集群对象放在了`org.apache.dubbo.rpc.cluster.support`下,方便访问受保护方法 + +### 2.2 Dubbo3集群插件定义 + +```java +@Injectable +@Extension(value = "ClusterDefinition_v2.7") +@ConditionalOnProperty(name = GovernanceConfig.CONFIG_FLOW_CONTROL_ENABLED, matchIfMissing = true) +@ConditionalOnProperty(name = GovernanceConfig.CONFIG_LIVE_DUBBO_ENABLED, matchIfMissing = true) +@ConditionalOnClass(ClusterDefinition.TYPE_ABSTRACT_CLUSTER) +@ConditionalOnClass(ClassLoaderFilterDefinition.TYPE_CONSUMER_CLASSLOADER_FILTER) +public class ClusterDefinition extends PluginDefinitionAdapter { + + protected static final String TYPE_ABSTRACT_CLUSTER = "org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker"; + + private static final String METHOD_DO_INVOKE = "doInvoke"; + + private static final String[] ARGUMENT_DO_INVOKE = new String[]{ + "org.apache.dubbo.rpc.Invocation", + "java.util.List", + "org.apache.dubbo.rpc.cluster.LoadBalance" + }; + + @Inject(InvocationContext.COMPONENT_INVOCATION_CONTEXT) + private InvocationContext context; + + public ClusterDefinition() { + this.matcher = () -> MatcherBuilder.isSubTypeOf(TYPE_ABSTRACT_CLUSTER) + .and(MatcherBuilder.not(MatcherBuilder.isAbstract())); + this.interceptors = new InterceptorDefinition[]{ + new InterceptorDefinitionAdapter( + MatcherBuilder.named(METHOD_DO_INVOKE) + .and(MatcherBuilder.arguments(ARGUMENT_DO_INVOKE)), + () -> new ClusterInterceptor(context) + ) + }; + } +} +``` +1. 插件定义从`PluginDefinitionAdapter`继承,实现了`PluginDefinition`接口 +2. 插件声明了扩展实现`@Extension` +3. 插件声明了多个扩展启用条件,描述了启用了流控、启用了Dubbo和在Dubbo3的运行环境下才能开启 +4. 插件注入了调用上下文`InvocationContext`,方便做多活流量控制 +5. 插件在构造函数中,采用`MatcherBuilder`来描述需要拦截的类和方法 + +### 2.3 Dubbo3集群插件拦截器 + +```java +public class ClusterInterceptor extends InterceptorAdaptor { + + private final InvocationContext context; + + private final Map, DubboCluster3> clusters = new ConcurrentHashMap<>(); + + public ClusterInterceptor(InvocationContext context) { + this.context = context; + } + + @Override + public void onEnter(ExecutableContext ctx) { + MethodContext mc = (MethodContext) ctx; + Object[] arguments = ctx.getArguments(); + DubboCluster3 cluster = clusters.computeIfAbsent((AbstractClusterInvoker) ctx.getTarget(), DubboCluster3::new); + List> invokers = (List>) arguments[1]; + List> instances = invokers.stream().map(DubboEndpoint::of).collect(Collectors.toList()); + DubboOutboundRequest request = new DubboOutboundRequest((Invocation) arguments[0]); + DubboOutboundInvocation invocation = new DubboOutboundInvocation(request, context); + DubboOutboundResponse response = cluster.request(context, invocation, instances); + if (response.getThrowable() != null) { + mc.setThrowable(response.getThrowable()); + } else { + mc.setResult(response.getResponse()); + } + mc.setSkip(true); + } + +} +``` +1. 拦截器声明了在进入方法`onEnter`进行拦截 +2. 通过上下文拿到调用的方法参数 +3. 根据参数创建了集群对象、后端实例列表、请求对象和调用对象 +4. 根据集群的策略同步调用获取应答 +5. 根据调用接口设置了应答,并设置跳过了原方法的处理 + diff --git a/docs/plugin.md b/docs/plugin.md new file mode 100644 index 000000000..01d8c3df2 --- /dev/null +++ b/docs/plugin.md @@ -0,0 +1,236 @@ +Plugin System +=== + +Plugins are based on extensions, consisting of multiple extensions to enhance a specific framework, implementing business logic such as multi-active traffic governance. + +A plugin is packaged into a directory, as shown below: + +``` +. +└── plugin + ├── dubbo + │   ├── joylive-registry-dubbo2.6-1.0.0-SNAPSHOT.jar + │   ├── joylive-registry-dubbo2.7-1.0.0-SNAPSHOT.jar + │   ├── joylive-registry-dubbo3-1.0.0-SNAPSHOT.jar + │   ├── joylive-router-dubbo2.6-1.0.0-SNAPSHOT.jar + │   ├── joylive-router-dubbo2.7-1.0.0-SNAPSHOT.jar + │   ├── joylive-router-dubbo3-1.0.0-SNAPSHOT.jar + │   ├── joylive-transmission-dubbo2.6-1.0.0-SNAPSHOT.jar + │   ├── joylive-transmission-dubbo2.7-1.0.0-SNAPSHOT.jar + │   └── joylive-transmission-dubbo3-1.0.0-SNAPSHOT.jar +``` +This Dubbo plugin supports 3 versions, enhancing the capabilities of the registry, routing, and link transmission. + +## 1. Plugin Extension Interface + +### 1.1 Plugin Definition Interface + +This interface describes the matching type and provides method interceptor definitions. + +```java +@Extensible("PluginDefinition") +public interface PluginDefinition { + + ElementMatcher getMatcher(); + + InterceptorDefinition[] getInterceptors(); +} +``` + +### 1.2 Interceptor Definition Interface + +This interface describes the matching methods and interceptors. + +```java +public interface InterceptorDefinition { + + ElementMatcher getMatcher(); + + Interceptor getInterceptor(); +} +``` + +### 1.2 Interceptor Interface + +This interface describes method interception points. + +```java +public interface Interceptor { + + void onEnter(ExecutableContext ctx); + + void onSuccess(ExecutableContext ctx); + + void onError(ExecutableContext ctx); + + void onExit(ExecutableContext ctx); +} +``` + +### 1.2 Context Object + +`ExecutableContext` has different implementations for intercepting member methods and constructors, namely `MethodContext` and `ConstructorContext`. + +```mermaid +classDiagram +direction BT +class ConstructorContext { + + getConstructor() Constructor~?~ +} +class ExecutableContext { + + getId() long + + getType() Class~?~ + + getArguments() Object[] + + getDescription() String + + getTarget() Object + + getThrowable() Throwable + + setTarget(Object) void + + setThrowable(Throwable) void + + isSuccess() boolean + + isSkip() boolean +} +class MethodContext { + + getMethod() Method + + getResult() Object + + setResult(Object) void + + setSkip(boolean) void + + success(Object) void + + isSkip() boolean + + toString() String + + invoke() Object +} + +ConstructorContext --> ExecutableContext +MethodContext --> ExecutableContext +``` + +## 2. Plugin Implementation + +Taking Dubbo3 as an example, let's see how to implement a business plugin. + +### 2.1 The directory structure of the plugin is as follows: + +``` +. +├── com +│   └── jd +│   └── live +│   └── agent +│   └── plugin +│   └── router +│   └── dubbo +│   └── v3 +│   ├── definition +│   │   ├── ClassLoaderFilterDefinition.java +│   │   ├── ClusterDefinition.java +│   │   └── LoadBalanceDefinition.java +│   ├── instance +│   │   └── DubboEndpoint.java +│   ├── interceptor +│   │   ├── ClassLoaderFilterInterceptor.java +│   │   ├── ClusterInterceptor.java +│   │   └── LoadBalanceInterceptor.java +│   ├── request +│   │   ├── DubboRequest.java +│   │   └── invoke +│   │   └── DubboInvocation.java +│   └── response +│   └── DubboResponse.java +└── org + └── apache + └── dubbo + └── rpc + └── cluster + └── support + └── DubboCluster3.java +``` + +1. `definition` is used to store plugin definitions. +2. `interceptor` is used to store interceptors. +3. `request` is used to store request objects. +4. `response` is used to store response objects. +5. `invoke` is used to store invocation objects. +6. `instance` is used to store backend instance objects. +7. The `DubboCluster3` cluster object is placed under `org.apache.dubbo.rpc.cluster.support` for easy access to protected methods. + +### 2.2 Dubbo3 Cluster Plugin Definition + +```java +@Injectable +@Extension(value = "ClusterDefinition_v2.7") +@ConditionalOnProperty(name = GovernanceConfig.CONFIG_FLOW_CONTROL_ENABLED, matchIfMissing = true) +@ConditionalOnProperty(name = GovernanceConfig.CONFIG_LIVE_DUBBO_ENABLED, matchIfMissing = true) +@ConditionalOnClass(ClusterDefinition.TYPE_ABSTRACT_CLUSTER) +@ConditionalOnClass(ClassLoaderFilterDefinition.TYPE_CONSUMER_CLASSLOADER_FILTER) +public class ClusterDefinition extends PluginDefinitionAdapter { + + protected static final String TYPE_ABSTRACT_CLUSTER = "org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker"; + + private static final String METHOD_DO_INVOKE = "doInvoke"; + + private static final String[] ARGUMENT_DO_INVOKE = new String[]{ + "org.apache.dubbo.rpc.Invocation", + "java.util.List", + "org.apache.dubbo.rpc.cluster.LoadBalance" + }; + + @Inject(InvocationContext.COMPONENT_INVOCATION_CONTEXT) + private InvocationContext context; + + public ClusterDefinition() { + this.matcher = () -> MatcherBuilder.isSubTypeOf(TYPE_ABSTRACT_CLUSTER) + .and(MatcherBuilder.not(MatcherBuilder.isAbstract())); + this.interceptors = new InterceptorDefinition[]{ + new InterceptorDefinitionAdapter( + MatcherBuilder.named(METHOD_DO_INVOKE) + .and(MatcherBuilder.arguments(ARGUMENT_DO_INVOKE)), + () -> new ClusterInterceptor(context) + ) + }; + } +} +``` +1. The plugin definition inherits from `PluginDefinitionAdapter`, implementing the `PluginDefinition` interface. +2. The plugin declares the extension implementation with `@Extension`. +3. The plugin declares multiple extension enabling conditions, describing that it is enabled when flow control is enabled, Dubbo is enabled, and it is running in the Dubbo3 environment. +4. The plugin injects the invocation context `InvocationContext` to facilitate multi-active traffic control. +5. In the constructor, the plugin uses `MatcherBuilder` to describe the classes and methods to be intercepted. + +### 2.3 Dubbo3 Cluster Plugin Interceptor + +```java +public class ClusterInterceptor extends InterceptorAdaptor { + + private final InvocationContext context; + + private final Map, DubboCluster3> clusters = new ConcurrentHashMap<>(); + + public ClusterInterceptor(InvocationContext context) { + this.context = context; + } + + @Override + public void onEnter(ExecutableContext ctx) { + MethodContext mc = (MethodContext) ctx; + Object[] arguments = ctx.getArguments(); + DubboCluster3 cluster = clusters.computeIfAbsent((AbstractClusterInvoker) ctx.getTarget(), DubboCluster3::new); + List> invokers = (List>) arguments[1]; + List> instances = invokers.stream().map(DubboEndpoint::of).collect(Collectors.toList()); + DubboOutboundRequest request = new DubboOutboundRequest((Invocation) arguments[0]); + DubboOutboundInvocation invocation = new DubboOutboundInvocation(request, context); + DubboOutboundResponse response = cluster.request(context, invocation, instances); + if (response.getThrowable() != null) { + mc.setThrowable(response.getThrowable()); + } else { + mc.setResult(response.getResponse()); + } + mc.setSkip(true); + } + +} +``` +1. The interceptor declares interception on method entry using `onEnter`. +2. It retrieves the method parameters through the context. +3. It creates a cluster object, backend instance list, request object, and invocation object based on the parameters. +4. It synchronously calls to get a response based on the cluster's strategy. +5. It sets the response based on the invocation interface and skips the original method's processing. \ No newline at end of file