Skip to content

Commit

Permalink
Merge pull request #61 from jd-opensource/58-support-spring-cloud-gat…
Browse files Browse the repository at this point in the history
…eway-only-enable-multi-live-and-lane-feature

Fix #58
  • Loading branch information
chenzhiguo authored Sep 23, 2024
2 parents c9b2032 + 3c2981b commit 645b570
Show file tree
Hide file tree
Showing 18 changed files with 342 additions and 63 deletions.
13 changes: 13 additions & 0 deletions RELEASE-zh.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# 发布历史

## 1.3.0
1. 在分区容错的时候支持本云优先,减少专线带宽
2. 熔断和重试,支持采用JsonPath从应答体中提取异常码
3. 支持自适应负载均衡算法
4. 在分区路由上增加了访问模式,使业务的分区故障切换不影响其它业务
5. 拆分了RouteFilter和OutboundFilter
6. 细化了拒绝类型,方便统计熔断限流数据
7. 增加了SofaRpc演示应用
8. 修复SofaRpc异常
9. 修复标签路由问题
10. 修复策略同步问题
11. 修复其它问题和稳定性提升.

## 1.2.0

1. 支持spring cloud 2023.
Expand Down
13 changes: 13 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Release History

## 1.3.0
1. Support for local cloud priority during cell fault tolerance, reducing dedicated line bandwidth usage.
2. Circuit breaker and retry, supporting the use of JsonPath to extract exception codes from response bodies.
3. Support for adaptive load balancing algorithms.
4. Add accessMode in CellRoute, ensuring that business cell failures do not affect other businesses.
5. RouteFilter and OutboundFilter have been split.
6. Rejection types have been refined, making it easier to collect circuit breaker and rate limiter data.
7. Added a demo application for SofaRpc.
8. Fixed issues in SofaRpc.
9. Fixed issues with tag routing.
10. Fixed policy synchronization problems.
11. Fixed other issues and improved stability.

## 1.2.0

1. Add spring cloud 2023 support.
Expand Down
43 changes: 29 additions & 14 deletions docs/cn/governance.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@
"retry": 10,
"retryInterval": 1000,
"timeout": 5000,
"codePolicy": {
"parser": "JsonPath",
"expression": "$.code",
"contentTypes": [
"application/json"
]
},
"retryStatuses": [
500,
502
Expand Down Expand Up @@ -273,21 +280,22 @@ stateDiagram-v2
Open --> Open : call / raise circuit open
```

| 元素 | 说明 |
| ---------------- | ----------------------------------------------------- |
| 名称 | 策略名称 |
| 级别 | SERVICE:服务级别;API:API级别;INSTANCE:实例级别 |
| 滑动窗口类型 | 指定滑动窗口的类型,count:次数;time:时间 |
| 滑动窗口大小 | 指定滑动窗口的大小,如果是count,则为调用次数,如果是time,则为秒 |
| 最小调用次数 | 保护阈值,防止调用次数过小的应用因为偶发故障产生熔断 |
| 错误码 | 请求返回列表中的响应码之一,则会被熔断器所记录一次失败 |
| 熔断失败比率 | 触发熔断的失败次数比率阈值 |
| 熔断慢调用比例 | 触发熔断的慢调用次数比率阈值 |
| 慢调用界定阈值 | 调用耗时多长以上算是慢调用 |
| 熔断时间 | 触发熔断时(状态为开启),多长时间内不允许授予访问令牌 |
| 元素 | 说明 |
|----------|---------------------------------------|
| 名称 | 策略名称 |
| 级别 | SERVICE:服务级别;API:API级别;INSTANCE:实例级别 |
| 滑动窗口类型 | 指定滑动窗口的类型,count:次数;time:时间 |
| 滑动窗口大小 | 指定滑动窗口的大小,如果是count,则为调用次数,如果是time,则为秒 |
| 最小调用次数 | 保护阈值,防止调用次数过小的应用因为偶发故障产生熔断 |
| 错误码策略 | 用于从应答体中提取错误码 |
| 错误码 | 请求返回列表中的响应码之一,则会被熔断器所记录一次失败 |
| 熔断失败比率 | 触发熔断的失败次数比率阈值 |
| 熔断慢调用比例 | 触发熔断的慢调用次数比率阈值 |
| 慢调用界定阈值 | 调用耗时多长以上算是慢调用 |
| 熔断时间 | 触发熔断时(状态为开启),多长时间内不允许授予访问令牌 |
| 半开状态调用次数 | 熔断器进入半开状态时,可以允许多少次尝试性访问 |
| 强制开启 | 强制开启熔断 |
| 降级配置 | 熔断发生时,若进行降级配置,则会返回配置数据作为响应 |
| 强制开启 | 强制开启熔断 |
| 降级配置 | 熔断发生时,若进行降级配置,则会返回配置数据作为响应 |

```json
{
Expand All @@ -298,6 +306,13 @@ stateDiagram-v2
"slidingWindowType": "count",
"slidingWindowSize": 5,
"minCallsThreshold": 1,
"codePolicy": {
"parser": "JsonPath",
"expression": "$.code",
"contentTypes": [
"application/json"
]
},
"errorCodes": [
"500",
"502"
Expand Down
45 changes: 30 additions & 15 deletions docs/governance.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ The default strategy for a service is set on the default group `default`.
"retry": 10,
"retryInterval": 1000,
"timeout": 5000,
"codePolicy": {
"parser": "JsonPath",
"expression": "$.code",
"contentTypes": [
"application/json"
]
},
"retryStatuses": [
500,
502
Expand Down Expand Up @@ -274,21 +281,22 @@ stateDiagram-v2
Open --> Open : call / raise circuit open
```

| Element | Description |
|----------|---------------------------------------|
| name | Policy name |
| level | SERVICE: service level; API: API level; INSTANCE: instance level |
| slidingWindowType | Specify the type of sliding window, count: number of times; time: duration |
| slidingWindowSize | Specify the size of the sliding window, if it is count, it represents the number of calls; if it is time, it represents seconds |
| minCallsThreshold | Protect the threshold to prevent applications with too few calls from experiencing a fuse due to occasional failures |
| errorCodes | If one of the response codes in the list is returned, it will be recorded as a failure by the circuit breaker |
| failureRateThreshold | Failure rate threshold to trigger circuit breaker |
| slowCallRateThreshold | Threshold for the slow call count ratio that triggers circuit breaking |
| slowCallDurationThreshold | How long does a call take to be considered slow |
| waitDurationInOpenState | When the circuit breaker is triggered (status is open), how long should access tokens not be granted |
| allowedCallsInHalfOpenState | When the circuit breaker enters the half-open state, how many attempts can be allowed for trial access |
| forceOpen | Forced to open the fuse |
| degradeConfig | When a fuse occurs, if the degradation configuration is performed, the configuration data will be returned as a response |
| Element | Description |
|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| name | Policy name |
| level | SERVICE: service level; API: API level; INSTANCE: instance level |
| slidingWindowType | Specify the type of sliding window, count: number of times; time: duration |
| slidingWindowSize | Specify the size of the sliding window, if it is count, it represents the number of calls; if it is time, it represents seconds |
| minCallsThreshold | Protect the threshold to prevent applications with too few calls from experiencing a fuse due to occasional failures |
| codePolicy | Used to extract error codes from response body |
| errorCodes | If one of the response codes in the list is returned, it will be recorded as a failure by the circuit breaker |
| failureRateThreshold | Failure rate threshold to trigger circuit breaker |
| slowCallRateThreshold | Threshold for the slow call count ratio that triggers circuit breaking |
| slowCallDurationThreshold | How long does a call take to be considered slow |
| waitDurationInOpenState | When the circuit breaker is triggered (status is open), how long should access tokens not be granted |
| allowedCallsInHalfOpenState | When the circuit breaker enters the half-open state, how many attempts can be allowed for trial access |
| forceOpen | Forced to open the fuse |
| degradeConfig | When a fuse occurs, if the degradation configuration is performed, the configuration data will be returned as a response |

```json
{
Expand All @@ -299,6 +307,13 @@ stateDiagram-v2
"slidingWindowType": "count",
"slidingWindowSize": 5,
"minCallsThreshold": 1,
"codePolicy": {
"parser": "JsonPath",
"expression": "$.code",
"contentTypes": [
"application/json"
]
},
"errorCodes": [
"500",
"502"
Expand Down
Binary file modified docs/image/architect-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/image/architect-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion joylive-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
</scm>

<properties>
<revision>1.3.0-SNAPSHOT</revision>
<revision>1.3.0</revision>
</properties>

<dependencyManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ default String getCell() {
return getLabel(Constants.LABEL_CELL, Constants.DEFAULT_VALUE);
}

/**
* Gets the cloud associated with this endpoint.
*
* @return The cloud, or the default value if not specified.
*/
default String getCloud() {
return getLabel(Constants.LABEL_CLOUD, Constants.DEFAULT_VALUE);
}

/**
* Gets the region associated with this endpoint.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,8 @@ private Election sponsor(OutboundInvocation<?> invocation,
// Check if the cell is accessible and has a non-empty route.
if (invocation.isAccessible(cell) && !cellRoute.isEmpty() && invocation.isAccessible(cellRoute.getAccessMode())) {
// Get the instance count for the cell from the unit group, if available.
Integer instance = unitGroup == null ? null : unitGroup.getSize(cellRoute.getCode());
CellGroup cellGroup = unitGroup == null ? null : unitGroup.getCell(cellRoute.getCode());
Integer instance = cellGroup == null ? null : cellGroup.size();
instance = instance == null ? 0 : instance;

// Degrade the weight to 0 if the cell has instances.
Expand All @@ -304,8 +305,13 @@ private Election sponsor(OutboundInvocation<?> invocation,
Integer threshold = !localFirst ? null : failoverThresholdFunc.apply(cell.getCode());
threshold = threshold == null ? 0 : threshold;

String cloud = cellRoute.getCell().getLabel(Cell.LABEL_CLOUD);
if (cloud == null) {
cloud = cellGroup == null || cellGroup.isEmpty() ? "" : cellGroup.getEndpoints().get(0).getCloud();
}

// Create a Candidate object and add it to the list of candidates.
Candidate candidate = new Candidate(cellRoute, instance, weight, priority, threshold);
Candidate candidate = new Candidate(cellRoute, instance, weight, priority, threshold, cloud);
candidates.add(candidate);

// Update the preferred candidate if the current cell has a higher priority.
Expand Down Expand Up @@ -433,8 +439,8 @@ private void sortByCloud(List<Candidate> candidates, Location location) {
if (cloud != null && !cloud.isEmpty()) {
// prefer local cloud
candidates.sort((o1, o2) -> {
String cloud1 = o1.getCellRoute().getCell().getLabel(Cell.LABEL_CLOUD);
String cloud2 = o2.getCellRoute().getCell().getLabel(Cell.LABEL_CLOUD);
String cloud1 = o1.getCloud();
String cloud2 = o2.getCloud();
if (cloud.equals(cloud1)) {
return cloud.equals(cloud2) ? randomOrder() : -1;
} else {
Expand Down Expand Up @@ -563,6 +569,11 @@ private static class Candidate {
*/
private final int threshold;

/**
* The cloud value for this candidate, which may be used to filter candidates during routing.
*/
private final String cloud;

/**
* Constructs a new Candidate with the provided cell route and routing attributes.
*
Expand All @@ -571,13 +582,15 @@ private static class Candidate {
* @param weight The weight of this candidate.
* @param priority The priority of this candidate.
* @param threshold The threshold value for this candidate.
* @param cloud The cloud value for this candidate.
*/
Candidate(CellRoute cellRoute, int instance, int weight, int priority, int threshold) {
Candidate(CellRoute cellRoute, int instance, int weight, int priority, int threshold, String cloud) {
this.cellRoute = cellRoute;
this.instance = instance;
this.weight = weight;
this.priority = priority;
this.threshold = threshold;
this.cloud = cloud;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
package com.jd.live.agent.plugin.router.springgateway.v3.definition;

import com.jd.live.agent.core.bytekit.matcher.MatcherBuilder;
import com.jd.live.agent.core.extension.annotation.*;
import com.jd.live.agent.core.extension.annotation.ConditionalOnClass;
import com.jd.live.agent.core.extension.annotation.ConditionalOnMissingClass;
import com.jd.live.agent.core.extension.annotation.ConditionalOnProperty;
import com.jd.live.agent.core.extension.annotation.Extension;
import com.jd.live.agent.core.inject.annotation.Config;
import com.jd.live.agent.core.inject.annotation.Inject;
import com.jd.live.agent.core.inject.annotation.Injectable;
Expand All @@ -29,22 +32,14 @@
import com.jd.live.agent.plugin.router.springgateway.v3.interceptor.GatewayClusterInterceptor;

/**
* FilteringWebHandlerPluginDefinition
* GatewayClusterDefinition
*
* @since 1.0.0
*/
@Extension(value = "FilteringWebHandlerPluginDefinition_Gateway_v3")
@ConditionalOnProperties(value = {
@ConditionalOnProperty(name = {
GovernanceConfig.CONFIG_LIVE_ENABLED,
GovernanceConfig.CONFIG_LANE_ENABLED,
GovernanceConfig.CONFIG_FLOW_CONTROL_ENABLED
}, matchIfMissing = true, relation = ConditionalRelation.OR),
@ConditionalOnProperty(name = {
GovernanceConfig.CONFIG_LIVE_SPRING_GATEWAY_ENABLED,
GovernanceConfig.CONFIG_LIVE_SPRING_ENABLED
}, matchIfMissing = true, relation = ConditionalRelation.AND),
}, relation = ConditionalRelation.AND)
@Extension(value = "GatewayClusterDefinition_v3")
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_FLOW_CONTROL_ENABLED, matchIfMissing = true)
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LIVE_SPRING_GATEWAY_ENABLED, matchIfMissing = true)
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LIVE_SPRING_ENABLED, matchIfMissing = true)
@ConditionalOnClass(GatewayClusterDefinition.TYPE_FILTERING_WEB_HANDLER)
@ConditionalOnClass(GatewayClusterDefinition.REACTOR_MONO)
@ConditionalOnMissingClass(GatewayClusterDefinition.TYPE_HTTP_STATUS_CODE)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright © ${year} ${owner} (${email})
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jd.live.agent.plugin.router.springgateway.v3.definition;

import com.jd.live.agent.core.bytekit.matcher.MatcherBuilder;
import com.jd.live.agent.core.extension.annotation.*;
import com.jd.live.agent.core.plugin.definition.InterceptorDefinition;
import com.jd.live.agent.core.plugin.definition.InterceptorDefinitionAdapter;
import com.jd.live.agent.core.plugin.definition.PluginDefinitionAdapter;
import com.jd.live.agent.governance.config.GovernanceConfig;
import com.jd.live.agent.plugin.router.springgateway.v3.interceptor.GatewayInterceptor;

/**
* GatewayDefinition
*
* @since 1.0.0
*/
@Extension(value = "GatewayDefinition_v3")
@ConditionalOnProperty(name = {GovernanceConfig.CONFIG_LIVE_ENABLED, GovernanceConfig.CONFIG_LANE_ENABLED}, matchIfMissing = true, relation = ConditionalRelation.OR)
@ConditionalOnProperty(name = GovernanceConfig.CONFIG_FLOW_CONTROL_ENABLED, value = "false")
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LIVE_SPRING_GATEWAY_ENABLED, matchIfMissing = true)
@ConditionalOnProperty(value = GovernanceConfig.CONFIG_LIVE_SPRING_ENABLED, matchIfMissing = true)
@ConditionalOnClass(GatewayDefinition.TYPE_FILTERING_WEB_HANDLER)
@ConditionalOnClass(GatewayDefinition.REACTOR_MONO)
@ConditionalOnMissingClass(GatewayDefinition.TYPE_HTTP_STATUS_CODE)
public class GatewayDefinition extends PluginDefinitionAdapter {

protected static final String TYPE_FILTERING_WEB_HANDLER = "org.springframework.cloud.gateway.handler.FilteringWebHandler";

protected static final String TYPE_HTTP_STATUS_CODE = "org.springframework.http.HttpStatusCode";

private static final String METHOD_HANDLE = "handle";

private static final String[] ARGUMENT_HANDLE = new String[]{
"org.springframework.web.server.ServerWebExchange"
};

protected static final String REACTOR_MONO = "reactor.core.publisher.Mono";

public GatewayDefinition() {
this.matcher = () -> MatcherBuilder.named(TYPE_FILTERING_WEB_HANDLER);
this.interceptors = new InterceptorDefinition[]{
new InterceptorDefinitionAdapter(
MatcherBuilder.named(METHOD_HANDLE).
and(MatcherBuilder.arguments(ARGUMENT_HANDLE)),
GatewayInterceptor::new
)
};
}
}
Loading

0 comments on commit 645b570

Please sign in to comment.