diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e872a2..08f67433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ | 版本 | 发版日期 | 变更内容 | | --- | ------------ | ------------------------------------------ | -| 4.4.10| 2024-04-22 | yos文件上传支持自定义文件名 | +| 4.4.11| 2024-04-25 | 路由切换功能单独分包,默认熔断时长10min,默认连接超时3s | +| 4.4.10| 2024-04-22 | yos文件上传支持自定义扩展名 | | 4.4.9 | 2024-01-23 | 支持多环境混合调用 | | 4.4.8 | 2023-12-15 | 域名切换逻辑抽离&优化,方便复用与扩展,切换更灵敏 | | 4.4.7 | 2023-11-14 | 修复json请求类型判断(影响业务SDK) | diff --git a/pom.xml b/pom.xml index 3e8df2d3..827417f5 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,8 @@ yop-java-sdk-invoke-api yop-java-sdk-invoke-base yop-java-sdk-api + yop-java-sdk-router + yop-java-sdk-utils yop-java-sdk-parent @@ -119,6 +121,16 @@ yop-java-sdk-api ${project.version} + + com.yeepay.yop.sdk + yop-java-sdk-router + ${project.version} + + + com.yeepay.yop.sdk + yop-java-sdk-utils + ${project.version} + com.google.guava guava @@ -238,12 +250,6 @@ ${slf4j-api.version} - - com.alibaba.csp - sentinel-core - ${sentinel.version} - - junit diff --git a/yop-java-sdk-base/pom.xml b/yop-java-sdk-base/pom.xml index cedefaa8..05c79c24 100644 --- a/yop-java-sdk-base/pom.xml +++ b/yop-java-sdk-base/pom.xml @@ -19,55 +19,23 @@ yop-java-sdk-invoke-base - com.google.guava - guava + com.yeepay.yop.sdk + yop-java-sdk-router - - commons-codec - commons-codec + com.yeepay.yop.sdk + yop-java-sdk-utils commons-io commons-io - - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.datatype - jackson-datatype-joda - - - joda-time - joda-time - - org.apache.tika tika-core - - org.apache.commons - commons-lang3 - - - org.apache.commons - commons-collections4 - - org.springframework spring-core @@ -78,16 +46,6 @@ slf4j-api - - com.jayway.jsonpath - json-path - - - - com.alibaba.csp - sentinel-core - - junit diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/YopConstants.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/YopConstants.java index d58a10dd..ebc71436 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/YopConstants.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/YopConstants.java @@ -21,7 +21,7 @@ */ public interface YopConstants { - String VERSION = "4.4.10"; + String VERSION = "4.4.11"; String DEFAULT_ENCODING = "UTF-8"; diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/AbstractServiceClientBuilder.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/AbstractServiceClientBuilder.java index 4738d545..1d4e55de 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/AbstractServiceClientBuilder.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/AbstractServiceClientBuilder.java @@ -10,6 +10,11 @@ import com.yeepay.yop.sdk.client.support.ClientConfigurationSupport; import com.yeepay.yop.sdk.config.YopSdkConfig; import com.yeepay.yop.sdk.config.provider.YopSdkConfigProvider; +import com.yeepay.yop.sdk.invoke.RouterPolicy; +import com.yeepay.yop.sdk.router.YopRouterConstants; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProvider; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProviderRegistry; +import com.yeepay.yop.sdk.router.policy.RouterPolicyFactory; import com.yeepay.yop.sdk.utils.ClientUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -50,6 +55,10 @@ public abstract class AbstractServiceClientBuilder preferredEndPoint; @@ -74,6 +83,9 @@ public final ServiceInterfaceToBuild build() { if (null == platformCredentialsProvider) { platformCredentialsProvider = YopPlatformCredentialsProviderRegistry.getProvider(); } + if (null == routeConfigProvider) { + routeConfigProvider = YopRouteConfigProviderRegistry.getProvider(); + } YopSdkConfig yopSdkConfig = yopSdkConfigProvider.getConfig(provider, env); if (null == clientConfiguration) { clientConfiguration = ClientConfigurationSupport.getClientConfiguration(yopSdkConfig); @@ -86,6 +98,9 @@ public final ServiceInterfaceToBuild build() { preferredYosEndPoint = CollectionUtils.isNotEmpty(yopSdkConfig.getPreferredYosServerRoots()) ? yopSdkConfig.getPreferredYosServerRoots().stream().map(URI::create).collect(Collectors.toList()) : Collections.emptyList(); } + if (null == routerPolicy) { + routerPolicy = RouterPolicyFactory.get(YopRouterConstants.ROUTER_POLICY_DEFAULT); + } ClientParams clientParams = ClientParams.Builder.builder() .withInner(this.inner) .withProvider(this.provider) @@ -93,6 +108,8 @@ public final ServiceInterfaceToBuild build() { .withCredentialsProvider(credentialsProvider) .withYopSdkConfigProvider(yopSdkConfigProvider) .withPlatformCredentialsProvider(platformCredentialsProvider) + .withRouteConfigProvider(routeConfigProvider) + .withRouterPolicy(routerPolicy) .withClientConfiguration(clientConfiguration) .withEndPoint(endpoint == null ? URI.create(StringUtils.defaultIfBlank(yopSdkConfig.getServerRoot(), YopConstants.DEFAULT_SERVER_ROOT)) : URI.create(endpoint)) .withYosEndPoint(yosEndPoint == null ? URI.create(StringUtils.defaultIfBlank(yopSdkConfig.getYosServerRoot(), YopConstants.DEFAULT_YOS_SERVER_ROOT)) : URI.create(yosEndPoint)) @@ -152,6 +169,16 @@ public SubClass withPlatformCredentialsProvider(YopPlatformCredentialsProvider p return getSubclass(); } + public SubClass withRouteConfigProvider(YopRouteConfigProvider routeConfigProvider) { + this.routeConfigProvider = routeConfigProvider; + return getSubclass(); + } + + public SubClass withRouterPolicy(RouterPolicy routerPolicy) { + this.routerPolicy = routerPolicy; + return getSubclass(); + } + public SubClass withClientConfiguration(ClientConfiguration clientConfiguration) { this.clientConfiguration = clientConfiguration; return getSubclass(); diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientConfiguration.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientConfiguration.java index 0abb315d..bd39465d 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientConfiguration.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientConfiguration.java @@ -1,18 +1,14 @@ package com.yeepay.yop.sdk.client; import com.google.common.base.Joiner; -import com.google.common.collect.Sets; import com.yeepay.yop.sdk.Region; import com.yeepay.yop.sdk.YopConstants; import com.yeepay.yop.sdk.auth.credentials.YopCredentials; -import com.yeepay.yop.sdk.config.provider.file.YopCircuitBreakerConfig; import com.yeepay.yop.sdk.http.Protocol; import com.yeepay.yop.sdk.http.RetryPolicy; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import java.net.InetAddress; -import java.util.Set; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -26,12 +22,12 @@ public class ClientConfiguration { /** * The default timeout for creating new connections. */ - public static final int DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS = 10 * 1000; + public static final int DEFAULT_CONNECTION_TIMEOUT_IN_MILLIS = 3 * 1000; /** * The default timeout for request new connections form pool. */ - public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT_IN_MILLIS = 10 * 1000; + public static final int DEFAULT_CONNECTION_REQUEST_TIMEOUT_IN_MILLIS = 3 * 1000; /** * The default timeout for reading from a connected socket. @@ -174,20 +170,6 @@ public class ClientConfiguration { */ private String clientImpl = YOP_HTTP_CLIENT_IMPL_DEFAULT; - private int maxRetryCount = 3; - - private Set retryExceptions = Sets.newHashSet("java.net.UnknownHostException", - "java.net.ConnectException:No route to host (connect failed)", - "java.net.ConnectException:Connection refused (Connection refused)", - "java.net.ConnectException:Connection refused: connect", - "java.net.SocketTimeoutException:connect timed out", - "java.net.NoRouteToHostException", - "org.apache.http.conn.ConnectTimeoutException", "com.yeepay.shade.org.apache.http.conn.ConnectTimeoutException", - "org.apache.http.conn.HttpHostConnectException", "com.yeepay.shade.org.apache.http.conn.HttpHostConnectException", - "java.net.ConnectException:Connection timed out","java.net.ConnectException:连接超时"); - - private YopCircuitBreakerConfig circuitBreakerConfig = YopCircuitBreakerConfig.DEFAULT_CONFIG; - // Initialize DEFAULT_USER_AGENT static { String language = System.getProperty("user.language"); @@ -238,9 +220,6 @@ public ClientConfiguration(ClientConfiguration other) { this.region = other.region; this.credentials = other.credentials; this.clientImpl = other.clientImpl; - this.maxRetryCount = other.maxRetryCount; - this.retryExceptions = other.retryExceptions; - this.circuitBreakerConfig = other.circuitBreakerConfig; } /** @@ -929,51 +908,6 @@ public ClientConfiguration withClientImpl(String clientImpl) { return this; } - public int getMaxRetryCount() { - return maxRetryCount; - } - - public void setMaxRetryCount(int maxRetryCount) { - if (maxRetryCount > 0) { - this.maxRetryCount = maxRetryCount; - } - } - - public ClientConfiguration withMaxRetryCount(int maxRetryCount) { - setMaxRetryCount(maxRetryCount); - return this; - } - - public Set getRetryExceptions() { - return retryExceptions; - } - - public void setRetryExceptions(Set retryExceptions) { - if (CollectionUtils.isNotEmpty(retryExceptions)) { - this.retryExceptions = retryExceptions; - } - } - - public ClientConfiguration withRetryExceptions(Set retryExceptions) { - setRetryExceptions(retryExceptions); - return this; - } - - public YopCircuitBreakerConfig getCircuitBreakerConfig() { - return circuitBreakerConfig; - } - - public void setCircuitBreakerConfig(YopCircuitBreakerConfig circuitBreakerConfig) { - if (null != circuitBreakerConfig) { - this.circuitBreakerConfig = circuitBreakerConfig; - } - } - - public ClientConfiguration withCircuitBreakerConfig(YopCircuitBreakerConfig circuitBreakerConfig) { - setCircuitBreakerConfig(circuitBreakerConfig); - return this; - } - @Override public String toString() { return "ClientConfiguration [ \n userAgent=" + userAgent @@ -991,10 +925,7 @@ public String toString() { + socketBufferSizeInBytes + ", \n region=" + region + ", \n credentials=" + credentials + ", \n clientImpl=" - + clientImpl + ", \n maxRetryCount=" - + maxRetryCount + ", \n retryExceptions=" - + retryExceptions + ", \n circuitBreakerConfig=" - + circuitBreakerConfig + "]\n"; + + clientImpl + "]\n"; } } diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientHandlerImpl.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientHandlerImpl.java index 50a52945..db703b0b 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientHandlerImpl.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientHandlerImpl.java @@ -1,9 +1,6 @@ package com.yeepay.yop.sdk.client; -import com.alibaba.csp.sentinel.Entry; -import com.alibaba.csp.sentinel.Tracer; -import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.yeepay.yop.sdk.YopConstants; import com.yeepay.yop.sdk.auth.credentials.CredentialsItem; import com.yeepay.yop.sdk.auth.credentials.YopCredentials; @@ -15,10 +12,7 @@ import com.yeepay.yop.sdk.auth.req.AuthorizationReqSupport; import com.yeepay.yop.sdk.base.auth.signer.YopSignerFactory; import com.yeepay.yop.sdk.base.cache.EncryptOptionsCache; -import com.yeepay.yop.sdk.base.cache.YopDegradeRuleHelper; -import com.yeepay.yop.sdk.client.router.GateWayRouter; import com.yeepay.yop.sdk.client.router.ServerRootSpace; -import com.yeepay.yop.sdk.client.router.SimpleGateWayRouter; import com.yeepay.yop.sdk.client.router.YopRouter; import com.yeepay.yop.sdk.config.provider.YopSdkConfigProvider; import com.yeepay.yop.sdk.config.provider.file.YopCircuitBreakerConfig; @@ -35,10 +29,16 @@ import com.yeepay.yop.sdk.model.BaseRequest; import com.yeepay.yop.sdk.model.BaseResponse; import com.yeepay.yop.sdk.model.YopRequestConfig; +import com.yeepay.yop.sdk.router.config.YopRouteConfig; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProvider; +import com.yeepay.yop.sdk.router.sentinel.YopDegradeRuleHelper; +import com.yeepay.yop.sdk.router.sentinel.YopSph; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Tracer; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; import com.yeepay.yop.sdk.security.CertTypeEnum; import com.yeepay.yop.sdk.security.encrypt.EncryptOptions; import com.yeepay.yop.sdk.security.encrypt.YopEncryptor; -import com.yeepay.yop.sdk.sentinel.YopSph; import com.yeepay.yop.sdk.utils.ClientUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; @@ -47,9 +47,10 @@ import org.slf4j.LoggerFactory; import java.net.URI; -import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Future; import static com.yeepay.yop.sdk.internal.RequestAnalyzer.*; @@ -68,6 +69,7 @@ public class ClientHandlerImpl implements ClientHandler { private static final Logger LOGGER = LoggerFactory.getLogger(ClientHandlerImpl.class); + private final String provider; private final String env; @@ -78,15 +80,13 @@ public class ClientHandlerImpl implements ClientHandler { private final YopPlatformCredentialsProvider platformCredentialsProvider; - private final AuthorizationReqRegistry authorizationReqRegistry; + private final YopRouteConfigProvider routeConfigProvider; - private final ClientConfiguration clientConfiguration; + private final AuthorizationReqRegistry authorizationReqRegistry; private final YopHttpClient client; - private final GateWayRouter gateWayRouter; - - private final YopCircuitBreakerConfig circuitBreakerConfig; + private final ServerRootSpace serverRootSpace; private final YopCircuitBreaker circuitBreaker; @@ -94,6 +94,8 @@ public class ClientHandlerImpl implements ClientHandler { private final String clientId; + private final RouterPolicy routerPolicy; + public ClientHandlerImpl(ClientHandlerParams handlerParams) { this.provider = handlerParams.getClientParams().getProvider(); @@ -101,21 +103,21 @@ public ClientHandlerImpl(ClientHandlerParams handlerParams) { this.yopCredentialsProvider = handlerParams.getClientParams().getCredentialsProvider(); this.yopSdkConfigProvider = handlerParams.getClientParams().getYopSdkConfigProvider(); this.platformCredentialsProvider = handlerParams.getClientParams().getPlatformCredentialsProvider(); + this.routeConfigProvider = handlerParams.getClientParams().getRouteConfigProvider(); + this.routerPolicy = handlerParams.getClientParams().getRouterPolicy(); this.authorizationReqRegistry = handlerParams.getClientParams().getAuthorizationReqRegistry(); - ServerRootSpace serverRootSpace = new ServerRootSpace(provider, env, handlerParams.getClientParams().getEndPoint(), - handlerParams.getClientParams().getYosEndPoint(), handlerParams.getClientParams().getPreferredEndPoint(), - handlerParams.getClientParams().getPreferredYosEndPoint(), handlerParams.getClientParams().getSandboxEndPoint()); - this.gateWayRouter = new SimpleGateWayRouter(serverRootSpace); - this.clientConfiguration = handlerParams.getClientParams().getClientConfiguration(); - this.client = buildHttpClient(handlerParams); - this.circuitBreakerConfig = this.clientConfiguration.getCircuitBreakerConfig(); - this.circuitBreaker = new YopSentinelCircuitBreaker(serverRootSpace, this.circuitBreakerConfig); this.clientId = handlerParams.getClientParams().getClientId(); if (isBasicClient(clientId)) { sdkSource = YopConstants.YOP_SDK_SOURCE_BASIC; } else { sdkSource = YopConstants.YOP_SDK_SOURCE_BIZ; } + this.serverRootSpace = new ServerRootSpace(provider, env, this.clientId, handlerParams.getClientParams().getEndPoint(), + handlerParams.getClientParams().getYosEndPoint(), handlerParams.getClientParams().getPreferredEndPoint(), + handlerParams.getClientParams().getPreferredYosEndPoint(), handlerParams.getClientParams().getSandboxEndPoint()); + + this.client = buildHttpClient(handlerParams); + this.circuitBreaker = new YopSentinelCircuitBreaker(this.serverRootSpace); } private YopHttpClient buildHttpClient(ClientHandlerParams handlerParams) { @@ -135,11 +137,9 @@ public Output execute( try { ExecutionContext executionContext = getExecutionContext(executionParams); return new UriResourceRouteInvokerWrapper<>( - new YopInvoker<>(executionParams, executionContext, new SimpleExceptionAnalyzer(null != circuitBreakerConfig ? - circuitBreakerConfig.getExcludeExceptions() : Collections.emptySet(), - clientConfiguration.getRetryExceptions()), true), - new SimpleUriRetryPolicy(clientConfiguration.getMaxRetryCount()), - new YopRouter<>(gateWayRouter)).invoke(); + new YopInvoker<>(executionParams, executionContext, true), + new SimpleRetryPolicy(executionParams.getInput().getRequestConfig().getMaxRetryCount()), + new YopRouter<>(serverRootSpace, routerPolicy)).invoke(); } finally { ClientUtils.removeCurrentClientId(); } @@ -153,18 +153,41 @@ Output execute(Request< } + private YopRouteConfig findRouteConfig(URI uri) { + String configKey = StringUtils.strip(uri.getHost().replaceAll("[^a-zA-Z0-9]", "_") + + (uri.getPort() > 0 ? "_" + uri.getPort() : ""), "_"); + // 指定配置 + YopRouteConfig routeConfig = routeConfigProvider.getRouteConfig(configKey); + // 默认配置 + if (null == routeConfig) { + routeConfig = routeConfigProvider.getRouteConfig(); + } + // 兜底配置 + return null == routeConfig ? YopRouteConfig.DEFAULT_CONFIG : routeConfig; + } + private class YopSentinelCircuitBreaker implements YopCircuitBreaker { - public YopSentinelCircuitBreaker(ServerRootSpace serverRootSpace, YopCircuitBreakerConfig circuitBreakerConfig) { - final ArrayList serverRoots = Lists.newArrayList(serverRootSpace.getYosServerRoot(), - serverRootSpace.getSandboxServerRoot()); - if (CollectionUtils.isNotEmpty(serverRootSpace.getPreferredEndPoint())) { - serverRoots.addAll(serverRootSpace.getPreferredEndPoint()); - } - if (CollectionUtils.isNotEmpty(serverRootSpace.getPreferredYosEndPoint())) { - serverRoots.addAll(serverRootSpace.getPreferredYosEndPoint()); - } - YopDegradeRuleHelper.initDegradeRule(serverRoots, circuitBreakerConfig); + public YopSentinelCircuitBreaker(ServerRootSpace serverRootSpace) { + Map circuitBreakerConfigMap = Maps.newHashMap(); + serverRootSpace.getMainServers().forEach((serverRootType, uri) -> { + YopRouteConfig routeConfig = findRouteConfig(uri); + circuitBreakerConfigMap.put(new UriResource( + UriResource.computeResourceGroup(serverRootSpace.getProvider(), + serverRootSpace.getEnv(), serverRootSpace.getServerGroup(), serverRootType), uri) + .computeResourceKey(), routeConfig.getCircuitBreakerConfig()); + }); + + serverRootSpace.getBackupServers().forEach((serverRootType, uris) -> { + for (URI uri : uris) { + YopRouteConfig routeConfig = findRouteConfig(uri); + circuitBreakerConfigMap.put(new UriResource( + UriResource.computeResourceGroup(serverRootSpace.getProvider(), + serverRootSpace.getEnv(), serverRootSpace.getServerGroup(), serverRootType), uri) + .computeResourceKey(), routeConfig.getCircuitBreakerConfig()); + } + }); + YopDegradeRuleHelper.initDegradeRule(circuitBreakerConfigMap); } @Override @@ -179,9 +202,10 @@ public Output execute(R try { // 请求保留资源时,不再熔断 if (!uriResource.isRetained()) { - final String resource = uriResource.computeResourceKey(); - YopDegradeRuleHelper.addDegradeRule(resource, circuitBreakerConfig); - entry = YopSph.getInstance().entry(resource); + YopRouteConfig routeConfig = findRouteConfig(uriResource.getResource()); + final String resourceKey = uriResource.computeResourceKey(); + YopDegradeRuleHelper.addDegradeRule(resourceKey, routeConfig.getCircuitBreakerConfig()); + entry = YopSph.getInstance().entry(resourceKey); } final Output output = doExecute(request, invoker); successInvoked = true; @@ -253,11 +277,9 @@ private class YopInvoker public YopInvoker(ClientExecutionParams executionParams, ExecutionContext executionContext, - ExceptionAnalyzer exceptionAnalyzer, boolean circuitBreaker) { setInput(executionParams); setContext(executionContext); - setExceptionAnalyzer(exceptionAnalyzer); if (circuitBreaker) { enableCircuitBreaker(); } else { @@ -265,6 +287,24 @@ public YopInvoker(ClientExecutionParams executionParams, } } + @Override + public ExceptionAnalyzer getExceptionAnalyzer() { + Set excludeExceptions = Collections.emptySet(); + Set retryExceptions = Collections.emptySet(); + final UriResource uriResource = getUriResource(); + final YopRouteConfig routeConfig = findRouteConfig(uriResource.getResource()); + if (null != routeConfig) { + if (null != routeConfig.getCircuitBreakerConfig() + && null != routeConfig.getCircuitBreakerConfig().getExcludeExceptions()) { + excludeExceptions = routeConfig.getCircuitBreakerConfig().getExcludeExceptions(); + } + if (null != routeConfig.getRetryExceptions()) { + retryExceptions = routeConfig.getRetryExceptions(); + } + } + return SimpleExceptionAnalyzer.from(excludeExceptions, retryExceptions); + } + @Override public Output invoke() { // 准备http参数 diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientParams.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientParams.java index 78e6c9eb..d380b328 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientParams.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/ClientParams.java @@ -4,6 +4,8 @@ import com.yeepay.yop.sdk.auth.credentials.provider.YopPlatformCredentialsProvider; import com.yeepay.yop.sdk.auth.req.AuthorizationReqRegistry; import com.yeepay.yop.sdk.config.provider.YopSdkConfigProvider; +import com.yeepay.yop.sdk.invoke.RouterPolicy; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProvider; import java.net.URI; import java.util.List; @@ -46,6 +48,10 @@ public class ClientParams { private final YopPlatformCredentialsProvider platformCredentialsProvider; + private final YopRouteConfigProvider routeConfigProvider; + + private final RouterPolicy routerPolicy; + private String clientId; private ClientParams(boolean inner, String provider,String env, @@ -53,7 +59,7 @@ private ClientParams(boolean inner, String provider,String env, ClientConfiguration clientConfiguration, AuthorizationReqRegistry authorizationReqRegistry, YopCredentialsProvider credentialsProvider, YopSdkConfigProvider yopSdkConfigProvider, - YopPlatformCredentialsProvider platformCredentialsProvider) { + YopPlatformCredentialsProvider platformCredentialsProvider, YopRouteConfigProvider routeConfigProvider, RouterPolicy routerPolicy) { this.inner = inner; this.endPoint = endPoint; this.yosEndPoint = yosEndPoint; @@ -65,6 +71,8 @@ private ClientParams(boolean inner, String provider,String env, this.credentialsProvider = credentialsProvider; this.yopSdkConfigProvider = yopSdkConfigProvider; this.platformCredentialsProvider = platformCredentialsProvider; + this.routeConfigProvider = routeConfigProvider; + this.routerPolicy = routerPolicy; this.provider = provider; this.env = env; } @@ -117,6 +125,14 @@ public YopPlatformCredentialsProvider getPlatformCredentialsProvider() { return platformCredentialsProvider; } + public YopRouteConfigProvider getRouteConfigProvider() { + return routeConfigProvider; + } + + public RouterPolicy getRouterPolicy() { + return routerPolicy; + } + void setClientId(String clientId) { this.clientId = clientId; } @@ -140,6 +156,8 @@ public static final class Builder { private YopCredentialsProvider credentialsProvider; private YopSdkConfigProvider yopSdkConfigProvider; private YopPlatformCredentialsProvider platformCredentialsProvider; + private YopRouteConfigProvider routeConfigProvider; + private RouterPolicy routerPolicy; private Builder() { } @@ -213,10 +231,20 @@ public Builder withPlatformCredentialsProvider(YopPlatformCredentialsProvider pl return this; } + public Builder withRouteConfigProvider(YopRouteConfigProvider routeConfigProvider) { + this.routeConfigProvider = routeConfigProvider; + return this; + } + + public Builder withRouterPolicy(RouterPolicy routerPolicy) { + this.routerPolicy = routerPolicy; + return this; + } + public ClientParams build() { return new ClientParams(inner, provider, env, endPoint, yosEndPoint, preferredEndPoint, preferredYosEndPoint, sandboxEndPoint, clientConfiguration, authorizationReqRegistry, - credentialsProvider, yopSdkConfigProvider, platformCredentialsProvider); + credentialsProvider, yopSdkConfigProvider, platformCredentialsProvider, routeConfigProvider, routerPolicy); } } } diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/metric/YopResourceBlockReportListener.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/metric/YopResourceBlockReportListener.java new file mode 100644 index 00000000..beae8039 --- /dev/null +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/metric/YopResourceBlockReportListener.java @@ -0,0 +1,52 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.client.metric; + +import com.yeepay.yop.sdk.client.ClientReporter; +import com.yeepay.yop.sdk.client.metric.report.host.YopHostStatusChangePayload; +import com.yeepay.yop.sdk.client.metric.report.host.YopHostStatusChangeReport; +import com.yeepay.yop.sdk.invoke.model.UriResource; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; + +/** + * title: 资源熔断上报
+ * description: 描述
+ * Copyright: Copyright (c)2014
+ * Company: 易宝支付(YeePay)
+ * + * @author wdc + * @version 1.0.0 + * @since 2024/4/3 + */ +public class YopResourceBlockReportListener implements CircuitBreakerStateChangeObserver { + + private static final Logger LOGGER = LoggerFactory.getLogger(YopResourceBlockReportListener.class); + + @Override + public void onStateChange(CircuitBreaker.State prevState, CircuitBreaker.State newState, DegradeRule rule, Double snapshotValue) { + try { + // 异步上报 + final UriResource uriResource = UriResource.parseResourceKey(rule.getResource()); + final URI serverRoot = uriResource.getResource(); + final String[] resourceGroupSplit = uriResource.parseResourceGroup(); + String provider = resourceGroupSplit[0], env = resourceGroupSplit[1]; + + final YopHostStatusChangeReport report = new YopHostStatusChangeReport( + new YopHostStatusChangePayload(serverRoot.toString(), prevState.name(), newState.name(), rule.toString())); + report.setProvider(provider); + report.setEnv(env); + ClientReporter.asyncReportToQueue(report); + } catch (Exception e) { + LOGGER.warn("UnexpectedError, ResourceBLockReport, rule:{}, prev:{}, current:{}", rule, prevState, newState, e); + } + } + +} diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/GateWayRouter.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/GateWayRouter.java deleted file mode 100644 index 1fc9cd52..00000000 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/GateWayRouter.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.yeepay.yop.sdk.client.router; - -import com.yeepay.yop.sdk.internal.Request; -import com.yeepay.yop.sdk.invoke.model.UriResource; - -import java.net.URI; -import java.util.List; - -/** - * title: 网关路由
- * description:
- * Copyright: Copyright (c) 2019
- * Company: 易宝支付(YeePay)
- * - * @author menghao.chen - * @version 1.0.0 - * @since 2019-03-12 17:20 - */ -public interface GateWayRouter { - - /** - * 路由 - * - * @param appKey 应用 - * @param request 请求 - * @param excludeServerRoots 已失败列表 - * @return serverRoot URI - */ - UriResource route(String appKey, Request request, List excludeServerRoots); - -} diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/RouteUtils.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/RouteUtils.java deleted file mode 100644 index f5c46253..00000000 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/RouteUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright: Copyright (c)2014 - * Company: 易宝支付(YeePay) - */ -package com.yeepay.yop.sdk.client.router; - -import com.yeepay.yop.sdk.utils.RandomUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * title: 路由工具
- * description: 描述
- * Copyright: Copyright (c)2014
- * Company: 易宝支付(YeePay)
- * - * @author wdc - * @version 1.0.0 - * @since 2023/3/31 - */ -public class RouteUtils { - - public static List randomList(List origin) { - List tmp = new ArrayList<>(origin); - Collections.shuffle(tmp, RandomUtils.secureRandom()); - return tmp; - } - - public static T randomOne(List origin) { - return origin.get(RandomUtils.secureRandom().nextInt(origin.size())); - } -} diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/ServerRootSpace.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/ServerRootSpace.java index 9a3c36af..1a38199b 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/ServerRootSpace.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/ServerRootSpace.java @@ -1,12 +1,17 @@ package com.yeepay.yop.sdk.client.router; -import com.yeepay.yop.sdk.YopConstants; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.utils.RandomUtils; import org.apache.commons.collections4.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.Serializable; import java.net.URI; -import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import static com.yeepay.yop.sdk.YopConstants.DEFAULT_PREFERRED_SERVER_ROOT; @@ -23,6 +28,8 @@ */ public class ServerRootSpace implements Serializable { + private static final Logger LOGGER = LoggerFactory.getLogger(ServerRootSpace.class); + private static final long serialVersionUID = -1L; private final String provider; @@ -39,29 +46,13 @@ public class ServerRootSpace implements Serializable { private final URI sandboxServerRoot; - public ServerRootSpace(URI serverRoot, URI yosServerRoot, URI sandboxServerRoot) { - this.provider = YopConstants.YOP_DEFAULT_PROVIDER; - this.env = YopConstants.YOP_DEFAULT_ENV; - this.serverRoot = serverRoot; - this.yosServerRoot = yosServerRoot; - this.preferredEndPoint = DEFAULT_PREFERRED_SERVER_ROOT; - this.preferredYosEndPoint = Collections.emptyList(); - this.sandboxServerRoot = sandboxServerRoot; - } + private final String serverGroup; - public ServerRootSpace(URI serverRoot, URI yosServerRoot, - List preferredEndPoint, List preferredYosEndPoint, - URI sandboxServerRoot) { - this.provider = YopConstants.YOP_DEFAULT_PROVIDER; - this.env = YopConstants.YOP_DEFAULT_ENV; - this.serverRoot = serverRoot; - this.yosServerRoot = yosServerRoot; - this.preferredEndPoint = CollectionUtils.isEmpty(preferredEndPoint) ? DEFAULT_PREFERRED_SERVER_ROOT : preferredEndPoint; - this.preferredYosEndPoint = preferredYosEndPoint; - this.sandboxServerRoot = sandboxServerRoot; - } + private final Map mainServers; + + private final Map> backupServers; - public ServerRootSpace(String provider, String env, + public ServerRootSpace(String provider, String env, String serverGroup, URI serverRoot, URI yosServerRoot, List preferredEndPoint, List preferredYosEndPoint, URI sandboxServerRoot) { @@ -72,8 +63,58 @@ public ServerRootSpace(String provider, String env, this.preferredEndPoint = CollectionUtils.isEmpty(preferredEndPoint) ? DEFAULT_PREFERRED_SERVER_ROOT : preferredEndPoint; this.preferredYosEndPoint = preferredYosEndPoint; this.sandboxServerRoot = sandboxServerRoot; + this.serverGroup = serverGroup; + + this.mainServers = Maps.newConcurrentMap(); + this.backupServers = Maps.newConcurrentMap(); + + // 随机选主:common + final List randomCommonList = RandomUtils.randomList(getPreferredEndPoint()); + if (recordMainServer(randomCommonList.remove(0), ServerRootType.COMMON, mainServers)) { + backupServers.put(ServerRootType.COMMON, randomCommonList); + } + // yos + final List randomYosList = RandomUtils.randomList(CollectionUtils.isEmpty(getPreferredYosEndPoint()) + ? Lists.newArrayList(getYosServerRoot()) : getPreferredYosEndPoint()); + if (recordMainServer(randomYosList.remove(0), ServerRootType.YOS, mainServers)) { + backupServers.put(ServerRootType.YOS, randomYosList); + } + // sandbox 兼容老沙箱 + final List randomSandboxList = RandomUtils.randomList(Lists.newArrayList(getSandboxServerRoot())); + if (recordMainServer(randomSandboxList.remove(0), ServerRootType.SANDBOX, mainServers)) { + backupServers.put(ServerRootType.SANDBOX, randomSandboxList); + } + } + + private boolean recordMainServer(URI serverRoot, ServerRootType serverRootType, Map mainServers) { + return recordMainServer(serverRoot, serverRootType, mainServers, false); } + private boolean recordMainServer(URI serverRoot, ServerRootType serverRootType, Map mainServers, boolean force) { + if (null == serverRoot) { + throw new YopClientException("Config Error, No ServerRoot Found, type:" + serverRootType); + } + final URI oldMain = mainServers.putIfAbsent(serverRootType, serverRoot); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Main ServerRoot Set, value:{}, type:{}", serverRoot, serverRootType); + } + if (null != oldMain) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Main ServerRoot Already Set, value:{}", oldMain); + } + if (force) { + mainServers.put(serverRootType, serverRoot); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Main ServerRoot Switched, old:{}, new:{}", oldMain, serverRoot); + } + return true; + } + return false; + } + return true; + } + + public String getProvider() { return provider; } @@ -102,9 +143,21 @@ public URI getSandboxServerRoot() { return sandboxServerRoot; } + public String getServerGroup() { + return serverGroup; + } + + public Map getMainServers() { + return mainServers; + } + + public Map> getBackupServers() { + return backupServers; + } + @Override public int hashCode() { - return Objects.hash(this.provider, this.env, this.serverRoot, + return Objects.hash(this.provider, this.env, this.serverGroup, this.serverRoot, this.yosServerRoot, this.sandboxServerRoot, this.preferredEndPoint, this.preferredYosEndPoint); } @@ -114,6 +167,7 @@ public boolean equals(Object obj) { final ServerRootSpace that = (ServerRootSpace) obj; return Objects.equals(this.provider, that.provider) && Objects.equals(this.env, that.env) && + Objects.equals(this.serverGroup, that.serverGroup) && Objects.equals(this.serverRoot, that.serverRoot) && Objects.equals(this.yosServerRoot, that.yosServerRoot) && Objects.equals(this.sandboxServerRoot, that.sandboxServerRoot) && @@ -122,4 +176,11 @@ public boolean equals(Object obj) { } return false; } + + public enum ServerRootType { + COMMON, + YOS, + @Deprecated + SANDBOX + } } diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/SimpleGateWayRouter.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/SimpleGateWayRouter.java deleted file mode 100644 index 81e0857f..00000000 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/SimpleGateWayRouter.java +++ /dev/null @@ -1,328 +0,0 @@ -package com.yeepay.yop.sdk.client.router; - -import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; -import com.yeepay.yop.sdk.YopConstants; -import com.yeepay.yop.sdk.client.ClientReporter; -import com.yeepay.yop.sdk.client.metric.report.host.YopHostStatusChangePayload; -import com.yeepay.yop.sdk.client.metric.report.host.YopHostStatusChangeReport; -import com.yeepay.yop.sdk.constants.CharacterConstants; -import com.yeepay.yop.sdk.exception.YopClientException; -import com.yeepay.yop.sdk.internal.Request; -import com.yeepay.yop.sdk.invoke.model.UriResource; -import com.yeepay.yop.sdk.model.YopRequestConfig; -import com.yeepay.yop.sdk.sentinel.YopSph; -import com.yeepay.yop.sdk.utils.CheckUtils; -import com.yeepay.yop.sdk.utils.EnvUtils; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.collections4.MapUtils; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; -import java.util.*; - - -/** - * title: 简单网关路由
- * description:
- * Copyright: Copyright (c) 2019
- * Company: 易宝支付(YeePay)
- * - * @author menghao.chen - * @version 1.0.0 - * @since 2019-03-12 19:58 - */ -public class SimpleGateWayRouter implements GateWayRouter { - - private static final Logger LOGGER = LoggerFactory.getLogger(SimpleGateWayRouter.class); - - private static final Map SERVER_ROOT_ROUTING = Maps.newConcurrentMap(); - private static final Map> ALL_SERVER_INFOS = Maps.newConcurrentMap(); - private static final YopSph.BlockResourcePool BLOCK_SERVER_POOL = new YopSph.BlockResourcePool(); - - static { - monitorServerRoot(); - } - - private class ServerRootRouting { - - private Map mainServers; - private Map> backupServers; - private Map> allServers; - - public ServerRootRouting(Map mainServers, Map> backupServers) { - this.mainServers = mainServers; - this.backupServers = backupServers; - this.allServers = Maps.newHashMap(); - if (MapUtils.isNotEmpty(mainServers)) { - mainServers.forEach((k, v) -> this.allServers.computeIfAbsent(k, p -> Lists.newArrayList()).add(v)); - } - if (MapUtils.isNotEmpty(backupServers)) { - backupServers.forEach((k, v) -> this.allServers.computeIfAbsent(k, p -> Lists.newArrayList()).addAll(v)); - } - } - - public Map getMainServers() { - return mainServers; - } - - public Map> getBackupServers() { - return backupServers; - } - - public Map> getAllServers() { - return allServers; - } - } - - - private final ServerRootSpace space; - - private final Set independentApiGroups; - - private final ServerRootRouting serverRootRouting; - - public SimpleGateWayRouter(ServerRootSpace space) { - this.space = space; - this.independentApiGroups = Collections.unmodifiableSet(Sets.newHashSet("bank-encryption")); - - this.serverRootRouting = SERVER_ROOT_ROUTING.computeIfAbsent(space, p -> { - collectServerRootTypes(space); - final Map mainServers = Maps.newConcurrentMap(); - final Map> backupServers = Maps.newConcurrentMap(); - - // 随机选主:common - final List randomCommonList = RouteUtils.randomList(space.getPreferredEndPoint()); - if (recordMainServer(randomCommonList.remove(0), ServerRootType.COMMON, mainServers)) { - backupServers.put(ServerRootType.COMMON, randomCommonList); - } - // yos - final List randomYosList = RouteUtils.randomList(CollectionUtils.isEmpty(space.getPreferredYosEndPoint()) - ? Lists.newArrayList(space.getYosServerRoot()) : space.getPreferredYosEndPoint()); - if (recordMainServer(randomYosList.remove(0), ServerRootType.YOS, mainServers)) { - backupServers.put(ServerRootType.YOS, randomYosList); - } - // sandbox 兼容老沙箱 - final List randomSandboxList = RouteUtils.randomList(Lists.newArrayList(space.getSandboxServerRoot())); - if (recordMainServer(randomSandboxList.remove(0), ServerRootType.SANDBOX, mainServers)) { - backupServers.put(ServerRootType.SANDBOX, randomYosList); - } - return new ServerRootRouting(mainServers, backupServers); - }); - } - - private boolean recordMainServer(URI serverRoot, ServerRootType serverRootType, Map mainServers) { - return recordMainServer(serverRoot, serverRootType, mainServers, false); - } - - private boolean recordMainServer(URI serverRoot, ServerRootType serverRootType, Map mainServers, boolean force) { - if (null == serverRoot) { - throw new YopClientException("Config Error, No ServerRoot Found, type:" + serverRootType); - } - final URI oldMain = mainServers.putIfAbsent(serverRootType, serverRoot); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Main ServerRoot Set, value:{}, type:{}", serverRoot, serverRootType); - } - if (null != oldMain) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Main ServerRoot Already Set, value:{}", oldMain); - } - if (force) { - mainServers.put(serverRootType, serverRoot); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Main ServerRoot Switched, old:{}, new:{}", oldMain, serverRoot); - } - return true; - } - return false; - } - return true; - } - - private void collectServerRootTypes(ServerRootSpace space) { - if (CollectionUtils.isNotEmpty(space.getPreferredEndPoint())) { - for (URI uri : space.getPreferredEndPoint()) { - collectServerRootType(space.getProvider(), space.getEnv(), uri, ServerRootType.COMMON); - } - } - - if (CollectionUtils.isNotEmpty(space.getPreferredYosEndPoint())) { - for (URI uri : space.getPreferredYosEndPoint()) { - collectServerRootType(space.getProvider(), space.getEnv(), uri, ServerRootType.YOS); - } - } - collectServerRootType(space.getProvider(), space.getEnv(), space.getYosServerRoot(), ServerRootType.YOS); - } - - private void collectServerRootType(String provider, String env, URI serverRoot, ServerRootType serverRootType) { - if (null != serverRoot) { - ALL_SERVER_INFOS.computeIfAbsent(serverRoot, - p -> Sets.newHashSet()).add(new ServerRootInfo(provider, env, serverRootType)); - } - } - - private static void monitorServerRoot() { - // sentinel监控 - EventObserverRegistry.getInstance().addStateChangeObserver("BLOCKED_SERVERS_CHANGED", - (prevState, newState, rule, snapshotValue) -> { - try { - final UriResource uriResource = UriResource.parseResourceKey(rule.getResource()); - final URI serverRoot = uriResource.getResource(); - LOGGER.info("ServerRoot Block State Changed, serverRoot:{}, old:{}, new:{}, rule:{}", - serverRoot, prevState, newState, rule); - Set serverRootInfos = ALL_SERVER_INFOS.get(serverRoot); - ServerRootInfo choosedServerRootInfo = ServerRootInfo.DEFAULT_INFO; - Set serverTypes = Collections.emptySet(); - if (CollectionUtils.isNotEmpty(serverRootInfos)) { - serverTypes = Sets.newHashSet(); - for (ServerRootInfo serverRootInfo : serverRootInfos) { - serverTypes.add(serverRootInfo.getServerRootType().name()); - } - choosedServerRootInfo = serverRootInfos.iterator().next(); - } - BLOCK_SERVER_POOL.onServerStatusChange(uriResource, prevState, newState, rule, serverTypes); - // 异步上报 - final YopHostStatusChangeReport report = new YopHostStatusChangeReport( - new YopHostStatusChangePayload(serverRoot.toString(), prevState.name(), newState.name(), rule.toString())); - report.setProvider(choosedServerRootInfo.getProvider()); - report.setEnv(choosedServerRootInfo.getEnv()); - ClientReporter.asyncReportToQueue(report); - } catch (Exception e) { - LOGGER.warn("UnexpectedError, MonitorServerRoot ex:", e); - } - }); - } - - @Override - public UriResource route(String appKey, Request request, List excludeServerRoots) { - // 兼容旧版沙箱调用 - if (!EnvUtils.isSandBoxEnv(space.getEnv()) && (EnvUtils.isSandboxApp(appKey) || EnvUtils.isSandBoxMode())) { - return new UriResource(space.getSandboxServerRoot()); - } - - final YopRequestConfig requestConfig = request.getOriginalRequestObject().getRequestConfig(); - final ServerRootType serverRootType = request.isYosRequest() ? ServerRootType.YOS : ServerRootType.COMMON; - - if (StringUtils.isNotBlank(requestConfig.getServerRoot())) { - URI serverRoot = CheckUtils.checkServerRoot(requestConfig.getServerRoot()); - if (isExcludeServerRoots(serverRoot, excludeServerRoots)) { - throw new YopClientException("RequestConfig Error, serverRoot excluded:" + serverRoot); - } - collectServerRootType(space.getProvider(), space.getEnv(), serverRoot, serverRootType); - return new UriResource(serverRoot); - } else { - // 独立网关,依然走openapi,serviceName是apiGroup的变形,需要还原 - String apiGroup = request.getServiceName().toLowerCase().replace(CharacterConstants.UNDER_LINE, CharacterConstants.DASH_LINE); - if (independentApiGroups.contains(apiGroup)) { - final URI independentServerRoot = independentServerRoot(apiGroup, request); - if (isExcludeServerRoots(independentServerRoot, excludeServerRoots)) { - throw new YopClientException("Config Error, ServerRoot excluded:" + independentServerRoot); - } - return new UriResource(independentServerRoot); - } - - // 主域名准备 - URI mainServer = this.serverRootRouting.getMainServers().get(serverRootType); - if (null == mainServer) { - throw new YopClientException("Config Error, Main ServerRoot NotFound" + serverRootType); - } - - // 主域名正常 - if (!isExcludeServerRoots(mainServer, excludeServerRoots)) { - return new UriResource(mainServer); - } - - // 主域名故障,临时启用备选域名 - final List backupServers = this.serverRootRouting.getBackupServers().get(serverRootType); - if (CollectionUtils.isNotEmpty(backupServers)) { - for (URI backup : backupServers) { - if (!isExcludeServerRoots(backup, excludeServerRoots)) { - return new UriResource(backup); - } - } - } - - // 备用域名故障,选用最早故障的域名 - return BLOCK_SERVER_POOL.select(serverRootType.name(), mainServer, - this.serverRootRouting.getAllServers().get(serverRootType)); - } - } - - private boolean isExcludeServerRoots(URI serverRoot, List excludeServerRoots) { - return null != excludeServerRoots && null != serverRoot && excludeServerRoots.contains(serverRoot); - } - - private URI independentServerRoot(String apiGroup, Request request) { - try { - URI serverRoot = request.isYosRequest() ? space.getYosServerRoot() : space.getServerRoot(); - return new URI(serverRoot.getScheme(), serverRoot.getUserInfo(), - getIndependentApiGroupHost(apiGroup, serverRoot.getHost(), request.isYosRequest()), - serverRoot.getPort(), serverRoot.getPath(), serverRoot.getQuery(), serverRoot.getFragment()); - } catch (Exception ex) { - throw new YopClientException("Route Request Failure, ex:", ex); - } - } - - private String getIndependentApiGroupHost(String apiGroup, String originHost, boolean isYosRequest) { - //目前只有普通api的请求才需要路由到独立网关 - if (isYosRequest) { - return originHost; - } - int index = StringUtils.indexOf(originHost, CharacterConstants.DOT); - return StringUtils.substring(originHost, 0, index) + CharacterConstants.DASH_LINE + apiGroup + StringUtils.substring(originHost, index); - } - - private enum ServerRootType { - COMMON, - YOS, - @Deprecated - SANDBOX - } - - private static class ServerRootInfo { - public static ServerRootInfo DEFAULT_INFO = new ServerRootInfo(YopConstants.YOP_DEFAULT_PROVIDER, - YopConstants.YOP_DEFAULT_ENV, ServerRootType.COMMON); - private String provider; - private String env; - private ServerRootType serverRootType; - - public ServerRootInfo(String provider, String env, ServerRootType serverRootType) { - this.provider = provider; - this.env = env; - this.serverRootType = serverRootType; - } - - public String getProvider() { - return provider; - } - - public String getEnv() { - return env; - } - - public ServerRootType getServerRootType() { - return serverRootType; - } - - @Override - public int hashCode() { - return Objects.hash(provider, env, serverRootType); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ServerRootInfo) { - ServerRootInfo that = (ServerRootInfo) obj; - return Objects.equals(this.provider, that.provider) && - Objects.equals(this.env, that.env) && - Objects.equals(this.serverRootType, that.serverRootType); - } - return false; - } - } - -} diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/YopRouter.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/YopRouter.java index 332a0c85..6a3e7ffd 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/YopRouter.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/router/YopRouter.java @@ -4,16 +4,28 @@ */ package com.yeepay.yop.sdk.client.router; +import com.google.common.collect.Lists; import com.yeepay.yop.sdk.client.ClientExecutionParams; +import com.yeepay.yop.sdk.exception.YopClientException; import com.yeepay.yop.sdk.http.ExecutionContext; import com.yeepay.yop.sdk.internal.Request; import com.yeepay.yop.sdk.invoke.Router; +import com.yeepay.yop.sdk.invoke.RouterPolicy; +import com.yeepay.yop.sdk.invoke.model.BlockResource; +import com.yeepay.yop.sdk.invoke.model.Resource; +import com.yeepay.yop.sdk.invoke.model.SimpleRouterParams; import com.yeepay.yop.sdk.invoke.model.UriResource; import com.yeepay.yop.sdk.model.BaseRequest; import com.yeepay.yop.sdk.model.BaseResponse; +import com.yeepay.yop.sdk.model.YopRequestConfig; +import com.yeepay.yop.sdk.utils.EnvUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import java.net.URI; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** * title: yop域名路由
@@ -28,15 +40,75 @@ public class YopRouter implements Router, ExecutionContext> { - private final GateWayRouter gateWayRouter; + private final RouterPolicy routerPolicy; - public YopRouter(GateWayRouter gateWayRouter) { - this.gateWayRouter = gateWayRouter; + private final ServerRootSpace serverRootSpace; + + public YopRouter(ServerRootSpace serverRootSpace, + RouterPolicy routerPolicy) { + this.serverRootSpace = serverRootSpace; + this.routerPolicy = routerPolicy; } @Override public UriResource route(ClientExecutionParams executionParams, ExecutionContext executionContext, Object...args) { + final String appKey = executionContext.getYopCredentials().getAppKey(); + List invokedServerRoots = null == args[0] ? Collections.emptyList() + : ((List) args[0]).stream().map(Object::toString).collect(Collectors.toList()); + // 兼容旧版沙箱调用 + if (!EnvUtils.isSandBoxEnv(serverRootSpace.getEnv()) && (EnvUtils.isSandboxApp(appKey) || EnvUtils.isSandBoxMode())) { + return doSingleRoute(serverRootSpace.getSandboxServerRoot(), ServerRootSpace.ServerRootType.SANDBOX, invokedServerRoots); + } + + // 手动指定 Request request = executionParams.getRequestMarshaller().marshall(executionParams.getInput()); - return gateWayRouter.route(executionContext.getYopCredentials().getAppKey(), request, (List) args[0]); + final ServerRootSpace.ServerRootType serverRootType = request.isYosRequest() ? ServerRootSpace.ServerRootType.YOS + : ServerRootSpace.ServerRootType.COMMON; + final YopRequestConfig requestConfig = request.getOriginalRequestObject().getRequestConfig(); + if (StringUtils.isNotBlank(requestConfig.getServerRoot())) { + return doSingleRoute(URI.create(requestConfig.getServerRoot()), serverRootType, invokedServerRoots); + } else { + URI mainServer = this.serverRootSpace.getMainServers().get(serverRootType); + if (null == mainServer) { + throw new YopClientException("Config Error, Main ServerRoot NotFound" + serverRootType); + } + final String resourceGroup = UriResource.computeResourceGroup(serverRootSpace.getProvider(), + serverRootSpace.getEnv(), serverRootSpace.getServerGroup(), serverRootType); + final List backupServers = this.serverRootSpace.getBackupServers().get(serverRootType); + List availableResources = Lists.newArrayList(new UriResource(resourceGroup, mainServer).computeResourceKey()); + if (CollectionUtils.isNotEmpty(backupServers)) { + backupServers.forEach(p -> availableResources.add(new UriResource(resourceGroup, p).computeResourceKey())); + } + return doBatchRoute(resourceGroup, availableResources, invokedServerRoots); + } + } + + private UriResource doBatchRoute(String resourceGroup, List availableResources, List invokedResources) { + final Resource routeResource = routerPolicy.select(new SimpleRouterParams(resourceGroup, + availableResources, invokedResources)); + UriResource uriResource = UriResource.parseResourceKey(routeResource.getResourceKey()); + + if (routeResource instanceof BlockResource) { + BlockResource blockResource = (BlockResource) routeResource; + return new UriResource(UriResource.ResourceType.BLOCKED, uriResource.getResourceGroup(), + String.valueOf(blockResource.getBlockSequence()), uriResource.getResource()); + } + return uriResource; } + + private UriResource doSingleRoute(URI serverRoot, ServerRootSpace.ServerRootType serverRootType, List invokeResources) { + final String resourceGroup = UriResource.computeResourceGroup(serverRootSpace.getProvider(), + serverRootSpace.getEnv(), serverRootSpace.getServerGroup(), serverRootType); + UriResource uriResource = new UriResource(resourceGroup, serverRoot); + + final Resource routeResource = routerPolicy.select(new SimpleRouterParams(resourceGroup, + Collections.singletonList(uriResource.computeResourceKey()), invokeResources)); + if (routeResource instanceof BlockResource) { + BlockResource blockResource = (BlockResource) routeResource; + return new UriResource(UriResource.ResourceType.BLOCKED, uriResource.getResourceGroup(), + String.valueOf(blockResource.getBlockSequence()), uriResource.getResource()); + } + return uriResource; + } + } diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/support/ClientConfigurationSupport.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/support/ClientConfigurationSupport.java index 82f861b8..34be59b9 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/support/ClientConfigurationSupport.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/client/support/ClientConfigurationSupport.java @@ -39,10 +39,7 @@ public static ClientConfiguration getClientConfiguration(YopSdkConfig yopSdkConf .withConnectionRequestTimeoutInMillis(yopHttpClientConfig.getConnectRequestTimeout()) .withSocketTimeoutInMillis(yopHttpClientConfig.getReadTimeout()) .withMaxConnectionsPerRoute(yopHttpClientConfig.getMaxConnPerRoute()) - .withClientImpl(yopHttpClientConfig.getClientImpl()) - .withMaxRetryCount(yopHttpClientConfig.getMaxRetryCount()) - .withRetryExceptions(yopHttpClientConfig.getRetryExceptions()) - .withCircuitBreakerConfig(yopHttpClientConfig.getCircuitBreakerConfig()); + .withClientImpl(yopHttpClientConfig.getClientImpl()); } return clientConfiguration; } diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/http/analyzer/YopContentDecryptAnalyzer.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/http/analyzer/YopContentDecryptAnalyzer.java index 80a00347..0ee9f88f 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/http/analyzer/YopContentDecryptAnalyzer.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/http/analyzer/YopContentDecryptAnalyzer.java @@ -3,14 +3,15 @@ import com.jayway.jsonpath.DocumentContext; import com.jayway.jsonpath.JsonPath; import com.yeepay.yop.sdk.auth.credentials.YopCredentials; +import com.yeepay.yop.sdk.base.security.encrypt.YopEncryptProtocol; import com.yeepay.yop.sdk.http.HttpResponseAnalyzer; import com.yeepay.yop.sdk.http.HttpResponseHandleContext; import com.yeepay.yop.sdk.http.YopHttpResponse; import com.yeepay.yop.sdk.model.BaseResponse; import com.yeepay.yop.sdk.model.YopResponseMetadata; import com.yeepay.yop.sdk.security.encrypt.EncryptOptions; -import com.yeepay.yop.sdk.base.security.encrypt.YopEncryptProtocol; import com.yeepay.yop.sdk.security.encrypt.YopEncryptor; +import com.yeepay.yop.sdk.utils.EncryptUtils; import com.yeepay.yop.sdk.utils.JsonUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; @@ -24,8 +25,6 @@ import static com.yeepay.yop.sdk.YopConstants.YOP_JSON_CONTENT_BIZ_KEY; import static com.yeepay.yop.sdk.YopConstants.YOP_JSON_CONTENT_FORMAT; import static com.yeepay.yop.sdk.utils.HttpUtils.isJsonResponse; -import static com.yeepay.yop.sdk.utils.JsonUtils.isTotalEncrypt; -import static com.yeepay.yop.sdk.utils.JsonUtils.resolveAllJsonPaths; /** * title: 结果解密
@@ -88,14 +87,14 @@ private void decryptJsonContent(YopHttpResponse httpResponse, YopEncryptProtocol Map yopResp = JsonUtils.fromJsonString(content, Map.class); Object encryptBizContent = yopResp.get(YOP_JSON_CONTENT_BIZ_KEY); - if (isTotalEncrypt(parsedEncryptProtocol.getEncryptParams())) { + if (EncryptUtils.isTotalEncrypt(parsedEncryptProtocol.getEncryptParams())) { httpResponse.setContent(String.format(YOP_JSON_CONTENT_FORMAT, encryptor.decryptFromBase64((String) encryptBizContent, encryptOptions))); return; } String jsonBizContent = JsonUtils.toJsonString(encryptBizContent); - Set encryptPaths = resolveAllJsonPaths(jsonBizContent, parsedEncryptProtocol.getEncryptParams()); + Set encryptPaths = EncryptUtils.resolveAllJsonPaths(jsonBizContent, parsedEncryptProtocol.getEncryptParams()); DocumentContext valReadWriteCtx = JsonPath.parse(jsonBizContent); for (String path : encryptPaths) { try { diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/internal/RequestEncryptor.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/internal/RequestEncryptor.java index 361823e5..209f7824 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/internal/RequestEncryptor.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/internal/RequestEncryptor.java @@ -20,6 +20,7 @@ import com.yeepay.yop.sdk.security.encrypt.EncryptOptions; import com.yeepay.yop.sdk.security.encrypt.YopEncryptor; import com.yeepay.yop.sdk.utils.Encodes; +import com.yeepay.yop.sdk.utils.EncryptUtils; import com.yeepay.yop.sdk.utils.JsonUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.IOUtils; @@ -42,7 +43,6 @@ import static com.yeepay.yop.sdk.constants.CharacterConstants.*; import static com.yeepay.yop.sdk.http.Headers.YOP_ENCRYPT; import static com.yeepay.yop.sdk.utils.HttpUtils.isJsonContentType; -import static com.yeepay.yop.sdk.utils.JsonUtils.resolveAllJsonPaths; /** * title: 负责加密YopRequest
@@ -215,9 +215,9 @@ private static byte[] encryptJsonParams(YopEncryptor encryptor, Set fina // 默认整体加 boolean totalEncrypt = true; if (BooleanUtils.isFalse(requestConfig.getTotalEncrypt())) { - encryptPaths = resolveAllJsonPaths(originJson, requestConfig.getEncryptParams()); + encryptPaths = EncryptUtils.resolveAllJsonPaths(originJson, requestConfig.getEncryptParams()); // 防止设置非法的jsonpath,再次校验参数 - totalEncrypt = JsonUtils.isTotalEncrypt(encryptPaths); + totalEncrypt = EncryptUtils.isTotalEncrypt(encryptPaths); } if (!totalEncrypt) { diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/model/YopRequestConfig.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/model/YopRequestConfig.java index 1fdc71e7..12bddd8b 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/model/YopRequestConfig.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/model/YopRequestConfig.java @@ -90,6 +90,11 @@ public class YopRequestConfig { */ private Boolean enableCircuitBreaker = true; + /** + * 最大重试次数,默认3次 + */ + private int maxRetryCount = 3; + public String getAppKey() { return appKey; } @@ -236,6 +241,15 @@ public YopRequestConfig setEnableCircuitBreaker(Boolean enableCircuitBreaker) { return this; } + public int getMaxRetryCount() { + return maxRetryCount; + } + + public YopRequestConfig setMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + return this; + } + public static final class Builder { private String appKey; private String securityReq; @@ -249,6 +263,7 @@ public static final class Builder { private int signExpirationInSeconds; private String serverRoot; private Boolean enableCircuitBreaker = true; + private int maxRetryCount = 3; private Builder() { } @@ -317,6 +332,11 @@ public Builder withEnableCircuitBreaker(Boolean enableCircuitBreaker) { return this; } + public Builder withMaxRetryCount(int maxRetryCount) { + this.maxRetryCount = maxRetryCount; + return this; + } + public YopRequestConfig build() { return new YopRequestConfig().setAppKey(appKey) .setSecurityReq(securityReq) @@ -329,7 +349,8 @@ public YopRequestConfig build() { .setSkipVerifySign(skipVerifySign) .setSignExpirationInSeconds(signExpirationInSeconds) .setServerRoot(serverRoot) - .setEnableCircuitBreaker(enableCircuitBreaker); + .setEnableCircuitBreaker(enableCircuitBreaker) + .setMaxRetryCount(maxRetryCount); } } } diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/sentinel/YopSph.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/sentinel/YopSph.java deleted file mode 100644 index ab8b927a..00000000 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/sentinel/YopSph.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright: Copyright (c)2014 - * Company: 易宝支付(YeePay) - */ -package com.yeepay.yop.sdk.sentinel; - -import com.alibaba.csp.sentinel.Constants; -import com.alibaba.csp.sentinel.Entry; -import com.alibaba.csp.sentinel.EntryType; -import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.context.ContextUtil; -import com.alibaba.csp.sentinel.context.NullContext; -import com.alibaba.csp.sentinel.init.InitExecutor; -import com.alibaba.csp.sentinel.log.RecordLog; -import com.alibaba.csp.sentinel.slotchain.*; -import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.alibaba.csp.sentinel.slots.block.Rule; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; -import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; -import com.google.common.collect.Maps; -import com.google.common.collect.Queues; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.yeepay.yop.sdk.base.cache.YopDegradeRuleHelper; -import com.yeepay.yop.sdk.constants.CharacterConstants; -import com.yeepay.yop.sdk.invoke.model.UriResource; -import org.apache.commons.collections4.CollectionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; -import java.util.*; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * title:
- * description: 描述
- * Copyright: Copyright (c)2014
- * Company: 易宝支付(YeePay)
- * - * @author wdc - * @version 1.0.0 - * @since 2023/12/11 - */ -public class YopSph { - - private static final Logger LOGGER = LoggerFactory.getLogger(YopSph.class); - private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); - private static final YopSph yopSph = new YopSph(); - - private static final ThreadPoolExecutor BLOCKED_SWEEPER = new ThreadPoolExecutor(2, 20, - 3, TimeUnit.MINUTES, Queues.newLinkedBlockingQueue(1000), - new ThreadFactoryBuilder().setNameFormat("yop-blocked-resource-sweeper-%d").setDaemon(true).build(), - new ThreadPoolExecutor.CallerRunsPolicy()); - - static { - // If init fails, the process will exit. - InitExecutor.doInit(); - } - - public static YopSph getInstance() { - return yopSph; - } - - private static final Object[] OBJECTS0 = new Object[0]; - - /** - * Same resource({@link ResourceWrapper#equals(Object)}) will share the same - * {@link ProcessorSlotChain}, no matter in which {@link Context}. - */ - private static volatile Map chainMap = new HashMap(); - - private static final Object LOCK = new Object(); - - private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) - throws BlockException { - Context context = ContextUtil.getContext(); - if (context instanceof NullContext) { - // The {@link NullContext} indicates that the amount of context has exceeded the threshold, - // so here init the entry only. No rule checking will be done. - return new YopEntry(resourceWrapper, null, context); - } - - if (context == null) { - // Using default context. - context = YopSph.InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME); - } - - // Global switch is close, no rule checking will do. - if (!Constants.ON) { - return new YopEntry(resourceWrapper, null, context); - } - - ProcessorSlot chain = lookProcessChain(resourceWrapper); - - /* - * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, - * so no rule checking will be done. - */ - if (chain == null) { - return new YopEntry(resourceWrapper, null, context); - } - - Entry e = new YopEntry(resourceWrapper, chain, context); - try { - chain.entry(context, resourceWrapper, null, count, prioritized, args); - } catch (BlockException e1) { - e.exit(count, args); - throw e1; - } catch (Throwable e1) { - // This should not happen, unless there are errors existing in Sentinel internal. - RecordLog.info("Sentinel unexpected exception", e1); - } - return e; - } - - /** - * Do all {@link Rule}s checking about the resource. - * - *

Each distinct resource will use a {@link ProcessorSlot} to do rules checking. Same resource will use - * same {@link ProcessorSlot} globally.

- * - *

Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE}, - * otherwise no rules checking will do. In this condition, all requests will pass directly, with no checking - * or exception.

- * - * @param resourceWrapper resource name - * @param count tokens needed - * @param args arguments of user method call - * @return {@link Entry} represents this call - * @throws BlockException if any rule's threshold is exceeded - */ - public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException { - return entryWithPriority(resourceWrapper, count, false, args); - } - - /** - * Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will - * be created if the resource doesn't relate one. - * - *

Same resource({@link ResourceWrapper#equals(Object)}) will share the same - * {@link ProcessorSlotChain} globally, no matter in which {@link Context}.

- * - *

- * Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE}, - * otherwise null will return. - *

- * - * @param resourceWrapper target resource - * @return {@link ProcessorSlotChain} of the resource - */ - ProcessorSlot lookProcessChain(ResourceWrapper resourceWrapper) { - String resourceName = resourceWrapper.getName(); - if (resourceName.contains(UriResource.RESOURCE_SEPERATOR)) { - final String[] split = resourceName.split(UriResource.RESOURCE_SEPERATOR); - resourceName = split[split.length -1]; - } - final StringResourceWrapper realResource = new StringResourceWrapper(resourceName, - resourceWrapper.getEntryType(), resourceWrapper.getResourceType()); - ProcessorSlotChain chain = chainMap.get(realResource); - if (chain == null) { - synchronized (LOCK) { - chain = chainMap.get(realResource); - if (chain == null) { - // Entry size limit. - if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) { - return null; - } - - chain = SlotChainProvider.newSlotChain(); - Map newMap = new HashMap( - chainMap.size() + 1); - newMap.putAll(chainMap); - newMap.put(resourceWrapper, chain); - chainMap = newMap; - } - } - } - return chain; - } - - /** - * This class is used for skip context name checking. - */ - private final static class InternalContextUtil extends ContextUtil { - static Context internalEnter(String name) { - return trueEnter(name, ""); - } - - static Context internalEnter(String name, String origin) { - return trueEnter(name, origin); - } - } - - public Entry entry(String name) throws BlockException { - StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT); - return entry(resource, 1, OBJECTS0); - } - - public static class BlockResourcePool { - private static final Map> serverBlockList = Maps.newConcurrentMap(); - private static final Map serverBLockSequence = Maps.newConcurrentMap(); - public UriResource select(String serverType, URI mainServer, List allServers) { - rwl.readLock().lock(); - try { - URI oldestFailServer = null; - final List failedServers = serverBlockList.get(serverType); - if (null != failedServers && !failedServers.isEmpty()) { - for (URI failedServer : failedServers) { - if (CollectionUtils.isNotEmpty(allServers) && allServers.contains(failedServer)) { - oldestFailServer = failedServer; - break; - } - } - } - // 熔断列表为空(说明其他线程已半开成功),选主域名即可 - if (null == oldestFailServer) { - oldestFailServer = mainServer; - } - return initServer(serverType, oldestFailServer); - } finally { - rwl.readLock().unlock(); - } - } - - private UriResource initServer(String serverType, URI oldestFailServer) { - - final String blockSequenceKey = getBlockSequenceKey(serverType, oldestFailServer); - final AtomicLong blockSequence = serverBLockSequence.computeIfAbsent(blockSequenceKey, - p -> new AtomicLong(0)); - - String resourcePrefix = getBlockResourcePrefix(serverType, blockSequence.get()); - return new UriResource(UriResource.ResourceType.BLOCKED, - resourcePrefix, oldestFailServer); - - } - - private String parseBlockServerType(String blockResourcePrefix) { - return blockResourcePrefix.split(CharacterConstants.COMMA)[0]; - } - - private Long parseBLockSequence(String blockResourcePrefix) { - return Long.valueOf(blockResourcePrefix.split(CharacterConstants.COMMA)[1]); - } - - private String getBlockResourcePrefix(String serverType, Long blockSequence) { - return serverType + CharacterConstants.COMMA + blockSequence; - } - - private String getBlockSequenceKey(String serverType, URI server) { - return serverType + CharacterConstants.COMMA + server.toString(); - } - - public void onServerStatusChange(UriResource uriResource, CircuitBreaker.State prevState, - CircuitBreaker.State newState, DegradeRule rule, - Set serverRootTypes) { - updateBlockedStatus(uriResource, serverRootTypes, !CircuitBreaker.State.OPEN.equals(newState)); - if (newState.equals(CircuitBreaker.State.OPEN) && UriResource.ResourceType.BLOCKED.equals(uriResource.getResourceType())) { - asyncDiscardOldServers(uriResource); - } - } - - // 更新熔断列表排序 - private void updateBlockedStatus(UriResource uriResource, Set serverRootTypes, - boolean successInvoked) { - rwl.writeLock().lock(); - try { - URI serverRoot = uriResource.getResource(); - for (String serverRootType : serverRootTypes) { - final List blockedServers = serverBlockList.computeIfAbsent(serverRootType, - p -> new ArrayList<>()); - blockedServers.removeIf(serverRoot::equals); - if (successInvoked) { - blockedServers.add(0, serverRoot); - } else { - blockedServers.add(serverRoot); - } - } - if (UriResource.ResourceType.BLOCKED.equals(uriResource.getResourceType()) && !successInvoked) { - final String serverRootType = parseBlockServerType(uriResource.getResourcePrefix()); - serverBLockSequence.computeIfAbsent(getBlockSequenceKey(serverRootType, uriResource.getResource()), - p -> new AtomicLong(0)).getAndAdd(1); - } - - } finally { - rwl.writeLock().unlock(); - } - } - - // 异步清理过期资源 - private void asyncDiscardOldServers(UriResource uriResource) { - BLOCKED_SWEEPER.submit(() -> { - try { - final String resource = uriResource.computeResourceKey(); - // 清理资源配置 - YopDegradeRuleHelper.removeDegradeRule(resource); - } catch (Exception e) { - LOGGER.warn("blocked sweeper failed, ex:", e); - } - }); - } - } -} diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/service/common/request/YopRequest.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/service/common/request/YopRequest.java index 700460f2..ed68e339 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/service/common/request/YopRequest.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/service/common/request/YopRequest.java @@ -21,7 +21,7 @@ import static com.yeepay.yop.sdk.YopConstants.TOTAL_ENCRYPT_PARAMS; import static com.yeepay.yop.sdk.constants.CharacterConstants.DOLLAR; -import static com.yeepay.yop.sdk.utils.JsonUtils.isTotalEncrypt; +import static com.yeepay.yop.sdk.utils.EncryptUtils.isTotalEncrypt; /** * title: Yop请求
diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/ClientUtils.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/ClientUtils.java index 11e19924..2132c92c 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/ClientUtils.java +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/ClientUtils.java @@ -75,6 +75,8 @@ public static ClientInst getOrBuildClientInst(ClientParams clientPa .withCredentialsProvider(clientParams.getCredentialsProvider()) .withYopSdkConfigProvider(clientParams.getYopSdkConfigProvider()) .withPlatformCredentialsProvider(clientParams.getPlatformCredentialsProvider()) + .withRouteConfigProvider(clientParams.getRouteConfigProvider()) + .withRouterPolicy(clientParams.getRouterPolicy()) .withClientConfiguration(clientParams.getClientConfiguration()) .withEndpoint(null != clientParams.getEndPoint() ? clientParams.getEndPoint().toString() : null) .withYosEndpoint(null != clientParams.getYosEndPoint() ? clientParams.getYosEndPoint().toString() : null) diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/EncryptUtils.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/EncryptUtils.java new file mode 100644 index 00000000..6ef4eda6 --- /dev/null +++ b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/EncryptUtils.java @@ -0,0 +1,75 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.utils; + +import com.google.common.collect.Sets; +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Option; +import com.yeepay.yop.sdk.exception.YopClientException; +import org.apache.commons.collections4.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +import static com.yeepay.yop.sdk.YopConstants.*; + +/** + * title:
+ * description: 描述
+ * Copyright: Copyright (c)2014
+ * Company: 易宝支付(YeePay)
+ * + * @author wdc + * @version 1.0.0 + * @since 2024/3/21 + */ +public class EncryptUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(EncryptUtils.class); + + public static boolean isTotalEncrypt(Set jsonPaths) { + boolean totalEncrypt = CollectionUtils.isSubCollection(jsonPaths, JSON_PATH_ROOT); + if (totalEncrypt) { + return true; + } + if (jsonPaths.size() > 1 && !CollectionUtils.intersection(jsonPaths, JSON_PATH_ROOT).isEmpty()) { + throw new YopClientException("illegal json paths:" + jsonPaths); + } + return false; + } + + /** + * 正序排列,保证优先加密对象 + * + * @param jsonContent + * @param jsonPathPatterns + * @return + */ + public static Set resolveAllJsonPaths(String jsonContent, Set jsonPathPatterns) { + DocumentContext pathReadCtx = JsonPath.using(Configuration.builder() + .options(Option.AS_PATH_LIST).build()).parse(jsonContent); + + SortedSet encryptPaths = Sets.newTreeSet(); + for (String encryptParam : jsonPathPatterns) { + if (JSON_PATH_ROOT.contains(encryptParam)) { + return TOTAL_ENCRYPT_PARAMS; + } + if (encryptParam.startsWith(JSON_PATH_PREFIX)) { + List pathList = pathReadCtx.read(encryptParam); + if (CollectionUtils.isNotEmpty(pathList)) { + encryptPaths.addAll(pathList); + } + } + } + encryptPaths.forEach(LOGGER::debug); + return encryptPaths; + } + +} diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/RandomUtils.java b/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/RandomUtils.java deleted file mode 100644 index baad9d2c..00000000 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/RandomUtils.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright: Copyright (c)2011 - * Company: 易宝支付(YeePay) - */ - -package com.yeepay.yop.sdk.utils; - -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -/** - * title:
- * description: 描述
- * Copyright: Copyright (c)2014
- * Company: 易宝支付(YeePay)
- * - * @author dreambt - * @version 1.0.0 - * @since 2021/5/6 17:46 - */ -public final class RandomUtils { - - private RandomUtils() { - // do nothing - } - - /** - * 使用性能更好的SHA1PRNG, Tomcat的sessionId生成也用此算法. - * 但JDK7中,需要在启动参数加入 -Djava.security=file:/dev/./urandom - */ - public static SecureRandom secureRandom() { - try { - return SecureRandom.getInstance("SHA1PRNG"); - } catch (NoSuchAlgorithmException e) {// NOSONAR - return new SecureRandom(); - } - } - -} diff --git a/yop-java-sdk-base/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.log.Logger b/yop-java-sdk-base/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.log.Logger deleted file mode 100644 index 16be797a..00000000 --- a/yop-java-sdk-base/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.log.Logger +++ /dev/null @@ -1 +0,0 @@ -com.yeepay.yop.sdk.log.YopSentinelRecordLogger \ No newline at end of file diff --git a/yop-java-sdk-base/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver b/yop-java-sdk-base/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver new file mode 100644 index 00000000..a0a75096 --- /dev/null +++ b/yop-java-sdk-base/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver @@ -0,0 +1 @@ +com.yeepay.yop.sdk.client.metric.YopResourceBlockReportListener \ No newline at end of file diff --git a/yop-java-sdk-base/src/main/resources/config/certs/yop_platform_sm_cert_4378650635.cer b/yop-java-sdk-base/src/main/resources/config/certs/yop_platform_sm_cert_4378650635.cer deleted file mode 100644 index 4fc330b2..00000000 --- a/yop-java-sdk-base/src/main/resources/config/certs/yop_platform_sm_cert_4378650635.cer +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDWDCCAvygAwIBAgIFQ3hlBjUwDAYIKoEcz1UBg3UFADBcMQswCQYDVQQGEwJD -TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y -aXR5MRswGQYDVQQDDBJDRkNBIEFDUyBTTTIgT0NBMzEwHhcNMjEwMzAxMDYyMjEx -WhcNMjMwMzAxMDYyMjExWjCBijELMAkGA1UEBhMCQ04xGzAZBgNVBAoMEkNGQ0Eg -QUNTIFNNMiBPQ0EzMTEPMA0GA1UECwwGWUVFUEFZMRkwFwYDVQQLDBBPcmdhbml6 -YXRpb25hbC0xMTIwMAYDVQQDDCkwNTFA5piT5a6d5byA5pS+5bmz5Y+wQDMxMTAw -MDAwMDU4MDQyMjlAMTBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABHeO0ffkb4+C -txuh07nYnR2214sQm0oUivNARRnDv0TryqLTzNvvPa/BWH8LQDdZ3C4a7jpu2/JU -Pv0XzsAtFoqjggF4MIIBdDBsBggrBgEFBQcBAQRgMF4wKAYIKwYBBQUHMAGGHGh0 -dHA6Ly9vY3NwLmNmY2EuY29tLmNuL29jc3AwMgYIKwYBBQUHMAKGJmh0dHA6Ly9j -cmwuY2ZjYS5jb20uY24vb2NhMzEvb2NhMzEuY2VyMB8GA1UdIwQYMBaAFAjY0SbE -SH2c7KyY6fF/YrmAzqlFMAwGA1UdEwEB/wQCMAAwSAYDVR0gBEEwPzA9BghggRyG -7yoBBDAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNuL3VzL3Vz -LTE0Lmh0bTA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLmNmY2EuY29tLmNu -L29jYTMxL1NNMi9jcmwxMTU2LmNybDAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0OBBYE -FE5HOBmKGG3rf/ogZbRt4nl0EEJFMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF -BQcDBDAMBggqgRzPVQGDdQUAA0gAMEUCIQDSBnL//IWjFx8z0LywDzlTpwfLLWYH -LKDy+6f1fQNGdAIgH9/A5O1a4xkBqxSXIivp1jlo70RtKAlIAo/36CK+7Hs= ------END CERTIFICATE----- \ No newline at end of file diff --git a/yop-java-sdk-base/src/main/resources/config/certs/yop_platform_sm_cert_4379555845.cer b/yop-java-sdk-base/src/main/resources/config/certs/yop_platform_sm_cert_4379555845.cer deleted file mode 100644 index 9e14a536..00000000 --- a/yop-java-sdk-base/src/main/resources/config/certs/yop_platform_sm_cert_4379555845.cer +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDWDCCAvygAwIBAgIFQ3lVWEUwDAYIKoEcz1UBg3UFADBcMQswCQYDVQQGEwJD -TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y -aXR5MRswGQYDVQQDDBJDRkNBIEFDUyBTTTIgT0NBMzEwHhcNMjEwMzAzMDcxMTEw -WhcNMjMwMzAzMDcxMTEwWjCBijELMAkGA1UEBhMCQ04xGzAZBgNVBAoMEkNGQ0Eg -QUNTIFNNMiBPQ0EzMTEPMA0GA1UECwwGWUVFUEFZMRkwFwYDVQQLDBBPcmdhbml6 -YXRpb25hbC0xMTIwMAYDVQQDDCkwNTFA5piT5a6d5byA5pS+5bmz5Y+wQDMxMTAw -MDAwMDU4MDQyMjlAMjBZMBMGByqGSM49AgEGCCqBHM9VAYItA0IABElJDLaHwHxi -JI+gQ9SttGHBUduM6pq7m+yGlSaB7H8d5aAk2bynsVz6Vp2GP2W/pj698Inriwh2 -ygdB6Ipk0wujggF4MIIBdDBsBggrBgEFBQcBAQRgMF4wKAYIKwYBBQUHMAGGHGh0 -dHA6Ly9vY3NwLmNmY2EuY29tLmNuL29jc3AwMgYIKwYBBQUHMAKGJmh0dHA6Ly9j -cmwuY2ZjYS5jb20uY24vb2NhMzEvb2NhMzEuY2VyMB8GA1UdIwQYMBaAFAjY0SbE -SH2c7KyY6fF/YrmAzqlFMAwGA1UdEwEB/wQCMAAwSAYDVR0gBEEwPzA9BghggRyG -7yoBBDAxMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmNmY2EuY29tLmNuL3VzL3Vz -LTE0Lmh0bTA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLmNmY2EuY29tLmNu -L29jYTMxL1NNMi9jcmwxMTYyLmNybDAOBgNVHQ8BAf8EBAMCBsAwHQYDVR0OBBYE -FOwuBzs1kxSleoMQVA39SiWFnHnMMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF -BQcDBDAMBggqgRzPVQGDdQUAA0gAMEUCIQCeBQtwOsucCOaG5eAhttWHkGsXfKrR -lY10+tazZ2VnQwIgLWckpCPpES17NSo2XjH2UTWMbVvAIeFCjAHPBkF6WY4= ------END CERTIFICATE----- \ No newline at end of file diff --git a/yop-java-sdk-base/src/main/resources/config/yop_sdk_config_default.json b/yop-java-sdk-base/src/main/resources/config/yop_sdk_config_default.json index e1028f9e..64048c74 100644 --- a/yop-java-sdk-base/src/main/resources/config/yop_sdk_config_default.json +++ b/yop-java-sdk-base/src/main/resources/config/yop_sdk_config_default.json @@ -18,44 +18,11 @@ "lazy": false }, "http_client": { - "connect_timeout": 10000, - "connect_request_timeout": 10000, + "connect_timeout": 3000, + "connect_request_timeout": 3000, "read_timeout": 30000, "max_conn_total": 200, - "max_conn_per_route": 100, - "retry_exceptions": [ - "java.net.UnknownHostException", - "java.net.ConnectException:No route to host (connect failed)", - "java.net.ConnectException:Connection refused (Connection refused)", - "java.net.ConnectException:Connection refused: connect", - "java.net.SocketTimeoutException:connect timed out", - "java.net.NoRouteToHostException", - "org.apache.http.conn.ConnectTimeoutException", "com.yeepay.shade.org.apache.http.conn.ConnectTimeoutException", - "org.apache.http.conn.HttpHostConnectException", "com.yeepay.shade.org.apache.http.conn.HttpHostConnectException", - "java.net.ConnectException:Connection timed out","java.net.ConnectException:连接超时" - ], - "max_retry_count": 3, - "circuit_breaker": { - "enable": true, - "yop_exclude_exceptions": [ - "com.yeepay.yop.sdk.exception.YopClientException" - ], - "rules": [ - { - "grade": 2, - "count": 4, - "time_window": 300, - "stat_interval_ms": 300000 - }, - { - "grade": 1, - "count": 0.2, - "time_window": 300, - "stat_interval_ms": 5000, - "min_request_amount": 5 - } - ] - } + "max_conn_per_route": 100 }, "yop_report": { "enable": true, diff --git a/yop-java-sdk-base/src/test/java/com/yeepay/yop/sdk/utils/X509CertUtilsTest.java b/yop-java-sdk-base/src/test/java/com/yeepay/yop/sdk/utils/X509CertUtilsTest.java index f15e5450..47d033bf 100644 --- a/yop-java-sdk-base/src/test/java/com/yeepay/yop/sdk/utils/X509CertUtilsTest.java +++ b/yop-java-sdk-base/src/test/java/com/yeepay/yop/sdk/utils/X509CertUtilsTest.java @@ -4,6 +4,7 @@ */ package com.yeepay.yop.sdk.utils; +import com.yeepay.yop.sdk.security.CertTypeEnum; import org.junit.Assert; import org.junit.Test; @@ -46,4 +47,5 @@ public void parseToDecimal() { Assert.assertEquals(X509CertUtils.parseToDecimal("4378650635"), "289782695477"); Assert.assertEquals(X509CertUtils.parseToDecimal("4379555845"), "289798445125"); } + } \ No newline at end of file diff --git a/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopCircuitBreakerConfig.java b/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopCircuitBreakerConfig.java index b85cffdf..71c0ca14 100644 --- a/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopCircuitBreakerConfig.java +++ b/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopCircuitBreakerConfig.java @@ -16,7 +16,6 @@ import java.util.Set; import static com.yeepay.yop.sdk.config.provider.file.YopCircuitBreakerRuleConfig.DEFAULT_ERROR_COUNT_CONFIG; -import static com.yeepay.yop.sdk.config.provider.file.YopCircuitBreakerRuleConfig.DEFAULT_ERROR_RATIO_CONFIG; /** * title: 熔断配置
@@ -44,7 +43,7 @@ public class YopCircuitBreakerConfig implements CircuitBreakerConfig rules = Lists.newArrayList(DEFAULT_ERROR_COUNT_CONFIG, DEFAULT_ERROR_RATIO_CONFIG); + private List rules = Lists.newArrayList(DEFAULT_ERROR_COUNT_CONFIG); // region yop扩展 /** diff --git a/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopCircuitBreakerRuleConfig.java b/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopCircuitBreakerRuleConfig.java index bc04701d..1220eb46 100644 --- a/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopCircuitBreakerRuleConfig.java +++ b/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopCircuitBreakerRuleConfig.java @@ -26,18 +26,12 @@ public class YopCircuitBreakerRuleConfig implements Serializable { public static final YopCircuitBreakerRuleConfig DEFAULT_CONFIG = new YopCircuitBreakerRuleConfig(); public static final YopCircuitBreakerRuleConfig DEFAULT_ERROR_COUNT_CONFIG; - public static final YopCircuitBreakerRuleConfig DEFAULT_ERROR_RATIO_CONFIG; static { DEFAULT_ERROR_COUNT_CONFIG = new YopCircuitBreakerRuleConfig(5 * 60 * 1000); DEFAULT_ERROR_COUNT_CONFIG.setGrade(2); - DEFAULT_ERROR_COUNT_CONFIG.setCount(4.0); - DEFAULT_ERROR_COUNT_CONFIG.setTimeWindow(5 * 60); - DEFAULT_ERROR_RATIO_CONFIG = new YopCircuitBreakerRuleConfig(5 * 1000); - DEFAULT_ERROR_RATIO_CONFIG.setGrade(1); - DEFAULT_ERROR_RATIO_CONFIG.setCount(0.2); - DEFAULT_ERROR_RATIO_CONFIG.setTimeWindow(5 * 60); - DEFAULT_ERROR_RATIO_CONFIG.setMinRequestAmount(5); + DEFAULT_ERROR_COUNT_CONFIG.setCount(1.0); + DEFAULT_ERROR_COUNT_CONFIG.setTimeWindow(10 * 60); } public YopCircuitBreakerRuleConfig() { @@ -66,7 +60,7 @@ public YopCircuitBreakerRuleConfig(int statIntervalMs) { *
    */ @JsonProperty("count") - private double count = 2.0; + private double count = 1.0; /** * 熔断间歇时长(秒,该窗口期后,会进入半开) @@ -74,7 +68,7 @@ public YopCircuitBreakerRuleConfig(int statIntervalMs) { * transform to half-open state for trying a few requests. */ @JsonProperty("time_window") - private int timeWindow = 5; + private int timeWindow = 10 * 60; /** * 请求量阈值(达到该数量,才会检查健康数据,进而熔断) @@ -101,7 +95,7 @@ public YopCircuitBreakerRuleConfig(int statIntervalMs) { * @since 1.8.0 */ @JsonProperty("stat_interval_ms") - private int statIntervalMs = 1000; + private int statIntervalMs = 5 * 60 * 1000; // endregion diff --git a/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopHttpClientConfig.java b/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopHttpClientConfig.java index ddca6128..6e6fac0a 100644 --- a/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopHttpClientConfig.java +++ b/yop-java-sdk-crypto-api/src/main/java/com/yeepay/yop/sdk/config/provider/file/YopHttpClientConfig.java @@ -5,7 +5,6 @@ import org.apache.commons.lang3.builder.ToStringStyle; import java.io.Serializable; -import java.util.Set; /** * title:
    @@ -25,13 +24,13 @@ public final class YopHttpClientConfig implements Serializable { * 建立连接的超时 */ @JsonProperty("connect_timeout") - private int connectTimeout = 10000; + private int connectTimeout = 3000; /** * 从连接池获取到连接的超时时间 */ @JsonProperty("connect_request_timeout") - private int connectRequestTimeout = 5000; + private int connectRequestTimeout = 3000; /** * 获取数据的超时时间 @@ -48,15 +47,6 @@ public final class YopHttpClientConfig implements Serializable { @JsonProperty("client_impl") private String clientImpl; - @JsonProperty("retry_exceptions") - private Set retryExceptions; - - @JsonProperty("max_retry_count") - private int maxRetryCount = 3; - - @JsonProperty("circuit_breaker") - private YopCircuitBreakerConfig circuitBreakerConfig; - public int getConnectTimeout() { return connectTimeout; } @@ -105,30 +95,6 @@ public void setClientImpl(String clientImpl) { this.clientImpl = clientImpl; } - public Set getRetryExceptions() { - return retryExceptions; - } - - public void setRetryExceptions(Set retryExceptions) { - this.retryExceptions = retryExceptions; - } - - public int getMaxRetryCount() { - return maxRetryCount; - } - - public void setMaxRetryCount(int maxRetryCount) { - this.maxRetryCount = maxRetryCount; - } - - public YopCircuitBreakerConfig getCircuitBreakerConfig() { - return circuitBreakerConfig; - } - - public void setCircuitBreakerConfig(YopCircuitBreakerConfig circuitBreakerConfig) { - this.circuitBreakerConfig = circuitBreakerConfig; - } - @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); diff --git a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/RandomRouterPolicy.java b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/RandomRouterPolicy.java new file mode 100644 index 00000000..c0367155 --- /dev/null +++ b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/RandomRouterPolicy.java @@ -0,0 +1,22 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke; + +import java.util.List; + +/** + * title: 随机
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/7 + */ +public interface RandomRouterPolicy { + + List shuffle(List origin); +} diff --git a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/ResourceRouteInvoker.java b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/ResourceRouteInvoker.java new file mode 100644 index 00000000..8965dbdd --- /dev/null +++ b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/ResourceRouteInvoker.java @@ -0,0 +1,32 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke; + +import com.yeepay.yop.sdk.invoke.model.AnalyzedException; +import com.yeepay.yop.sdk.invoke.model.ExceptionAnalyzer; +import com.yeepay.yop.sdk.invoke.model.Resource; + +/** + * title: 资源路由调用器
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/8 + */ +public interface ResourceRouteInvoker + extends Invoker { + + Resource getResource(); + + void setResource(Resource resource); + + ExceptionAnalyzer getExceptionAnalyzer(); + + void setExceptionAnalyzer(ExceptionAnalyzer analyzer); + +} diff --git a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/RouterPolicy.java b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/RouterPolicy.java new file mode 100644 index 00000000..5d5e88ad --- /dev/null +++ b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/RouterPolicy.java @@ -0,0 +1,36 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke; + +import com.yeepay.yop.sdk.invoke.model.Resource; +import com.yeepay.yop.sdk.invoke.model.RouterParams; + +/** + * title: 路由策略
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/3/29 + */ +public interface RouterPolicy { + + /** + * 策略名称 + * + * @return String + */ + String name(); + + /** + * 策略逻辑 + * + * @param params 路由参数 + * @return String 资源 + */ + Resource select(RouterParams params); +} diff --git a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/BlockResource.java b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/BlockResource.java new file mode 100644 index 00000000..f305f979 --- /dev/null +++ b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/BlockResource.java @@ -0,0 +1,38 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke.model; + +/** + * title: 熔断资源
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/1 + */ +public class BlockResource extends SimpleResource { + + private static final long serialVersionUID = -1L; + + /** + * 熔断资源序列号 + */ + private long blockSequence; + + public BlockResource(String resourceKey, long blockSequence) { + super(resourceKey); + this.blockSequence = blockSequence; + } + + public long getBlockSequence() { + return blockSequence; + } + + public String getBlockResourceKey() { + return getResourceKey() + "," + getBlockSequence(); + } +} diff --git a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/Resource.java b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/Resource.java new file mode 100644 index 00000000..ca6e9709 --- /dev/null +++ b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/Resource.java @@ -0,0 +1,25 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke.model; + +/** + * title:
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/1 + */ +public interface Resource { + + /** + * 资源标识 + * + * @return String + */ + String getResourceKey(); +} diff --git a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/RouterParams.java b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/RouterParams.java new file mode 100644 index 00000000..f7883f89 --- /dev/null +++ b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/RouterParams.java @@ -0,0 +1,41 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke.model; + +import java.util.List; + +/** + * title:
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/1 + */ +public interface RouterParams { + + /** + * 资源分组 + * + * @return String + */ + String getResourceGroup(); + + /** + * 可用资源列表 + * + * @return List + */ + List getAvailableResources(); + + /** + * 已调用资源列表(当笔重试),按调用时间正序 + * + * @return List + */ + List getInvokedResources(); +} diff --git a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/SimpleResource.java b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/SimpleResource.java new file mode 100644 index 00000000..67396df3 --- /dev/null +++ b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/SimpleResource.java @@ -0,0 +1,40 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke.model; + +import java.io.Serializable; + +/** + * title:
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/1 + */ +public class SimpleResource implements Resource, Serializable { + + private static final long serialVersionUID = -1L; + + public SimpleResource(String resourceKey) { + this.resourceKey = resourceKey; + } + + /** + * 资源标识 + */ + private String resourceKey; + + @Override + public String getResourceKey() { + return this.resourceKey; + } + + public void setResourceKey(String resourceKey) { + this.resourceKey = resourceKey; + } +} diff --git a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/SimpleRouterParams.java b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/SimpleRouterParams.java new file mode 100644 index 00000000..0e53c32b --- /dev/null +++ b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/SimpleRouterParams.java @@ -0,0 +1,71 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke.model; + +import java.util.List; + +/** + * title: 简单路由参数
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/3/29 + */ +public class SimpleRouterParams implements RouterParams { + + /** + * 资源分组 + */ + private String resourceGroup; + + /** + * 可用资源列表 + */ + private List availableResources; + + /** + * 已调用资源列表 + */ + private List invokedResources; + + public SimpleRouterParams() { + } + + public SimpleRouterParams(String resourceGroup, List availableResources, List invokedResources) { + this.resourceGroup = resourceGroup; + this.availableResources = availableResources; + this.invokedResources = invokedResources; + } + + @Override + public String getResourceGroup() { + return resourceGroup; + } + + public void setResourceGroup(String resourceGroup) { + this.resourceGroup = resourceGroup; + } + + @Override + public List getAvailableResources() { + return availableResources; + } + + public void setAvailableResources(List availableResources) { + this.availableResources = availableResources; + } + + @Override + public List getInvokedResources() { + return invokedResources; + } + + public void setInvokedResources(List invokedResources) { + this.invokedResources = invokedResources; + } +} diff --git a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/UriResource.java b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/UriResource.java index ae2248d3..c43609a0 100644 --- a/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/UriResource.java +++ b/yop-java-sdk-invoke-api/src/main/java/com/yeepay/yop/sdk/invoke/model/UriResource.java @@ -10,6 +10,7 @@ import java.net.URI; import java.util.Objects; +import static com.yeepay.yop.sdk.invoke.model.UriResource.ResourceType.BLOCKED; import static com.yeepay.yop.sdk.invoke.model.UriResource.ResourceType.COMMON; /** @@ -28,8 +29,15 @@ public class UriResource implements Serializable { public static final String RESOURCE_SEPERATOR = "####"; + public static final String RESOURCE_GROUP_SEPERATOR = "::"; + public static final String RETAIN_RESOURCE_ID = "0000"; + /** + * 资源分组 + */ + private String resourceGroup = CharacterConstants.EMPTY; + /** * URI路径 */ @@ -52,10 +60,16 @@ public UriResource(URI uri) { this.resourceType = COMMON; } - public UriResource(ResourceType resourceType, String resourcePrefix, URI uri) { + public UriResource(String resourceGroup, URI uri) { this.resource = uri; + this.resourceType = COMMON; + setResourceGroup(resourceGroup); + } + + public UriResource(ResourceType resourceType, String resourcePrefix, URI uri) { this.resourceType = resourceType; this.resourcePrefix = resourcePrefix; + this.resource = uri; } public UriResource(ResourceType resourceType, String resourcePrefix, URI resource, Callback callback) { @@ -65,6 +79,13 @@ public UriResource(ResourceType resourceType, String resourcePrefix, URI resourc this.callback = callback; } + public UriResource(ResourceType resourceType, String resourceGroup, String resourcePrefix, URI resource) { + this.resourceType = resourceType; + setResourceGroup(resourceGroup); + this.resourcePrefix = resourcePrefix; + this.resource = resource; + } + public URI getResource() { return resource; } @@ -73,6 +94,16 @@ public void setResource(URI resource) { this.resource = resource; } + public String getResourceGroup() { + return resourceGroup; + } + + public void setResourceGroup(String resourceGroup) { + if (null != resourceGroup) { + this.resourceGroup = resourceGroup; + } + } + public ResourceType getResourceType() { return resourceType; } @@ -93,6 +124,10 @@ public boolean isRetained() { return RETAIN_RESOURCE_ID.equals(this.resourcePrefix); } + public boolean isBlocked() { + return BLOCKED.equals(this.resourceType); + } + public Callback getCallback() { return callback; } @@ -111,12 +146,13 @@ public enum ResourceType { @Override public String toString() { - return this.resourceType + RESOURCE_SEPERATOR + this.resourcePrefix + RESOURCE_SEPERATOR + this.resource.toString(); + return this.resourceType + RESOURCE_SEPERATOR + this.resourceGroup + RESOURCE_SEPERATOR + + this.resourcePrefix + RESOURCE_SEPERATOR + this.resource.toString(); } @Override public int hashCode() { - return Objects.hash(this.resourceType, this.resourcePrefix, this.resource); + return Objects.hash(this.resourceType, this.resourceGroup, this.resourcePrefix, this.resource); } @Override @@ -124,6 +160,7 @@ public boolean equals(Object obj) { if (obj instanceof UriResource) { UriResource that = (UriResource) obj; return this.resourceType.equals(that.getResourceType()) + && this.resourceGroup.equals(that.resourceGroup) && this.resourcePrefix.equals(that.resourcePrefix) && this.resource.equals(that.resource); } @@ -131,19 +168,36 @@ public boolean equals(Object obj) { } public String computeResourceKey() { - if (COMMON.equals(this.resourceType)) { - return this.resource.toString(); - } - return this.toString(); + return this.resourceType + RESOURCE_SEPERATOR + this.resourceGroup + RESOURCE_SEPERATOR + + this.resourcePrefix + RESOURCE_SEPERATOR + this.resource.toString(); } public static UriResource parseResourceKey(String resourceKey) { final String[] resourceSeperated = resourceKey.split(RESOURCE_SEPERATOR); - if (resourceSeperated.length == 1) { - return new UriResource(URI.create(resourceKey)); - } return new UriResource(ResourceType.valueOf(resourceSeperated[0]), - resourceSeperated[1], URI.create(resourceSeperated[2])); + resourceSeperated[1], resourceSeperated[2], URI.create(resourceSeperated[3])); + } + + public String[] parseResourceGroup() { + return this.resourceGroup.split(RESOURCE_GROUP_SEPERATOR); + } + + public static String computeResourceGroup(Object...args) { + if (null == args) { + return CharacterConstants.EMPTY; + } + StringBuilder resourceGroup = new StringBuilder(); + for (int i = 0; i < args.length; i++) { + if (null == args[i]) { + resourceGroup.append(CharacterConstants.EMPTY); + } else { + resourceGroup.append(args[i]); + } + if (i != args.length - 1) { + resourceGroup.append(RESOURCE_GROUP_SEPERATOR); + } + } + return resourceGroup.toString(); } public interface Callback { diff --git a/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/AbstractResourceRouteInvoker.java b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/AbstractResourceRouteInvoker.java new file mode 100644 index 00000000..435fd520 --- /dev/null +++ b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/AbstractResourceRouteInvoker.java @@ -0,0 +1,90 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke; + +import com.yeepay.yop.sdk.invoke.model.AnalyzedException; +import com.yeepay.yop.sdk.invoke.model.ExceptionAnalyzer; +import com.yeepay.yop.sdk.invoke.model.Resource; + +import java.util.LinkedList; +import java.util.List; + +/** + * title:
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/8 + */ +public abstract class AbstractResourceRouteInvoker + implements ResourceRouteInvoker { + + private Resource resource; + private Input input; + private Context context; + private List exceptions = new LinkedList<>(); + private Exception lastException; + private ExceptionAnalyzer exceptionAnalyzer; + + @Override + public Resource getResource() { + return resource; + } + + @Override + public void setResource(Resource resource) { + this.resource = resource; + } + + @Override + public Input getInput() { + return input; + } + + @Override + public void setInput(Input input) { + this.input = input; + } + + @Override + public Context getContext() { + return context; + } + + @Override + public void setContext(Context context) { + this.context = context; + } + + @Override + public void addException(Exception exception) { + this.exceptions.add(exception); + this.lastException = exception; + } + + @Override + public List getExceptions() { + return exceptions; + } + + @Override + public Exception getLastException() { + return lastException; + } + + @Override + public ExceptionAnalyzer getExceptionAnalyzer() { + return exceptionAnalyzer; + } + + @Override + public void setExceptionAnalyzer(ExceptionAnalyzer exceptionAnalyzer) { + this.exceptionAnalyzer = exceptionAnalyzer; + } + +} diff --git a/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/ResourceRouteInvokerWrapper.java b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/ResourceRouteInvokerWrapper.java new file mode 100644 index 00000000..85e43a72 --- /dev/null +++ b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/ResourceRouteInvokerWrapper.java @@ -0,0 +1,148 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.invoke; + +import com.google.common.collect.Lists; +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.exception.YopUnknownException; +import com.yeepay.yop.sdk.invoke.model.AnalyzedException; +import com.yeepay.yop.sdk.invoke.model.Resource; +import com.yeepay.yop.sdk.invoke.model.RetryContext; +import com.yeepay.yop.sdk.invoke.model.RetryPolicy; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * title: 资源路由调用器封装
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/8 + */ +public class ResourceRouteInvokerWrapper + implements Invoker { + + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceRouteInvokerWrapper.class); + + private ResourceRouteInvoker invoker; + + private Policy retryPolicy; + + private Router router; + + public ResourceRouteInvokerWrapper(ResourceRouteInvoker invoker, + Policy retryPolicy, + Router router) { + this.invoker = invoker; + this.retryPolicy = retryPolicy; + this.router = router; + } + + @Override + public Output invoke() { + final long start = System.currentTimeMillis(); + List invokedResources = Lists.newArrayList(); + Resource lastInvokedResource = null; + Throwable currentEx; + boolean needRetry; + do { + try { + lastInvokedResource = router.route(getInput(), getContext(), invokedResources); + invoker.setResource(lastInvokedResource); + final Output result = invoker.invoke(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Success ServerRoot, {}, elapsed:{}, retryCount:{}", lastInvokedResource, + System.currentTimeMillis() - start, getContext().retryCount()); + } + return result; + } catch (Throwable throwable) { + currentEx = throwable; + // 路由异常,客户端配置问题 + if (null == lastInvokedResource || null == lastInvokedResource.getResourceKey()) { + throw new YopClientException("Config Error, No RouteResource Found", throwable); + } + + // 客户端异常、业务异常,直接抛给上层 + if (throwable instanceof YopClientException) { + throw (YopClientException) throwable; + } + + // 其他已分析异常 + final Exception analyzedException = getLastException(); + if (null == analyzedException) { + throw handleUnExpectedError(currentEx); + } + + // 重试准备 + needRetry = analyzedException.isBlocked() || + (analyzedException.isNeedRetry() && null != retryPolicy && retryPolicy.allowRetry(this)); + if (needRetry) { + invokedResources.add(lastInvokedResource.getResourceKey()); + if (!analyzedException.isBlocked()) { + getContext().markRetried(1); + } + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Fail ServerRoot, {}, exDetail:{}, elapsed:{}, needRetry:{}", lastInvokedResource, + ExceptionUtils.getMessage(currentEx), System.currentTimeMillis() - start, needRetry); + } + } + } while (needRetry); + + // 非预期异常处理 + throw handleUnExpectedError(currentEx); + + } + + private RuntimeException handleUnExpectedError(Throwable ex) { + if (ex instanceof YopUnknownException) { + return (YopUnknownException) ex; + } + return new YopUnknownException("UnExpected Error, ", ex); + } + + @Override + public Input getInput() { + return invoker.getInput(); + } + + @Override + public void setInput(Input input) { + invoker.setInput(input); + } + + @Override + public void setContext(Context context) { + invoker.setContext(context); + } + + @Override + public Context getContext() { + return invoker.getContext(); + } + + @Override + public List getExceptions() { + return invoker.getExceptions(); + } + + @Override + public void addException(Exception exception) { + invoker.addException(exception); + } + + @Override + public Exception getLastException() { + return invoker.getLastException(); + } +} diff --git a/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleExceptionAnalyzer.java b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleExceptionAnalyzer.java index 7dd90f63..9a8f34d5 100644 --- a/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleExceptionAnalyzer.java +++ b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleExceptionAnalyzer.java @@ -5,6 +5,7 @@ package com.yeepay.yop.sdk.invoke; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.yeepay.yop.sdk.exception.YopBlockException; import com.yeepay.yop.sdk.exception.YopClientException; import com.yeepay.yop.sdk.invoke.model.AnalyzedException; @@ -15,6 +16,7 @@ import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import static com.yeepay.yop.sdk.constants.CharacterConstants.COLON; @@ -35,6 +37,15 @@ public class SimpleExceptionAnalyzer implements ExceptionAnalyzer retryExceptions; + private static final Map CACHED_ANALYZERS = Maps.newHashMap(); + + public static SimpleExceptionAnalyzer from(Set excludeExceptions, Set retryExceptions) { + Set excludes = null != excludeExceptions ? excludeExceptions : Collections.emptySet(); + Set retries = null != retryExceptions ? retryExceptions : Collections.emptySet(); + return CACHED_ANALYZERS.computeIfAbsent(StringUtils.join(excludes, "##") + "," + StringUtils.join(retries, "$$"), + p -> new SimpleExceptionAnalyzer(excludeExceptions, retryExceptions)); + } + public SimpleExceptionAnalyzer(Set excludeExceptions, Set retryExceptions) { this.excludeExceptions = null != excludeExceptions ? excludeExceptions : Collections.emptySet(); this.retryExceptions = null != retryExceptions ? retryExceptions : Collections.emptySet(); @@ -48,13 +59,13 @@ public AnalyzedException analyze(Throwable e, Object... args) { // 客户端异常&业务异常,不重试,不计入熔断笔数 if (e instanceof YopClientException) { result.setExDetail(e.getClass().getCanonicalName() + COLON + - StringUtils.defaultString(e.getMessage())); + StringUtils.defaultString(e.getMessage()).trim()); return result; } // 熔断异常,直接重试 if (e instanceof YopBlockException) { - result.setExDetail(e.getClass().getCanonicalName() + COLON + StringUtils.defaultString(e.getMessage())); + result.setExDetail(e.getClass().getCanonicalName() + COLON + StringUtils.defaultString(e.getMessage()).trim()); result.setNeedRetry(true); result.setBlocked(true); return result; @@ -66,7 +77,7 @@ public AnalyzedException analyze(Throwable e, Object... args) { for (int i = 0; i < allExceptions.length; i++) { Throwable rootCause = allExceptions[i]; final String exType = rootCause.getClass().getCanonicalName(), - exTypeAndMsg = exType + COLON + StringUtils.defaultString(rootCause.getMessage()); + exTypeAndMsg = exType + COLON + StringUtils.defaultString(rootCause.getMessage()).trim(); exceptionDetails.add(exType); exceptionDetails.add(exTypeAndMsg); if (retryExceptions.contains(exType) || @@ -81,7 +92,7 @@ public AnalyzedException analyze(Throwable e, Object... args) { // 默认异常消息,取最后一个caused by Throwable lastCause = allExceptions[allExceptions.length -1]; result.setExDetail(lastCause.getClass().getCanonicalName() + COLON + - StringUtils.defaultString(lastCause.getMessage())); + StringUtils.defaultString(lastCause.getMessage()).trim()); // 预期异常,不重试,不计入熔断笔数 if (CollectionUtils.containsAny(excludeExceptions, exceptionDetails)) { diff --git a/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleUriRetryPolicy.java b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleRetryPolicy.java similarity index 81% rename from yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleUriRetryPolicy.java rename to yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleRetryPolicy.java index 43c9ff47..61e3206c 100644 --- a/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleUriRetryPolicy.java +++ b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/SimpleRetryPolicy.java @@ -17,14 +17,14 @@ * @version 1.0.0 * @since 2023/11/7 */ -public class SimpleUriRetryPolicy implements RetryPolicy { +public class SimpleRetryPolicy implements RetryPolicy { - private static final SimpleUriRetryPolicy INSTANCE = new SimpleUriRetryPolicy(); + private static final SimpleRetryPolicy INSTANCE = new SimpleRetryPolicy(); - public SimpleUriRetryPolicy() { + public SimpleRetryPolicy() { } - public SimpleUriRetryPolicy(int maxRetryCount) { + public SimpleRetryPolicy(int maxRetryCount) { if (maxRetryCount > 0) { this.maxRetryCount = maxRetryCount; } diff --git a/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/UriResourceRouteInvokerWrapper.java b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/UriResourceRouteInvokerWrapper.java index 971154b2..4555513a 100644 --- a/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/UriResourceRouteInvokerWrapper.java +++ b/yop-java-sdk-invoke-base/src/main/java/com/yeepay/yop/sdk/invoke/UriResourceRouteInvokerWrapper.java @@ -15,7 +15,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.net.URI; import java.util.List; /** @@ -51,13 +50,13 @@ public UriResourceRouteInvokerWrapper(UriResourceRouteInvoker excludeServerRoots = Lists.newArrayList(); + List invokedServerRoots = Lists.newArrayList(); UriResource lastServerRoot = null; Throwable currentEx; boolean needRetry; do { try { - lastServerRoot = uriRouter.route(getInput(), getContext(), excludeServerRoots); + lastServerRoot = uriRouter.route(getInput(), getContext(), invokedServerRoots); invoker.setUriResource(lastServerRoot); final Output result = invoker.invoke(); if (LOGGER.isDebugEnabled()) { @@ -84,11 +83,12 @@ public Output invoke() { } // 重试准备 - needRetry = analyzedException.isNeedRetry() - && null != retryPolicy - && retryPolicy.allowRetry(this); + needRetry = analyzedException.isBlocked() || + (analyzedException.isNeedRetry() && null != retryPolicy && retryPolicy.allowRetry(this)); if (needRetry) { - excludeServerRoots.add(lastServerRoot.getResource()); + invokedServerRoots.add(lastServerRoot.isBlocked() ? + new UriResource(lastServerRoot.getResourceGroup(), lastServerRoot.getResource()).computeResourceKey() + : lastServerRoot.computeResourceKey()); if (!analyzedException.isBlocked()) { getContext().markRetried(1); } diff --git a/yop-java-sdk-router/pom.xml b/yop-java-sdk-router/pom.xml new file mode 100644 index 00000000..e447cc34 --- /dev/null +++ b/yop-java-sdk-router/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + + com.yeepay.yop.sdk + yop-java-sdk-parent + 4.4.11-SNAPSHOT + + + yop-java-sdk-router + + + + com.yeepay.yop.sdk + yop-java-sdk-crypto-api + + + com.yeepay.yop.sdk + yop-java-sdk-invoke-base + + + com.yeepay.yop.sdk + yop-java-sdk-utils + + + com.fasterxml.jackson.core + jackson-databind + + + org.slf4j + slf4j-simple + test + + + + + + \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/ResourceInvocation.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/ResourceInvocation.java new file mode 100644 index 00000000..2073135c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/ResourceInvocation.java @@ -0,0 +1,29 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +import com.yeepay.yop.sdk.invoke.model.Resource; + +/** + * title: 资源调用逻辑
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/8 + */ +public interface ResourceInvocation { + + /** + * 资源访问逻辑 + * + * @param resource 目标资源 + * @param context 上下文信息 + * @return 业务结果 + */ + Output doInvoke(Resource resource, SimpleContext context); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/ResourceRouteClient.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/ResourceRouteClient.java new file mode 100644 index 00000000..c0c18502 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/ResourceRouteClient.java @@ -0,0 +1,239 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.exception.YopHostBlockException; +import com.yeepay.yop.sdk.exception.YopHttpException; +import com.yeepay.yop.sdk.exception.YopUnknownException; +import com.yeepay.yop.sdk.invoke.*; +import com.yeepay.yop.sdk.invoke.model.*; +import com.yeepay.yop.sdk.router.config.YopRouteConfig; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProvider; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProviderRegistry; +import com.yeepay.yop.sdk.router.policy.RouterPolicyFactory; +import com.yeepay.yop.sdk.router.sentinel.YopDegradeRuleHelper; +import com.yeepay.yop.sdk.router.sentinel.YopSph; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Tracer; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import org.apache.commons.collections4.CollectionUtils; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * title: 资源路由客户端
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/8 + */ +public class ResourceRouteClient { + + private RetryPolicy retryPolicy; + private YopRouteConfigProvider routeConfigProvider; + private Router router; + + public ResourceRouteClient(List targetResources) { + this(targetResources, RouterPolicyFactory.get(YopRouterConstants.ROUTER_POLICY_DEFAULT)); + } + + public ResourceRouteClient(List targetResources, RouterPolicy routerPolicy) { + this(targetResources, routerPolicy, SimpleRetryPolicy.singleton()); + } + + public ResourceRouteClient(List targetResources, RouterPolicy routerPolicy, RetryPolicy retryPolicy) { + this(YopRouteConfigProviderRegistry.getProvider(), + new ResourceRouter<>(targetResources, routerPolicy), retryPolicy); + } + + public ResourceRouteClient(YopRouteConfigProvider routeConfigProvider, + Router router, RetryPolicy retryPolicy) { + this.routeConfigProvider = routeConfigProvider; + this.router = router; + this.retryPolicy = retryPolicy; + } + + /** + * 发起路由调用 + * + * @param invocation 业务逻辑 + * @param 出参范型 + * @return 业务出参 + */ + public Output route(ResourceInvocation invocation) { + return route(invocation, new SimpleContext()); + } + + /** + * 发起路由调用 + * + * @param invocation 业务逻辑 + * @param 出参范型 + * @return 业务出参 + */ + public Output route(ResourceInvocation invocation, SimpleContext context) { + // 业务处理、熔断操作、异常分析封装 + ResourceRouteInvoker resourceRouteInvoker + = new ResourceInvoker<>(invocation, context, this.routeConfigProvider); + // 路由切换、重试策略封装 + return new ResourceRouteInvokerWrapper<>(resourceRouteInvoker, retryPolicy, router).invoke(); + } + + public static class ResourceInvoker + extends AbstractResourceRouteInvoker { + + private final ResourceInvocation invocation; + + private final YopRouteConfigProvider routeConfigProvider; + + public ResourceInvoker(ResourceInvocation invocation, + SimpleContext context, + YopRouteConfigProvider routeConfigProvider) { + this.invocation = invocation; + this.routeConfigProvider = routeConfigProvider; + setContext(context); + } + + @Override + public Output invoke() { + final Resource resource = getResource(); + Entry entry = null; + boolean successInvoked = false; + try { + final YopRouteConfig routeConfig = findRouteConfig(resource.getResourceKey()); + final String sentinelResourceKey = resource instanceof BlockResource + ? ((BlockResource) resource).getBlockResourceKey() : resource.getResourceKey(); + YopDegradeRuleHelper.addDegradeRule(sentinelResourceKey, + routeConfig.getCircuitBreakerConfig()); + entry = YopSph.getInstance().entry(sentinelResourceKey); + final Output output = doInvoke(resource); + successInvoked = true; + return output; + } catch (YopClientException | YopHttpException | YopUnknownException ex) { + throw ex; + } catch (Throwable ex) { + if (BlockException.isBlockException(ex)) { + final YopHostBlockException hostBlockException = new YopHostBlockException("ServerRoot Blocked, ex:", ex); + addException(getExceptionAnalyzer().analyze(hostBlockException)); + throw hostBlockException; + } + throw new YopUnknownException("UnExpected Error, ", ex); + } finally { + if (null != entry) { + final AnalyzedException lastException = getLastException(); + if (!successInvoked && null != lastException && lastException.isNeedDegrade()) { + Tracer.trace(lastException.getException()); + } + entry.exit(); + } + } + } + + @Override + public ExceptionAnalyzer getExceptionAnalyzer() { + Set excludeExceptions = Collections.emptySet(); + Set retryExceptions = Collections.emptySet(); + final Resource resource = getResource(); + final YopRouteConfig routeConfig = findRouteConfig(resource.getResourceKey()); + if (null != routeConfig) { + if (null != routeConfig.getCircuitBreakerConfig() + && null != routeConfig.getCircuitBreakerConfig().getExcludeExceptions()) { + excludeExceptions = routeConfig.getCircuitBreakerConfig().getExcludeExceptions(); + } + if (null != routeConfig.getRetryExceptions()) { + retryExceptions = routeConfig.getRetryExceptions(); + } + } + return SimpleCustomExceptionAnalyzer.from(excludeExceptions, retryExceptions); + } + + private YopRouteConfig findRouteConfig(String resourceKey) { + // 指定配置 + YopRouteConfig routeConfig = routeConfigProvider.getRouteConfig(resourceKey); + // 默认配置 + if (null == routeConfig) { + routeConfig = routeConfigProvider.getRouteConfig(); + } + // 兜底配置 + return null == routeConfig ? YopRouteConfig.DEFAULT_CONFIG : routeConfig; + } + + private Output doInvoke(Resource resource){ + Throwable throwable = null; + try { + beforeInvoke(); + final Output result = invocation.doInvoke(resource, getContext()); + afterInvoke(); + return result; + } catch (YopClientException clientError) {//客户端异常&业务异常 + throwable = clientError; + throw clientError; + } catch (YopHttpException httpException) {//HTTP调用异常 + throwable = httpException; + throw httpException; + } catch (Throwable ex) {// 非预期异常 + throwable = ex; + throw new YopUnknownException("UnExpected Error, ", ex); + } finally { + if (null != throwable) { + addException(getExceptionAnalyzer().analyze(throwable)); + } + } + } + + protected void beforeInvoke() throws IOException { + + } + + protected void afterInvoke() throws IOException { + + } + } + + public static class ResourceRouter implements Router { + + private final String resourceGroup; + private final List availableResources; + private final RouterPolicy routerPolicy; + + public ResourceRouter(List availableResources, RouterPolicy routerPolicy) { + this(UUID.randomUUID().toString(), availableResources, routerPolicy); + } + + public ResourceRouter(String resourceGroup, List availableResources, RouterPolicy routerPolicy) { + this.resourceGroup = resourceGroup; + if (CollectionUtils.isEmpty(availableResources)) { + throw new YopClientException("availableResources is empty"); + } + List targetResources; + if (routerPolicy instanceof RandomRouterPolicy) { + targetResources = ((RandomRouterPolicy) routerPolicy).shuffle(availableResources); + } else { + targetResources = availableResources; + } + this.availableResources = targetResources; + this.routerPolicy = routerPolicy; + } + + @Override + public Resource route(Object o, Context context, Object... args) { + List invokedResources = null == args[0] ? Collections.emptyList() + : ((List) args[0]).stream().map(Object::toString).collect(Collectors.toList()); + + return this.routerPolicy.select(new SimpleRouterParams(this.resourceGroup, + this.availableResources, invokedResources)); + } + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleContext.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleContext.java new file mode 100644 index 00000000..c0c8ae56 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleContext.java @@ -0,0 +1,58 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +import com.yeepay.yop.sdk.invoke.model.RetryContext; + +import java.util.Map; + +/** + * title: 上下文信息
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/2 + */ +public class SimpleContext implements RetryContext { + + /** + * 上下文参数 + */ + private Map context; + + /** + * 重试次数 + */ + private int retryCount = 0; + + public int getRetryCount() { + return retryCount; + } + + public void addRetryCount(int i) { + this.retryCount += i; + } + + @Override + public void markRetried(Object... args) { + addRetryCount((int) args[0]); + } + + @Override + public int retryCount() { + return this.getRetryCount(); + } + + public Map getContext() { + return context; + } + + public void setContext(Map context) { + this.context = context; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleCustomExceptionAnalyzer.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleCustomExceptionAnalyzer.java new file mode 100644 index 00000000..aa6e894c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleCustomExceptionAnalyzer.java @@ -0,0 +1,127 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.yeepay.yop.sdk.exception.YopBlockException; +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.invoke.model.AnalyzedException; +import com.yeepay.yop.sdk.invoke.model.ExceptionAnalyzer; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import static com.yeepay.yop.sdk.constants.CharacterConstants.COLON; + +/** + * title: 简单异常分析器(支持父类异常配置)
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/18 + */ +public class SimpleCustomExceptionAnalyzer implements ExceptionAnalyzer { + + private static final Map CACHED_ANALYZERS = Maps.newHashMap(); + + private final Set excludeExceptions; + + private final Set retryExceptions; + + public SimpleCustomExceptionAnalyzer(Set excludeExceptions, Set retryExceptions) { + this.excludeExceptions = null != excludeExceptions ? excludeExceptions : Collections.emptySet(); + this.retryExceptions = null != retryExceptions ? retryExceptions : Collections.emptySet(); + } + + public static SimpleCustomExceptionAnalyzer from(Set excludeExceptions, Set retryExceptions) { + Set excludes = null != excludeExceptions ? excludeExceptions : Collections.emptySet(); + Set retries = null != retryExceptions ? retryExceptions : Collections.emptySet(); + return CACHED_ANALYZERS.computeIfAbsent(StringUtils.join(excludes, "##") + "," + StringUtils.join(retries, "$$"), + p -> new SimpleCustomExceptionAnalyzer(excludeExceptions, retryExceptions)); + } + + @Override + public AnalyzedException analyze(Throwable exception, Object... args) { + final AnalyzedException result = new AnalyzedException(); + result.setException(exception); + + // 客户端异常&业务异常,不重试,不计入熔断笔数 + if (exception instanceof YopClientException) { + result.setExDetail(exception.getClass().getCanonicalName() + COLON + + StringUtils.defaultString(exception.getMessage()).trim()); + return result; + } + + // 熔断异常,直接重试 + if (exception instanceof YopBlockException) { + result.setExDetail(exception.getClass().getCanonicalName() + COLON + StringUtils.defaultString(exception.getMessage()).trim()); + result.setNeedRetry(true); + result.setBlocked(true); + return result; + } + + // 分析堆栈,预期异常,可重试 + final Throwable[] allExceptions = ExceptionUtils.getThrowables(exception); + final Set exceptionDetails = Sets.newHashSet(); + for (int i = 0; i < allExceptions.length; i++) { + Throwable rootCause = allExceptions[i]; + // 当前异常 + final String exType = rootCause.getClass().getCanonicalName(), + exTypeAndMsg = exType + COLON + StringUtils.defaultString(rootCause.getMessage()).trim(); + exceptionDetails.add(exType); + exceptionDetails.add(exTypeAndMsg); + + if (retryExceptions.contains(exType) || + retryExceptions.contains(exTypeAndMsg)) { + result.setExDetail(exTypeAndMsg); + result.setNeedRetry(true); + result.setNeedDegrade(true); + return result; + } + + // 父类异常 + Set> superClasses = Sets.newHashSet(); + superClasses.addAll(ClassUtils.getAllSuperclasses(rootCause.getClass())); + superClasses.addAll(ClassUtils.getAllInterfaces(rootCause.getClass())); + for (Class superClass : superClasses) { + final String superExType = superClass.getCanonicalName(), + superExTypeAndMsg = exType + COLON + StringUtils.defaultString(rootCause.getMessage()).trim(); + exceptionDetails.add(superExType); + exceptionDetails.add(superExTypeAndMsg); + + if (retryExceptions.contains(superExType) || + retryExceptions.contains(superExTypeAndMsg)) { + result.setExDetail(superExTypeAndMsg); + result.setNeedRetry(true); + result.setNeedDegrade(true); + return result; + } + } + } + + // 默认异常消息,取最后一个caused by + Throwable lastCause = allExceptions[allExceptions.length -1]; + result.setExDetail(lastCause.getClass().getCanonicalName() + COLON + + StringUtils.defaultString(lastCause.getMessage()).trim()); + + // 预期异常,不重试,不计入熔断笔数 + if (CollectionUtils.containsAny(excludeExceptions, exceptionDetails)) { + return result; + } + + // 其他异常,不重试,计入熔断笔数 + result.setNeedDegrade(true); + return result; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceBusinessLogic.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceBusinessLogic.java new file mode 100644 index 00000000..7e74fa7b --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceBusinessLogic.java @@ -0,0 +1,29 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +import com.yeepay.yop.sdk.invoke.model.UriResource; + +/** + * title: 封装商户业务逻辑
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/5 + */ +public interface SimpleUriResourceBusinessLogic { + + /** + * 业务逻辑 + * + * @param targetResource 路由目标 + * @param context 上下文信息 + * @return 业务结果 + */ + Output doBusiness(UriResource targetResource, SimpleContext context); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceInvoker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceInvoker.java new file mode 100644 index 00000000..9062dbae --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceInvoker.java @@ -0,0 +1,152 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.exception.YopHostBlockException; +import com.yeepay.yop.sdk.exception.YopHttpException; +import com.yeepay.yop.sdk.exception.YopUnknownException; +import com.yeepay.yop.sdk.invoke.AbstractUriResourceRouteInvoker; +import com.yeepay.yop.sdk.invoke.model.AnalyzedException; +import com.yeepay.yop.sdk.invoke.model.ExceptionAnalyzer; +import com.yeepay.yop.sdk.invoke.model.UriResource; +import com.yeepay.yop.sdk.router.config.YopRouteConfig; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProvider; +import com.yeepay.yop.sdk.router.sentinel.YopDegradeRuleHelper; +import com.yeepay.yop.sdk.router.sentinel.YopSph; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Tracer; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.Set; + +/** + * title: 基于Function调用器
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/2 + */ +public class SimpleUriResourceInvoker + extends AbstractUriResourceRouteInvoker { + + private final SimpleUriResourceBusinessLogic businessLogic; + + private final YopRouteConfigProvider routeConfigProvider; + + public SimpleUriResourceInvoker(SimpleUriResourceBusinessLogic businessLogic, + SimpleContext context, + YopRouteConfigProvider routeConfigProvider) { + this.businessLogic = businessLogic; + this.routeConfigProvider = routeConfigProvider; + setContext(context); + } + + @Override + public Output invoke() { + final UriResource uriResource = getUriResource(); + Entry entry = null; + boolean successInvoked = false; + try { + final String resource = uriResource.computeResourceKey(); + final YopRouteConfig routeConfig = findRouteConfig(uriResource.getResource()); + YopDegradeRuleHelper.addDegradeRule(resource, routeConfig.getCircuitBreakerConfig()); + entry = YopSph.getInstance().entry(resource); + final Output output = doBusiness(uriResource); + successInvoked = true; + return output; + } catch (YopClientException | YopHttpException | YopUnknownException ex) { + throw ex; + } catch (Throwable ex) { + if (BlockException.isBlockException(ex)) { + final YopHostBlockException hostBlockException = new YopHostBlockException("ServerRoot Blocked, ex:", ex); + addException(getExceptionAnalyzer().analyze(hostBlockException)); + throw hostBlockException; + } + throw new YopUnknownException("UnExpected Error, ", ex); + } finally { + if (null != entry) { + final AnalyzedException lastException = getLastException(); + if (!successInvoked && null != lastException && lastException.isNeedDegrade()) { + Tracer.trace(lastException.getException()); + } + entry.exit(); + } + } + } + + @Override + public ExceptionAnalyzer getExceptionAnalyzer() { + Set excludeExceptions = Collections.emptySet(); + Set retryExceptions = Collections.emptySet(); + final UriResource uriResource = getUriResource(); + final YopRouteConfig routeConfig = findRouteConfig(uriResource.getResource()); + if (null != routeConfig) { + if (null != routeConfig.getCircuitBreakerConfig() + && null != routeConfig.getCircuitBreakerConfig().getExcludeExceptions()) { + excludeExceptions = routeConfig.getCircuitBreakerConfig().getExcludeExceptions(); + } + if (null != routeConfig.getRetryExceptions()) { + retryExceptions = routeConfig.getRetryExceptions(); + } + } + return SimpleCustomExceptionAnalyzer.from(excludeExceptions, retryExceptions); + } + + private YopRouteConfig findRouteConfig(URI uri) { + String configKey = StringUtils.strip(uri.getHost().replaceAll("[^a-zA-Z0-9]", "_") + + (uri.getPort() > 0 ? "_" + uri.getPort() : ""), "_"); + // 指定配置 + YopRouteConfig routeConfig = routeConfigProvider.getRouteConfig(configKey); + // 默认配置 + if (null == routeConfig) { + routeConfig = routeConfigProvider.getRouteConfig(); + } + // 兜底配置 + return null == routeConfig ? YopRouteConfig.DEFAULT_CONFIG : routeConfig; + } + + private Output doBusiness(UriResource targetServer){ + Throwable throwable = null; + try { + beforeBusiness(); + final Output result = businessLogic.doBusiness(targetServer, getContext()); + afterBusiness(); + return result; + } catch (YopClientException clientError) {//客户端异常&业务异常 + throwable = clientError; + throw clientError; + } catch (YopHttpException httpException) {//HTTP调用异常 + throwable = httpException; + throw httpException; + } catch (Throwable ex) {// 非预期异常 + throwable = ex; + throw new YopUnknownException("UnExpected Error, ", ex); + } finally { + if (null != throwable) { + addException(getExceptionAnalyzer().analyze(throwable)); + } + } + } + + protected void beforeBusiness() throws IOException { + + } + + protected void afterBusiness() throws IOException { + + } + + protected void afterFinish() throws IOException { + + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceRouteClient.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceRouteClient.java new file mode 100644 index 00000000..88735c84 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceRouteClient.java @@ -0,0 +1,149 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +import com.yeepay.yop.sdk.invoke.Router; +import com.yeepay.yop.sdk.invoke.RouterPolicy; +import com.yeepay.yop.sdk.invoke.SimpleRetryPolicy; +import com.yeepay.yop.sdk.invoke.UriResourceRouteInvoker; +import com.yeepay.yop.sdk.invoke.model.AnalyzedException; +import com.yeepay.yop.sdk.invoke.model.RetryPolicy; +import com.yeepay.yop.sdk.invoke.model.UriResource; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProvider; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProviderRegistry; +import com.yeepay.yop.sdk.router.policy.RouterPolicyFactory; +import com.yeepay.yop.sdk.router.utils.InvokeUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * title: 域名切换客户端
    + * description: 请使用单例模式
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/5 + */ +public class SimpleUriResourceRouteClient { + private static final Map CACHED_ROUTERS = new ConcurrentHashMap<>(); + + private final InnerRouteClient innerRouteClient; + + public SimpleUriResourceRouteClient(List targetServers) { + this(targetServers, RouterPolicyFactory.get(YopRouterConstants.ROUTER_POLICY_DEFAULT)); + } + + public SimpleUriResourceRouteClient(List targetServers, RouterPolicy routerPolicy) { + this(targetServers, routerPolicy, SimpleRetryPolicy.singleton()); + } + + public SimpleUriResourceRouteClient(List targetServers, RouterPolicy routerPolicy, RetryPolicy retryPolicy) { + this(targetServers, routerPolicy, retryPolicy, YopRouteConfigProviderRegistry.getProvider()); + } + + + + public SimpleUriResourceRouteClient(List targetServers, RouterPolicy routerPolicy, + RetryPolicy retryPolicy, YopRouteConfigProvider routeConfigProvider) { + this(targetServers, routerPolicy, retryPolicy, routeConfigProvider, false); + } + + /** + * 构造路由客户端 + * + * @param targetServers 目标地址 + * @param routerPolicy 路由策略 + * @param retryPolicy 重试策略 + * @param routeConfigProvider 路由配置加载器 + * @param prototype 是否新建隔离资源池资源池 + */ + public SimpleUriResourceRouteClient(List targetServers, RouterPolicy routerPolicy, + RetryPolicy retryPolicy, YopRouteConfigProvider routeConfigProvider, boolean prototype) { + if (prototype) { + this.innerRouteClient = newInnerClient(UUID.randomUUID().toString(), targetServers, routerPolicy, retryPolicy, routeConfigProvider); + return; + } + + String serverKey = StringUtils.join(targetServers, ","); + if (CACHED_ROUTERS.containsKey(serverKey)) { + this.innerRouteClient = CACHED_ROUTERS.get(serverKey); + return; + } + + synchronized (CACHED_ROUTERS) { + if (CACHED_ROUTERS.containsKey(serverKey)) { + this.innerRouteClient = CACHED_ROUTERS.get(serverKey); + } else { + this.innerRouteClient = newInnerClient(serverKey, targetServers, routerPolicy, retryPolicy, routeConfigProvider); + } + } + } + + private InnerRouteClient newInnerClient(String clientCacheKey, List targetServers, RouterPolicy routerPolicy, + RetryPolicy retryPolicy, YopRouteConfigProvider routeConfigProvider) { + + CACHED_ROUTERS.put(clientCacheKey, new InnerRouteClient(routeConfigProvider, + new SimpleUriResourceRouter<>(UUID.randomUUID().toString(), targetServers, routerPolicy), retryPolicy)); + return CACHED_ROUTERS.get(clientCacheKey); + } + + /** + * 发起路由调用 + * + * @param businessLogic 业务逻辑 + * @param 出参范型 + * @return 业务出参 + */ + public Output route(SimpleUriResourceBusinessLogic businessLogic) { + return this.innerRouteClient.route(businessLogic, new SimpleContext()); + } + + /** + * 发起路由调用 + * + * @param businessLogic 业务逻辑 + * @param 出参范型 + * @return 业务出参 + */ + public Output route(SimpleUriResourceBusinessLogic businessLogic, SimpleContext context) { + return this.innerRouteClient.route(businessLogic, context); + } + + private class InnerRouteClient { + private YopRouteConfigProvider routeConfigProvider; + private Router router; + private RetryPolicy retryPolicy; + + public InnerRouteClient(YopRouteConfigProvider routeConfigProvider, + Router router, + RetryPolicy retryPolicy) { + this.routeConfigProvider = routeConfigProvider; + this.router = router; + this.retryPolicy = retryPolicy; + } + + /** + * 发起路由调用 + * + * @param businessLogic 业务逻辑 + * @param 出参范型 + * @return 业务出参 + */ + private Output route(SimpleUriResourceBusinessLogic businessLogic, SimpleContext context) { + // 业务处理、熔断操作、异常分析封装 + UriResourceRouteInvoker uriResourceRouteInvoker + = new SimpleUriResourceInvoker<>(businessLogic, context, this.routeConfigProvider); + return InvokeUtils.invoke(uriResourceRouteInvoker, this.router, this.retryPolicy); + } + + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceRouter.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceRouter.java new file mode 100644 index 00000000..38b0ac6e --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/SimpleUriResourceRouter.java @@ -0,0 +1,76 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.invoke.RandomRouterPolicy; +import com.yeepay.yop.sdk.invoke.Router; +import com.yeepay.yop.sdk.invoke.RouterPolicy; +import com.yeepay.yop.sdk.invoke.model.BlockResource; +import com.yeepay.yop.sdk.invoke.model.Resource; +import com.yeepay.yop.sdk.invoke.model.SimpleRouterParams; +import com.yeepay.yop.sdk.invoke.model.UriResource; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * title: 域名路由器,控制每笔请求的域名选择
    + * description: 默认实现:优先选用主域名,主域名熔断则选备用域名,主备均发生熔断后,选择最早熔断域名
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/2 + */ +public class SimpleUriResourceRouter implements Router { + + private final String resourceGroup; + private final List availableResources; + private final RouterPolicy routerPolicy; + + public SimpleUriResourceRouter(String resourceGroup, List availableUris, RouterPolicy routerPolicy) { + if (CollectionUtils.isEmpty(availableUris)) { + throw new YopClientException("availableServerRoots is empty"); + } + this.resourceGroup = StringUtils.defaultString(resourceGroup, ""); + + List targetServers; + if (routerPolicy instanceof RandomRouterPolicy) { + targetServers = ((RandomRouterPolicy) routerPolicy).shuffle(availableUris); + } else { + targetServers = availableUris; + } + + this.availableResources = new ArrayList<>(targetServers.size()); + for (String uri : targetServers) { + this.availableResources.add(new UriResource(resourceGroup, URI.create(uri)).computeResourceKey()); + } + this.routerPolicy = routerPolicy; + } + + @Override + public UriResource route(Object inputParams, Context context, Object... args) { + List invokedResources = null == args[0] ? Collections.emptyList() + : ((List) args[0]).stream().map(Object::toString).collect(Collectors.toList()); + + final Resource routeResource = this.routerPolicy.select(new SimpleRouterParams(this.resourceGroup, + this.availableResources, invokedResources)); + UriResource uriResource = UriResource.parseResourceKey(routeResource.getResourceKey()); + + if (routeResource instanceof BlockResource) { + BlockResource blockResource = (BlockResource) routeResource; + return new UriResource(UriResource.ResourceType.BLOCKED, uriResource.getResourceGroup(), + String.valueOf(blockResource.getBlockSequence()), uriResource.getResource()); + } + return uriResource; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/YopRouterConstants.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/YopRouterConstants.java new file mode 100644 index 00000000..917a9eb7 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/YopRouterConstants.java @@ -0,0 +1,20 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +/** + * title:
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/7 + */ +public interface YopRouterConstants { + + String ROUTER_POLICY_DEFAULT = "com.yeepay.yop.sdk.router.policy.AbAndFirstBlockPolicy"; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopFileRouteConfigProvider.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopFileRouteConfigProvider.java new file mode 100644 index 00000000..59338723 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopFileRouteConfigProvider.java @@ -0,0 +1,73 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.config; + +import com.google.common.collect.Maps; +import com.yeepay.yop.sdk.utils.JsonUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.util.Map; + +/** + * title: 基于配置文件的路由配置
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/2 + */ +public class YopFileRouteConfigProvider implements YopRouteConfigProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(YopFileRouteConfigProvider.class); + private static final String SDK_CONFIG_DIR = "config"; + private static final String DEFAULT_CONFIG_FILE_NAME_FORMAT = "yop_route_config_%s.json"; + private static final String DEFAULT_CONFIG_FILE_NAME = String.format(DEFAULT_CONFIG_FILE_NAME_FORMAT, "default"); + private static final String DEFAULT_CONFIG_FILE = SDK_CONFIG_DIR + "/" + DEFAULT_CONFIG_FILE_NAME; + + public static YopRouteConfigProvider INSTANCE = new YopFileRouteConfigProvider(); + + private final Map ROUTE_CONFIG_MAP = Maps.newHashMap(); + + private final String configFile; + + public YopFileRouteConfigProvider() { + this.configFile = DEFAULT_CONFIG_FILE; + } + + public YopFileRouteConfigProvider(String configFile) { + this.configFile = configFile; + } + + @Override + public YopRouteConfig getRouteConfig() { + return ROUTE_CONFIG_MAP.computeIfAbsent(this.configFile, this::loadRouteConfigFile); + } + + @Override + public YopRouteConfig getRouteConfig(String configKey) { + return ROUTE_CONFIG_MAP.computeIfAbsent(configKey, + p -> this.loadRouteConfigFile(SDK_CONFIG_DIR + "/" + + String.format(DEFAULT_CONFIG_FILE_NAME_FORMAT, configKey))); + } + + private YopRouteConfig loadRouteConfigFile(String configFile) { + try (InputStream inputStream = Thread.currentThread() + .getContextClassLoader().getResourceAsStream(configFile)){ + if (null != inputStream) { + return JsonUtils.loadFrom(inputStream, YopRouteConfig.class); + } + LOGGER.warn("yop route config not found, file:{}", configFile); + } catch (Exception e) { + LOGGER.error("error when load route config file, ex:", e); + } + if (!this.configFile.equals(configFile)) { + return getRouteConfig(); + } + return YopRouteConfig.DEFAULT_CONFIG; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopRouteConfig.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopRouteConfig.java new file mode 100644 index 00000000..cd0c9dfe --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopRouteConfig.java @@ -0,0 +1,75 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.Sets; +import com.yeepay.yop.sdk.config.provider.file.YopCircuitBreakerConfig; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.io.Serializable; +import java.util.Set; + +/** + * title: 路由切换策略配置
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/2 + */ +public class YopRouteConfig implements Serializable { + + private static final long serialVersionUID = -1L; + + public static final YopRouteConfig DEFAULT_CONFIG = new YopRouteConfig(); + + /** + * 配置需要切换域名重试异常 + */ + @JsonProperty("retry_exceptions") + private Set retryExceptions = Sets.newHashSet("java.net.UnknownHostException", + "java.net.ConnectException:No route to host (connect failed)", + "java.net.ConnectException:Connection refused (Connection refused)", + "java.net.ConnectException:Connection refused: connect", + "java.net.SocketTimeoutException:connect timed out", + "java.net.NoRouteToHostException", + "org.apache.http.conn.ConnectTimeoutException", "com.yeepay.shade.org.apache.http.conn.ConnectTimeoutException", + "org.apache.http.conn.HttpHostConnectException", "com.yeepay.shade.org.apache.http.conn.HttpHostConnectException", + "java.net.ConnectException:Connection timed out","java.net.ConnectException:连接超时"); + + /** + * 配置熔断规则 + * 默认规则:5分钟内累计5笔,或者5s内故障率达到20%即熔断域名 + * 可根据需要自行覆盖 + */ + @JsonProperty("circuit_breaker") + private YopCircuitBreakerConfig circuitBreakerConfig = YopCircuitBreakerConfig.DEFAULT_CONFIG; + + public Set getRetryExceptions() { + return retryExceptions; + } + + public void setRetryExceptions(Set retryExceptions) { + this.retryExceptions = retryExceptions; + } + + public YopCircuitBreakerConfig getCircuitBreakerConfig() { + return circuitBreakerConfig; + } + + public void setCircuitBreakerConfig(YopCircuitBreakerConfig circuitBreakerConfig) { + this.circuitBreakerConfig = circuitBreakerConfig; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopRouteConfigProvider.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopRouteConfigProvider.java new file mode 100644 index 00000000..7a3181e3 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopRouteConfigProvider.java @@ -0,0 +1,33 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.config; + +/** + * title: 路由配置提供者
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/2 + */ +public interface YopRouteConfigProvider { + + /** + * 获取默认配置 + * + * @return YopRouteConfig + */ + YopRouteConfig getRouteConfig(); + + /** + * 获取指定配置 + * + * @param configKey 指定标识 + * @return YopRouteConfig + */ + YopRouteConfig getRouteConfig(String configKey); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopRouteConfigProviderRegistry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopRouteConfigProviderRegistry.java new file mode 100644 index 00000000..b0d887dd --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/config/YopRouteConfigProviderRegistry.java @@ -0,0 +1,36 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.config; + +/** + * title: 路由配置工厂
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/2 + */ +public class YopRouteConfigProviderRegistry { + + private static final YopRouteConfigProvider DEFAULT_PROVIDER = YopFileRouteConfigProvider.INSTANCE; + private static volatile YopRouteConfigProvider CUSTOM_PROVIDER = null; + + public static YopRouteConfigProvider getProvider() { + return null != CUSTOM_PROVIDER ? CUSTOM_PROVIDER : DEFAULT_PROVIDER; + } + + public static void registerProvider(YopRouteConfigProvider customProvider) { + if (null == CUSTOM_PROVIDER && null != customProvider) { + CUSTOM_PROVIDER = customProvider; + } + } + + public static YopRouteConfigProvider getDefaultProvider() { + return DEFAULT_PROVIDER; + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/AbAndFirstBlockPolicy.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/AbAndFirstBlockPolicy.java new file mode 100644 index 00000000..9ccdf175 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/AbAndFirstBlockPolicy.java @@ -0,0 +1,61 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePa~y) + */ +package com.yeepay.yop.sdk.router.policy; + +import com.yeepay.yop.sdk.invoke.RandomRouterPolicy; +import com.yeepay.yop.sdk.invoke.model.BlockResource; +import com.yeepay.yop.sdk.invoke.model.Resource; +import com.yeepay.yop.sdk.invoke.model.RouterParams; +import com.yeepay.yop.sdk.invoke.model.SimpleResource; +import com.yeepay.yop.sdk.router.sentinel.YopSentinelMetricsHelper; +import com.yeepay.yop.sdk.utils.RandomUtils; + +import java.util.Collections; +import java.util.List; + +/** + * title: 主->备->最早熔断
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/3/29 + */ +public class AbAndFirstBlockPolicy extends BaseRouterPolicy implements RandomRouterPolicy { + + @Override + public Resource select(RouterParams params) { + validateParams(params); + final List availableResources = params.getAvailableResources(), + invokedResources = null == params.getInvokedResources() + ? Collections.emptyList() : params.getInvokedResources(); + + if (invokedResources.isEmpty()) { + return new SimpleResource(params.getAvailableResources().get(0)); + } + + for (String availableResource : availableResources) { + if (!invokedResources.contains(availableResource)) { + return new SimpleResource(availableResource); + } + } + + final BlockResource firstBlockResourceByGroup = YopSentinelMetricsHelper.findFirstBlockResourceByGroup(params.getResourceGroup()); + // 熔断列表为空(说明其他线程已半开成功),选主域名即可 + if (null == firstBlockResourceByGroup) { + final String mainResource = availableResources.get(0); + return YopSentinelMetricsHelper.findCurrentBlockResource(mainResource); + } + return firstBlockResourceByGroup; + } + + @Override + public List shuffle(List originResources) { + // 随机选主 + return RandomUtils.randomList(originResources); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/AbAndRoundRobinPolicy.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/AbAndRoundRobinPolicy.java new file mode 100644 index 00000000..8508adbb --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/AbAndRoundRobinPolicy.java @@ -0,0 +1,45 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.policy; + +import com.yeepay.yop.sdk.invoke.model.Resource; +import com.yeepay.yop.sdk.invoke.model.RouterParams; +import com.yeepay.yop.sdk.invoke.model.SimpleResource; +import com.yeepay.yop.sdk.router.sentinel.YopSentinelMetricsHelper; + +import java.util.Collections; +import java.util.List; + +/** + * title: 主-备-主-备-主-备……如此循环
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/1 + */ +public class AbAndRoundRobinPolicy extends BaseRouterPolicy { + + @Override + public Resource select(RouterParams params) { + validateParams(params); + final List availableResources = params.getAvailableResources(), + invokedResources = null == params.getInvokedResources() + ? Collections.emptyList() : params.getInvokedResources(); + if (invokedResources.size() < availableResources.size()) { + return new SimpleResource(availableResources.get(invokedResources.size())); + } + + final String lastInvokedResource = invokedResources.get(invokedResources.size() - 1); + final int i = availableResources.indexOf(lastInvokedResource); + if (i < (availableResources.size() - 1)) { + return YopSentinelMetricsHelper.findCurrentBlockResource(availableResources.get(i + 1)); + } else { + return YopSentinelMetricsHelper.findCurrentBlockResource(availableResources.get(0)); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/BaseRouterPolicy.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/BaseRouterPolicy.java new file mode 100644 index 00000000..d2c864d5 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/BaseRouterPolicy.java @@ -0,0 +1,45 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.policy; + +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.invoke.RouterPolicy; +import com.yeepay.yop.sdk.invoke.model.RouterParams; + +/** + * title:
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/1 + */ +public abstract class BaseRouterPolicy implements RouterPolicy { + + @Override + public String name() { + return this.getClass().getCanonicalName(); + } + + protected void validateParams(RouterParams params) { + if (null == params.getResourceGroup()) { + throw new YopClientException("ConfigProblem, resource group is not specified"); + } + if (null == params.getAvailableResources() || params.getAvailableResources().isEmpty()) { + throw new YopClientException("ConfigProblem, no available resource is specified"); + } + if (null != params.getInvokedResources()) { + for (String invokedResource : params.getInvokedResources()) { + if (!params.getAvailableResources().contains(invokedResource)) { + throw new YopClientException("ConfigProblem, invoked resource is not belong to the available resource"); + } + } + } + } + + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/RouterPolicyFactory.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/RouterPolicyFactory.java new file mode 100644 index 00000000..02ab0bd5 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/policy/RouterPolicyFactory.java @@ -0,0 +1,65 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.policy; + +import com.google.common.collect.Maps; +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.invoke.RouterPolicy; + +import java.util.Map; +import java.util.ServiceLoader; + +/** + * title: 路由策略工厂
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/1 + */ +public class RouterPolicyFactory { + + /** + * 路由策略Map + *

    + * key: 路由策略名称 + * value: 路由策略 + */ + private static final Map SERVICE_MAP = Maps.newHashMap(); + + static { + ServiceLoader serviceLoader = ServiceLoader.load(RouterPolicy.class); + for (RouterPolicy item : serviceLoader) { + SERVICE_MAP.put(item.name(), item); + } + } + + /** + * 扩展路由策略 + * + * @param name 路由策略名称 + * @param item 路由策略 + */ + public static void register(String name, RouterPolicy item) { + SERVICE_MAP.put(name, item); + } + + /** + * 根据路由策略名称获取路由策略 + * + * @param name 路由策略名称 + * @return 路由策略 + */ + public static RouterPolicy get(String name) { + final RouterPolicy routerPolicy = SERVICE_MAP.get(name); + if (null == routerPolicy) { + throw new YopClientException("ConfigProblem, RouterPolicy NotFound, name:" + name); + } + return routerPolicy; + } + +} diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/base/cache/YopDegradeRuleHelper.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopDegradeRuleHelper.java similarity index 74% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/base/cache/YopDegradeRuleHelper.java rename to yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopDegradeRuleHelper.java index 26929d81..bc8b53a0 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/base/cache/YopDegradeRuleHelper.java +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopDegradeRuleHelper.java @@ -2,22 +2,24 @@ * Copyright: Copyright (c)2014 * Company: 易宝支付(YeePay) */ -package com.yeepay.yop.sdk.base.cache; +package com.yeepay.yop.sdk.router.sentinel; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; -import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import com.google.common.collect.Sets; -import com.yeepay.yop.sdk.YopConstants; import com.yeepay.yop.sdk.config.provider.file.YopCircuitBreakerConfig; import com.yeepay.yop.sdk.config.provider.file.YopCircuitBreakerRuleConfig; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URI; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -38,6 +40,47 @@ public class YopDegradeRuleHelper { private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); + + + /** + * 初始化降级配置 + * + * @param circuitBreakerConfigMap 降级规则Map<资源标识,熔断规则列表> + */ + public static void initDegradeRule(Map circuitBreakerConfigMap) { + if (initialized) { + return; + } + + synchronized (YopDegradeRuleHelper.class) { + if (initialized) { + return; + } + + if (MapUtils.isEmpty(circuitBreakerConfigMap)) { + LOGGER.warn("Empty DegradeRule, Please Check Your Config And Try Again"); + initialized = true; + return; + } + + Set allRules = Sets.newHashSet(); + circuitBreakerConfigMap.forEach((resource, circuitBreakerConfig) -> { + if (StringUtils.isBlank(resource) || null == circuitBreakerConfig + || !circuitBreakerConfig.isEnable() || CollectionUtils.isEmpty(circuitBreakerConfig.getRules()) + || DegradeRuleManager.hasConfig(resource)) { + return; + } + allRules.addAll(initDegradeRuleForResource(resource, circuitBreakerConfig)); + }); + + if (CollectionUtils.isNotEmpty(allRules)) { + DegradeRuleManager.loadRules(new ArrayList<>(allRules)); + } + initialized = true; + LOGGER.info("DegradeRule Inited, rules:{}", allRules); + } + } + /** * 初始化降级配置 * @@ -133,7 +176,7 @@ public static boolean addDegradeRule(String resource, YopCircuitBreakerConfig ci Set rules = initDegradeRuleForResource(resource, circuitBreakerConfig); boolean ruleAdded = updateRulesForResource(resource, rules, false); - if (YopConstants.SDK_DEBUG && ruleAdded) { + if (YopSentinelConstants.SDK_ROUTER_SENTINEL_DEBUG && ruleAdded) { LOGGER.info("DegradeRule Added, rules:{}", rules); } return ruleAdded; @@ -152,7 +195,7 @@ public static boolean updateDegradeRule(String resource, YopCircuitBreakerConfig Set rules = initDegradeRuleForResource(resource, circuitBreakerConfig); boolean ruleUpdated = updateRulesForResource(resource, rules, true); - if (YopConstants.SDK_DEBUG && ruleUpdated) { + if (YopSentinelConstants.SDK_ROUTER_SENTINEL_DEBUG && ruleUpdated) { LOGGER.info("DegradeRule Updated, rules:{}", rules); } return ruleUpdated; @@ -199,7 +242,7 @@ public static boolean removeDegradeRule(String resource) { } final boolean ruleRemoved = updateRulesForResource(resource, null, true); - if (YopConstants.SDK_DEBUG && ruleRemoved) { + if (YopSentinelConstants.SDK_ROUTER_SENTINEL_DEBUG && ruleRemoved) { LOGGER.info("DegradeRule Removed, resource:{}", resource); } return ruleRemoved; diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/sentinel/YopEntry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopEntry.java similarity index 83% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/sentinel/YopEntry.java rename to yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopEntry.java index e5d56c49..0d6b5354 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/sentinel/YopEntry.java +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopEntry.java @@ -2,23 +2,23 @@ * Copyright: Copyright (c)2014 * Company: 易宝支付(YeePay) */ -package com.yeepay.yop.sdk.sentinel; - -import com.alibaba.csp.sentinel.Entry; -import com.alibaba.csp.sentinel.ErrorEntryFreeException; -import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.context.ContextUtil; -import com.alibaba.csp.sentinel.context.NullContext; -import com.alibaba.csp.sentinel.log.RecordLog; -import com.alibaba.csp.sentinel.node.Node; -import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; -import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; -import com.alibaba.csp.sentinel.util.function.BiConsumer; +package com.yeepay.yop.sdk.router.sentinel; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ErrorEntryFreeException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.NullContext; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.BiConsumer; import java.util.LinkedList; /** - * title:
    + * title: Yop定制-适配YopSph
    * description: 描述
    * Copyright: Copyright (c)2014
    * Company: 易宝支付(YeePay)
    diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelConstants.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelConstants.java new file mode 100644 index 00000000..22496fcd --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelConstants.java @@ -0,0 +1,19 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.sentinel; + +/** + * title:
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/1/29 + */ +public class YopSentinelConstants { + public static final boolean SDK_ROUTER_SENTINEL_DEBUG = Boolean.parseBoolean(System.getProperty("yop.sdk.router.sentinel.debug", "false")); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelInit.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelInit.java new file mode 100644 index 00000000..82dba02d --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelInit.java @@ -0,0 +1,32 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.sentinel; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init.InitFunc; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry; + +import java.util.ServiceLoader; + +/** + * title:
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/3 + */ +public class YopSentinelInit implements InitFunc { + + @Override + public void init() throws Exception { + ServiceLoader serviceLoader = ServiceLoader.load(CircuitBreakerStateChangeObserver .class); + for (CircuitBreakerStateChangeObserver item : serviceLoader) { + EventObserverRegistry.getInstance().addStateChangeObserver(item.getClass().getCanonicalName(), item); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelMetricsHelper.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelMetricsHelper.java new file mode 100644 index 00000000..9929bf4a --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelMetricsHelper.java @@ -0,0 +1,173 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.sentinel; + +import com.google.common.collect.Maps; +import com.yeepay.yop.sdk.invoke.model.BlockResource; + +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * title: sentinel资源监控数据封装
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/3/29 + */ +public class YopSentinelMetricsHelper { + + private static final ReentrantReadWriteLock READ_WRITE_LOCK = new ReentrantReadWriteLock(); + + /** + * 历史熔断资源列表,按自定义规则分组,组内资源对等,按熔断时间倒序排列,可互相切换 + */ + private static final Map> BLOCK_RESOURCE_BY_GROUP = Maps.newConcurrentMap(); + + /** + * 资源熔断次数序列号 + */ + private static final Map RESOURCE_BLOCK_COUNTER = Maps.newConcurrentMap(); + + /** + * 资源监控数据读写锁 + * + * @return ReentrantReadWriteLock + */ + public static ReentrantReadWriteLock getMetricsReadWriteLock() { + return READ_WRITE_LOCK; + } + + + /** + * 资源熔断事件 + * + * @param resourceGroup 资源分组 + * @param resource 资源 + * @param isBlockedResource 是否为熔断资源 + */ + public static void onResourceBlocked(String resourceGroup, String resource, boolean isBlockedResource) { + READ_WRITE_LOCK.writeLock().lock(); + try { + LinkedList blockResources = BLOCK_RESOURCE_BY_GROUP.get(resourceGroup); + if (null == blockResources) { + blockResources = new LinkedList<>(); + } else { + blockResources.remove(resource); + } + blockResources.add(resource); + + // 熔断资源,再次熔断时,计数器自增 + if (isBlockedResource) { + AtomicLong resourceSequence = RESOURCE_BLOCK_COUNTER.get(resource); + if (null == resourceSequence) { + resourceSequence = new AtomicLong(1); + RESOURCE_BLOCK_COUNTER.put(resource, resourceSequence); + } else { + resourceSequence.getAndAdd(1); + } + } + + } finally { + READ_WRITE_LOCK.writeLock().unlock(); + } + } + + /** + * 资源熔断恢复事件 + * + * @param resourceGroup 资源分组 + * @param resource 资源 + */ + public static void onResourceAvailable(String resourceGroup, String resource) { + READ_WRITE_LOCK.writeLock().lock(); + try { + LinkedList blockResources = BLOCK_RESOURCE_BY_GROUP.get(resourceGroup); + if (null == blockResources) { + blockResources = new LinkedList<>(); + } else { + blockResources.remove(resource); + } + blockResources.addFirst(resource); + } finally { + READ_WRITE_LOCK.writeLock().unlock(); + } + } + + /** + * 获取分组内最早熔断资源 + * + * @param resourceGroup 资源分组 + * @return BlockResource + */ + public static BlockResource findFirstBlockResourceByGroup(String resourceGroup) { + READ_WRITE_LOCK.readLock().lock(); + try { + LinkedList blockResources = BLOCK_RESOURCE_BY_GROUP.get(resourceGroup); + if (null == blockResources || blockResources.isEmpty()) { + return null; + } + + final String firstBlockResource = blockResources.peek(); + if (null == firstBlockResource) { + return null; + } + + AtomicLong resourceSequence = RESOURCE_BLOCK_COUNTER.get(firstBlockResource); + if (null == resourceSequence) { + READ_WRITE_LOCK.readLock().unlock(); + READ_WRITE_LOCK.writeLock().lock(); + try { + resourceSequence = RESOURCE_BLOCK_COUNTER.get(firstBlockResource); + if (null == resourceSequence) { + resourceSequence = new AtomicLong(1); + RESOURCE_BLOCK_COUNTER.put(firstBlockResource, resourceSequence); + } + READ_WRITE_LOCK.readLock().lock(); + } finally { + READ_WRITE_LOCK.writeLock().unlock(); + } + } + return new BlockResource(firstBlockResource, resourceSequence.get()); + } finally { + READ_WRITE_LOCK.readLock().unlock(); + } + } + + /** + * 获取当前熔断资源 + * + * @param resource 资源 + * @return BlockResource + */ + public static BlockResource findCurrentBlockResource(String resource) { + READ_WRITE_LOCK.readLock().lock(); + try { + AtomicLong resourceSequence = RESOURCE_BLOCK_COUNTER.get(resource); + if (null == resourceSequence) { + READ_WRITE_LOCK.readLock().unlock(); + READ_WRITE_LOCK.writeLock().lock(); + try { + resourceSequence = RESOURCE_BLOCK_COUNTER.get(resource); + if (null == resourceSequence) { + resourceSequence = new AtomicLong(1); + RESOURCE_BLOCK_COUNTER.put(resource, resourceSequence); + } + READ_WRITE_LOCK.readLock().lock(); + } finally { + READ_WRITE_LOCK.writeLock().unlock(); + } + } + return new BlockResource(resource, resourceSequence.get()); + } finally { + READ_WRITE_LOCK.readLock().unlock(); + } + } +} diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/log/YopSentinelRecordLogger.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelRecordLogger.java similarity index 80% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/log/YopSentinelRecordLogger.java rename to yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelRecordLogger.java index f14a5ae0..34e8b400 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/log/YopSentinelRecordLogger.java +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSentinelRecordLogger.java @@ -2,15 +2,14 @@ * Copyright: Copyright (c)2014 * Company: 易宝支付(YeePay) */ -package com.yeepay.yop.sdk.log; +package com.yeepay.yop.sdk.router.sentinel; -import com.alibaba.csp.sentinel.log.LogTarget; -import com.alibaba.csp.sentinel.log.Logger; -import com.yeepay.yop.sdk.YopConstants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogTarget; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.Logger; import org.slf4j.LoggerFactory; /** - * title:
    + * title: yop定制,record-log输出控制
    * description: 描述
    * Copyright: Copyright (c)2014
    * Company: 易宝支付(YeePay)
    @@ -26,14 +25,14 @@ public class YopSentinelRecordLogger implements Logger { @Override public void info(String format, Object... arguments) { - if (YopConstants.SDK_DEBUG) { + if (YopSentinelConstants.SDK_ROUTER_SENTINEL_DEBUG) { LOGGER.info(format, arguments); } } @Override public void info(String msg, Throwable e) { - if (YopConstants.SDK_DEBUG) { + if (YopSentinelConstants.SDK_ROUTER_SENTINEL_DEBUG) { LOGGER.info(msg, e); } } diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSph.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSph.java new file mode 100644 index 00000000..12a94abb --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/YopSph.java @@ -0,0 +1,180 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.sentinel; + +import com.yeepay.yop.sdk.invoke.model.UriResource; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.NullContext; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init.InitExecutor; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.*; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.Rule; + +import java.util.HashMap; +import java.util.Map; + +/** + * title: yop定制sentinel代码,重写部分代码
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2023/12/11 + */ +public class YopSph { + + private static final YopSph yopSph = new YopSph(); + + static { + // If init fails, the process will exit. + InitExecutor.doInit(); + } + + public static YopSph getInstance() { + return yopSph; + } + + private static final Object[] OBJECTS0 = new Object[0]; + + /** + * Same resource({@link ResourceWrapper#equals(Object)}) will share the same + * {@link ProcessorSlotChain}, no matter in which {@link Context}. + */ + private static volatile Map chainMap = new HashMap(); + + private static final Object LOCK = new Object(); + + private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) + throws BlockException { + Context context = ContextUtil.getContext(); + if (context instanceof NullContext) { + // The {@link NullContext} indicates that the amount of context has exceeded the threshold, + // so here init the entry only. No rule checking will be done. + return new YopEntry(resourceWrapper, null, context); + } + + if (context == null) { + // Using default context. + context = YopSph.InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME); + } + + // Global switch is close, no rule checking will do. + if (!Constants.ON) { + return new YopEntry(resourceWrapper, null, context); + } + + ProcessorSlot chain = lookProcessChain(resourceWrapper); + + /* + * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, + * so no rule checking will be done. + */ + if (chain == null) { + return new YopEntry(resourceWrapper, null, context); + } + + Entry e = new YopEntry(resourceWrapper, chain, context); + try { + chain.entry(context, resourceWrapper, null, count, prioritized, args); + } catch (BlockException e1) { + e.exit(count, args); + throw e1; + } catch (Throwable e1) { + // This should not happen, unless there are errors existing in Sentinel internal. + RecordLog.info("Sentinel unexpected exception", e1); + } + return e; + } + + /** + * Do all {@link Rule}s checking about the resource. + * + *

    Each distinct resource will use a {@link ProcessorSlot} to do rules checking. Same resource will use + * same {@link ProcessorSlot} globally.

    + * + *

    Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE}, + * otherwise no rules checking will do. In this condition, all requests will pass directly, with no checking + * or exception.

    + * + * @param resourceWrapper resource name + * @param count tokens needed + * @param args arguments of user method call + * @return {@link Entry} represents this call + * @throws BlockException if any rule's threshold is exceeded + */ + public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException { + return entryWithPriority(resourceWrapper, count, false, args); + } + + /** + * Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will + * be created if the resource doesn't relate one. + * + *

    Same resource({@link ResourceWrapper#equals(Object)}) will share the same + * {@link ProcessorSlotChain} globally, no matter in which {@link Context}.

    + * + *

    + * Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE}, + * otherwise null will return. + *

    + * + * @param resourceWrapper target resource + * @return {@link ProcessorSlotChain} of the resource + */ + ProcessorSlot lookProcessChain(ResourceWrapper resourceWrapper) { + String resourceName = resourceWrapper.getName(); + if (resourceName.contains(UriResource.RESOURCE_SEPERATOR)) { + final String[] split = resourceName.split(UriResource.RESOURCE_SEPERATOR); + resourceName = split[split.length -1]; + } + final StringResourceWrapper realResource = new StringResourceWrapper(resourceName, + resourceWrapper.getEntryType(), resourceWrapper.getResourceType()); + ProcessorSlotChain chain = chainMap.get(realResource); + if (chain == null) { + synchronized (LOCK) { + chain = chainMap.get(realResource); + if (chain == null) { + // Entry size limit. + if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) { + return null; + } + + chain = SlotChainProvider.newSlotChain(); + Map newMap = new HashMap( + chainMap.size() + 1); + newMap.putAll(chainMap); + newMap.put(realResource, chain); + chainMap = newMap; + } + } + } + return chain; + } + + /** + * This class is used for skip context name checking. + */ + private final static class InternalContextUtil extends ContextUtil { + static Context internalEnter(String name) { + return trueEnter(name, ""); + } + + static Context internalEnter(String name, String origin) { + return trueEnter(name, origin); + } + } + + public Entry entry(String name) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT); + return entry(resource, 1, OBJECTS0); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/listener/YopResourceStatusListener.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/listener/YopResourceStatusListener.java new file mode 100644 index 00000000..447992bf --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/sentinel/listener/YopResourceStatusListener.java @@ -0,0 +1,78 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.sentinel.listener; + +import com.google.common.collect.Queues; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.yeepay.yop.sdk.invoke.model.UriResource; +import com.yeepay.yop.sdk.router.sentinel.YopDegradeRuleHelper; +import com.yeepay.yop.sdk.router.sentinel.YopSentinelMetricsHelper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * title: 资源状态变更监听
    + * description: 描述
    + * Copyright: Copyright (c)2014
    + * Company: 易宝支付(YeePay)
    + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/3 + */ +public class YopResourceStatusListener implements CircuitBreakerStateChangeObserver { + + private static final Logger LOGGER = LoggerFactory.getLogger(YopResourceStatusListener.class); + + private static final ThreadPoolExecutor BLOCKED_SWEEPER = new ThreadPoolExecutor(2, 20, + 3, TimeUnit.MINUTES, Queues.newLinkedBlockingQueue(1000), + new ThreadFactoryBuilder().setNameFormat("yop-blocked-resource-sweeper-%d").setDaemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()); + + @Override + public void onStateChange(CircuitBreaker.State prevState, CircuitBreaker.State newState, + DegradeRule rule, Double snapshotValue) { + + try { + final UriResource uriResource = UriResource.parseResourceKey(rule.getResource()); + final URI serverRoot = uriResource.getResource(); + LOGGER.info("ServerRoot Block State Changed, serverRoot:{}, old:{}, new:{}, rule:{}", + serverRoot, prevState, newState, rule); + + final String commonResourceKey = new UriResource(uriResource.getResourceGroup(), serverRoot).computeResourceKey(); + final boolean isBlockResource = UriResource.ResourceType.BLOCKED.equals(uriResource.getResourceType()); + if (newState.equals(CircuitBreaker.State.OPEN)) { + YopSentinelMetricsHelper.onResourceBlocked(uriResource.getResourceGroup(), commonResourceKey, isBlockResource); + if (isBlockResource) { + asyncDiscardOldServers(uriResource); + } + } else if (newState.equals(CircuitBreaker.State.CLOSED)) { + YopSentinelMetricsHelper.onResourceAvailable(uriResource.getResourceGroup(), commonResourceKey); + } + } catch (Exception e) { + LOGGER.warn("UnexpectedError, MonitorServerRoot ex:", e); + } + } + + // 异步清理过期资源 + private void asyncDiscardOldServers(UriResource uriResource) { + BLOCKED_SWEEPER.submit(() -> { + try { + final String resource = uriResource.computeResourceKey(); + // 清理资源配置 + YopDegradeRuleHelper.removeDegradeRule(resource); + } catch (Exception e) { + LOGGER.warn("blocked sweeper failed, ex:", e); + } + }); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/AsyncEntry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/AsyncEntry.java new file mode 100644 index 00000000..9ef4d818 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/AsyncEntry.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ErrorEntryFreeException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.NullContext; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; + +/** + * The entry for asynchronous resources. + * + * @author Eric Zhao + * @since 0.2.0 + */ +public class AsyncEntry extends CtEntry { + + private Context asyncContext; + + AsyncEntry(ResourceWrapper resourceWrapper, ProcessorSlot chain, Context context) { + super(resourceWrapper, chain, context); + } + + /** + * Remove current entry from local context, but does not exit. + */ + void cleanCurrentEntryInLocal() { + if (context instanceof NullContext) { + return; + } + Context originalContext = context; + if (originalContext != null) { + Entry curEntry = originalContext.getCurEntry(); + if (curEntry == this) { + Entry parent = this.parent; + originalContext.setCurEntry(parent); + if (parent != null) { + ((CtEntry)parent).child = null; + } + } else { + String curEntryName = curEntry == null ? "none" + : curEntry.resourceWrapper.getName() + "@" + curEntry.hashCode(); + String msg = String.format("Bad async context state, expected entry: %s, but actual: %s", + getResourceWrapper().getName() + "@" + hashCode(), curEntryName); + throw new IllegalStateException(msg); + } + } + } + + public Context getAsyncContext() { + return asyncContext; + } + + /** + * The async context should not be initialized until the node for current resource has been set to current entry. + */ + void initAsyncContext() { + if (asyncContext == null) { + if (context instanceof NullContext) { + asyncContext = context; + return; + } + this.asyncContext = Context.newAsyncContext(context.getEntranceNode(), context.getName()) + .setOrigin(context.getOrigin()) + .setCurEntry(this); + } else { + RecordLog.warn( + "[AsyncEntry] Duplicate initialize of async context for entry: " + resourceWrapper.getName()); + } + } + + @Override + protected void clearEntryContext() { + super.clearEntryContext(); + this.asyncContext = null; + } + + @Override + protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { + exitForContext(asyncContext, count, args); + + return parent; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Constants.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Constants.java new file mode 100755 index 00000000..58dd1842 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Constants.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ResourceTypeConstants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.ClusterNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.EntranceNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.VersionUtil; + +/** + * Universal constants of Sentinel. + * + * @author qinan.qn + * @author youji.zj + * @author jialiang.linjl + * @author Eric Zhao + */ +public final class Constants { + + public static final String SENTINEL_VERSION = VersionUtil.getVersion("1.8.6"); + + public final static int MAX_CONTEXT_NAME_SIZE = 2000; + public final static int MAX_SLOT_CHAIN_SIZE = 6000; + + public final static String ROOT_ID = "machine-root"; + public final static String CONTEXT_DEFAULT_NAME = "sentinel_default_context"; + + /** + * A virtual resource identifier for total inbound statistics (since 1.5.0). + */ + public final static String TOTAL_IN_RESOURCE_NAME = "__total_inbound_traffic__"; + + /** + * A virtual resource identifier for cpu usage statistics (since 1.6.1). + */ + public final static String CPU_USAGE_RESOURCE_NAME = "__cpu_usage__"; + + /** + * A virtual resource identifier for system load statistics (since 1.6.1). + */ + public final static String SYSTEM_LOAD_RESOURCE_NAME = "__system_load__"; + + /** + * Global ROOT statistic node that represents the universal parent node. + */ + public final static DefaultNode ROOT = new EntranceNode(new StringResourceWrapper(ROOT_ID, EntryType.IN), + new ClusterNode(ROOT_ID, ResourceTypeConstants.COMMON)); + + /** + * Global statistic node for inbound traffic. Usually used for {@code SystemRule} checking. + */ + public final static ClusterNode ENTRY_NODE = new ClusterNode(TOTAL_IN_RESOURCE_NAME, ResourceTypeConstants.COMMON); + + /** + * The global switch for Sentinel. + */ + public static volatile boolean ON = true; + + /** + * Order of default processor slots + */ + public static final int ORDER_NODE_SELECTOR_SLOT = -10000; + public static final int ORDER_CLUSTER_BUILDER_SLOT = -9000; + public static final int ORDER_LOG_SLOT = -8000; + public static final int ORDER_STATISTIC_SLOT = -7000; + public static final int ORDER_AUTHORITY_SLOT = -6000; + public static final int ORDER_SYSTEM_SLOT = -5000; + public static final int ORDER_FLOW_SLOT = -2000; + public static final int ORDER_DEGRADE_SLOT = -1000; + + private Constants() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/CtEntry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/CtEntry.java new file mode 100644 index 00000000..297691cf --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/CtEntry.java @@ -0,0 +1,155 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import java.util.LinkedList; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ErrorEntryFreeException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.NullContext; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.BiConsumer; + +/** + * Linked entry within current context. + * + * @author jialiang.linjl + * @author Eric Zhao + */ +class CtEntry extends Entry { + + protected Entry parent = null; + protected Entry child = null; + + protected ProcessorSlot chain; + protected Context context; + protected LinkedList> exitHandlers; + + CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot chain, Context context) { + super(resourceWrapper); + this.chain = chain; + this.context = context; + + setUpEntryFor(context); + } + + private void setUpEntryFor(Context context) { + // The entry should not be associated to NullContext. + if (context instanceof NullContext) { + return; + } + this.parent = context.getCurEntry(); + if (parent != null) { + ((CtEntry) parent).child = this; + } + context.setCurEntry(this); + } + + @Override + public void exit(int count, Object... args) throws ErrorEntryFreeException { + trueExit(count, args); + } + + /** + * Note: the exit handlers will be called AFTER onExit of slot chain. + */ + private void callExitHandlersAndCleanUp(Context ctx) { + if (exitHandlers != null && !exitHandlers.isEmpty()) { + for (BiConsumer handler : this.exitHandlers) { + try { + handler.accept(ctx, this); + } catch (Exception e) { + RecordLog.warn("Error occurred when invoking entry exit handler, current entry: " + + resourceWrapper.getName(), e); + } + } + exitHandlers = null; + } + } + + protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException { + if (context != null) { + // Null context should exit without clean-up. + if (context instanceof NullContext) { + return; + } + + if (context.getCurEntry() != this) { + String curEntryNameInContext = context.getCurEntry() == null ? null + : context.getCurEntry().getResourceWrapper().getName(); + // Clean previous call stack. + CtEntry e = (CtEntry) context.getCurEntry(); + while (e != null) { + e.exit(count, args); + e = (CtEntry) e.parent; + } + String errorMessage = String.format("The order of entry exit can't be paired with the order of entry" + + ", current entry in context: <%s>, but expected: <%s>", curEntryNameInContext, + resourceWrapper.getName()); + throw new ErrorEntryFreeException(errorMessage); + } else { + // Go through the onExit hook of all slots. + if (chain != null) { + chain.exit(context, resourceWrapper, count, args); + } + // Go through the existing terminate handlers (associated to this invocation). + callExitHandlersAndCleanUp(context); + + // Restore the call stack. + context.setCurEntry(parent); + if (parent != null) { + ((CtEntry) parent).child = null; + } + if (parent == null) { + // Default context (auto entered) will be exited automatically. + if (ContextUtil.isDefaultContext(context)) { + ContextUtil.exit(); + } + } + // Clean the reference of context in current entry to avoid duplicate exit. + clearEntryContext(); + } + } + } + + protected void clearEntryContext() { + this.context = null; + } + + @Override + public void whenTerminate(BiConsumer handler) { + if (this.exitHandlers == null) { + this.exitHandlers = new LinkedList<>(); + } + this.exitHandlers.add(handler); + } + + @Override + protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { + exitForContext(context, count, args); + + return parent; + } + + @Override + public Node getLastNode() { + return parent == null ? null : parent.getCurNode(); + } +} \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/CtSph.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/CtSph.java new file mode 100755 index 00000000..0c996a90 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/CtSph.java @@ -0,0 +1,357 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.*; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.NullContext; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.MethodResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.SlotChainProvider; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.Rule; + +/** + * {@inheritDoc} + * + * @author jialiang.linjl + * @author leyou(lihao) + * @author Eric Zhao + * @see Sph + */ +public class CtSph implements Sph { + + private static final Object[] OBJECTS0 = new Object[0]; + + /** + * Same resource({@link ResourceWrapper#equals(Object)}) will share the same + * {@link ProcessorSlotChain}, no matter in which {@link Context}. + */ + private static volatile Map chainMap + = new HashMap(); + + private static final Object LOCK = new Object(); + + private AsyncEntry asyncEntryWithNoChain(ResourceWrapper resourceWrapper, Context context) { + AsyncEntry entry = new AsyncEntry(resourceWrapper, null, context); + entry.initAsyncContext(); + // The async entry will be removed from current context as soon as it has been created. + entry.cleanCurrentEntryInLocal(); + return entry; + } + + private AsyncEntry asyncEntryWithPriorityInternal(ResourceWrapper resourceWrapper, int count, boolean prioritized, + Object... args) throws BlockException { + Context context = ContextUtil.getContext(); + if (context instanceof NullContext) { + // The {@link NullContext} indicates that the amount of context has exceeded the threshold, + // so here init the entry only. No rule checking will be done. + return asyncEntryWithNoChain(resourceWrapper, context); + } + if (context == null) { + // Using default context. + context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME); + } + + // Global switch is turned off, so no rule checking will be done. + if (!Constants.ON) { + return asyncEntryWithNoChain(resourceWrapper, context); + } + + ProcessorSlot chain = lookProcessChain(resourceWrapper); + + // Means processor cache size exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, so no rule checking will be done. + if (chain == null) { + return asyncEntryWithNoChain(resourceWrapper, context); + } + + AsyncEntry asyncEntry = new AsyncEntry(resourceWrapper, chain, context); + try { + chain.entry(context, resourceWrapper, null, count, prioritized, args); + // Initiate the async context only when the entry successfully passed the slot chain. + asyncEntry.initAsyncContext(); + // The asynchronous call may take time in background, and current context should not be hanged on it. + // So we need to remove current async entry from current context. + asyncEntry.cleanCurrentEntryInLocal(); + } catch (BlockException e1) { + // When blocked, the async entry will be exited on current context. + // The async context will not be initialized. + asyncEntry.exitForContext(context, count, args); + throw e1; + } catch (Throwable e1) { + // This should not happen, unless there are errors existing in Sentinel internal. + // When this happens, async context is not initialized. + RecordLog.warn("Sentinel unexpected exception in asyncEntryInternal", e1); + + asyncEntry.cleanCurrentEntryInLocal(); + } + return asyncEntry; + } + + private AsyncEntry asyncEntryInternal(ResourceWrapper resourceWrapper, int count, Object... args) + throws BlockException { + return asyncEntryWithPriorityInternal(resourceWrapper, count, false, args); + } + + private Entry entryWithPriority(ResourceWrapper resourceWrapper, int count, boolean prioritized, Object... args) + throws BlockException { + Context context = ContextUtil.getContext(); + if (context instanceof NullContext) { + // The {@link NullContext} indicates that the amount of context has exceeded the threshold, + // so here init the entry only. No rule checking will be done. + return new CtEntry(resourceWrapper, null, context); + } + + if (context == null) { + // Using default context. + context = InternalContextUtil.internalEnter(Constants.CONTEXT_DEFAULT_NAME); + } + + // Global switch is close, no rule checking will do. + if (!Constants.ON) { + return new CtEntry(resourceWrapper, null, context); + } + + ProcessorSlot chain = lookProcessChain(resourceWrapper); + + /* + * Means amount of resources (slot chain) exceeds {@link Constants.MAX_SLOT_CHAIN_SIZE}, + * so no rule checking will be done. + */ + if (chain == null) { + return new CtEntry(resourceWrapper, null, context); + } + + Entry e = new CtEntry(resourceWrapper, chain, context); + try { + chain.entry(context, resourceWrapper, null, count, prioritized, args); + } catch (BlockException e1) { + e.exit(count, args); + throw e1; + } catch (Throwable e1) { + // This should not happen, unless there are errors existing in Sentinel internal. + RecordLog.info("Sentinel unexpected exception", e1); + } + return e; + } + + /** + * Do all {@link Rule}s checking about the resource. + * + *

    Each distinct resource will use a {@link ProcessorSlot} to do rules checking. Same resource will use + * same {@link ProcessorSlot} globally.

    + * + *

    Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE}, + * otherwise no rules checking will do. In this condition, all requests will pass directly, with no checking + * or exception.

    + * + * @param resourceWrapper resource name + * @param count tokens needed + * @param args arguments of user method call + * @return {@link Entry} represents this call + * @throws BlockException if any rule's threshold is exceeded + */ + public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) throws BlockException { + return entryWithPriority(resourceWrapper, count, false, args); + } + + /** + * Get {@link ProcessorSlotChain} of the resource. new {@link ProcessorSlotChain} will + * be created if the resource doesn't relate one. + * + *

    Same resource({@link ResourceWrapper#equals(Object)}) will share the same + * {@link ProcessorSlotChain} globally, no matter in which {@link Context}.

    + * + *

    + * Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE}, + * otherwise null will return. + *

    + * + * @param resourceWrapper target resource + * @return {@link ProcessorSlotChain} of the resource + */ + ProcessorSlot lookProcessChain(ResourceWrapper resourceWrapper) { + ProcessorSlotChain chain = chainMap.get(resourceWrapper); + if (chain == null) { + synchronized (LOCK) { + chain = chainMap.get(resourceWrapper); + if (chain == null) { + // Entry size limit. + if (chainMap.size() >= Constants.MAX_SLOT_CHAIN_SIZE) { + return null; + } + + chain = SlotChainProvider.newSlotChain(); + Map newMap = new HashMap( + chainMap.size() + 1); + newMap.putAll(chainMap); + newMap.put(resourceWrapper, chain); + chainMap = newMap; + } + } + } + return chain; + } + + /** + * Get current size of created slot chains. + * + * @return size of created slot chains + * @since 0.2.0 + */ + public static int entrySize() { + return chainMap.size(); + } + + /** + * Reset the slot chain map. Only for internal test. + * + * @since 0.2.0 + */ + static void resetChainMap() { + chainMap.clear(); + } + + /** + * Only for internal test. + * + * @since 0.2.0 + */ + static Map getChainMap() { + return chainMap; + } + + /** + * This class is used for skip context name checking. + */ + private final static class InternalContextUtil extends ContextUtil { + static Context internalEnter(String name) { + return trueEnter(name, ""); + } + + static Context internalEnter(String name, String origin) { + return trueEnter(name, origin); + } + } + + @Override + public Entry entry(String name) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT); + return entry(resource, 1, OBJECTS0); + } + + @Override + public Entry entry(Method method) throws BlockException { + MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT); + return entry(resource, 1, OBJECTS0); + } + + @Override + public Entry entry(Method method, EntryType type) throws BlockException { + MethodResourceWrapper resource = new MethodResourceWrapper(method, type); + return entry(resource, 1, OBJECTS0); + } + + @Override + public Entry entry(String name, EntryType type) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, type); + return entry(resource, 1, OBJECTS0); + } + + @Override + public Entry entry(Method method, EntryType type, int count) throws BlockException { + MethodResourceWrapper resource = new MethodResourceWrapper(method, type); + return entry(resource, count, OBJECTS0); + } + + @Override + public Entry entry(String name, EntryType type, int count) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, type); + return entry(resource, count, OBJECTS0); + } + + @Override + public Entry entry(Method method, int count) throws BlockException { + MethodResourceWrapper resource = new MethodResourceWrapper(method, EntryType.OUT); + return entry(resource, count, OBJECTS0); + } + + @Override + public Entry entry(String name, int count) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, EntryType.OUT); + return entry(resource, count, OBJECTS0); + } + + @Override + public Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException { + MethodResourceWrapper resource = new MethodResourceWrapper(method, type); + return entry(resource, count, args); + } + + @Override + public Entry entry(String name, EntryType type, int count, Object... args) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, type); + return entry(resource, count, args); + } + + @Override + public AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, type); + return asyncEntryInternal(resource, count, args); + } + + @Override + public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, type); + return entryWithPriority(resource, count, prioritized); + } + + @Override + public Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized, Object... args) + throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, type); + return entryWithPriority(resource, count, prioritized, args); + } + + @Override + public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args) + throws BlockException { + return entryWithType(name, resourceType, entryType, count, false, args); + } + + @Override + public Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized, + Object[] args) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType); + return entryWithPriority(resource, count, prioritized, args); + } + + @Override + public AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType entryType, int count, + boolean prioritized, Object[] args) throws BlockException { + StringResourceWrapper resource = new StringResourceWrapper(name, entryType, resourceType); + return asyncEntryWithPriorityInternal(resource, count, prioritized, args); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Entry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Entry.java new file mode 100755 index 00000000..04c8d7c0 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Entry.java @@ -0,0 +1,193 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ErrorEntryFreeException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.BiConsumer; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; + +/** + * Each {@link SphU}#entry() will return an {@link Entry}. This class holds information of current invocation:
    + * + *
      + *
    • createTime, the create time of this entry, using for rt statistics.
    • + *
    • current {@link Node}, that is statistics of the resource in current context.
    • + *
    • origin {@link Node}, that is statistics for the specific origin. Usually the + * origin could be the Service Consumer's app name, see + * {@link ContextUtil#enter(String name, String origin)}
    • + *
    • {@link ResourceWrapper}, that is resource name.
    • + *
      + *
    + * + *

    + * A invocation tree will be created if we invoke SphU#entry() multi times in the same {@link Context}, + * so parent or child entry may be held by this to form the tree. Since {@link Context} always holds + * the current entry in the invocation tree, every {@link Entry#exit()} call should modify + * {@link Context#setCurEntry(Entry)} as parent entry of this. + *

    + * + * @author qinan.qn + * @author jialiang.linjl + * @author leyou(lihao) + * @author Eric Zhao + * @see SphU + * @see Context + * @see ContextUtil + */ +public abstract class Entry implements AutoCloseable { + + private static final Object[] OBJECTS0 = new Object[0]; + + private final long createTimestamp; + private long completeTimestamp; + + private Node curNode; + /** + * {@link Node} of the specific origin, Usually the origin is the Service Consumer. + */ + private Node originNode; + + private Throwable error; + private BlockException blockError; + + protected final ResourceWrapper resourceWrapper; + + public Entry(ResourceWrapper resourceWrapper) { + this.resourceWrapper = resourceWrapper; + this.createTimestamp = TimeUtil.currentTimeMillis(); + } + + public ResourceWrapper getResourceWrapper() { + return resourceWrapper; + } + + /** + * Complete the current resource entry and restore the entry stack in context. + * + * @throws ErrorEntryFreeException if entry in current context does not match current entry + */ + public void exit() throws ErrorEntryFreeException { + exit(1, OBJECTS0); + } + + public void exit(int count) throws ErrorEntryFreeException { + exit(count, OBJECTS0); + } + + /** + * Equivalent to {@link #exit()}. Support try-with-resources since JDK 1.7. + * + * @since 1.5.0 + */ + @Override + public void close() { + exit(); + } + + /** + * Exit this entry. This method should invoke if and only if once at the end of the resource protection. + * + * @param count tokens to release. + * @param args extra parameters + * @throws ErrorEntryFreeException, if {@link Context#getCurEntry()} is not this entry. + */ + public abstract void exit(int count, Object... args) throws ErrorEntryFreeException; + + /** + * Exit this entry. + * + * @param count tokens to release. + * @param args extra parameters + * @return next available entry after exit, that is the parent entry. + * @throws ErrorEntryFreeException, if {@link Context#getCurEntry()} is not this entry. + */ + protected abstract Entry trueExit(int count, Object... args) throws ErrorEntryFreeException; + + /** + * Get related {@link Node} of the parent {@link Entry}. + * + * @return + */ + public abstract Node getLastNode(); + + public long getCreateTimestamp() { + return createTimestamp; + } + + public long getCompleteTimestamp() { + return completeTimestamp; + } + + public Entry setCompleteTimestamp(long completeTimestamp) { + this.completeTimestamp = completeTimestamp; + return this; + } + + public Node getCurNode() { + return curNode; + } + + public void setCurNode(Node node) { + this.curNode = node; + } + + public BlockException getBlockError() { + return blockError; + } + + public Entry setBlockError(BlockException blockError) { + this.blockError = blockError; + return this; + } + + public Throwable getError() { + return error; + } + + public void setError(Throwable error) { + this.error = error; + } + + /** + * Get origin {@link Node} of the this {@link Entry}. + * + * @return origin {@link Node} of the this {@link Entry}, may be null if no origin specified by + * {@link ContextUtil#enter(String name, String origin)}. + */ + public Node getOriginNode() { + return originNode; + } + + public void setOriginNode(Node originNode) { + this.originNode = originNode; + } + + /** + * Like {@code CompletableFuture} since JDK 8, it guarantees specified handler + * is invoked when this entry terminated (exited), no matter it's blocked or permitted. + * Use it when you did some STATEFUL operations on entries. + * + * @param handler handler function on the invocation terminates + * @since 1.8.0 + */ + public abstract void whenTerminate(BiConsumer handler); + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/EntryType.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/EntryType.java new file mode 100755 index 00000000..07408781 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/EntryType.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +/** + * An enum marks resource invocation direction. + * + * @author jialiang.linjl + * @author Yanming Zhou + */ +public enum EntryType { + /** + * Inbound traffic + */ + IN, + /** + * Outbound traffic + */ + OUT; + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Env.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Env.java new file mode 100755 index 00000000..de0ea0a5 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Env.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.CtSph; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Sph; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init.InitExecutor; + +/** + * Sentinel Env. This class will trigger all initialization for Sentinel. + * + *

    + * NOTE: to prevent deadlocks, other classes' static code block or static field should + * NEVER refer to this class. + *

    + * + * @author jialiang.linjl + */ +public class Env { + + public static final Sph sph = new CtSph(); + + static { + // If init fails, the process will exit. + InitExecutor.doInit(); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/ErrorEntryFreeException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/ErrorEntryFreeException.java new file mode 100755 index 00000000..ee244d04 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/ErrorEntryFreeException.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +/** + * Represents order mismatch of resource entry and resource exit (pair mismatch). + * + * @author qinan.qn + */ +public class ErrorEntryFreeException extends RuntimeException { + + public ErrorEntryFreeException(String s) { + super(s); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/ResourceTypeConstants.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/ResourceTypeConstants.java new file mode 100644 index 00000000..1c3b5bc3 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/ResourceTypeConstants.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +/** + * @author Eric Zhao + * @since 1.7.0 + */ +public final class ResourceTypeConstants { + + public static final int COMMON = 0; + public static final int COMMON_WEB = 1; + public static final int COMMON_RPC = 2; + public static final int COMMON_API_GATEWAY = 3; + public static final int COMMON_DB_SQL = 4; + + private ResourceTypeConstants() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Sph.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Sph.java new file mode 100755 index 00000000..c93ab9e9 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Sph.java @@ -0,0 +1,200 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import java.lang.reflect.Method; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.AsyncEntry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.SphResourceTypeSupport; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemRule; + +/** + * The basic interface for recording statistics and performing rule checking for resources. + * + * @author qinan.qn + * @author jialiang.linjl + * @author leyou + * @author Eric Zhao + */ +public interface Sph extends SphResourceTypeSupport { + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name of the protected resource + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). + * @throws BlockException if the block criteria is met + */ + Entry entry(String name) throws BlockException; + + /** + * Record statistics and perform rule checking for the given method. + * + * @param method the protected method + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). + * @throws BlockException if the block criteria is met + */ + Entry entry(Method method) throws BlockException; + + /** + * Record statistics and perform rule checking for the given method. + * + * @param method the protected method + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). + * @throws BlockException if the block criteria is met + */ + Entry entry(Method method, int batchCount) throws BlockException; + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique string for the resource + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). + * @throws BlockException if the block criteria is met + */ + Entry entry(String name, int batchCount) throws BlockException; + + /** + * Record statistics and perform rule checking for the given method. + * + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). + * @throws BlockException if the block criteria is met + */ + Entry entry(Method method, EntryType trafficType) throws BlockException; + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). + * @throws BlockException if the block criteria is met + */ + Entry entry(String name, EntryType trafficType) throws BlockException; + + /** + * Record statistics and perform rule checking for the given method. + * + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). + * @throws BlockException if the block criteria is met + */ + Entry entry(Method method, EntryType trafficType, int batchCount) throws BlockException; + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). + * @throws BlockException if the block criteria is met + */ + Entry entry(String name, EntryType trafficType, int batchCount) throws BlockException; + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args parameters of the method for flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). + * @throws BlockException if the block criteria is met + */ + Entry entry(Method method, EntryType trafficType, int batchCount, Object... args) throws BlockException; + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met + */ + Entry entry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException; + + /** + * Create a protected asynchronous resource. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return created asynchronous entry + * @throws BlockException if the block criteria is met + * @since 0.2.0 + */ + AsyncEntry asyncEntry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException; + + /** + * Create a protected resource with priority. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param prioritized whether the entry is prioritized + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met + * @since 1.4.0 + */ + Entry entryWithPriority(String name, EntryType trafficType, int batchCount, boolean prioritized) + throws BlockException; + + /** + * Create a protected resource with priority. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param prioritized whether the entry is prioritized + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met + * @since 1.5.0 + */ + Entry entryWithPriority(String name, EntryType trafficType, int batchCount, boolean prioritized, Object... args) + throws BlockException; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/SphO.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/SphO.java new file mode 100755 index 00000000..d690f76f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/SphO.java @@ -0,0 +1,229 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import java.lang.reflect.Method; +import java.util.List; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ErrorEntryFreeException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.SphU; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.Rule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemRuleManager; + +/** + * Conceptually, physical or logical resource that need protection should be + * surrounded by an entry. The requests to this resource will be blocked if any + * criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked, + * {@link SphO}#entry() will return false. + * + *

    + * To configure the criteria, we can use XXXRuleManager.loadRules() to add rules. eg. + * {@link FlowRuleManager#loadRules(List)}, {@link DegradeRuleManager#loadRules(List)}, + * {@link SystemRuleManager#loadRules(List)}. + *

    + * + *

    + * Following code is an example. {@code "abc"} represent a unique name for the + * protected resource: + *

    + * + *
    + * public void foo() {
    + *    if (SphO.entry("abc")) {
    + *        try {
    + *            // business logic
    + *        } finally {
    + *            SphO.exit(); // must exit()
    + *        }
    + *    } else {
    + *        // failed to enter the protected resource.
    + *    }
    + * }
    + * 
    + * + * Make sure {@code SphO.entry()} and {@link SphO#exit()} be paired in the same thread, + * otherwise {@link ErrorEntryFreeException} will be thrown. + * + * @author jialiang.linjl + * @author leyou + * @author Eric Zhao + * @see SphU + */ +public class SphO { + + private static final Object[] OBJECTS0 = new Object[0]; + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name of the protected resource + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(String name) { + return entry(name, EntryType.OUT, 1, OBJECTS0); + } + + /** + * Checking all {@link Rule}s about the protected method. + * + * @param method the protected method + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(Method method) { + return entry(method, EntryType.OUT, 1, OBJECTS0); + } + + /** + * Checking all {@link Rule}s about the protected method. + * + * @param method the protected method + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(Method method, int batchCount) { + return entry(method, EntryType.OUT, batchCount, OBJECTS0); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique string for the resource + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(String name, int batchCount) { + return entry(name, EntryType.OUT, batchCount, OBJECTS0); + } + + /** + * Checking all {@link Rule}s about the protected method. + * + * @param method the protected method + * @param type the resource is an inbound or an outbound method. This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(Method method, EntryType type) { + return entry(method, type, 1, OBJECTS0); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param type the resource is an inbound or an outbound method. This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(String name, EntryType type) { + return entry(name, type, 1, OBJECTS0); + } + + /** + * Checking all {@link Rule}s about the protected method. + * + * @param method the protected method + * @param type the resource is an inbound or an outbound method. This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param count the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(Method method, EntryType type, int count) { + return entry(method, type, count, OBJECTS0); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param type the resource is an inbound or an outbound method. This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param count the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(String name, EntryType type, int count) { + return entry(name, type, count, OBJECTS0); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(String name, EntryType trafficType, int batchCount, Object... args) { + try { + Env.sph.entry(name, trafficType, batchCount, args); + } catch (BlockException e) { + return false; + } catch (Throwable e) { + RecordLog.warn("SphO fatal error", e); + return true; + } + return true; + } + + /** + * Record statistics and perform rule checking for the given method resource. + * + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return true if no rule's threshold is exceeded, otherwise return false. + */ + public static boolean entry(Method method, EntryType trafficType, int batchCount, Object... args) { + try { + Env.sph.entry(method, trafficType, batchCount, args); + } catch (BlockException e) { + return false; + } catch (Throwable e) { + RecordLog.warn("SphO fatal error", e); + return true; + } + return true; + } + + public static void exit(int count, Object... args) { + ContextUtil.getContext().getCurEntry().exit(count, args); + } + + public static void exit(int count) { + ContextUtil.getContext().getCurEntry().exit(count, OBJECTS0); + } + + public static void exit() { + exit(1, OBJECTS0); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/SphResourceTypeSupport.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/SphResourceTypeSupport.java new file mode 100644 index 00000000..be2b5e37 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/SphResourceTypeSupport.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemRule; + +/** + * @author Eric Zhao + * @since 1.7.0 + */ +public interface SphResourceTypeSupport { + + /** + * Record statistics and perform rule checking for the given resource with provided classification. + * + * @param name the unique name of the protected resource + * @param resourceType the classification of the resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met + */ + Entry entryWithType(String name, int resourceType, EntryType trafficType, int batchCount, Object[] args) + throws BlockException; + + /** + * Record statistics and perform rule checking for the given resource with the provided classification. + * + * @param name the unique name of the protected resource + * @param resourceType classification of the resource (e.g. Web or RPC) + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param prioritized whether the entry is prioritized + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met + */ + Entry entryWithType(String name, int resourceType, EntryType trafficType, int batchCount, boolean prioritized, + Object[] args) throws BlockException; + + /** + * Record statistics and perform rule checking for the given resource that indicates an async invocation. + * + * @param name the unique name for the protected resource + * @param resourceType classification of the resource (e.g. Web or RPC) + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param prioritized whether the entry is prioritized + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met + */ + AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType trafficType, int batchCount, + boolean prioritized, + Object[] args) throws BlockException; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/SphU.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/SphU.java new file mode 100755 index 00000000..58295987 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/SphU.java @@ -0,0 +1,372 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import java.lang.reflect.Method; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.AsyncEntry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ErrorEntryFreeException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.Rule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemRule; + +/** + *

    The fundamental Sentinel API for recording statistics and performing rule checking for resources.

    + *

    + * Conceptually, physical or logical resource that need protection should be + * surrounded by an entry. The requests to this resource will be blocked if any + * criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked, + * a {@link BlockException} will be thrown. + *

    + *

    + * To configure the criteria, we can use XxxRuleManager.loadRules() to load rules. + *

    + * + *

    + * Following code is an example, {@code "abc"} represent a unique name for the + * protected resource: + *

    + * + *
    + *  public void foo() {
    + *     Entry entry = null;
    + *     try {
    + *        entry = SphU.entry("abc");
    + *        // resource that need protection
    + *     } catch (BlockException blockException) {
    + *         // when goes there, it is blocked
    + *         // add blocked handle logic here
    + *     } catch (Throwable bizException) {
    + *         // business exception
    + *         Tracer.trace(bizException);
    + *     } finally {
    + *         // ensure finally be executed
    + *         if (entry != null){
    + *             entry.exit();
    + *         }
    + *     }
    + *  }
    + * 
    + * + *

    + * Make sure {@code SphU.entry()} and {@link Entry#exit()} be paired in the same thread, + * otherwise {@link ErrorEntryFreeException} will be thrown. + *

    + * + * @author jialiang.linjl + * @author Eric Zhao + * @see SphO + */ +public class SphU { + + private static final Object[] OBJECTS0 = new Object[0]; + + private SphU() {} + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name of the protected resource + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(String name) throws BlockException { + return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0); + } + + /** + * Checking all {@link Rule}s about the protected method. + * + * @param method the protected method + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(Method method) throws BlockException { + return Env.sph.entry(method, EntryType.OUT, 1, OBJECTS0); + } + + /** + * Checking all {@link Rule}s about the protected method. + * + * @param method the protected method + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(Method method, int batchCount) throws BlockException { + return Env.sph.entry(method, EntryType.OUT, batchCount, OBJECTS0); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique string for the resource + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(String name, int batchCount) throws BlockException { + return Env.sph.entry(name, EntryType.OUT, batchCount, OBJECTS0); + } + + /** + * Checking all {@link Rule}s about the protected method. + * + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(Method method, EntryType trafficType) throws BlockException { + return Env.sph.entry(method, trafficType, 1, OBJECTS0); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(String name, EntryType trafficType) throws BlockException { + return Env.sph.entry(name, trafficType, 1, OBJECTS0); + } + + /** + * Checking all {@link Rule}s about the protected method. + * + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(Method method, EntryType trafficType, int batchCount) throws BlockException { + return Env.sph.entry(method, trafficType, batchCount, OBJECTS0); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(String name, EntryType trafficType, int batchCount) throws BlockException { + return Env.sph.entry(name, trafficType, batchCount, OBJECTS0); + } + + /** + * Checking all {@link Rule}s about the protected method. + * + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(Method method, EntryType trafficType, int batchCount, Object... args) + throws BlockException { + return Env.sph.entry(method, trafficType, batchCount, args); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + */ + public static Entry entry(String name, EntryType trafficType, int batchCount, Object... args) + throws BlockException { + return Env.sph.entry(name, trafficType, batchCount, args); + } + + /** + * Record statistics and check all rules of the resource that indicates an async invocation. + * + * @param name the unique name of the protected resource + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 0.2.0 + */ + public static AsyncEntry asyncEntry(String name) throws BlockException { + return Env.sph.asyncEntry(name, EntryType.OUT, 1, OBJECTS0); + } + + /** + * Record statistics and check all rules of the resource that indicates an async invocation. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 0.2.0 + */ + public static AsyncEntry asyncEntry(String name, EntryType trafficType) throws BlockException { + return Env.sph.asyncEntry(name, trafficType, 1, OBJECTS0); + } + + /** + * Record statistics and check all rules of the resource that indicates an async invocation. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 0.2.0 + */ + public static AsyncEntry asyncEntry(String name, EntryType trafficType, int batchCount, Object... args) + throws BlockException { + return Env.sph.asyncEntry(name, trafficType, batchCount, args); + } + + /** + * Record statistics and perform rule checking for the given resource. The entry is prioritized. + * + * @param name the unique name for the protected resource + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 1.4.0 + */ + public static Entry entryWithPriority(String name) throws BlockException { + return Env.sph.entryWithPriority(name, EntryType.OUT, 1, true); + } + + /** + * Record statistics and perform rule checking for the given resource. The entry is prioritized. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 1.4.0 + */ + public static Entry entryWithPriority(String name, EntryType trafficType) throws BlockException { + return Env.sph.entryWithPriority(name, trafficType, 1, true); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param resourceType classification of the resource (e.g. Web or RPC) + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 1.7.0 + */ + public static Entry entry(String name, int resourceType, EntryType trafficType) throws BlockException { + return Env.sph.entryWithType(name, resourceType, trafficType, 1, OBJECTS0); + } + + /** + * Record statistics and perform rule checking for the given resource. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param resourceType classification of the resource (e.g. Web or RPC) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 1.7.0 + */ + public static Entry entry(String name, int resourceType, EntryType trafficType, Object[] args) + throws BlockException { + return Env.sph.entryWithType(name, resourceType, trafficType, 1, args); + } + + /** + * Record statistics and perform rule checking for the given resource that indicates an async invocation. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param resourceType classification of the resource (e.g. Web or RPC) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 1.7.0 + */ + public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType) + throws BlockException { + return Env.sph.asyncEntryWithType(name, resourceType, trafficType, 1, false, OBJECTS0); + } + + /** + * Record statistics and perform rule checking for the given resource that indicates an async invocation. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param resourceType classification of the resource (e.g. Web or RPC) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 1.7.0 + */ + public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType, Object[] args) + throws BlockException { + return Env.sph.asyncEntryWithType(name, resourceType, trafficType, 1, false, args); + } + + /** + * Record statistics and perform rule checking for the given resource that indicates an async invocation. + * + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param resourceType classification of the resource (e.g. Web or RPC) + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) + * @since 1.7.0 + */ + public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType, int batchCount, + Object[] args) throws BlockException { + return Env.sph.asyncEntryWithType(name, resourceType, trafficType, batchCount, false, args); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Tracer.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Tracer.java new file mode 100755 index 00000000..8d7afdae --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/Tracer.java @@ -0,0 +1,226 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.NullContext; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Predicate; + +/** + * This class is used to record other exceptions except block exception. + * + * @author jialiang.linjl + * @author Eric Zhao + */ +public class Tracer { + + protected static Class[] traceClasses; + protected static Class[] ignoreClasses; + + protected static Predicate exceptionPredicate; + + protected Tracer() {} + + /** + * Trace provided {@link Throwable} to the resource entry in current context. + * + * @param e exception to record + */ + public static void trace(Throwable e) { + traceContext(e, ContextUtil.getContext()); + } + + /** + * Trace provided {@link Throwable} to current entry in current context. + * + * @param e exception to record + * @param count exception count to add + */ + @Deprecated + public static void trace(Throwable e, int count) { + traceContext(e, count, ContextUtil.getContext()); + } + + /** + * Trace provided {@link Throwable} to current entry of given entrance context. + * + * @param e exception to record + * @param context target entrance context + * @since 1.8.0 + */ + public static void traceContext(Throwable e, Context context) { + if (!shouldTrace(e)) { + return; + } + + if (context == null || context instanceof NullContext) { + return; + } + traceEntryInternal(e, context.getCurEntry()); + } + + /** + * Trace provided {@link Throwable} and add exception count to current entry in provided context. + * + * @param e exception to record + * @param count exception count to add + * @since 1.4.2 + */ + @Deprecated + public static void traceContext(Throwable e, int count, Context context) { + if (!shouldTrace(e)) { + return; + } + + if (context == null || context instanceof NullContext) { + return; + } + traceEntryInternal(e, context.getCurEntry()); + } + + /** + * Trace provided {@link Throwable} to the given resource entry. + * + * @param e exception to record + * @since 1.4.2 + */ + public static void traceEntry(Throwable e, Entry entry) { + if (!shouldTrace(e)) { + return; + } + traceEntryInternal(e, entry); + } + + private static void traceEntryInternal(/*@NeedToTrace*/ Throwable e, Entry entry) { + if (entry == null) { + return; + } + + entry.setError(e); + } + + /** + * Set exception to trace. If not set, all Exception except for {@link BlockException} will be traced. + *

    + * Note that if both {@link #setExceptionsToIgnore(Class[])} and this method is set, + * the ExceptionsToIgnore will be of higher precedence. + *

    + * + * @param traceClasses the list of exception classes to trace. + * @since 1.6.1 + */ + @SafeVarargs + public static void setExceptionsToTrace(Class... traceClasses) { + checkNotNull(traceClasses); + Tracer.traceClasses = traceClasses; + } + + /** + * Get exception classes to trace. + * + * @return an array of exception classes to trace. + * @since 1.6.1 + */ + public static Class[] getExceptionsToTrace() { + return traceClasses; + } + + /** + * Set exceptions to ignore. if not set, all Exception except for {@link BlockException} will be traced. + *

    + * Note that if both {@link #setExceptionsToTrace(Class[])} and this method is set, + * the ExceptionsToIgnore will be of higher precedence. + *

    + * + * @param ignoreClasses the list of exception classes to ignore. + * @since 1.6.1 + */ + @SafeVarargs + public static void setExceptionsToIgnore(Class... ignoreClasses) { + checkNotNull(ignoreClasses); + Tracer.ignoreClasses = ignoreClasses; + } + + /** + * Get exception classes to ignore. + * + * @return an array of exception classes to ignore. + * @since 1.6.1 + */ + public static Class[] getExceptionsToIgnore() { + return ignoreClasses; + } + + /** + * Get exception predicate + * @return the exception predicate. + */ + public static Predicate getExceptionPredicate() { + return exceptionPredicate; + } + + /** + * set an exception predicate which indicates the exception should be traced(return true) or ignored(return false) + * except for {@link BlockException} + * @param exceptionPredicate the exception predicate + */ + public static void setExceptionPredicate(Predicate exceptionPredicate) { + AssertUtil.notNull(exceptionPredicate, "exception predicate must not be null"); + Tracer.exceptionPredicate = exceptionPredicate; + } + + private static void checkNotNull(Class[] classes) { + AssertUtil.notNull(classes, "trace or ignore classes must not be null"); + for (Class clazz : classes) { + AssertUtil.notNull(clazz, "trace or ignore classes must not be null"); + } + } + + /** + * Check whether the throwable should be traced. + * + * @param t the throwable to check. + * @return true if the throwable should be traced, else return false. + */ + protected static boolean shouldTrace(Throwable t) { + if (t == null || t instanceof BlockException) { + return false; + } + if (exceptionPredicate != null) { + return exceptionPredicate.test(t); + } + + if (ignoreClasses != null) { + for (Class clazz : ignoreClasses) { + if (clazz != null && clazz.isAssignableFrom(t.getClass())) { + return false; + } + } + } + if (traceClasses != null) { + for (Class clazz : traceClasses) { + if (clazz != null && clazz.isAssignableFrom(t.getClass())) { + return true; + } + } + return false; + } + return true; + } +} \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/annotation/SentinelResource.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/annotation/SentinelResource.java new file mode 100644 index 00000000..2b8a013b --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/annotation/SentinelResource.java @@ -0,0 +1,106 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.annotation; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; + +import java.lang.annotation.*; + +/** + * The annotation indicates a definition of Sentinel resource. + * + * @author Eric Zhao + * @author zhaoyuguang + * @since 0.1.1 + */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +public @interface SentinelResource { + + /** + * @return name of the Sentinel resource + */ + String value() default ""; + + /** + * @return the entry type (inbound or outbound), outbound by default + */ + EntryType entryType() default EntryType.OUT; + + /** + * @return the classification (type) of the resource + * @since 1.7.0 + */ + int resourceType() default 0; + + /** + * @return name of the block exception function, empty by default + */ + String blockHandler() default ""; + + /** + * The {@code blockHandler} is located in the same class with the original method by default. + * However, if some methods share the same signature and intend to set the same block handler, + * then users can set the class where the block handler exists. Note that the block handler method + * must be static. + * + * @return the class where the block handler exists, should not provide more than one classes + */ + Class[] blockHandlerClass() default {}; + + /** + * @return name of the fallback function, empty by default + */ + String fallback() default ""; + + /** + * The {@code defaultFallback} is used as the default universal fallback method. + * It should not accept any parameters, and the return type should be compatible + * with the original method. + * + * @return name of the default fallback method, empty by default + * @since 1.6.0 + */ + String defaultFallback() default ""; + + /** + * The {@code fallback} is located in the same class with the original method by default. + * However, if some methods share the same signature and intend to set the same fallback, + * then users can set the class where the fallback function exists. Note that the shared fallback method + * must be static. + * + * @return the class where the fallback method is located (only single class) + * @since 1.6.0 + */ + Class[] fallbackClass() default {}; + + /** + * @return the list of exception classes to trace, {@link Throwable} by default + * @since 1.5.1 + */ + Class[] exceptionsToTrace() default {Throwable.class}; + + /** + * Indicates the exceptions to be ignored. Note that {@code exceptionsToTrace} should + * not appear with {@code exceptionsToIgnore} at the same time, or {@code exceptionsToIgnore} + * will be of higher precedence. + * + * @return the list of exception classes to ignore, empty by default + * @since 1.6.0 + */ + Class[] exceptionsToIgnore() default {}; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java new file mode 100644 index 00000000..266ac72c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/ClusterStateManager.java @@ -0,0 +1,274 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.client.ClusterTokenClient; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServer; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init.InitExecutor; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.DynamicSentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.PropertyListener; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; + +/** + *

    + * Global state manager for Sentinel cluster. + * This enables switching between cluster token client and server mode. + *

    + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterStateManager { + + public static final int CLUSTER_CLIENT = 0; + public static final int CLUSTER_SERVER = 1; + public static final int CLUSTER_NOT_STARTED = -1; + + private static volatile int mode = CLUSTER_NOT_STARTED; + private static volatile long lastModified = -1; + + private static volatile SentinelProperty stateProperty = new DynamicSentinelProperty(); + private static final PropertyListener PROPERTY_LISTENER = new ClusterStatePropertyListener(); + + static { + InitExecutor.doInit(); + stateProperty.addListener(PROPERTY_LISTENER); + } + + public static void registerProperty(SentinelProperty property) { + synchronized (PROPERTY_LISTENER) { + RecordLog.info("[ClusterStateManager] Registering new property to cluster state manager"); + stateProperty.removeListener(PROPERTY_LISTENER); + property.addListener(PROPERTY_LISTENER); + stateProperty = property; + } + } + + public static int getMode() { + return mode; + } + + public static boolean isClient() { + return mode == CLUSTER_CLIENT; + } + + public static boolean isServer() { + return mode == CLUSTER_SERVER; + } + + /** + *

    + * Set current mode to client mode. If Sentinel currently works in server mode, + * it will be turned off. Then the cluster client will be started. + *

    + */ + public static boolean setToClient() { + if (mode == CLUSTER_CLIENT) { + return true; + } + mode = CLUSTER_CLIENT; + sleepIfNeeded(); + lastModified = TimeUtil.currentTimeMillis(); + return startClient(); + } + + private static boolean startClient() { + try { + EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); + if (server != null) { + server.stop(); + } + ClusterTokenClient tokenClient = TokenClientProvider.getClient(); + if (tokenClient != null) { + tokenClient.start(); + RecordLog.info("[ClusterStateManager] Changing cluster mode to client"); + return true; + } else { + RecordLog.warn("[ClusterStateManager] Cannot change to client (no client SPI found)"); + return false; + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to client", ex); + return false; + } + } + + private static boolean stopClient() { + try { + ClusterTokenClient tokenClient = TokenClientProvider.getClient(); + if (tokenClient != null) { + tokenClient.stop(); + RecordLog.info("[ClusterStateManager] Stopping the cluster token client"); + return true; + } else { + RecordLog.warn("[ClusterStateManager] Cannot stop cluster token client (no server SPI found)"); + return false; + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when stopping cluster token client", ex); + return false; + } + } + + /** + *

    + * Set current mode to server mode. If Sentinel currently works in client mode, + * it will be turned off. Then the cluster server will be started. + *

    + */ + public static boolean setToServer() { + if (mode == CLUSTER_SERVER) { + return true; + } + mode = CLUSTER_SERVER; + sleepIfNeeded(); + lastModified = TimeUtil.currentTimeMillis(); + return startServer(); + } + + private static boolean startServer() { + try { + ClusterTokenClient tokenClient = TokenClientProvider.getClient(); + if (tokenClient != null) { + tokenClient.stop(); + } + EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); + if (server != null) { + server.start(); + RecordLog.info("[ClusterStateManager] Changing cluster mode to server"); + return true; + } else { + RecordLog.warn("[ClusterStateManager] Cannot change to server (no server SPI found)"); + return false; + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when changing cluster mode to server", ex); + return false; + } + } + + private static boolean stopServer() { + try { + EmbeddedClusterTokenServer server = EmbeddedClusterTokenServerProvider.getServer(); + if (server != null) { + server.stop(); + RecordLog.info("[ClusterStateManager] Stopping the cluster server"); + return true; + } else { + RecordLog.warn("[ClusterStateManager] Cannot stop server (no server SPI found)"); + return false; + } + } catch (Exception ex) { + RecordLog.warn("[ClusterStateManager] Error when stopping server", ex); + return false; + } + } + + /** + * The interval between two change operations should be greater than {@code MIN_INTERVAL} (by default 10s). + * Or we need to wait for a while. + */ + private static void sleepIfNeeded() { + if (lastModified <= 0) { + return; + } + long now = TimeUtil.currentTimeMillis(); + long durationPast = now - lastModified; + long estimated = durationPast - MIN_INTERVAL; + if (estimated < 0) { + try { + Thread.sleep(-estimated); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + public static long getLastModified() { + return lastModified; + } + + private static class ClusterStatePropertyListener implements PropertyListener { + @Override + public synchronized void configLoad(Integer value) { + applyStateInternal(value); + } + + @Override + public synchronized void configUpdate(Integer value) { + applyStateInternal(value); + } + } + + private static boolean applyStateInternal(Integer state) { + if (state == null || state < CLUSTER_NOT_STARTED) { + return false; + } + if (state == mode) { + return true; + } + try { + switch (state) { + case CLUSTER_CLIENT: + return setToClient(); + case CLUSTER_SERVER: + return setToServer(); + case CLUSTER_NOT_STARTED: + setStop(); + return true; + default: + RecordLog.warn("[ClusterStateManager] Ignoring unknown cluster state: " + state); + return false; + } + } catch (Throwable t) { + RecordLog.warn("[ClusterStateManager] Fatal error when applying state: " + state, t); + return false; + } + } + + private static void setStop() { + if (mode == CLUSTER_NOT_STARTED) { + return; + } + RecordLog.info("[ClusterStateManager] Changing cluster mode to not-started"); + mode = CLUSTER_NOT_STARTED; + + sleepIfNeeded(); + lastModified = TimeUtil.currentTimeMillis(); + + stopClient(); + stopServer(); + } + + /** + * Apply given state to cluster mode. + * + * @param state valid state to apply + */ + public static void applyState(Integer state) { + stateProperty.updateValue(state); + } + + public static void markToServer() { + mode = CLUSTER_SERVER; + } + + private static final int MIN_INTERVAL = 5 * 1000; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenResult.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenResult.java new file mode 100644 index 00000000..5ae7e57f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenResult.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster; + +import java.util.Map; + +/** + * Result entity of acquiring cluster flow token. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class TokenResult { + + private Integer status; + + private int remaining; + private int waitInMs; + + private long tokenId; + + private Map attachments; + + public TokenResult() { + } + + public TokenResult(Integer status) { + this.status = status; + } + + public long getTokenId() { + return tokenId; + } + + public void setTokenId(long tokenId) { + this.tokenId = tokenId; + } + + public Integer getStatus() { + return status; + } + + public TokenResult setStatus(Integer status) { + this.status = status; + return this; + } + + public int getRemaining() { + return remaining; + } + + public TokenResult setRemaining(int remaining) { + this.remaining = remaining; + return this; + } + + public int getWaitInMs() { + return waitInMs; + } + + public TokenResult setWaitInMs(int waitInMs) { + this.waitInMs = waitInMs; + return this; + } + + public Map getAttachments() { + return attachments; + } + + public TokenResult setAttachments(Map attachments) { + this.attachments = attachments; + return this; + } + + @Override + public String toString() { + return "TokenResult{" + + "status=" + status + + ", remaining=" + remaining + + ", waitInMs=" + waitInMs + + ", attachments=" + attachments + + ", tokenId=" + tokenId + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java new file mode 100644 index 00000000..3879c817 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class TokenResultStatus { + + /** + * Bad client request. + */ + public static final int BAD_REQUEST = -4; + /** + * Too many request in server. + */ + public static final int TOO_MANY_REQUEST = -2; + /** + * Server or client unexpected failure (due to transport or serialization failure). + */ + public static final int FAIL = -1; + + /** + * Token acquired. + */ + public static final int OK = 0; + + /** + * Token acquire failed (blocked). + */ + public static final int BLOCKED = 1; + /** + * Should wait for next buckets. + */ + public static final int SHOULD_WAIT = 2; + /** + * Token acquire failed (no rule exists). + */ + public static final int NO_RULE_EXISTS = 3; + /** + * Token acquire failed (reference resource is not available). + */ + public static final int NO_REF_RULE_EXISTS = 4; + /** + * Token acquire failed (strategy not available). + */ + public static final int NOT_AVAILABLE = 5; + /** + * Token is successfully released. + */ + public static final int RELEASE_OK = 6; + /** + * Token already is released before the request arrives. + */ + public static final int ALREADY_RELEASE=7; + + private TokenResultStatus() { + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java new file mode 100644 index 00000000..933d7b4a --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenServerDescriptor.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster; + +/** + * A simple descriptor for Sentinel token server. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class TokenServerDescriptor { + + private final String host; + private final int port; + + private String type = "default"; + + public TokenServerDescriptor(String host, int port) { + this.host = host; + this.port = port; + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getType() { + return type; + } + + public TokenServerDescriptor setType(String type) { + this.type = type; + return this; + } + + @Override + public String toString() { + return "TokenServerDescriptor{" + + "host='" + host + '\'' + + ", port=" + port + + ", type='" + type + '\'' + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenService.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenService.java new file mode 100644 index 00000000..52cb401d --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/TokenService.java @@ -0,0 +1,63 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster; + +import java.util.Collection; + +/** + * Service interface of flow control. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface TokenService { + + /** + * Request tokens from remote token server. + * + * @param ruleId the unique rule ID + * @param acquireCount token count to acquire + * @param prioritized whether the request is prioritized + * @return result of the token request + */ + TokenResult requestToken(Long ruleId, int acquireCount, boolean prioritized); + + /** + * Request tokens for a specific parameter from remote token server. + * + * @param ruleId the unique rule ID + * @param acquireCount token count to acquire + * @param params parameter list + * @return result of the token request + */ + TokenResult requestParamToken(Long ruleId, int acquireCount, Collection params); + + /** + * Request acquire concurrent tokens from remote token server. + * + * @param clientAddress the address of the request belong. + * @param ruleId ruleId the unique rule ID + * @param acquireCount token count to acquire + * @return result of the token request + */ + TokenResult requestConcurrentToken(String clientAddress,Long ruleId,int acquireCount); + /** + * Request release concurrent tokens from remote token server asynchronously. + * + * @param tokenId the unique token ID + */ + void releaseConcurrentToken(Long tokenId); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java new file mode 100644 index 00000000..ae52d579 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/client/ClusterTokenClient.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.client; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.TokenServerDescriptor; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.TokenService; + +/** + * Token client interface for distributed flow control. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ClusterTokenClient extends TokenService { + + /** + * Get descriptor of current token server. + * + * @return current token server if connected, otherwise null + */ + TokenServerDescriptor currentServer(); + + /** + * Start the token client. + * + * @throws Exception some error occurs + */ + void start() throws Exception; + + /** + * Stop the token client. + * + * @throws Exception some error occurs + */ + void stop() throws Exception; + + /** + * Get state of the cluster token client. + * + * @return state of the cluster token client + */ + int getState(); +} \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java new file mode 100644 index 00000000..8dfc48e8 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.client; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoader; + +/** + * Provider for a universal {@link ClusterTokenClient} instance. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public final class TokenClientProvider { + + private static ClusterTokenClient client = null; + + static { + // Not strictly thread-safe, but it's OK since it will be resolved only once. + resolveTokenClientInstance(); + } + + public static ClusterTokenClient getClient() { + return client; + } + + private static void resolveTokenClientInstance() { + ClusterTokenClient resolvedClient = SpiLoader.of(ClusterTokenClient.class).loadFirstInstance(); + if (resolvedClient == null) { + RecordLog.info( + "[TokenClientProvider] No existing cluster token client, cluster client mode will not be activated"); + } else { + client = resolvedClient; + RecordLog.info("[TokenClientProvider] Cluster token client resolved: {}", + client.getClass().getCanonicalName()); + } + } + + public static boolean isClientSpiAvailable() { + return getClient() != null; + } + + private TokenClientProvider() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java new file mode 100644 index 00000000..be3b877f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/log/ClusterClientStatLogUtil.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.log; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.EagleEye; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatLogger; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogBase; + +/** + * @author jialiang.linjl + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterClientStatLogUtil { + + private static final String FILE_NAME = "sentinel-cluster-client.log"; + + private static StatLogger statLogger; + + static { + String path = LogBase.getLogBaseDir() + FILE_NAME; + + statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-client-record") + .intervalSeconds(1) + .entryDelimiter('|') + .keyDelimiter(',') + .valueDelimiter(',') + .maxEntryCount(5000) + .configLogFilePath(path) + .maxFileSizeMB(300) + .maxBackupIndex(3) + .buildSingleton(); + } + + public static void log(String msg) { + statLogger.stat(msg).count(); + } + + public static void log(String msg, int count) { + statLogger.stat(msg).count(count); + } + + private ClusterClientStatLogUtil() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java new file mode 100644 index 00000000..e54f1e0c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/log/ClusterStatLogUtil.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.log; + +import java.io.File; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.EagleEye; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatLogger; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogBase; + +/** + * @author jialiang.linjl + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterStatLogUtil { + + private static final String FILE_NAME = "sentinel-cluster.log"; + + private static StatLogger statLogger; + + static { + String path = LogBase.getLogBaseDir() + FILE_NAME; + + statLogger = EagleEye.statLoggerBuilder("sentinel-cluster-record") + .intervalSeconds(1) + .entryDelimiter('|') + .keyDelimiter(',') + .valueDelimiter(',') + .maxEntryCount(5000) + .configLogFilePath(path) + .maxFileSizeMB(300) + .maxBackupIndex(3) + .buildSingleton(); + } + + public static void log(String msg) { + statLogger.stat(msg).count(); + } + + public static void log(String msg, int count) { + statLogger.stat(msg).count(count); + } + + private ClusterStatLogUtil() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java new file mode 100644 index 00000000..ddbd799f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/server/ClusterTokenServer.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.server; + +/** + * Token server interface for distributed flow control. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface ClusterTokenServer { + + /** + * Start the Sentinel cluster server. + * + * @throws Exception if any error occurs + */ + void start() throws Exception; + + /** + * Stop the Sentinel cluster server. + * + * @throws Exception if any error occurs + */ + void stop() throws Exception; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java new file mode 100644 index 00000000..4ca6fefa --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServer.java @@ -0,0 +1,27 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.server; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.TokenService; + +/** + * Embedded token server interface that can work in embedded mode. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public interface EmbeddedClusterTokenServer extends ClusterTokenServer, TokenService { +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java new file mode 100644 index 00000000..ee73fe09 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.server; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoader; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class EmbeddedClusterTokenServerProvider { + + private static EmbeddedClusterTokenServer server = null; + + static { + resolveInstance(); + } + + private static void resolveInstance() { + EmbeddedClusterTokenServer s = SpiLoader.of(EmbeddedClusterTokenServer.class).loadFirstInstance(); + if (s == null) { + RecordLog.warn("[EmbeddedClusterTokenServerProvider] No existing cluster token server, cluster server mode will not be activated"); + } else { + server = s; + RecordLog.info("[EmbeddedClusterTokenServerProvider] Cluster token server resolved: {}", + server.getClass().getCanonicalName()); + } + } + + public static EmbeddedClusterTokenServer getServer() { + return server; + } + + public static boolean isServerSpiAvailable() { + return getServer() != null; + } + + private EmbeddedClusterTokenServerProvider() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/concurrent/NamedThreadFactory.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/concurrent/NamedThreadFactory.java new file mode 100755 index 00000000..537b7f58 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/concurrent/NamedThreadFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.concurrent; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Wrapped thread factory for better use. + */ +public class NamedThreadFactory implements ThreadFactory { + + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + + private final String namePrefix; + private final boolean daemon; + + public NamedThreadFactory(String namePrefix, boolean daemon) { + this.daemon = daemon; + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : + Thread.currentThread().getThreadGroup(); + this.namePrefix = namePrefix; + } + + public NamedThreadFactory(String namePrefix) { + this(namePrefix, false); + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, namePrefix + "-thread-" + threadNumber.getAndIncrement(), 0); + t.setDaemon(daemon); + return t; + } +} \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/config/SentinelConfig.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/config/SentinelConfig.java new file mode 100755 index 00000000..a7764d57 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/config/SentinelConfig.java @@ -0,0 +1,346 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfigLoader; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +import java.io.File; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The universal local configuration center of Sentinel. The config is retrieved from command line arguments + * and customized properties file by default. + * + * @author leyou + * @author Eric Zhao + * @author Lin Liang + */ +public final class SentinelConfig { + + /** + * The default application type. + * + * @since 1.6.0 + */ + public static final int APP_TYPE_COMMON = 0; + + /** + * Parameter value for using context classloader. + */ + private static final String CLASSLOADER_CONTEXT = "context"; + + private static final Map props = new ConcurrentHashMap<>(); + + private static int appType = APP_TYPE_COMMON; + private static String appName = ""; + + public static final String PROJECT_NAME_PROP_KEY = "project.name"; + public static final String APP_NAME_PROP_KEY = "csp.sentinel.app.name"; + public static final String APP_TYPE_PROP_KEY = "csp.sentinel.app.type"; + public static final String CHARSET = "csp.sentinel.charset"; + public static final String SINGLE_METRIC_FILE_SIZE = "csp.sentinel.metric.file.single.size"; + public static final String TOTAL_METRIC_FILE_COUNT = "csp.sentinel.metric.file.total.count"; + public static final String COLD_FACTOR = "csp.sentinel.flow.cold.factor"; + public static final String STATISTIC_MAX_RT = "csp.sentinel.statistic.max.rt"; + public static final String SPI_CLASSLOADER = "csp.sentinel.spi.classloader"; + public static final String METRIC_FLUSH_INTERVAL = "csp.sentinel.metric.flush.interval"; + + public static final String DEFAULT_CHARSET = "UTF-8"; + public static final long DEFAULT_SINGLE_METRIC_FILE_SIZE = 1024 * 1024 * 50; + public static final int DEFAULT_TOTAL_METRIC_FILE_COUNT = 6; + public static final int DEFAULT_COLD_FACTOR = 3; + public static final int DEFAULT_STATISTIC_MAX_RT = 5000; + public static final long DEFAULT_METRIC_FLUSH_INTERVAL = 1L; + + static { + try { + initialize(); + loadProps(); + resolveAppName(); + resolveAppType(); + RecordLog.info("[SentinelConfig] Application type resolved: {}", appType); + } catch (Throwable ex) { + RecordLog.warn("[SentinelConfig] Failed to initialize", ex); + ex.printStackTrace(); + } + } + + private static void resolveAppType() { + try { + String type = getConfig(APP_TYPE_PROP_KEY); + if (type == null) { + appType = APP_TYPE_COMMON; + return; + } + appType = Integer.parseInt(type); + if (appType < 0) { + appType = APP_TYPE_COMMON; + } + } catch (Exception ex) { + appType = APP_TYPE_COMMON; + } + } + + private static void initialize() { + // Init default properties. + setConfig(CHARSET, DEFAULT_CHARSET); + setConfig(SINGLE_METRIC_FILE_SIZE, String.valueOf(DEFAULT_SINGLE_METRIC_FILE_SIZE)); + setConfig(TOTAL_METRIC_FILE_COUNT, String.valueOf(DEFAULT_TOTAL_METRIC_FILE_COUNT)); + setConfig(COLD_FACTOR, String.valueOf(DEFAULT_COLD_FACTOR)); + setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT)); + setConfig(METRIC_FLUSH_INTERVAL, String.valueOf(DEFAULT_METRIC_FLUSH_INTERVAL)); + } + + private static void loadProps() { + Properties properties = SentinelConfigLoader.getProperties(); + for (Object key : properties.keySet()) { + setConfig((String) key, (String) properties.get(key)); + } + } + + /** + * Get config value of the specific key. + * + * @param key config key + * @return the config value. + */ + public static String getConfig(String key) { + AssertUtil.notNull(key, "key cannot be null"); + return props.get(key); + } + + /** + * Get config value of the specific key. + * + * @param key config key + * @param envVariableKey Get the value of the environment variable with the given key + * @return the config value. + */ + public static String getConfig(String key, boolean envVariableKey) { + AssertUtil.notNull(key, "key cannot be null"); + if (envVariableKey) { + String value = System.getenv(key); + if (StringUtil.isNotEmpty(value)) { + return value; + } + } + return getConfig(key); + } + + public static void setConfig(String key, String value) { + AssertUtil.notNull(key, "key cannot be null"); + AssertUtil.notNull(value, "value cannot be null"); + props.put(key, value); + } + + public static String removeConfig(String key) { + AssertUtil.notNull(key, "key cannot be null"); + return props.remove(key); + } + + public static void setConfigIfAbsent(String key, String value) { + AssertUtil.notNull(key, "key cannot be null"); + AssertUtil.notNull(value, "value cannot be null"); + String v = props.get(key); + if (v == null) { + props.put(key, value); + } + } + + public static String getAppName() { + return appName; + } + + /** + * Get application type. + * + * @return application type, common (0) by default + * @since 1.6.0 + */ + public static int getAppType() { + return appType; + } + + public static String charset() { + return props.get(CHARSET); + } + + /** + * Get the metric log flush interval in second + * @return the metric log flush interval in second + * @since 1.8.1 + */ + public static long metricLogFlushIntervalSec() { + String flushIntervalStr = SentinelConfig.getConfig(METRIC_FLUSH_INTERVAL); + if (flushIntervalStr == null) { + return DEFAULT_METRIC_FLUSH_INTERVAL; + } + try { + return Long.parseLong(flushIntervalStr); + } catch (Throwable throwable) { + RecordLog.warn("[SentinelConfig] Parse the metricLogFlushInterval fail, use default value: " + + DEFAULT_METRIC_FLUSH_INTERVAL, throwable); + return DEFAULT_METRIC_FLUSH_INTERVAL; + } + } + + public static long singleMetricFileSize() { + try { + return Long.parseLong(props.get(SINGLE_METRIC_FILE_SIZE)); + } catch (Throwable throwable) { + RecordLog.warn("[SentinelConfig] Parse singleMetricFileSize fail, use default value: " + + DEFAULT_SINGLE_METRIC_FILE_SIZE, throwable); + return DEFAULT_SINGLE_METRIC_FILE_SIZE; + } + } + + public static int totalMetricFileCount() { + try { + return Integer.parseInt(props.get(TOTAL_METRIC_FILE_COUNT)); + } catch (Throwable throwable) { + RecordLog.warn("[SentinelConfig] Parse totalMetricFileCount fail, use default value: " + + DEFAULT_TOTAL_METRIC_FILE_COUNT, throwable); + return DEFAULT_TOTAL_METRIC_FILE_COUNT; + } + } + + public static int coldFactor() { + try { + int coldFactor = Integer.parseInt(props.get(COLD_FACTOR)); + // check the cold factor larger than 1 + if (coldFactor <= 1) { + coldFactor = DEFAULT_COLD_FACTOR; + RecordLog.warn("cold factor=" + coldFactor + ", should be larger than 1, use default value: " + + DEFAULT_COLD_FACTOR); + } + return coldFactor; + } catch (Throwable throwable) { + RecordLog.warn("[SentinelConfig] Parse coldFactor fail, use default value: " + + DEFAULT_COLD_FACTOR, throwable); + return DEFAULT_COLD_FACTOR; + } + } + + /** + *

    Get the max RT value that Sentinel could accept for system BBR strategy.

    + * + * @return the max allowed RT value + * @since 1.4.1 + */ + public static int statisticMaxRt() { + String v = props.get(STATISTIC_MAX_RT); + try { + if (StringUtil.isEmpty(v)) { + return DEFAULT_STATISTIC_MAX_RT; + } + return Integer.parseInt(v); + } catch (Throwable throwable) { + RecordLog.warn("[SentinelConfig] Invalid statisticMaxRt value: {}, using the default value instead: " + + DEFAULT_STATISTIC_MAX_RT, v, throwable); + SentinelConfig.setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT)); + return DEFAULT_STATISTIC_MAX_RT; + } + } + + /** + * Function for resolving project name. The order is elaborated below: + * + *
      + *
    1. Resolve the value from {@code CSP_SENTINEL_APP_NAME} system environment;
    2. + *
    3. Resolve the value from {@code csp.sentinel.app.name} system property;
    4. + *
    5. Resolve the value from {@code project.name} system property (for compatibility);
    6. + *
    7. Resolve the value from {@code sun.java.command} system property, then remove path, arguments and ".jar" or ".JAR" + * suffix, use the result as app name. Note that whitespace in file name or path is not allowed, or a + * wrong app name may be gotten, For example: + *

      + * + * "test.Main" -> test.Main
      + * "/target/test.Main" -> test.Main
      + * "/target/test.Main args1" -> test.Main
      + * "Main.jar" -> Main
      + * "/target/Main.JAR args1" -> Main
      + * "Mai n.jar" -> Mai // whitespace in file name is not allowed
      + *
      + *

      + *
    8. + *
    + */ + private static void resolveAppName() { + // Priority: system env -> csp.sentinel.app.name -> project.name -> main class (or jar) name + String envKey = toEnvKey(APP_NAME_PROP_KEY); + String n = System.getenv(envKey); + if (!StringUtil.isBlank(n)) { + appName = n; + RecordLog.info("App name resolved from system env {}: {}", envKey, appName); + return; + } + n = props.get(APP_NAME_PROP_KEY); + if (!StringUtil.isBlank(n)) { + appName = n; + RecordLog.info("App name resolved from property {}: {}", APP_NAME_PROP_KEY, appName); + return; + } + n = props.get(PROJECT_NAME_PROP_KEY); + if (!StringUtil.isBlank(n)) { + appName = n; + RecordLog.info("App name resolved from property {}: {}", PROJECT_NAME_PROP_KEY, appName); + return; + } + // Parse sun.java.command property by default. + String command = System.getProperty("sun.java.command"); + if (StringUtil.isBlank(command)) { + RecordLog.warn("Cannot resolve default appName from property sun.java.command"); + return; + } + command = command.split("\\s")[0]; + String separator = File.separator; + if (command.contains(separator)) { + String[] strs; + if ("\\".equals(separator)) { + // Handle separator in Windows. + strs = command.split("\\\\"); + } else { + strs = command.split(separator); + } + command = strs[strs.length - 1]; + } + if (command.toLowerCase().endsWith(".jar")) { + command = command.substring(0, command.length() - 4); + } + appName = command; + RecordLog.info("App name resolved from default: {}", appName); + } + + private static String toEnvKey(/*@NotBlank*/ String propKey) { + return propKey.toUpperCase().replace('.', '_'); + } + /** + * Whether use context classloader via config parameter + * + * @return Whether use context classloader + */ + public static boolean shouldUseContextClassloader() { + String classloaderConf = SentinelConfig.getConfig(SentinelConfig.SPI_CLASSLOADER); + return CLASSLOADER_CONTEXT.equalsIgnoreCase(classloaderConf); + } + + private SentinelConfig() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java new file mode 100644 index 00000000..283cd4d2 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AppNameUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.ConfigUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +import java.io.File; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.CopyOnWriteArraySet; + +import static com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator; + +/** + *

    The loader that responsible for loading Sentinel common configurations.

    + * + * @author lianglin + * @since 1.7.0 + */ +public final class SentinelConfigLoader { + + public static final String SENTINEL_CONFIG_ENV_KEY = "CSP_SENTINEL_CONFIG_FILE"; + public static final String SENTINEL_CONFIG_PROPERTY_KEY = "csp.sentinel.config.file"; + + private static final String DEFAULT_SENTINEL_CONFIG_FILE = "classpath:sentinel.properties"; + + private static Properties properties = new Properties(); + + static { + try { + load(); + } catch (Throwable t) { + RecordLog.warn("[SentinelConfigLoader] Failed to initialize configuration items", t); + } + } + + private static void load() { + // Order: system property -> system env -> default file (classpath:sentinel.properties) -> legacy path + String fileName = System.getProperty(SENTINEL_CONFIG_PROPERTY_KEY); + if (StringUtil.isBlank(fileName)) { + fileName = System.getenv(SENTINEL_CONFIG_ENV_KEY); + if (StringUtil.isBlank(fileName)) { + fileName = DEFAULT_SENTINEL_CONFIG_FILE; + } + } + + Properties p = ConfigUtil.loadProperties(fileName); + if (p != null && !p.isEmpty()) { + RecordLog.info("[SentinelConfigLoader] Loading Sentinel config from {}", fileName); + properties.putAll(p); + } + + for (Map.Entry entry : new CopyOnWriteArraySet<>(System.getProperties().entrySet())) { + String configKey = entry.getKey().toString(); + String newConfigValue = entry.getValue().toString(); + String oldConfigValue = properties.getProperty(configKey); + properties.put(configKey, newConfigValue); + if (oldConfigValue != null) { + RecordLog.info("[SentinelConfigLoader] JVM parameter overrides {}: {} -> {}", + configKey, oldConfigValue, newConfigValue); + } + } + } + + + public static Properties getProperties() { + return properties; + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/Context.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/Context.java new file mode 100755 index 00000000..360e1206 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/Context.java @@ -0,0 +1,202 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.SphO; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.SphU; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.EntranceNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; + +/** + * This class holds metadata of current invocation:
    + * + *
      + *
    • the {@link EntranceNode}: the root of the current invocation + * tree.
    • + *
    • the current {@link Entry}: the current invocation point.
    • + *
    • the current {@link Node}: the statistics related to the + * {@link Entry}.
    • + *
    • the origin: The origin is useful when we want to control different + * invoker/consumer separately. Usually the origin could be the Service Consumer's app name + * or origin IP.
    • + *
    + *

    + * Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context}, + * if we don't invoke {@link ContextUtil}#enter() explicitly, DEFAULT context will be used. + *

    + *

    + * A invocation tree will be created if we invoke {@link SphU}#entry() multi times in + * the same context. + *

    + *

    + * Same resource in different context will count separately, see {@link NodeSelectorSlot}. + *

    + * + * @author jialiang.linjl + * @author leyou(lihao) + * @author Eric Zhao + * @see ContextUtil + * @see NodeSelectorSlot + */ +public class Context { + + /** + * Context name. + */ + private final String name; + + /** + * The entrance node of current invocation tree. + */ + private DefaultNode entranceNode; + + /** + * Current processing entry. + */ + private Entry curEntry; + + /** + * The origin of this context (usually indicate different invokers, e.g. service consumer name or origin IP). + */ + private String origin = ""; + + private final boolean async; + + /** + * Create a new async context. + * + * @param entranceNode entrance node of the context + * @param name context name + * @return the new created context + * @since 0.2.0 + */ + public static Context newAsyncContext(DefaultNode entranceNode, String name) { + return new Context(name, entranceNode, true); + } + + public Context(DefaultNode entranceNode, String name) { + this(name, entranceNode, false); + } + + public Context(String name, DefaultNode entranceNode, boolean async) { + this.name = name; + this.entranceNode = entranceNode; + this.async = async; + } + + public boolean isAsync() { + return async; + } + + public String getName() { + return name; + } + + public Node getCurNode() { + return curEntry == null ? null : curEntry.getCurNode(); + } + + public Context setCurNode(Node node) { + this.curEntry.setCurNode(node); + return this; + } + + public Entry getCurEntry() { + return curEntry; + } + + public Context setCurEntry(Entry curEntry) { + this.curEntry = curEntry; + return this; + } + + public String getOrigin() { + return origin; + } + + public Context setOrigin(String origin) { + this.origin = origin; + return this; + } + + public double getOriginTotalQps() { + return getOriginNode() == null ? 0 : getOriginNode().totalQps(); + } + + public double getOriginBlockQps() { + return getOriginNode() == null ? 0 : getOriginNode().blockQps(); + } + + public double getOriginPassReqQps() { + return getOriginNode() == null ? 0 : getOriginNode().successQps(); + } + + public double getOriginPassQps() { + return getOriginNode() == null ? 0 : getOriginNode().passQps(); + } + + public long getOriginTotalRequest() { + return getOriginNode() == null ? 0 : getOriginNode().totalRequest(); + } + + public long getOriginBlockRequest() { + return getOriginNode() == null ? 0 : getOriginNode().blockRequest(); + } + + public double getOriginAvgRt() { + return getOriginNode() == null ? 0 : getOriginNode().avgRt(); + } + + public int getOriginCurThreadNum() { + return getOriginNode() == null ? 0 : getOriginNode().curThreadNum(); + } + + public DefaultNode getEntranceNode() { + return entranceNode; + } + + /** + * Get the parent {@link Node} of the current. + * + * @return the parent node of the current. + */ + public Node getLastNode() { + if (curEntry != null && curEntry.getLastNode() != null) { + return curEntry.getLastNode(); + } else { + return entranceNode; + } + } + + public Node getOriginNode() { + return curEntry == null ? null : curEntry.getOriginNode(); + } + + @Override + public String toString() { + return "Context{" + + "name='" + name + '\'' + + ", entranceNode=" + entranceNode + + ", curEntry=" + curEntry + + ", origin='" + origin + '\'' + + ", async=" + async + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/ContextNameDefineException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/ContextNameDefineException.java new file mode 100755 index 00000000..dc523697 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/ContextNameDefineException.java @@ -0,0 +1,26 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context; + +/** + * @author qinan.qn + */ +public class ContextNameDefineException extends RuntimeException { + + public ContextNameDefineException(String message) { + super(message); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/ContextUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/ContextUtil.java new file mode 100755 index 00000000..6bfda0bc --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/ContextUtil.java @@ -0,0 +1,283 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.SphO; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.SphU; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextNameDefineException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.NullContext; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.EntranceNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; + +/** + * Utility class to get or create {@link Context} in current thread. + * + *

    + * Each {@link SphU}#entry() or {@link SphO}#entry() should be in a {@link Context}. + * If we don't invoke {@link ContextUtil}#enter() explicitly, DEFAULT context will be used. + *

    + * + * @author jialiang.linjl + * @author leyou(lihao) + * @author Eric Zhao + */ +public class ContextUtil { + + /** + * Store the context in ThreadLocal for easy access. + */ + private static ThreadLocal contextHolder = new ThreadLocal<>(); + + /** + * Holds all {@link EntranceNode}. Each {@link EntranceNode} is associated with a distinct context name. + */ + private static volatile Map contextNameNodeMap = new HashMap<>(); + + private static final ReentrantLock LOCK = new ReentrantLock(); + private static final Context NULL_CONTEXT = new NullContext(); + + static { + // Cache the entrance node for default context. + initDefaultContext(); + } + + private static void initDefaultContext() { + String defaultContextName = Constants.CONTEXT_DEFAULT_NAME; + EntranceNode node = new EntranceNode(new StringResourceWrapper(defaultContextName, EntryType.IN), null); + Constants.ROOT.addChild(node); + contextNameNodeMap.put(defaultContextName, node); + } + + /** + * Not thread-safe, only for test. + */ + static void resetContextMap() { + if (contextNameNodeMap != null) { + RecordLog.warn("Context map cleared and reset to initial state"); + contextNameNodeMap.clear(); + initDefaultContext(); + } + } + + /** + *

    + * Enter the invocation context, which marks as the entrance of an invocation chain. + * The context is wrapped with {@code ThreadLocal}, meaning that each thread has it's own {@link Context}. + * New context will be created if current thread doesn't have one. + *

    + *

    + * A context will be bound with an {@link EntranceNode}, which represents the entrance statistic node + * of the invocation chain. New {@link EntranceNode} will be created if + * current context does't have one. Note that same context name will share + * same {@link EntranceNode} globally. + *

    + *

    + * The origin node will be created in {@link com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot}. + * Note that each distinct {@code origin} of different resources will lead to creating different new + * {@link Node}, meaning that total amount of created origin statistic nodes will be:
    + * {@code distinct resource name amount * distinct origin count}.
    + * So when there are too many origins, memory footprint should be carefully considered. + *

    + *

    + * Same resource in different context will count separately, see {@link NodeSelectorSlot}. + *

    + * + * @param name the context name + * @param origin the origin of this invocation, usually the origin could be the Service + * Consumer's app name. The origin is useful when we want to control different + * invoker/consumer separately. + * @return The invocation context of the current thread + */ + public static Context enter(String name, String origin) { + if (Constants.CONTEXT_DEFAULT_NAME.equals(name)) { + throw new ContextNameDefineException( + "The " + Constants.CONTEXT_DEFAULT_NAME + " can't be permit to defined!"); + } + return trueEnter(name, origin); + } + + protected static Context trueEnter(String name, String origin) { + Context context = contextHolder.get(); + if (context == null) { + Map localCacheNameMap = contextNameNodeMap; + DefaultNode node = localCacheNameMap.get(name); + if (node == null) { + if (localCacheNameMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { + setNullContext(); + return NULL_CONTEXT; + } else { + LOCK.lock(); + try { + node = contextNameNodeMap.get(name); + if (node == null) { + if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { + setNullContext(); + return NULL_CONTEXT; + } else { + node = new EntranceNode(new StringResourceWrapper(name, EntryType.IN), null); + // Add entrance node. + Constants.ROOT.addChild(node); + + Map newMap = new HashMap<>(contextNameNodeMap.size() + 1); + newMap.putAll(contextNameNodeMap); + newMap.put(name, node); + contextNameNodeMap = newMap; + } + } + } finally { + LOCK.unlock(); + } + } + } + context = new Context(node, name); + context.setOrigin(origin); + contextHolder.set(context); + } + + return context; + } + + private static boolean shouldWarn = true; + + private static void setNullContext() { + contextHolder.set(NULL_CONTEXT); + // Don't need to be thread-safe. + if (shouldWarn) { + RecordLog.warn("[SentinelStatusChecker] WARN: Amount of context exceeds the threshold " + + Constants.MAX_CONTEXT_NAME_SIZE + ". Entries in new contexts will NOT take effect!"); + shouldWarn = false; + } + } + + /** + *

    + * Enter the invocation context, which marks as the entrance of an invocation chain. + * The context is wrapped with {@code ThreadLocal}, meaning that each thread has it's own {@link Context}. + * New context will be created if current thread doesn't have one. + *

    + *

    + * A context will be bound with an {@link EntranceNode}, which represents the entrance statistic node + * of the invocation chain. New {@link EntranceNode} will be created if + * current context does't have one. Note that same context name will share + * same {@link EntranceNode} globally. + *

    + *

    + * Same resource in different context will count separately, see {@link NodeSelectorSlot}. + *

    + * + * @param name the context name + * @return The invocation context of the current thread + */ + public static Context enter(String name) { + return enter(name, ""); + } + + /** + * Exit context of current thread, that is removing {@link Context} in the + * ThreadLocal. + */ + public static void exit() { + Context context = contextHolder.get(); + if (context != null && context.getCurEntry() == null) { + contextHolder.set(null); + } + } + + /** + * Get current size of context entrance node map. + * + * @return current size of context entrance node map + * @since 0.2.0 + */ + public static int contextSize() { + return contextNameNodeMap.size(); + } + + /** + * Check if provided context is a default auto-created context. + * + * @param context context to check + * @return true if it is a default context, otherwise false + * @since 0.2.0 + */ + public static boolean isDefaultContext(Context context) { + if (context == null) { + return false; + } + return Constants.CONTEXT_DEFAULT_NAME.equals(context.getName()); + } + + /** + * Get {@link Context} of current thread. + * + * @return context of current thread. Null value will be return if current + * thread does't have context. + */ + public static Context getContext() { + return contextHolder.get(); + } + + /** + *

    + * Replace current context with the provided context. + * This is mainly designed for context switching (e.g. in asynchronous invocation). + *

    + *

    + * Note: When switching context manually, remember to restore the original context. + * For common scenarios, you can use {@link #runOnContext(Context, Runnable)}. + *

    + * + * @param newContext new context to set + * @return old context + * @since 0.2.0 + */ + static Context replaceContext(Context newContext) { + Context backupContext = contextHolder.get(); + if (newContext == null) { + contextHolder.remove(); + } else { + contextHolder.set(newContext); + } + return backupContext; + } + + /** + * Execute the code within provided context. + * This is mainly designed for context switching (e.g. in asynchronous invocation). + * + * @param context the context + * @param f lambda to run within the context + * @since 0.2.0 + */ + public static void runOnContext(Context context, Runnable f) { + Context curContext = replaceContext(context); + try { + f.run(); + } finally { + replaceContext(curContext); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/NullContext.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/NullContext.java new file mode 100755 index 00000000..c7c4f8ea --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/context/NullContext.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; + +/** + * If total {@link Context} exceed {@link Constants#MAX_CONTEXT_NAME_SIZE}, a + * {@link NullContext} will get when invoke {@link ContextUtil}.enter(), means + * no rules checking will do. + * + * @author qinan.qn + */ +public class NullContext extends Context { + + public NullContext() { + super(null, "null_context_internal"); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/BaseLoggerBuilder.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/BaseLoggerBuilder.java new file mode 100755 index 00000000..d8d040c4 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/BaseLoggerBuilder.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.EagleEyeCoreUtils; + +class BaseLoggerBuilder> { + + protected final String loggerName; + + protected String filePath = null; + + protected long maxFileSize = 1024; + + protected char entryDelimiter = '|'; + + protected int maxBackupIndex = 3; + + BaseLoggerBuilder(String loggerName) { + this.loggerName = loggerName; + } + + public T logFilePath(String logFilePath) { + return configLogFilePath(logFilePath, EagleEye.EAGLEEYE_LOG_DIR); + } + + public T appFilePath(String appFilePath) { + return configLogFilePath(appFilePath, EagleEye.APP_LOG_DIR); + } + + public T baseLogFilePath(String baseLogFilePath) { + return configLogFilePath(baseLogFilePath, EagleEye.BASE_LOG_DIR); + } + + @SuppressWarnings("unchecked") + private T configLogFilePath(String filePathToConfig, String basePath) { + EagleEyeCoreUtils.checkNotNullEmpty(filePathToConfig, "filePath"); + if (filePathToConfig.charAt(0) != '/') { + filePathToConfig = basePath + filePathToConfig; + } + this.filePath = filePathToConfig; + return (T)this; + } + + @SuppressWarnings("unchecked") + public T configLogFilePath(String filePath) { + EagleEyeCoreUtils.checkNotNullEmpty(filePath, "filePath"); + this.filePath = filePath; + return (T)this; + } + + @SuppressWarnings("unchecked") + public T maxFileSizeMB(long maxFileSizeMB) { + if (maxFileSize < 10) { + throw new IllegalArgumentException("Invalid maxFileSizeMB"); + } + this.maxFileSize = maxFileSizeMB * 1024 * 1024; + return (T)this; + } + + @SuppressWarnings("unchecked") + public T maxBackupIndex(int maxBackupIndex) { + if (maxBackupIndex < 1) { + throw new IllegalArgumentException(""); + } + this.maxBackupIndex = maxBackupIndex; + return (T)this; + } + + @SuppressWarnings("unchecked") + public T entryDelimiter(char entryDelimiter) { + this.entryDelimiter = entryDelimiter; + return (T)this; + } + + String getLoggerName() { + return loggerName; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEye.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEye.java new file mode 100755 index 00000000..75e8ba1e --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEye.java @@ -0,0 +1,237 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.*; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.concurrent.TimeUnit; + +public final class EagleEye { + + public static final String CLASS_LOCATION = getEagleEyeLocation(); + + static final String USER_HOME = locateUserHome(); + + static final String BASE_LOG_DIR = locateBaseLogPath(); + + static final String EAGLEEYE_LOG_DIR = locateEagleEyeLogPath(); + + static final String APP_LOG_DIR = locateAppLogPath(); + + static final Charset DEFAULT_CHARSET = getDefaultOutputCharset(); + + static final String EAGLEEYE_SELF_LOG_FILE = EagleEye.EAGLEEYE_LOG_DIR + "eagleeye-self.log"; + + // 200MB + static final long MAX_SELF_LOG_FILE_SIZE = 200 * 1024 * 1024; + + static EagleEyeAppender selfAppender = createSelfLogger(); + + static private TokenBucket exceptionBucket = new TokenBucket(10, TimeUnit.SECONDS.toMillis(10)); + + static String getEagleEyeLocation() { + try { + URL resource = EagleEye.class.getProtectionDomain().getCodeSource().getLocation(); + if (resource != null) { + return resource.toString(); + } + } catch (Throwable t) { + // ignore + } + return "unknown location"; + } + + static Charset getDefaultOutputCharset() { + Charset cs; + String charsetName = EagleEyeCoreUtils.getSystemProperty("EAGLEEYE.CHARSET"); + if (EagleEyeCoreUtils.isNotBlank(charsetName)) { + charsetName = charsetName.trim(); + try { + cs = Charset.forName(charsetName); + if (cs != null) { + return cs; + } + } catch (Exception e) { + // quietly + } + } + try { + cs = Charset.forName("GB18030"); + } catch (Exception e) { + try { + cs = Charset.forName("GBK"); + } catch (Exception e2) { + cs = Charset.forName("UTF-8"); + } + } + return cs; + } + + private static String locateUserHome() { + String userHome = EagleEyeCoreUtils.getSystemProperty("user.home"); + if (EagleEyeCoreUtils.isNotBlank(userHome)) { + if (!userHome.endsWith(File.separator)) { + userHome += File.separator; + } + } else { + userHome = "/tmp/"; + } + return userHome; + } + + private static String locateBaseLogPath() { + String tmpPath = EagleEyeCoreUtils.getSystemProperty("JM.LOG.PATH"); + if (EagleEyeCoreUtils.isNotBlank(tmpPath)) { + if (!tmpPath.endsWith(File.separator)) { + tmpPath += File.separator; + } + } else { + tmpPath = USER_HOME + "logs" + File.separator; + } + return tmpPath; + } + + private static String locateEagleEyeLogPath() { + String tmpPath = EagleEyeCoreUtils.getSystemProperty("EAGLEEYE.LOG.PATH"); + if (EagleEyeCoreUtils.isNotBlank(tmpPath)) { + if (!tmpPath.endsWith(File.separator)) { + tmpPath += File.separator; + } + } else { + tmpPath = BASE_LOG_DIR + "eagleeye" + File.separator; + } + return tmpPath; + } + + private static String locateAppLogPath() { + String appName = EagleEyeCoreUtils.getSystemProperty("project.name"); + if (EagleEyeCoreUtils.isNotBlank(appName)) { + return USER_HOME + appName + File.separator + "logs" + File.separator; + } else { + return EAGLEEYE_LOG_DIR; + } + } + + static private final EagleEyeAppender createSelfLogger() { + EagleEyeRollingFileAppender selfAppender = new EagleEyeRollingFileAppender(EAGLEEYE_SELF_LOG_FILE, + EagleEyeCoreUtils.getSystemPropertyForLong("EAGLEEYE.LOG.SELF.FILESIZE", MAX_SELF_LOG_FILE_SIZE), + false); + return new SyncAppender(selfAppender); + } + + static { + initEagleEye(); + } + + private static void initEagleEye() { + try { + selfLog("[INFO] EagleEye started (" + CLASS_LOCATION + ")" + ", classloader=" + + EagleEye.class.getClassLoader()); + } catch (Throwable e) { + selfLog("[INFO] EagleEye started (" + CLASS_LOCATION + ")"); + } + + try { + EagleEyeLogDaemon.start(); + } catch (Throwable e) { + selfLog("[ERROR] fail to start EagleEyeLogDaemon", e); + } + try { + StatLogController.start(); + } catch (Throwable e) { + selfLog("[ERROR] fail to start StatLogController", e); + } + + } + + public static void shutdown() { + selfLog("[WARN] EagleEye is shutting down (" + CLASS_LOCATION + ")"); + + EagleEye.flush(); + + try { + StatLogController.stop(); + EagleEye.selfLog("[INFO] StatLogController stopped"); + } catch (Throwable e) { + selfLog("[ERROR] fail to stop StatLogController", e); + } + + try { + EagleEyeLogDaemon.stop(); + EagleEye.selfLog("[INFO] EagleEyeLogDaemon stopped"); + } catch (Throwable e) { + selfLog("[ERROR] fail to stop EagleEyeLogDaemon", e); + } + + EagleEye.selfLog("[WARN] EagleEye shutdown successfully (" + CLASS_LOCATION + ")"); + try { + selfAppender.close(); + } catch (Throwable e) { + // ignore + } + } + + private EagleEye() { + } + + static public StatLogger statLogger(String loggerName) { + return statLoggerBuilder(loggerName).buildSingleton(); + } + + static public StatLoggerBuilder statLoggerBuilder(String loggerName) { + return new StatLoggerBuilder(loggerName); + } + + static void setEagelEyeSelfAppender(EagleEyeAppender appender) { + selfAppender = appender; + } + + public static void selfLog(String log) { + try { + String timestamp = EagleEyeCoreUtils.formatTime(System.currentTimeMillis()); + String line = "[" + timestamp + "] " + log + EagleEyeCoreUtils.NEWLINE; + selfAppender.append(line); + } catch (Throwable t) { + } + } + + public static void selfLog(String log, Throwable e) { + long now = System.currentTimeMillis(); + if (exceptionBucket.accept(now)) { + try { + String timestamp = EagleEyeCoreUtils.formatTime(now); + StringWriter sw = new StringWriter(4096); + PrintWriter pw = new PrintWriter(sw, false); + pw.append('[').append(timestamp).append("] ").append(log).append(EagleEyeCoreUtils.NEWLINE); + e.printStackTrace(pw); + pw.println(); + pw.flush(); + selfAppender.append(sw.toString()); + } catch (Throwable t) { + } + } + } + + static public void flush() { + EagleEyeLogDaemon.flushAndWait(); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeAppender.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeAppender.java new file mode 100755 index 00000000..727a364c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeAppender.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +public abstract class EagleEyeAppender { + + public abstract void append(String log); + + public void flush() { + // do nothing + } + + public void rollOver() { + // do nothing + } + + public void reload() { + // do nothing + } + + public void close() { + // do nothing + } + + public void cleanup() { + // do nothing + } + + public String getOutputLocation() { + return null; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtils.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtils.java new file mode 100755 index 00000000..c5b7cec8 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtils.java @@ -0,0 +1,197 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +final class EagleEyeCoreUtils { + + public static final String EMPTY_STRING = ""; + public static final String NEWLINE = "\r\n"; + + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + + public static boolean isBlank(String str) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if ((!Character.isWhitespace(str.charAt(i)))) { + return false; + } + } + return true; + } + + public static String checkNotNullEmpty(String value, String name) throws IllegalArgumentException { + if (isBlank(value)) { + throw new IllegalArgumentException(name + " is null or empty"); + } + return value; + } + + public static T checkNotNull(T value, String name) throws IllegalArgumentException { + if (value == null) { + throw new IllegalArgumentException(name + " is null"); + } + return value; + } + + public static T defaultIfNull(T value, T defaultValue) { + return (value == null) ? defaultValue : value; + } + + public static boolean isNotBlank(String str) { + return !isBlank(str); + } + + public static boolean isNotEmpty(String str) { + return str != null && str.length() > 0; + } + + public static String trim(String str) { + return str == null ? null : str.trim(); + } + + public static String[] split(String str, char separatorChar) { + return splitWorker(str, separatorChar, false); + } + + private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) { + if (str == null) { + return null; + } + int len = str.length(); + if (len == 0) { + return EMPTY_STRING_ARRAY; + } + List list = new ArrayList(); + int i = 0, start = 0; + boolean match = false; + boolean lastMatch = false; + while (i < len) { + if (str.charAt(i) == separatorChar) { + if (match || preserveAllTokens) { + list.add(str.substring(start, i)); + match = false; + lastMatch = true; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + if (match || (preserveAllTokens && lastMatch)) { + list.add(str.substring(start, i)); + } + return list.toArray(new String[list.size()]); + } + + public static StringBuilder appendWithBlankCheck(String str, String defaultValue, StringBuilder appender) { + if (isNotBlank(str)) { + appender.append(str); + } else { + appender.append(defaultValue); + } + return appender; + } + + public static StringBuilder appendWithNullCheck(Object obj, String defaultValue, StringBuilder appender) { + if (obj != null) { + appender.append(obj.toString()); + } else { + appender.append(defaultValue); + } + return appender; + } + + public static StringBuilder appendLog(String str, StringBuilder appender, char delimiter) { + if (str != null) { + int len = str.length(); + appender.ensureCapacity(appender.length() + len); + for (int i = 0; i < len; i++) { + char c = str.charAt(i); + if (c == '\n' || c == '\r' || c == delimiter) { + c = ' '; + } + appender.append(c); + } + } + return appender; + } + + private static final ThreadLocal dateFmt = new ThreadLocal() { + @Override + protected FastDateFormat initialValue() { + return new FastDateFormat(); + } + }; + + public static String formatTime(long timestamp) { + return dateFmt.get().format(timestamp); + } + + public static String getSystemProperty(String key) { + try { + return System.getProperty(key); + } catch (Throwable t) { + return null; + } + } + + public static long getSystemPropertyForLong(String key, long defaultValue) { + try { + return Long.parseLong(System.getProperty(key)); + } catch (Throwable t) { + return defaultValue; + } + } + + public static boolean isHexNumeric(char ch) { + return (ch >= 'a' && ch <= 'f') || (ch >= '0' && ch <= '9'); + } + + public static boolean isNumeric(char ch) { + return ch >= '0' && ch <= '9'; + } + + public static void shutdownThreadPool(ExecutorService pool, long awaitTimeMillis) { + try { + pool.shutdown(); + + boolean done = false; + if (awaitTimeMillis > 0) { + try { + done = pool.awaitTermination(awaitTimeMillis, TimeUnit.MILLISECONDS); + } catch (Exception e) { + } + } + + if (!done) { + pool.shutdownNow(); + } + } catch (Exception e) { + // quietly + } + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeLogDaemon.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeLogDaemon.java new file mode 100755 index 00000000..5f71b42a --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeLogDaemon.java @@ -0,0 +1,140 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +class EagleEyeLogDaemon implements Runnable { + + private static final long LOG_CHECK_INTERVAL = TimeUnit.SECONDS.toMillis(20); + + private static AtomicBoolean running = new AtomicBoolean(false); + + private static Thread worker = null; + + private static final CopyOnWriteArrayList watchedAppenders + = new CopyOnWriteArrayList(); + + static EagleEyeAppender watch(EagleEyeAppender appender) { + watchedAppenders.addIfAbsent(appender); + return appender; + } + + static boolean unwatch(EagleEyeAppender appender) { + return watchedAppenders.remove(appender); + } + + @Override + public void run() { + while (running.get()) { + + cleanupFiles(); + + try { + Thread.sleep(LOG_CHECK_INTERVAL); + } catch (InterruptedException e) { + + } + + flushAndReload(); + } + } + + private void cleanupFiles() { + for (EagleEyeAppender watchedAppender : watchedAppenders) { + try { + watchedAppender.cleanup(); + } catch (Exception e) { + EagleEye.selfLog("[ERROR] fail to cleanup: " + watchedAppender, e); + } + } + try { + EagleEye.selfAppender.cleanup(); + } catch (Exception e) { + // quietly + } + } + + private void flushAndReload() { + for (EagleEyeAppender watchedAppender : watchedAppenders) { + try { + watchedAppender.reload(); + } catch (Exception e) { + EagleEye.selfLog("[ERROR] fail to reload: " + watchedAppender, e); + } + } + try { + EagleEye.selfAppender.reload(); + } catch (Exception e) { + // quietly + } + } + + static void start() { + if (running.compareAndSet(false, true)) { + Thread worker = new Thread(new EagleEyeLogDaemon()); + worker.setDaemon(true); + worker.setName("EagleEye-LogDaemon-Thread"); + worker.start(); + EagleEyeLogDaemon.worker = worker; + } + } + + static void stop() { + if (running.compareAndSet(true, false)) { + + closeAppenders(); + + final Thread worker = EagleEyeLogDaemon.worker; + if (worker != null) { + try { + worker.interrupt(); + } catch (Exception e) { + // ignore + } + try { + worker.join(1000); + } catch (Exception e) { + // ignore + } + } + } + } + + private static void closeAppenders() { + for (EagleEyeAppender watchedAppender : watchedAppenders) { + try { + watchedAppender.close(); + } catch (Exception e) { + EagleEye.selfLog("[ERROR] fail to close: " + watchedAppender, e); + } + } + } + + static void flushAndWait() { + for (EagleEyeAppender watchedAppender : watchedAppenders) { + try { + watchedAppender.flush(); + } catch (Exception e) { + EagleEye.selfLog("[ERROR] fail to flush: " + watchedAppender, e); + } + } + } + + private EagleEyeLogDaemon() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeRollingFileAppender.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeRollingFileAppender.java new file mode 100755 index 00000000..59da6b42 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/EagleEyeRollingFileAppender.java @@ -0,0 +1,332 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileLock; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +class EagleEyeRollingFileAppender extends EagleEyeAppender { + + private static final long LOG_FLUSH_INTERVAL = TimeUnit.SECONDS.toMillis(1); + + private static final int DEFAULT_BUFFER_SIZE = 4 * 1024; // 4KB + + private final int maxBackupIndex = 3; + + private final long maxFileSize; + + private final int bufferSize = DEFAULT_BUFFER_SIZE; + + private final String filePath; + + private final AtomicBoolean isRolling = new AtomicBoolean(false); + + private BufferedOutputStream bos = null; + + private long nextFlushTime = 0L; + + private long lastRollOverTime = 0L; + + private long outputByteSize = 0L; + + private final boolean selfLogEnabled; + + private boolean multiProcessDetected = false; + + private static final String DELETE_FILE_SUFFIX = ".deleted"; + + public EagleEyeRollingFileAppender(String filePath, long maxFileSize) { + this(filePath, maxFileSize, true); + } + + public EagleEyeRollingFileAppender(String filePath, long maxFileSize, boolean selfLogEnabled) { + this.filePath = filePath; + this.maxFileSize = maxFileSize; + this.selfLogEnabled = selfLogEnabled; + setFile(); + } + + private void setFile() { + try { + File logFile = new File(filePath); + if (!logFile.exists()) { + File parentFile = logFile.getParentFile(); + if (!parentFile.exists() && !parentFile.mkdirs()) { + doSelfLog("[ERROR] Fail to mkdirs: " + parentFile.getAbsolutePath()); + return; + } + try { + if (!logFile.createNewFile()) { + doSelfLog("[ERROR] Fail to create file, it exists: " + logFile.getAbsolutePath()); + } + } catch (IOException e) { + doSelfLog( + "[ERROR] Fail to create file: " + logFile.getAbsolutePath() + ", error=" + e.getMessage()); + } + } + if (!logFile.isFile() || !logFile.canWrite()) { + doSelfLog("[ERROR] Invalid file, exists=" + logFile.exists() + ", isFile=" + logFile.isFile() + + ", canWrite=" + logFile.canWrite() + ", path=" + logFile.getAbsolutePath()); + return; + } + FileOutputStream ostream = new FileOutputStream(logFile, true); + // true + // O_APPEND + this.bos = new BufferedOutputStream(ostream, bufferSize); + this.lastRollOverTime = System.currentTimeMillis(); + this.outputByteSize = logFile.length(); + } catch (Throwable e) { + doSelfLog("[ERROR] Fail to create file to write: " + filePath + ", error=" + e.getMessage()); + } + } + + @Override + public void append(String log) { + BufferedOutputStream bos = this.bos; + if (bos != null) { + try { + waitUntilRollFinish(); + + byte[] bytes = log.getBytes(EagleEye.DEFAULT_CHARSET); + int len = bytes.length; + if (len > DEFAULT_BUFFER_SIZE && this.multiProcessDetected) { + len = DEFAULT_BUFFER_SIZE; + bytes[len - 1] = '\n'; + } + bos.write(bytes, 0, len); + outputByteSize += len; + + if (outputByteSize >= maxFileSize) { + rollOver(); + } else { + if (System.currentTimeMillis() >= nextFlushTime) { + flush(); + } + } + } catch (Exception e) { + doSelfLog("[ERROR] fail to write log to file " + filePath + ", error=" + e.getMessage()); + close(); + setFile(); + } + } + } + + @Override + public void flush() { + final BufferedOutputStream bos = this.bos; + if (bos != null) { + try { + bos.flush(); + nextFlushTime = System.currentTimeMillis() + LOG_FLUSH_INTERVAL; + } catch (Exception e) { + doSelfLog("[WARN] Fail to flush OutputStream: " + filePath + ", " + e.getMessage()); + } + } + } + + @Override + public void rollOver() { + final String lockFilePath = filePath + ".lock"; + final File lockFile = new File(lockFilePath); + + RandomAccessFile raf = null; + FileLock fileLock = null; + + if (!isRolling.compareAndSet(false, true)) { + return; + } + + try { + raf = new RandomAccessFile(lockFile, "rw"); + fileLock = raf.getChannel().tryLock(); + + if (fileLock != null) { + File target; + File file; + final int maxBackupIndex = this.maxBackupIndex; + + reload(); + if (outputByteSize >= maxFileSize) { + file = new File(filePath + '.' + maxBackupIndex); + if (file.exists()) { + target = new File(filePath + '.' + maxBackupIndex + DELETE_FILE_SUFFIX); + if (!file.renameTo(target) && !file.delete()) { + doSelfLog("[ERROR] Fail to delete or rename file: " + file.getAbsolutePath() + " to " + + target.getAbsolutePath()); + } + } + + for (int i = maxBackupIndex - 1; i >= 1; i--) { + file = new File(filePath + '.' + i); + if (file.exists()) { + target = new File(filePath + '.' + (i + 1)); + if (!file.renameTo(target) && !file.delete()) { + doSelfLog("[ERROR] Fail to delete or rename file: " + file.getAbsolutePath() + " to " + + target.getAbsolutePath()); + } + } + } + + target = new File(filePath + "." + 1); + + close(); + + file = new File(filePath); + if (file.renameTo(target)) { + doSelfLog("[INFO] File rolled to " + target.getAbsolutePath() + ", " + + TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - lastRollOverTime) + + " minutes since last roll"); + } else { + doSelfLog("[WARN] Fail to rename file: " + file.getAbsolutePath() + " to " + + target.getAbsolutePath()); + } + + setFile(); + } + } + } catch (IOException e) { + doSelfLog("[ERROR] Fail rollover file: " + filePath + ", error=" + e.getMessage()); + } finally { + isRolling.set(false); + + if (fileLock != null) { + try { + fileLock.release(); + } catch (IOException e) { + doSelfLog("[ERROR] Fail to release file lock: " + lockFilePath + ", error=" + e.getMessage()); + } + } + + if (raf != null) { + try { + raf.close(); + } catch (IOException e) { + doSelfLog("[WARN] Fail to close file lock: " + lockFilePath + ", error=" + e.getMessage()); + } + } + + if (fileLock != null) { + if (!lockFile.delete() && lockFile.exists()) { + doSelfLog("[WARN] Fail to delete file lock: " + lockFilePath); + } + } + } + } + + @Override + public void close() { + BufferedOutputStream bos = this.bos; + if (bos != null) { + try { + bos.close(); + } catch (IOException e) { + doSelfLog("[WARN] Fail to close OutputStream: " + e.getMessage()); + } + this.bos = null; + } + } + + @Override + public void reload() { + flush(); + File logFile = new File(filePath); + long fileSize = logFile.length(); + boolean fileNotExists = fileSize <= 0 && !logFile.exists(); + + if (this.bos == null || fileSize < outputByteSize || fileNotExists) { + doSelfLog("[INFO] Log file rolled over by outside: " + filePath + ", force reload"); + close(); + setFile(); + } else if (fileSize > outputByteSize) { + this.outputByteSize = fileSize; + if (!this.multiProcessDetected) { + this.multiProcessDetected = true; + if (selfLogEnabled) { + doSelfLog("[WARN] Multi-process file write detected: " + filePath); + } + } + } else { + + } + } + + @Override + public void cleanup() { + try { + File logFile = new File(filePath); + File parentDir = logFile.getParentFile(); + if (parentDir != null && parentDir.isDirectory()) { + final String baseFileName = logFile.getName(); + File[] filesToDelete = parentDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + if (name != null && name.startsWith(baseFileName) && name.endsWith(DELETE_FILE_SUFFIX)) { + return true; + } + return false; + } + }); + if (filesToDelete != null && filesToDelete.length > 0) { + for (File f : filesToDelete) { + boolean success = f.delete() || !f.exists(); + if (success) { + doSelfLog("[INFO] Deleted log file: " + f.getAbsolutePath()); + } else if (f.exists()) { + doSelfLog("[ERROR] Fail to delete log file: " + f.getAbsolutePath()); + } + } + } + } + } catch (Exception e) { + doSelfLog("[ERROR] Fail to cleanup log file, error=" + e.getMessage()); + } + } + + void waitUntilRollFinish() { + while (isRolling.get()) { + try { + Thread.sleep(1L); + } catch (Exception e) { + // quietly + } + } + } + + private void doSelfLog(String log) { + if (selfLogEnabled) { + EagleEye.selfLog(log); + } else { + System.out.println("[EagleEye]" + log); + } + } + + @Override + public String getOutputLocation() { + return filePath; + } + + @Override + public String toString() { + return "EagleEyeRollingFileAppender [filePath=" + filePath + "]"; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java new file mode 100755 index 00000000..e26e73fc --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +class FastDateFormat { + + private final SimpleDateFormat fmt = createSimpleDateFormat(); + + private char[] buffer = new char[23]; + + private long lastSecond = -1; + private long lastMillis = -1; + + public String format(long timestamp) { + formatToBuffer(timestamp); + return new String(buffer, 0, 23); + } + + public String format(Date date) { + return format(date.getTime()); + } + + public void formatAndAppendTo(long timestamp, StringBuilder appender) { + formatToBuffer(timestamp); + appender.append(buffer, 0, 23); + } + + private void formatToBuffer(long timestamp) { + if (timestamp == lastMillis) { + return; + } + long diff = timestamp - lastSecond; + if (diff >= 0 && diff < 1000) { + int ms = (int)(timestamp % 1000); + buffer[22] = (char)(ms % 10 + '0'); + ms /= 10; + buffer[21] = (char)(ms % 10 + '0'); + buffer[20] = (char)(ms / 10 + '0'); + lastMillis = timestamp; + } else { + String result = fmt.format(new Date(timestamp)); + result.getChars(0, result.length(), buffer, 0); + lastSecond = timestamp / 1000 * 1000; + lastMillis = timestamp; + } + } + + String formatWithoutMs(long timestamp) { + long diff = timestamp - lastSecond; + if (diff < 0 || diff >= 1000) { + String result = fmt.format(new Date(timestamp)); + result.getChars(0, result.length(), buffer, 0); + lastSecond = timestamp / 1000 * 1000; + lastMillis = timestamp; + } + return new String(buffer, 0, 19); + } + + private SimpleDateFormat createSimpleDateFormat() { + SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + fmt.setTimeZone(TimeZone.getDefault()); + return fmt; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatEntry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatEntry.java new file mode 100755 index 00000000..e6eb18ec --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatEntry.java @@ -0,0 +1,183 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatEntryFunc; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatEntryFuncFactory; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatLogger; + +import java.util.Arrays; +import java.util.List; + +public final class StatEntry { + + private final StatLogger statLogger; + + private final String[] keys; + private transient int hash; + + public StatEntry(StatLogger statLogger, String key) { + this.statLogger = statLogger; + this.keys = new String[] {key}; + } + + public StatEntry(StatLogger statLogger, String key1, String key2) { + this.statLogger = statLogger; + this.keys = new String[] {key1, key2}; + } + + public StatEntry(StatLogger statLogger, String key1, String key2, String key3) { + this.statLogger = statLogger; + this.keys = new String[] {key1, key2, key3}; + } + + public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4) { + this.statLogger = statLogger; + this.keys = new String[] {key1, key2, key3, key4}; + } + + public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5) { + this.statLogger = statLogger; + this.keys = new String[] {key1, key2, key3, key4, key5}; + } + + public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5, + String key6) { + this.statLogger = statLogger; + this.keys = new String[] {key1, key2, key3, key4, key5, key6}; + } + + public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5, + String key6, String key7) { + this.statLogger = statLogger; + this.keys = new String[] {key1, key2, key3, key4, key5, key6, key7}; + } + + public StatEntry(StatLogger statLogger, String key1, String key2, String key3, String key4, String key5, + String key6, String key7, String key8) { + this.statLogger = statLogger; + this.keys = new String[] {key1, key2, key3, key4, key5, key6, key7, key8}; + } + + public StatEntry(StatLogger statLogger, String key1, String... moreKeys) { + String[] keys = new String[1 + moreKeys.length]; + keys[0] = key1; + for (int i = 0; i < moreKeys.length; ++i) { + keys[i + 1] = moreKeys[i]; + } + this.statLogger = statLogger; + this.keys = keys; + } + + public StatEntry(StatLogger statLogger, List keys) { + if (keys == null || keys.isEmpty()) { + throw new IllegalArgumentException("keys empty or null: " + keys); + } + this.statLogger = statLogger; + this.keys = keys.toArray(new String[keys.size()]); + } + + public StatEntry(StatLogger statLogger, String[] keys) { + if (keys == null || keys.length == 0) { + throw new IllegalArgumentException("keys empty or null"); + } + this.statLogger = statLogger; + this.keys = Arrays.copyOf(keys, keys.length); + } + + public String[] getKeys() { + return keys; + } + + void appendTo(StringBuilder appender, char delimiter) { + final int len = keys.length; + if (len > 0) { + appender.append(keys[0]); + for (int i = 1; i < len; ++i) { + appender.append(delimiter).append(keys[i]); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(64); + sb.append("StatKeys ["); + appendTo(sb, ','); + sb.append("]"); + return sb.toString(); + } + + @Override + public int hashCode() { + if (hash == 0) { + int result = 1; + result = 31 * result + Arrays.hashCode(keys); + hash = result; + } + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + StatEntry other = (StatEntry)obj; + if (hash != 0 && other.hash != 0 && hash != other.hash) { + return false; + } + if (!Arrays.equals(keys, other.keys)) { + return false; + } + + return true; + } + + StatEntryFunc getFunc(final StatEntryFuncFactory factory) { + return this.statLogger.getRollingData().getStatEntryFunc(this, factory); + } + + public void count() { + count(1); + } + + public void count(long count) { + getFunc(StatEntryFuncFactory.COUNT_SUM).count(count); + } + + public void countAndSum(long valueToSum) { + countAndSum(1, valueToSum); + } + + public void countAndSum(long count, long valueToSum) { + getFunc(StatEntryFuncFactory.COUNT_SUM).countAndSum(count, valueToSum); + } + + public void minMax(long candidate) { + minMax(candidate, null); + } + + public void minMax(long candidate, String ref) { + getFunc(StatEntryFuncFactory.MIN_MAX).minMax(candidate, ref); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatEntryFunc.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatEntryFunc.java new file mode 100755 index 00000000..87cf5bf9 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatEntryFunc.java @@ -0,0 +1,205 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.LongAdder; + +interface StatEntryFunc { + + void appendTo(StringBuilder appender, char delimiter); + + int getStatType(); + + Object[] getValues(); + + void count(long count); + + void countAndSum(long count, long value); + + void arrayAdd(long... values); + + void arraySet(long... values); + + void minMax(long candidate, String ref); + + void batchAdd(long... values); + + void strArray(String... values); +} + +enum StatEntryFuncFactory { + COUNT_SUM { + @Override + StatEntryFunc create() { + return new StatEntryFuncCountAndSum(); + } + }, + MIN_MAX { + @Override + StatEntryFunc create() { + return new StatEntryFuncMinMax(); + } + }; + + abstract StatEntryFunc create(); +} + +class StatEntryFuncCountAndSum implements StatEntryFunc { + + private LongAdder count = new LongAdder(); + private LongAdder value = new LongAdder(); + + @Override + public void appendTo(StringBuilder appender, char delimiter) { + appender.append(count.sum()).append(delimiter).append(value.sum()); + } + + @Override + public Object[] getValues() { + return new Object[] {count.sum(), value.sum()}; + } + + @Override + public int getStatType() { + return 1; + } + + @Override + public void count(long count) { + this.count.add(count); + } + + @Override + public void countAndSum(long count, long value) { + this.count.add(count); + this.value.add(value); + } + + @Override + public void arrayAdd(long... values) { + throw new IllegalStateException("arrayAdd() is unavailable if countAndSum() has been called"); + } + + @Override + public void arraySet(long... values) { + throw new IllegalStateException("arraySet() is unavailable if countAndSum() has been called"); + } + + @Override + public void minMax(long candidate, String ref) { + throw new IllegalStateException("minMax() is unavailable if countAndSum() has been called"); + } + + @Override + public void batchAdd(long... values) { + throw new IllegalStateException("batchAdd() is unavailable if countAndSum() has been called"); + } + + @Override + public void strArray(String... values) { + throw new IllegalStateException("strArray() is unavailable if countAndSum() has been called"); + } +} + +class StatEntryFuncMinMax implements StatEntryFunc { + + private AtomicReference max = new AtomicReference(new ValueRef(Long.MIN_VALUE, null)); + private AtomicReference min = new AtomicReference(new ValueRef(Long.MAX_VALUE, null)); + + @Override + public void appendTo(StringBuilder appender, char delimiter) { + ValueRef lmax = max.get(); + ValueRef lmin = min.get(); + + appender.append(lmax.value).append(delimiter); + if (lmax.ref != null) { + appender.append(lmax.ref); + } + appender.append(delimiter); + + appender.append(lmin.value).append(delimiter); + if (lmin.ref != null) { + appender.append(lmin.ref); + } + } + + @Override + public Object[] getValues() { + ValueRef lmax = max.get(); + ValueRef lmin = min.get(); + return new Object[] {lmax.value, lmax.ref, lmin.value, lmin.ref}; + } + + @Override + public int getStatType() { + return 4; + } + + @Override + public void count(long count) { + throw new IllegalStateException("count() is unavailable if minMax() has been called"); + } + + @Override + public void countAndSum(long count, long value) { + throw new IllegalStateException("countAndSum() is unavailable if minMax() has been called"); + } + + @Override + public void arrayAdd(long... values) { + throw new IllegalStateException("arrayAdd() is unavailable if minMax() has been called"); + } + + @Override + public void arraySet(long... values) { + throw new IllegalStateException("arraySet() is unavailable if minMax() has been called"); + } + + @Override + public void batchAdd(long... values) { + throw new IllegalStateException("batchAdd() is unavailable if minMax() has been called"); + } + + @Override + public void minMax(long candidate, String ref) { + ValueRef lmax = max.get(); + if (lmax.value <= candidate) { + final ValueRef cmax = new ValueRef(candidate, ref); + while (!max.compareAndSet(lmax, cmax) && (lmax = max.get()).value <= candidate) { ; } + } + ValueRef lmin = min.get(); + if (lmin.value >= candidate) { + final ValueRef cmin = new ValueRef(candidate, ref); + while (!min.compareAndSet(lmin, cmin) && (lmin = min.get()).value >= candidate) { ; } + } + } + + @Override + public void strArray(String... values) { + throw new IllegalStateException("strArray() is unavailable if minMax() has been called"); + } + + private static final class ValueRef { + final long value; + final String ref; + + ValueRef(long value, String ref) { + this.value = value; + this.ref = ref; + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatLogController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatLogController.java new file mode 100755 index 00000000..a79411ca --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatLogController.java @@ -0,0 +1,191 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatLoggerBuilder; + +class StatLogController { + + private static final Map statLoggers = new ConcurrentHashMap(); + + private static final int STAT_ENTRY_COOL_DOWN_MILLIS = 200; + + private static final ScheduledThreadPoolExecutor rollerThreadPool = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory( + "EagleEye-StatLogController-roller", true)); + + private static final ScheduledThreadPoolExecutor writerThreadPool = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory( + "EagleEye-StatLogController-writer", true)); + + private static AtomicBoolean running = new AtomicBoolean(false); + + static StatLogger createLoggerIfNotExists(StatLoggerBuilder builder) { + String loggerName = builder.getLoggerName(); + StatLogger statLogger = statLoggers.get(loggerName); + if (statLogger == null) { + synchronized (StatLogController.class) { + if ((statLogger = statLoggers.get(loggerName)) == null) { + statLogger = builder.create(); + statLoggers.put(loggerName, statLogger); + + writerThreadPool.setMaximumPoolSize(Math.max(1, statLoggers.size())); + + scheduleNextRollingTask(statLogger); + EagleEye.selfLog("[INFO] created statLogger[" + statLogger.getLoggerName() + + "]: " + statLogger.getAppender()); + } + } + } + return statLogger; + } + + static Map getAllStatLoggers() { + return Collections.unmodifiableMap(statLoggers); + } + + private static void scheduleNextRollingTask(StatLogger statLogger) { + if (!running.get()) { + EagleEye.selfLog("[INFO] stopped rolling statLogger[" + statLogger.getLoggerName() + "]"); + return; + } + + StatLogRollingTask rollingTask = new StatLogRollingTask(statLogger); + + long rollingTimeMillis = statLogger.getRollingData().getRollingTimeMillis(); + long delayMillis = rollingTimeMillis - System.currentTimeMillis(); + if (delayMillis > 5) { + rollerThreadPool.schedule(rollingTask, delayMillis, TimeUnit.MILLISECONDS); + } else if (-delayMillis > statLogger.getIntervalMillis()) { + EagleEye.selfLog("[WARN] unusual delay of statLogger[" + statLogger.getLoggerName() + + "], delay=" + (-delayMillis) + "ms, submit now"); + rollerThreadPool.submit(rollingTask); + } else { + rollerThreadPool.submit(rollingTask); + } + } + + static void scheduleWriteTask(StatRollingData statRollingData) { + if (statRollingData != null) { + try { + StatLogWriteTask task = new StatLogWriteTask(statRollingData); + writerThreadPool.schedule(task, STAT_ENTRY_COOL_DOWN_MILLIS, TimeUnit.MILLISECONDS); + } catch (Throwable t) { + EagleEye.selfLog("[ERROR] fail to roll statLogger[" + + statRollingData.getStatLogger().getLoggerName() + "]", t); + } + } + } + + private static class StatLogRollingTask implements Runnable { + + final StatLogger statLogger; + + StatLogRollingTask(StatLogger statLogger) { + this.statLogger = statLogger; + } + + @Override + public void run() { + scheduleWriteTask(statLogger.rolling()); + scheduleNextRollingTask(statLogger); + } + } + + private static class StatLogWriteTask implements Runnable { + + final StatRollingData statRollingData; + + StatLogWriteTask(StatRollingData statRollingData) { + this.statRollingData = statRollingData; + } + + @Override + public void run() { + final StatRollingData data = statRollingData; + final StatLogger logger = data.getStatLogger(); + try { + final FastDateFormat fmt = new FastDateFormat(); + final StringBuilder buffer = new StringBuilder(256); + final String timeStr = fmt.formatWithoutMs(data.getTimeSlot()); + + final EagleEyeAppender appender = logger.getAppender(); + final Set> entrySet = data.getStatEntrySet(); + final char entryDelimiter = logger.getEntryDelimiter(); + final char keyDelimiter = logger.getKeyDelimiter(); + final char valueDelimiter = logger.getValueDelimiter(); + + for (Entry entry : entrySet) { + buffer.delete(0, buffer.length()); + StatEntryFunc func = entry.getValue(); + // time|statType|keys|values + buffer.append(timeStr).append(entryDelimiter); + buffer.append(func.getStatType()).append(entryDelimiter); + entry.getKey().appendTo(buffer, keyDelimiter); + buffer.append(entryDelimiter); + func.appendTo(buffer, valueDelimiter); + buffer.append(EagleEyeCoreUtils.NEWLINE); + appender.append(buffer.toString()); + } + + appender.flush(); + } catch (Throwable t) { + EagleEye.selfLog("[WARN] fail to write statLogger[" + + logger.getLoggerName() + "]", t); + } + } + } + + static void start() { + if (running.compareAndSet(false, true)) { + rollerThreadPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + writerThreadPool.setExecuteExistingDelayedTasksAfterShutdownPolicy(true); + } + } + + static void stop() { + if (running.compareAndSet(true, false)) { + EagleEyeCoreUtils.shutdownThreadPool(rollerThreadPool, 0); + EagleEye.selfLog("[INFO] StatLoggerController: roller ThreadPool shutdown successfully"); + + for (StatLogger statLogger : statLoggers.values()) { + new StatLogRollingTask(statLogger).run(); + } + + try { + Thread.sleep(STAT_ENTRY_COOL_DOWN_MILLIS); + } catch (InterruptedException e) { + // quietly + } + + EagleEyeCoreUtils.shutdownThreadPool(writerThreadPool, 2000); + EagleEye.selfLog("[INFO] StatLoggerController: writer ThreadPool shutdown successfully"); + } + } + + private StatLogController() { + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatLogger.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatLogger.java new file mode 100755 index 00000000..3196cc21 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatLogger.java @@ -0,0 +1,148 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.EagleEyeAppender; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatRollingData; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author jifeng + */ +public final class StatLogger { + + private final String loggerName; + + private final EagleEyeAppender appender; + + private final AtomicReference ref; + + private final long intervalMillis; + + private final int maxEntryCount; + + private final char entryDelimiter; + private final char keyDelimiter; + private final char valueDelimiter; + + StatLogger(String loggerName, EagleEyeAppender appender, long intervalMillis, int maxEntryCount, + char entryDelimiter, char keyDelimiter, char valueDelimiter) { + this.loggerName = loggerName; + this.appender = appender; + this.intervalMillis = intervalMillis; + this.maxEntryCount = maxEntryCount; + this.entryDelimiter = entryDelimiter; + this.keyDelimiter = keyDelimiter; + this.valueDelimiter = valueDelimiter; + this.ref = new AtomicReference(); + rolling(); + } + + public String getLoggerName() { + return loggerName; + } + + EagleEyeAppender getAppender() { + return appender; + } + + StatRollingData getRollingData() { + return ref.get(); + } + + long getIntervalMillis() { + return intervalMillis; + } + + int getMaxEntryCount() { + return maxEntryCount; + } + + char getEntryDelimiter() { + return entryDelimiter; + } + + char getKeyDelimiter() { + return keyDelimiter; + } + + char getValueDelimiter() { + return valueDelimiter; + } + + StatRollingData rolling() { + do { + long now = System.currentTimeMillis(); + long timeSlot = now - now % intervalMillis; + + StatRollingData prevData = ref.get(); + long rollingTimeMillis = timeSlot + intervalMillis; + int initialCapacity = prevData != null ? prevData.getStatCount() : 16; + StatRollingData nextData = new StatRollingData( + this, initialCapacity, timeSlot, rollingTimeMillis); + if (ref.compareAndSet(prevData, nextData)) { + return prevData; + } + } while (true); + } + + public StatEntry stat(String key) { + return new StatEntry(this, key); + } + + public StatEntry stat(String key1, String key2) { + return new StatEntry(this, key1, key2); + } + + public StatEntry stat(String key1, String key2, String key3) { + return new StatEntry(this, key1, key2, key3); + } + + public StatEntry stat(String key1, String key2, String key3, String key4) { + return new StatEntry(this, key1, key2, key3, key4); + } + + public StatEntry stat(String key1, String key2, String key3, String key4, String key5) { + return new StatEntry(this, key1, key2, key3, key4, key5); + } + + public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6) { + return new StatEntry(this, key1, key2, key3, key4, key5, key6); + } + + public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6, String key7) { + return new StatEntry(this, key1, key2, key3, key4, key5, key6, key7); + } + + public StatEntry stat(String key1, String key2, String key3, String key4, String key5, String key6, String key7, + String key8) { + return new StatEntry(this, key1, key2, key3, key4, key5, key6, key7, key8); + } + + public StatEntry stat(String key1, String... moreKeys) { + return new StatEntry(this, key1, moreKeys); + } + + public StatEntry stat(List keys) { + return new StatEntry(this, keys); + } + + public StatEntry stat(String[] keys) { + return new StatEntry(this, keys); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatLoggerBuilder.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatLoggerBuilder.java new file mode 100755 index 00000000..4f06c2bb --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatLoggerBuilder.java @@ -0,0 +1,115 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.EagleEyeRollingFileAppender; + +import java.util.concurrent.TimeUnit; + +/** + * @author jifeng + */ +public final class StatLoggerBuilder extends BaseLoggerBuilder { + + private int intervalSeconds = 60; + + private int maxEntryCount = 20000; + + private char keyDelimiter = ','; + + private char valueDelimiter = ','; + + private EagleEyeAppender appender = null; + + StatLoggerBuilder(String loggerName) { + super(loggerName); + } + + public StatLoggerBuilder intervalSeconds(int intervalSeconds) { + validateInterval(intervalSeconds); + this.intervalSeconds = intervalSeconds; + return this; + } + + public StatLoggerBuilder maxEntryCount(int maxEntryCount) { + if (maxEntryCount < 1) { + throw new IllegalArgumentException("Max entry count should be at least 1: " + maxEntryCount); + } + this.maxEntryCount = maxEntryCount; + return this; + } + + public StatLoggerBuilder keyDelimiter(char keyDelimiter) { + this.keyDelimiter = keyDelimiter; + return this; + } + + public StatLoggerBuilder valueDelimiter(char valueDelimiter) { + this.valueDelimiter = valueDelimiter; + return this; + } + + StatLoggerBuilder appender(EagleEyeAppender appender) { + this.appender = appender; + return this; + } + + StatLogger create() { + long intervalMillis = TimeUnit.SECONDS.toMillis(this.intervalSeconds); + + String filePath; + if (this.filePath == null) { + filePath = EagleEye.EAGLEEYE_LOG_DIR + "stat-" + loggerName + ".log"; + } else if (this.filePath.endsWith("/") || this.filePath.endsWith("\\")) { + filePath = this.filePath + "stat-" + loggerName + ".log"; + } else { + filePath = this.filePath; + } + + EagleEyeAppender appender = this.appender; + if (appender == null) { + EagleEyeRollingFileAppender rfAppender = new EagleEyeRollingFileAppender(filePath, maxFileSize); + appender = new SyncAppender(rfAppender); + } + + EagleEyeLogDaemon.watch(appender); + return new StatLogger(loggerName, appender, intervalMillis, maxEntryCount, + entryDelimiter, keyDelimiter, valueDelimiter); + } + + public StatLogger buildSingleton() { + return StatLogController.createLoggerIfNotExists(this); + } + + static void validateInterval(final long intervalSeconds) throws IllegalArgumentException { + if (intervalSeconds < 1) { + throw new IllegalArgumentException("Interval cannot be less than 1" + intervalSeconds); + } else if (intervalSeconds < 60) { + if (60 % intervalSeconds != 0) { + throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds); + } + } else if (intervalSeconds <= 5 * 60) { + if (intervalSeconds % 60 != 0) { + throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds); + } + if (60 % intervalSeconds != 0) { + throw new IllegalArgumentException("Invalid second interval (cannot divide by 60): " + intervalSeconds); + } + } else if (intervalSeconds > 5 * 60) { + throw new IllegalArgumentException("Interval should be less than 5 min: " + intervalSeconds); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatRollingData.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatRollingData.java new file mode 100755 index 00000000..2e4744f1 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/StatRollingData.java @@ -0,0 +1,110 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatLogController; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +/** + * @author jifeng + */ +final class StatRollingData { + + private final StatLogger statLogger; + + private final long timeSlot; + + private final long rollingTimeMillis; + + private final ReentrantLock writeLock; + + private final Map statMap; + + StatRollingData(StatLogger statLogger, int initialCapacity, long timeSlot, long rollingTimeMillis) { + this(statLogger, timeSlot, rollingTimeMillis, + new ConcurrentHashMap( + Math.min(initialCapacity, statLogger.getMaxEntryCount()))); + } + + private StatRollingData(StatLogger statLogger, long timeSlot, long rollingTimeMillis, + Map statMap) { + this.statLogger = statLogger; + this.timeSlot = timeSlot; + this.rollingTimeMillis = rollingTimeMillis; + this.writeLock = new ReentrantLock(); + this.statMap = statMap; + } + + StatEntryFunc getStatEntryFunc( + final StatEntry statEntry, final StatEntryFuncFactory factory) { + StatEntryFunc func = statMap.get(statEntry); + if (func == null) { + StatRollingData clone = null; + writeLock.lock(); + try { + int entryCount = statMap.size(); + if (entryCount < statLogger.getMaxEntryCount()) { + func = statMap.get(statEntry); + if (func == null) { + func = factory.create(); + statMap.put(statEntry, func); + } + } else { + Map cloneStatMap = + new HashMap(statMap); + statMap.clear(); + + func = factory.create(); + statMap.put(statEntry, func); + clone = new StatRollingData(statLogger, timeSlot, rollingTimeMillis, cloneStatMap); + } + } finally { + writeLock.unlock(); + } + + if (clone != null) { + StatLogController.scheduleWriteTask(clone); + } + } + return func; + } + + StatLogger getStatLogger() { + return statLogger; + } + + long getRollingTimeMillis() { + return rollingTimeMillis; + } + + long getTimeSlot() { + return timeSlot; + } + + int getStatCount() { + return statMap.size(); + } + + Set> getStatEntrySet() { + return statMap.entrySet(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/SyncAppender.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/SyncAppender.java new file mode 100755 index 00000000..3b4fb649 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/SyncAppender.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.EagleEyeAppender; + +/** + * @author jifeng + */ +final class SyncAppender extends EagleEyeAppender { + + private final EagleEyeAppender delegate; + private final Object lock = new Object(); + + public SyncAppender(EagleEyeAppender delegate) { + this.delegate = delegate; + } + + @Override + public void append(String log) { + synchronized (lock) { + delegate.append(log); + } + } + + @Override + public void flush() { + synchronized (lock) { + delegate.flush(); + } + } + + @Override + public void rollOver() { + synchronized (lock) { + delegate.rollOver(); + } + } + + @Override + public void reload() { + synchronized (lock) { + delegate.reload(); + } + } + + @Override + public void close() { + synchronized (lock) { + delegate.close(); + } + } + + @Override + public void cleanup() { + delegate.cleanup(); + } + + @Override + public String getOutputLocation() { + return delegate.getOutputLocation(); + } + + @Override + public String toString() { + return "SyncAppender [appender=" + delegate + "]"; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/TokenBucket.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/TokenBucket.java new file mode 100755 index 00000000..9da10911 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/eagleeye/TokenBucket.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye; + +import java.util.concurrent.atomic.AtomicLong; + +class TokenBucket { + + private final long maxTokens; + + private final long intervalMillis; + + private volatile long nextUpdate; + + private AtomicLong tokens; + + public TokenBucket(long maxTokens, long intervalMillis) { + if (maxTokens <= 0) { + throw new IllegalArgumentException("maxTokens should > 0, but given: " + maxTokens); + } + if (intervalMillis < 1000) { + throw new IllegalArgumentException("intervalMillis should be at least 1000, but given: " + intervalMillis); + } + this.maxTokens = maxTokens; + this.intervalMillis = intervalMillis; + this.nextUpdate = System.currentTimeMillis() / 1000 * 1000 + intervalMillis; + this.tokens = new AtomicLong(maxTokens); + } + + public boolean accept(long now) { + long currTokens; + if (now > nextUpdate) { + currTokens = tokens.get(); + if (tokens.compareAndSet(currTokens, maxTokens)) { + nextUpdate = System.currentTimeMillis() / 1000 * 1000 + intervalMillis; + } + } + + do { + currTokens = tokens.get(); + } while (currTokens > 0 && !tokens.compareAndSet(currTokens, currTokens - 1)); + + return currTokens > 0; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/init/InitExecutor.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/init/InitExecutor.java new file mode 100755 index 00000000..e9f28fb4 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/init/InitExecutor.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init; + +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoader; + +/** + * Load registered init functions and execute in order. + * + * @author Eric Zhao + */ +public final class InitExecutor { + + private static AtomicBoolean initialized = new AtomicBoolean(false); + + /** + * If one {@link InitFunc} throws an exception, the init process + * will immediately be interrupted and the application will exit. + * + * The initialization will be executed only once. + */ + public static void doInit() { + if (!initialized.compareAndSet(false, true)) { + return; + } + try { + List initFuncs = SpiLoader.of(InitFunc.class).loadInstanceListSorted(); + List initList = new ArrayList(); + for (InitFunc initFunc : initFuncs) { + RecordLog.info("[InitExecutor] Found init func: {}", initFunc.getClass().getCanonicalName()); + insertSorted(initList, initFunc); + } + for (OrderWrapper w : initList) { + w.func.init(); + RecordLog.info("[InitExecutor] Executing {} with order {}", + w.func.getClass().getCanonicalName(), w.order); + } + } catch (Exception ex) { + RecordLog.warn("[InitExecutor] WARN: Initialization failed", ex); + ex.printStackTrace(); + } catch (Error error) { + RecordLog.warn("[InitExecutor] ERROR: Initialization failed with fatal error", error); + error.printStackTrace(); + } + } + + private static void insertSorted(List list, InitFunc func) { + int order = resolveOrder(func); + int idx = 0; + for (; idx < list.size(); idx++) { + if (list.get(idx).getOrder() > order) { + break; + } + } + list.add(idx, new OrderWrapper(order, func)); + } + + private static int resolveOrder(InitFunc func) { + if (!func.getClass().isAnnotationPresent(InitOrder.class)) { + return InitOrder.LOWEST_PRECEDENCE; + } else { + return func.getClass().getAnnotation(InitOrder.class).value(); + } + } + + private InitExecutor() {} + + private static class OrderWrapper { + private final int order; + private final InitFunc func; + + OrderWrapper(int order, InitFunc func) { + this.order = order; + this.func = func; + } + + int getOrder() { + return order; + } + + InitFunc getFunc() { + return func; + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/init/InitFunc.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/init/InitFunc.java new file mode 100755 index 00000000..809e5f25 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/init/InitFunc.java @@ -0,0 +1,24 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init; + +/** + * @author Eric Zhao + */ +public interface InitFunc { + + void init() throws Exception; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/init/InitOrder.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/init/InitOrder.java new file mode 100755 index 00000000..9a044ba5 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/init/InitOrder.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Eric Zhao + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +public @interface InitOrder { + + int LOWEST_PRECEDENCE = Integer.MAX_VALUE; + int HIGHEST_PRECEDENCE = Integer.MIN_VALUE; + + /** + * The order value. Lowest precedence by default. + * + * @return the order value + */ + int value() default LOWEST_PRECEDENCE; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LogBase.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LogBase.java new file mode 100755 index 00000000..6512ebb4 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LogBase.java @@ -0,0 +1,166 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log; + + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogConfigLoader; + +import java.io.File; +import java.util.Properties; +import java.util.logging.Level; + +import static com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator; + +/** + *

    The base config class for logging.

    + * + *

    + * The default log base directory is {@code ${user.home}/logs/csp/}. We can use the {@link #LOG_DIR} + * property to override it. The default log file name dose not contain pid, but if multi-instances of the same service + * are running in the same machine, we may want to distinguish the log file by process ID number. + * In this case, {@link #LOG_NAME_USE_PID} property could be configured as "true" to turn on this switch. + *

    + * + * @author Carpenter Lee + * @author Eric Zhao + */ +public class LogBase { + + public static final String LOG_DIR = "csp.sentinel.log.dir"; + public static final String LOG_NAME_USE_PID = "csp.sentinel.log.use.pid"; + public static final String LOG_OUTPUT_TYPE = "csp.sentinel.log.output.type"; + public static final String LOG_CHARSET = "csp.sentinel.log.charset"; + public static final String LOG_LEVEL = "csp.sentinel.log.level"; + + /** + * Output biz log (e.g. RecordLog and CommandCenterLog) to file. + */ + public static final String LOG_OUTPUT_TYPE_FILE = "file"; + /** + * Output biz log (e.g. RecordLog and CommandCenterLog) to console. + */ + public static final String LOG_OUTPUT_TYPE_CONSOLE = "console"; + public static final String LOG_CHARSET_UTF8 = "utf-8"; + + private static final String DIR_NAME = "logs" + File.separator + "csp"; + private static final String USER_HOME = "user.home"; + private static final Level LOG_DEFAULT_LEVEL = Level.INFO; + + + private static boolean logNameUsePid; + private static String logOutputType; + private static String logBaseDir; + private static String logCharSet; + private static Level logLevel; + + static { + try { + initializeDefault(); + loadProperties(); + } catch (Throwable t) { + System.err.println("[LogBase] FATAL ERROR when initializing logging config"); + t.printStackTrace(); + } + } + + private static void initializeDefault() { + logNameUsePid = false; + logOutputType = LOG_OUTPUT_TYPE_FILE; + logBaseDir = addSeparator(System.getProperty(USER_HOME)) + DIR_NAME + File.separator; + logCharSet = LOG_CHARSET_UTF8; + logLevel = LOG_DEFAULT_LEVEL; + } + + private static void loadProperties() { + Properties properties = LogConfigLoader.getProperties(); + + logOutputType = properties.get(LOG_OUTPUT_TYPE) == null ? logOutputType : properties.getProperty(LOG_OUTPUT_TYPE); + if (!LOG_OUTPUT_TYPE_FILE.equalsIgnoreCase(logOutputType) && !LOG_OUTPUT_TYPE_CONSOLE.equalsIgnoreCase(logOutputType)) { + logOutputType = LOG_OUTPUT_TYPE_FILE; + } + System.out.println("INFO: Sentinel log output type is: " + logOutputType); + + logCharSet = properties.getProperty(LOG_CHARSET) == null ? logCharSet : properties.getProperty(LOG_CHARSET); + System.out.println("INFO: Sentinel log charset is: " + logCharSet); + + + logBaseDir = properties.getProperty(LOG_DIR) == null ? logBaseDir : properties.getProperty(LOG_DIR); + logBaseDir = addSeparator(logBaseDir); + File dir = new File(logBaseDir); + if (!dir.exists()) { + if (!dir.mkdirs()) { + System.err.println("ERROR: create Sentinel log base directory error: " + logBaseDir); + } + } + System.out.println("INFO: Sentinel log base directory is: " + logBaseDir); + + String usePid = properties.getProperty(LOG_NAME_USE_PID); + logNameUsePid = "true".equalsIgnoreCase(usePid); + System.out.println("INFO: Sentinel log name use pid is: " + logNameUsePid); + + // load log level + String logLevelString = properties.getProperty(LOG_LEVEL); + if (logLevelString != null && (logLevelString = logLevelString.trim()).length() > 0) { + try { + logLevel = Level.parse(logLevelString); + } catch (IllegalArgumentException e) { + System.out.println("Log level : " + logLevel + " is invalid. Use default : " + LOG_DEFAULT_LEVEL.toString()); + } + } + System.out.println("INFO: Sentinel log level is: " + logLevel); + } + + + /** + * Whether log file name should contain pid. This switch is configured by {@link #LOG_NAME_USE_PID} system property. + * + * @return true if log file name should contain pid, return true, otherwise false + */ + public static boolean isLogNameUsePid() { + return logNameUsePid; + } + + /** + * Get the log file base directory path, which is guaranteed ended with {@link File#separator}. + * + * @return log file base directory path + */ + public static String getLogBaseDir() { + return logBaseDir; + } + + /** + * Get the log file output type. + * + * @return log output type, "file" by default + */ + public static String getLogOutputType() { + return logOutputType; + } + + /** + * Get the log file charset. + * + * @return the log file charset, "utf-8" by default + */ + public static String getLogCharset() { + return logCharSet; + } + + public static Level getLogLevel() { + return logLevel; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LogConfigLoader.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LogConfigLoader.java new file mode 100644 index 00000000..80184d65 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LogConfigLoader.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.ConfigUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + *

    The loader that responsible for loading Sentinel log configurations.

    + * + * @author lianglin + * @since 1.7.0 + */ +public class LogConfigLoader { + + public static final String LOG_CONFIG_ENV_KEY = "CSP_SENTINEL_CONFIG_FILE"; + public static final String LOG_CONFIG_PROPERTY_KEY = "csp.sentinel.config.file"; + + private static final String DEFAULT_LOG_CONFIG_FILE = "classpath:sentinel.properties"; + + private static final Properties properties = new Properties(); + + static { + try { + load(); + } catch (Throwable t) { + // NOTE: do not use RecordLog here, or there will be circular class dependency! + System.err.println("[LogConfigLoader] Failed to initialize configuration items"); + t.printStackTrace(); + } + } + + private static void load() { + // Order: system property -> system env -> default file (classpath:sentinel.properties) -> legacy path + String fileName = System.getProperty(LOG_CONFIG_PROPERTY_KEY); + if (StringUtil.isBlank(fileName)) { + fileName = System.getenv(LOG_CONFIG_ENV_KEY); + if (StringUtil.isBlank(fileName)) { + fileName = DEFAULT_LOG_CONFIG_FILE; + } + } + + Properties p = ConfigUtil.loadProperties(fileName); + if (p != null && !p.isEmpty()) { + properties.putAll(p); + } + + CopyOnWriteArraySet> copy = new CopyOnWriteArraySet<>(System.getProperties().entrySet()); + for (Map.Entry entry : copy) { + String configKey = entry.getKey().toString(); + String newConfigValue = entry.getValue().toString(); + properties.put(configKey, newConfigValue); + } + } + + public static Properties getProperties() { + return properties; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LogTarget.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LogTarget.java new file mode 100644 index 00000000..04baa1db --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LogTarget.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; + +import java.lang.annotation.*; + +/** + * @author xue8 + * @since 1.7.2 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Documented +public @interface LogTarget { + /** + * Returns the logger name. + * + * @return the logger name. Record logger by default + */ + String value() default RecordLog.LOGGER_NAME; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/Logger.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/Logger.java new file mode 100644 index 00000000..4d46a4b1 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/Logger.java @@ -0,0 +1,118 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log; + +/** + *

    The universal logger SPI interface.

    + *

    Notice: the placeholder only supports the most popular placeholder convention (slf4j). + * So, if you're not using slf4j, you should create adapters compatible with placeholders "{}".

    + * + * @author xue8 + * @since 1.7.2 + */ +public interface Logger { + + /** + * Log a message at the INFO level according to the specified format + * and arguments. + * + * @param format the format string + * @param arguments a list of arguments + */ + void info(String format, Object... arguments); + + /** + * Log an exception (throwable) at the INFO level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param e the exception (throwable) to log + */ + void info(String msg, Throwable e); + + /** + * Log a message at the WARN level according to the specified format + * and arguments. + * + * @param format the format string + * @param arguments a list of arguments + */ + void warn(String format, Object... arguments); + + /** + * Log an exception (throwable) at the WARN level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param e the exception (throwable) to log + */ + void warn(String msg, Throwable e); + + /** + * Log a message at the TRACE level according to the specified format + * and arguments. + * + * @param format the format string + * @param arguments a list of arguments + */ + void trace(String format, Object... arguments); + + /** + * Log an exception (throwable) at the TRACE level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param e the exception (throwable) to log + */ + void trace(String msg, Throwable e); + + /** + * Log a message at the DEBUG level according to the specified format + * and arguments. + * + * @param format the format string + * @param arguments a list of arguments + */ + void debug(String format, Object... arguments); + + /** + * Log an exception (throwable) at the DEBUG level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param e the exception (throwable) to log + */ + void debug(String msg, Throwable e); + + /** + * Log a message at the ERROR level according to the specified format + * and arguments. + * + * @param format the format string + * @param arguments a list of arguments + */ + void error(String format, Object... arguments); + + /** + * Log an exception (throwable) at the ERROR level with an + * accompanying message. + * + * @param msg the message accompanying the exception + * @param e the exception (throwable) to log + */ + void error(String msg, Throwable e); + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LoggerSpiProvider.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LoggerSpiProvider.java new file mode 100644 index 00000000..7b4bf39d --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/LoggerSpiProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log; + +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +/** + * SPI provider of Sentinel {@link Logger}. + * + * @author Eric Zhao + * @since 1.7.2 + */ +public final class LoggerSpiProvider { + + private static final Map LOGGER_MAP = new HashMap<>(); + + static { + // NOTE: this class SHOULD NOT depend on any other Sentinel classes + // except the util classes to avoid circular dependency. + try { + resolveLoggers(); + } catch (Throwable t) { + System.err.println("Failed to resolve Sentinel Logger SPI"); + t.printStackTrace(); + } + } + + public static Logger getLogger(String name) { + if (name == null) { + return null; + } + return LOGGER_MAP.get(name); + } + + private static void resolveLoggers() { + // NOTE: Here we cannot use {@code SpiLoader} directly because it depends on the RecordLog. + ServiceLoader loggerLoader = ServiceLoader.load(Logger.class); + + for (Logger logger : loggerLoader) { + LogTarget annotation = logger.getClass().getAnnotation(LogTarget.class); + if (annotation == null) { + continue; + } + String name = annotation.value(); + // Load first encountered logger if multiple loggers are associated with the same name. + if (StringUtil.isNotBlank(name) && !LOGGER_MAP.containsKey(name)) { + LOGGER_MAP.put(name, logger); + System.out.println("Sentinel Logger SPI loaded for <" + name + ">: " + + logger.getClass().getCanonicalName()); + } + } + } + + private LoggerSpiProvider() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/RecordLog.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/RecordLog.java new file mode 100755 index 00000000..6d98d334 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/RecordLog.java @@ -0,0 +1,89 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LoggerSpiProvider; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul.JavaLoggingAdapter; + +/** + * The basic biz logger of Sentinel. + * + * @author youji.zj + * @author Eric Zhao + */ +public class RecordLog { + + public static final String LOGGER_NAME = "sentinelRecordLogger"; + public static final String DEFAULT_LOG_FILENAME = "sentinel-record.log"; + + private static com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.Logger logger = null; + + static { + try { + // Load user-defined logger implementation first. + logger = LoggerSpiProvider.getLogger(LOGGER_NAME); + if (logger == null) { + // If no customized loggers are provided, we use the default logger based on JUL. + logger = new JavaLoggingAdapter(LOGGER_NAME, DEFAULT_LOG_FILENAME); + } + } catch (Throwable t) { + System.err.println("Error: failed to initialize Sentinel RecordLog"); + t.printStackTrace(); + } + } + + public static void info(String format, Object... arguments) { + logger.info(format, arguments); + } + + public static void info(String msg, Throwable e) { + logger.info(msg, e); + } + + public static void warn(String format, Object... arguments) { + logger.warn(format, arguments); + } + + public static void warn(String msg, Throwable e) { + logger.warn(msg, e); + } + + public static void trace(String format, Object... arguments) { + logger.trace(format, arguments); + } + + public static void trace(String msg, Throwable e) { + logger.trace(msg, e); + } + + public static void debug(String format, Object... arguments) { + logger.debug(format, arguments); + } + + public static void debug(String msg, Throwable e) { + logger.debug(msg, e); + } + + public static void error(String format, Object... arguments) { + logger.error(format, arguments); + } + + public static void error(String msg, Throwable e) { + logger.error(msg, e); + } + + private RecordLog() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/BaseJulLogger.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/BaseJulLogger.java new file mode 100644 index 00000000..ab1b07b2 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/BaseJulLogger.java @@ -0,0 +1,134 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul; + +import java.io.IOException; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogBase; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul.ConsoleHandler; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul.DateFileLogHandler; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul.FormattingTuple; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul.MessageFormatter; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.PidUtil; + +import static com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE_CONSOLE; +import static com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE_FILE; + +/** + * The default logger based on java.util.logging. + * + * @author Eric Zhao + * @since 1.7.2 + */ +public class BaseJulLogger { + + protected void log(Logger logger, Handler handler, Level level, String detail, Object... params) { + if (detail == null) { + return; + } + disableOtherHandlers(logger, handler); + + // Compatible with slf4j placeholder format "{}". + FormattingTuple formattingTuple = MessageFormatter.arrayFormat(detail, params); + String message = formattingTuple.getMessage(); + logger.log(level, message); + } + + protected void log(Logger logger, Handler handler, Level level, String detail, Throwable throwable) { + if (detail == null) { + return; + } + disableOtherHandlers(logger, handler); + logger.log(level, detail, throwable); + } + + protected Handler makeLoggingHandler(String logName, Logger heliumRecordLog) { + CspFormatter formatter = new CspFormatter(); + String logCharSet = LogBase.getLogCharset(); + Handler handler = null; + + // Create handler according to logOutputType, set formatter to CspFormatter, set encoding to LOG_CHARSET + switch (LogBase.getLogOutputType()) { + case LOG_OUTPUT_TYPE_FILE: + String fileName = LogBase.getLogBaseDir() + logName; + if (LogBase.isLogNameUsePid()) { + fileName += ".pid" + PidUtil.getPid(); + } + try { + handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 4, true); + handler.setFormatter(formatter); + handler.setEncoding(logCharSet); + handler.setLevel(LogBase.getLogLevel()); + } catch (IOException e) { + e.printStackTrace(); + } + break; + case LOG_OUTPUT_TYPE_CONSOLE: + try { + handler = new ConsoleHandler(); + handler.setFormatter(formatter); + handler.setEncoding(logCharSet); + handler.setLevel(LogBase.getLogLevel()); + } catch (IOException e) { + e.printStackTrace(); + } + break; + default: + break; + } + + if (handler != null) { + disableOtherHandlers(heliumRecordLog, handler); + } + + // Set log level to INFO by default + heliumRecordLog.setLevel(LogBase.getLogLevel()); + return handler; + } + + /** + * Remove all current handlers from the logger and attach it with the given log handler. + * + * @param logger logger + * @param handler the log handler + */ + static void disableOtherHandlers(Logger logger, Handler handler) { + if (logger == null) { + return; + } + + synchronized (logger) { + Handler[] handlers = logger.getHandlers(); + if (handlers == null) { + return; + } + if (handlers.length == 1 && handlers[0].equals(handler)) { + return; + } + + logger.setUseParentHandlers(false); + // Remove all current handlers. + for (Handler h : handlers) { + logger.removeHandler(h); + } + // Attach the given handler. + logger.addHandler(handler); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java new file mode 100644 index 00000000..289472e2 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul; + +import java.io.UnsupportedEncodingException; +import java.util.logging.*; +import java.util.logging.Level; + +/** + * This Handler publishes log records to console by using {@link StreamHandler}. + * + * Print log of WARNING level or above to System.err, + * and print log of INFO level or below to System.out. + * + * To use this handler, add the following VM argument: + *
    + * -Dcsp.sentinel.log.output.type=console
    + * 
    + * + * @author cdfive + */ +class ConsoleHandler extends Handler { + + /** + * A Handler which publishes log records to System.out. + */ + private StreamHandler stdoutHandler; + + /** + * A Handler which publishes log records to System.err. + */ + private StreamHandler stderrHandler; + + public ConsoleHandler() { + this.stdoutHandler = new StreamHandler(System.out, new CspFormatter()); + this.stderrHandler = new StreamHandler(System.err, new CspFormatter()); + } + + @Override + public synchronized void setFormatter(Formatter newFormatter) throws SecurityException { + this.stdoutHandler.setFormatter(newFormatter); + this.stderrHandler.setFormatter(newFormatter); + } + + @Override + public synchronized void setEncoding(String encoding) throws SecurityException, UnsupportedEncodingException { + this.stdoutHandler.setEncoding(encoding); + this.stderrHandler.setEncoding(encoding); + } + + @Override + public void publish(LogRecord record) { + if (record.getLevel().intValue() >= Level.WARNING.intValue()) { + stderrHandler.publish(record); + stderrHandler.flush(); + } else { + stdoutHandler.publish(record); + stdoutHandler.flush(); + } + } + + @Override + public void flush() { + stdoutHandler.flush(); + stderrHandler.flush(); + } + + @Override + public void close() throws SecurityException { + stdoutHandler.close(); + stderrHandler.close(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/CspFormatter.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/CspFormatter.java new file mode 100755 index 00000000..5ee55c05 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/CspFormatter.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +/** + * @author xuyue + */ +class CspFormatter extends Formatter { + + private final ThreadLocal dateFormatThreadLocal = new ThreadLocal() { + @Override + public SimpleDateFormat initialValue() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + } + }; + + @Override + public String format(LogRecord record) { + final DateFormat df = dateFormatThreadLocal.get(); + StringBuilder builder = new StringBuilder(1000); + builder.append(df.format(new Date(record.getMillis()))).append(" "); + builder.append(record.getLevel().getName()).append(" "); + builder.append(formatMessage(record)); + + String throwable = ""; + if (record.getThrown() != null) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.println(); + record.getThrown().printStackTrace(pw); + pw.close(); + throwable = sw.toString(); + } + builder.append(throwable); + if ("".equals(throwable)) { + builder.append("\n"); + } + return builder.toString(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java new file mode 100755 index 00000000..4c330708 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java @@ -0,0 +1,148 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +class DateFileLogHandler extends Handler { + + private final ThreadLocal dateFormatThreadLocal = new ThreadLocal() { + @Override + public SimpleDateFormat initialValue() { + return new SimpleDateFormat("yyyy-MM-dd"); + } + }; + + private volatile FileHandler handler; + + private final String pattern; + private final int limit; + private final int count; + private final boolean append; + + private volatile boolean initialized = false; + + private volatile long startDate = System.currentTimeMillis(); + private volatile long endDate; + + private final Object monitor = new Object(); + + DateFileLogHandler(String pattern, int limit, int count, boolean append) throws SecurityException { + this.pattern = pattern; + this.limit = limit; + this.count = count; + this.append = append; + rotateDate(); + this.initialized = true; + } + + @Override + public void close() throws SecurityException { + handler.close(); + } + + @Override + public void flush() { + handler.flush(); + } + + @Override + public void publish(LogRecord record) { + if (shouldRotate(record)) { + synchronized (monitor) { + if (shouldRotate(record)) { + rotateDate(); + } + } + } + if (System.currentTimeMillis() - startDate > 25 * 60 * 60 * 1000) { + String msg = record.getMessage(); + record.setMessage("missed file rolling at: " + new Date(endDate) + "\n" + msg); + } + handler.publish(record); + } + + private boolean shouldRotate(LogRecord record) { + if (endDate <= record.getMillis() || !logFileExits()) { + return true; + } + return false; + } + + @Override + public void setFormatter(Formatter newFormatter) { + super.setFormatter(newFormatter); + if (handler != null) { handler.setFormatter(newFormatter); } + } + + private boolean logFileExits() { + try { + SimpleDateFormat format = dateFormatThreadLocal.get(); + String fileName = pattern.replace("%d", format.format(new Date())); + // When file count is not 1, the first log file name will end with ".0" + if (count != 1) { + fileName += ".0"; + } + File logFile = new File(fileName); + return logFile.exists(); + } catch (Throwable e) { + + } + return false; + } + + private void rotateDate() { + this.startDate = System.currentTimeMillis(); + if (handler != null) { + handler.close(); + } + SimpleDateFormat format = dateFormatThreadLocal.get(); + String newPattern = pattern.replace("%d", format.format(new Date())); + // Get current date. + Calendar next = Calendar.getInstance(); + // Begin of next date. + next.set(Calendar.HOUR_OF_DAY, 0); + next.set(Calendar.MINUTE, 0); + next.set(Calendar.SECOND, 0); + next.set(Calendar.MILLISECOND, 0); + next.add(Calendar.DATE, 1); + this.endDate = next.getTimeInMillis(); + + try { + this.handler = new FileHandler(newPattern, limit, count, append); + if (initialized) { + handler.setEncoding(this.getEncoding()); + handler.setErrorManager(this.getErrorManager()); + handler.setFilter(this.getFilter()); + handler.setFormatter(this.getFormatter()); + handler.setLevel(this.getLevel()); + } + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/FormattingTuple.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/FormattingTuple.java new file mode 100644 index 00000000..f8318ede --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/FormattingTuple.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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. + */ + +// Copyright notice: This code was copied from SLF4J which licensed under the MIT License. +package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul.MessageFormatter; + +/** + * Holds the results of formatting done by {@link MessageFormatter}. + * + * @author Joern Huxhorn + */ +public class FormattingTuple { + + static public FormattingTuple NULL = new FormattingTuple(null); + + private String message; + private Throwable throwable; + private Object[] argArray; + + public FormattingTuple(String message) { + this(message, null, null); + } + + public FormattingTuple(String message, Object[] argArray, Throwable throwable) { + this.message = message; + this.throwable = throwable; + this.argArray = argArray; + } + + public String getMessage() { + return message; + } + + public Object[] getArgArray() { + return argArray; + } + + public Throwable getThrowable() { + return throwable; + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/JavaLoggingAdapter.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/JavaLoggingAdapter.java new file mode 100644 index 00000000..6949af3f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/JavaLoggingAdapter.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul; + +import java.util.logging.Handler; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.Logger; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; + +/** + * JUL adapter for Sentinel {@link Logger} SPI. + * + * @author Eric Zhao + * @since 1.7.2 + */ +public class JavaLoggingAdapter extends BaseJulLogger implements Logger { + + private final String loggerName; + private final String fileNamePattern; + + private final java.util.logging.Logger julLogger; + private final Handler logHandler; + + public JavaLoggingAdapter(String loggerName, String fileNamePattern) { + AssertUtil.assertNotBlank(loggerName, "loggerName cannot be blank"); + AssertUtil.assertNotBlank(fileNamePattern, "fileNamePattern cannot be blank"); + this.loggerName = loggerName; + this.fileNamePattern = fileNamePattern; + + this.julLogger = java.util.logging.Logger.getLogger(loggerName); + this.logHandler = makeLoggingHandler(fileNamePattern, julLogger); + } + + @Override + public void info(String format, Object... arguments) { + log(julLogger, logHandler, Level.INFO, format, arguments); + } + + @Override + public void info(String msg, Throwable e) { + log(julLogger, logHandler, Level.INFO, msg, e); + } + + @Override + public void warn(String format, Object... arguments) { + log(julLogger, logHandler, Level.WARNING, format, arguments); + } + + @Override + public void warn(String msg, Throwable e) { + log(julLogger, logHandler, Level.WARNING, msg, e); + } + + @Override + public void trace(String format, Object... arguments) { + log(julLogger, logHandler, Level.TRACE, format, arguments); + } + + @Override + public void trace(String msg, Throwable e) { + log(julLogger, logHandler, Level.TRACE, msg, e); + } + + @Override + public void debug(String format, Object... arguments) { + log(julLogger, logHandler, Level.DEBUG, format, arguments); + } + + @Override + public void debug(String msg, Throwable e) { + log(julLogger, logHandler, Level.DEBUG, msg, e); + } + + @Override + public void error(String format, Object... arguments) { + log(julLogger, logHandler, Level.ERROR, format, arguments); + } + + @Override + public void error(String msg, Throwable e) { + log(julLogger, logHandler, Level.ERROR, msg, e); + } + + public String getLoggerName() { + return loggerName; + } + + public String getFileNamePattern() { + return fileNamePattern; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/Level.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/Level.java new file mode 100644 index 00000000..1ac17cae --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/Level.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul; + +/** + * JUL logging levels. + * + * @author xue8 + */ +public class Level extends java.util.logging.Level { + private static final String defaultBundle = "sun.util.logging.resources.logging"; + + public static final Level ERROR = new Level("ERROR", 1000); + public static final Level WARNING = new Level("WARNING", 900); + public static final Level INFO = new Level("INFO", 800); + public static final Level DEBUG = new Level("DEBUG", 700); + public static final Level TRACE = new Level("TRACE", 600); + + protected Level(String name, int value) { + super(name, value, defaultBundle); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/MessageFormatter.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/MessageFormatter.java new file mode 100644 index 00000000..9ce350ee --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/log/jul/MessageFormatter.java @@ -0,0 +1,417 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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. + */ + +// Copyright notice: This code was copied from SLF4J which licensed under the MIT License. +package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.jul; + + +// contributors: lizongbo: proposed special treatment of array parameter values +// Joern Huxhorn: pointed out double[] omission, suggested deep array copy + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +/** + * Formats messages according to very simple substitution rules. Substitutions + * can be made 1, 2 or more arguments. + * + *

    + * For example, + * + *

    + * MessageFormatter.format("Hi {}.", "there")
    + * 
    + * + * will return the string "Hi there.". + *

    + * The {} pair is called the formatting anchor. It serves to designate + * the location where arguments need to be substituted within the message + * pattern. + *

    + * In case your message contains the '{' or the '}' character, you do not have + * to do anything special unless the '}' character immediately follows '{'. For + * example, + * + *

    + * MessageFormatter.format("Set {1,2,3} is not equal to {}.", "1,2");
    + * 
    + * + * will return the string "Set {1,2,3} is not equal to 1,2.". + * + *

    + * If for whatever reason you need to place the string "{}" in the message + * without its formatting anchor meaning, then you need to escape the + * '{' character with '\', that is the backslash character. Only the '{' + * character should be escaped. There is no need to escape the '}' character. + * For example, + * + *

    + * MessageFormatter.format("Set \\{} is not equal to {}.", "1,2");
    + * 
    + * + * will return the string "Set {} is not equal to 1,2.". + * + *

    + * The escaping behavior just described can be overridden by escaping the escape + * character '\'. Calling + * + *

    + * MessageFormatter.format("File name is C:\\\\{}.", "file.zip");
    + * 
    + * + * will return the string "File name is C:\file.zip". + * + *

    + * The formatting conventions are different than those of {@link MessageFormat} + * which ships with the Java platform. This is justified by the fact that + * SLF4J's implementation is 10 times faster than that of {@link MessageFormat}. + * This local performance difference is both measurable and significant in the + * larger context of the complete logging processing chain. + * + *

    + * See also {@link #format(String, Object)}, + * {@link #format(String, Object, Object)} and + * {@link #arrayFormat(String, Object[])} methods for more details. + * + * @author Ceki Gülcü + * @author Joern Huxhorn + */ +final public class MessageFormatter { + static final char DELIM_START = '{'; + static final char DELIM_STOP = '}'; + static final String DELIM_STR = "{}"; + private static final char ESCAPE_CHAR = '\\'; + + /** + * Performs single argument substitution for the 'messagePattern' passed as + * parameter. + *

    + * For example, + * + *

    +     * MessageFormatter.format("Hi {}.", "there");
    +     * 
    + * + * will return the string "Hi there.". + *

    + * + * @param messagePattern + * The message pattern which will be parsed and formatted + * @param arg + * The argument to be substituted in place of the formatting anchor + * @return The formatted message + */ + final public static FormattingTuple format(String messagePattern, Object arg) { + return arrayFormat(messagePattern, new Object[] { arg }); + } + + /** + * + * Performs a two argument substitution for the 'messagePattern' passed as + * parameter. + *

    + * For example, + * + *

    +     * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob");
    +     * 
    + * + * will return the string "Hi Alice. My name is Bob.". + * + * @param messagePattern + * The message pattern which will be parsed and formatted + * @param arg1 + * The argument to be substituted in place of the first formatting + * anchor + * @param arg2 + * The argument to be substituted in place of the second formatting + * anchor + * @return The formatted message + */ + final public static FormattingTuple format(final String messagePattern, Object arg1, Object arg2) { + return arrayFormat(messagePattern, new Object[] { arg1, arg2 }); + } + + + static final Throwable getThrowableCandidate(Object[] argArray) { + if (argArray == null || argArray.length == 0) { + return null; + } + + final Object lastEntry = argArray[argArray.length - 1]; + if (lastEntry instanceof Throwable) { + return (Throwable) lastEntry; + } + return null; + } + + final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray) { + Throwable throwableCandidate = getThrowableCandidate(argArray); + Object[] args = argArray; + if (throwableCandidate != null) { + args = trimmedCopy(argArray); + } + return arrayFormat(messagePattern, args, throwableCandidate); + } + + private static Object[] trimmedCopy(Object[] argArray) { + if (argArray == null || argArray.length == 0) { + throw new IllegalStateException("non-sensical empty or null argument array"); + } + final int trimemdLen = argArray.length - 1; + Object[] trimmed = new Object[trimemdLen]; + System.arraycopy(argArray, 0, trimmed, 0, trimemdLen); + return trimmed; + } + + final public static FormattingTuple arrayFormat(final String messagePattern, final Object[] argArray, Throwable throwable) { + + if (messagePattern == null) { + return new FormattingTuple(null, argArray, throwable); + } + + if (argArray == null) { + return new FormattingTuple(messagePattern); + } + + int i = 0; + int j; + // use string builder for better multicore performance + StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50); + + int L; + for (L = 0; L < argArray.length; L++) { + + j = messagePattern.indexOf(DELIM_STR, i); + + if (j == -1) { + // no more variables + if (i == 0) { // this is a simple string + return new FormattingTuple(messagePattern, argArray, throwable); + } else { // add the tail string which contains no variables and return + // the result. + sbuf.append(messagePattern, i, messagePattern.length()); + return new FormattingTuple(sbuf.toString(), argArray, throwable); + } + } else { + if (isEscapedDelimeter(messagePattern, j)) { + if (!isDoubleEscaped(messagePattern, j)) { + L--; // DELIM_START was escaped, thus should not be incremented + sbuf.append(messagePattern, i, j - 1); + sbuf.append(DELIM_START); + i = j + 1; + } else { + // The escape character preceding the delimiter start is + // itself escaped: "abc x:\\{}" + // we have to consume one backward slash + sbuf.append(messagePattern, i, j - 1); + deeplyAppendParameter(sbuf, argArray[L], new HashMap()); + i = j + 2; + } + } else { + // normal case + sbuf.append(messagePattern, i, j); + deeplyAppendParameter(sbuf, argArray[L], new HashMap()); + i = j + 2; + } + } + } + // append the characters following the last {} pair. + sbuf.append(messagePattern, i, messagePattern.length()); + return new FormattingTuple(sbuf.toString(), argArray, throwable); + } + + final static boolean isEscapedDelimeter(String messagePattern, int delimeterStartIndex) { + + if (delimeterStartIndex == 0) { + return false; + } + char potentialEscape = messagePattern.charAt(delimeterStartIndex - 1); + if (potentialEscape == ESCAPE_CHAR) { + return true; + } else { + return false; + } + } + + final static boolean isDoubleEscaped(String messagePattern, int delimeterStartIndex) { + if (delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR) { + return true; + } else { + return false; + } + } + + // special treatment of array values was suggested by 'lizongbo' + private static void deeplyAppendParameter(StringBuilder sbuf, Object o, Map seenMap) { + if (o == null) { + sbuf.append("null"); + return; + } + if (!o.getClass().isArray()) { + safeObjectAppend(sbuf, o); + } else { + // check for primitive array types because they + // unfortunately cannot be cast to Object[] + if (o instanceof boolean[]) { + booleanArrayAppend(sbuf, (boolean[]) o); + } else if (o instanceof byte[]) { + byteArrayAppend(sbuf, (byte[]) o); + } else if (o instanceof char[]) { + charArrayAppend(sbuf, (char[]) o); + } else if (o instanceof short[]) { + shortArrayAppend(sbuf, (short[]) o); + } else if (o instanceof int[]) { + intArrayAppend(sbuf, (int[]) o); + } else if (o instanceof long[]) { + longArrayAppend(sbuf, (long[]) o); + } else if (o instanceof float[]) { + floatArrayAppend(sbuf, (float[]) o); + } else if (o instanceof double[]) { + doubleArrayAppend(sbuf, (double[]) o); + } else { + objectArrayAppend(sbuf, (Object[]) o, seenMap); + } + } + } + + private static void safeObjectAppend(StringBuilder sbuf, Object o) { + try { + String oAsString = o.toString(); + sbuf.append(oAsString); + } catch (Throwable t) { + sbuf.append("[FAILED toString()]"); + } + + } + + private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Map seenMap) { + sbuf.append('['); + if (!seenMap.containsKey(a)) { + seenMap.put(a, null); + final int len = a.length; + for (int i = 0; i < len; i++) { + deeplyAppendParameter(sbuf, a[i], seenMap); + if (i != len - 1) { + sbuf.append(", "); + } + } + // allow repeats in siblings + seenMap.remove(a); + } else { + sbuf.append("..."); + } + sbuf.append(']'); + } + + private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void byteArrayAppend(StringBuilder sbuf, byte[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void charArrayAppend(StringBuilder sbuf, char[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void shortArrayAppend(StringBuilder sbuf, short[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void intArrayAppend(StringBuilder sbuf, int[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void longArrayAppend(StringBuilder sbuf, long[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void floatArrayAppend(StringBuilder sbuf, float[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + + private static void doubleArrayAppend(StringBuilder sbuf, double[] a) { + sbuf.append('['); + final int len = a.length; + for (int i = 0; i < len; i++) { + sbuf.append(a[i]); + if (i != len - 1) { + sbuf.append(", "); + } + } + sbuf.append(']'); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/AdvancedMetricExtension.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/AdvancedMetricExtension.java new file mode 100644 index 00000000..07b98a2c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/AdvancedMetricExtension.java @@ -0,0 +1,74 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * Extended {@link MetricExtension} extending input parameters of each metric + * collection method with {@link EntryType}. + * + * @author bill_yip + * @author Eric Zhao + * @since 1.8.0 + */ +public interface AdvancedMetricExtension extends MetricExtension { + + /** + * Add current pass count of the resource name. + * + * @param rw resource representation (including resource name, traffic type, etc.) + * @param batchCount count to add + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void onPass(ResourceWrapper rw, int batchCount, Object[] args); + + /** + * Add current block count of the resource name. + * + * @param rw resource representation (including resource name, traffic type, etc.) + * @param batchCount count to add + * @param origin the origin of caller (if present) + * @param e the associated {@code BlockException} + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void onBlocked(ResourceWrapper rw, int batchCount, String origin, BlockException e, + Object[] args); + + /** + * Add current completed count of the resource name. + * + * @param rw resource representation (including resource name, traffic type, etc.) + * @param batchCount count to add + * @param rt response time of current invocation + * @param args additional arguments of the resource + */ + void onComplete(ResourceWrapper rw, long rt, int batchCount, Object[] args); + + /** + * Add current exception count of the resource name. + * + * @param rw resource representation (including resource name, traffic type, etc.) + * @param batchCount count to add + * @param throwable exception related. + * @param args additional arguments of the resource + */ + void onError(ResourceWrapper rw, Throwable throwable, int batchCount, Object[] args); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/MetricCallbackInit.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/MetricCallbackInit.java new file mode 100644 index 00000000..3f962f90 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/MetricCallbackInit.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init.InitFunc; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.callback.MetricEntryCallback; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.callback.MetricExitCallback; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.StatisticSlotCallbackRegistry; + +/** + * Register callbacks for metric extension. + * + * @author Carpenter Lee + * @since 1.6.1 + */ +public class MetricCallbackInit implements InitFunc { + @Override + public void init() throws Exception { + StatisticSlotCallbackRegistry.addEntryCallback(MetricEntryCallback.class.getCanonicalName(), + new MetricEntryCallback()); + StatisticSlotCallbackRegistry.addExitCallback(MetricExitCallback.class.getCanonicalName(), + new MetricExitCallback()); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/MetricExtension.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/MetricExtension.java new file mode 100644 index 00000000..d5bf56a5 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/MetricExtension.java @@ -0,0 +1,101 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * This interface provides extension to Sentinel internal statistics. + *

    + * Please note that all method in this class will invoke in the same thread of biz logic. + * It's necessary to not do time-consuming operation in any of the interface's method, + * otherwise biz logic will be blocked. + *

    + * + * @author Carpenter Lee + * @since 1.6.1 + */ +public interface MetricExtension { + + /** + * Add current pass count of the resource name. + * + * @param n count to add + * @param resource resource name + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void addPass(String resource, int n, Object... args); + + /** + * Add current block count of the resource name. + * + * @param n count to add + * @param resource resource name + * @param origin the original invoker. + * @param blockException block exception related. + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void addBlock(String resource, int n, String origin, BlockException blockException, Object... args); + + /** + * Add current completed count of the resource name. + * + * @param n count to add + * @param resource resource name + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void addSuccess(String resource, int n, Object... args); + + /** + * Add current exception count of the resource name. + * + * @param n count to add + * @param resource resource name + * @param throwable exception related. + */ + void addException(String resource, int n, Throwable throwable); + + /** + * Add response time of the resource name. + * + * @param rt response time in millisecond + * @param resource resource name + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void addRt(String resource, long rt, Object... args); + + /** + * Increase current thread count of the resource name. + * + * @param resource resource name + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void increaseThreadNum(String resource, Object... args); + + /** + * Decrease current thread count of the resource name. + * + * @param resource resource name + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void decreaseThreadNum(String resource, Object... args); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/MetricExtensionProvider.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/MetricExtensionProvider.java new file mode 100644 index 00000000..036d7b46 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/MetricExtensionProvider.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension; + +import java.util.ArrayList; +import java.util.List; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoader; + +/** + * Get all {@link MetricExtension} via SPI. + * + * @author Carpenter Lee + * @since 1.6.1 + */ +public class MetricExtensionProvider { + private static List metricExtensions = new ArrayList<>(); + + static { + resolveInstance(); + } + + private static void resolveInstance() { + List extensions = SpiLoader.of(MetricExtension.class).loadInstanceList(); + + if (extensions.isEmpty()) { + RecordLog.info("[MetricExtensionProvider] No existing MetricExtension found"); + } else { + metricExtensions.addAll(extensions); + RecordLog.info("[MetricExtensionProvider] MetricExtension resolved, size={}", extensions.size()); + } + } + + /** + *

    Get all registered metric extensions.

    + *

    DO NOT MODIFY the returned list, use {@link #addMetricExtension(MetricExtension)}.

    + * + * @return all registered metric extensions + */ + public static List getMetricExtensions() { + return metricExtensions; + } + + /** + * Add metric extension. + *

    + * Note that this method is NOT thread safe. + *

    + * + * @param metricExtension the metric extension to add. + */ + public static void addMetricExtension(MetricExtension metricExtension) { + metricExtensions.add(metricExtension); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallback.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallback.java new file mode 100644 index 00000000..66d1f319 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallback.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.callback; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.MetricExtension; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * Metric extension entry callback. + * + * @author Carpenter Lee + * @since 1.6.1 + */ +public class MetricEntryCallback implements ProcessorSlotEntryCallback { + + @Override + public void onPass(Context context, ResourceWrapper rw, DefaultNode param, int count, Object... args) + throws Exception { + for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { + if (m instanceof AdvancedMetricExtension) { + ((AdvancedMetricExtension) m).onPass(rw, count, args); + } else { + m.increaseThreadNum(rw.getName(), args); + m.addPass(rw.getName(), count, args); + } + } + } + + @Override + public void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, DefaultNode param, + int count, Object... args) { + for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { + if (m instanceof AdvancedMetricExtension) { + ((AdvancedMetricExtension) m).onBlocked(resourceWrapper, count, context.getOrigin(), ex, args); + } else { + m.addBlock(resourceWrapper.getName(), count, context.getOrigin(), ex, args); + } + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallback.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallback.java new file mode 100644 index 00000000..4c8092e6 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallback.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.callback; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.MetricExtension; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; + +/** + * Metric extension exit callback. + * + * @author Carpenter Lee + * @author Eric Zhao + * @since 1.6.1 + */ +public class MetricExitCallback implements ProcessorSlotExitCallback { + + @Override + public void onExit(Context context, ResourceWrapper rw, int acquireCount, Object... args) { + Entry curEntry = context.getCurEntry(); + if (curEntry == null) { + return; + } + for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { + if (curEntry.getBlockError() != null) { + continue; + } + String resource = rw.getName(); + Throwable ex = curEntry.getError(); + long completeTime = curEntry.getCompleteTimestamp(); + if (completeTime <= 0) { + completeTime = TimeUtil.currentTimeMillis(); + } + long rt = completeTime - curEntry.getCreateTimestamp(); + + if (m instanceof AdvancedMetricExtension) { + // Since 1.8.0 (as a temporary workaround for compatibility) + ((AdvancedMetricExtension) m).onComplete(rw, rt, acquireCount, args); + if (ex != null) { + ((AdvancedMetricExtension) m).onError(rw, ex, acquireCount, args); + } + } else { + m.addRt(resource, rt, args); + m.addSuccess(resource, acquireCount, args); + m.decreaseThreadNum(resource, args); + if (null != ex) { + m.addException(resource, acquireCount, ex); + } + } + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/ClusterNode.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/ClusterNode.java new file mode 100755 index 00000000..666bbef2 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/ClusterNode.java @@ -0,0 +1,127 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ResourceTypeConstants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.StatisticNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; + +/** + *

    + * This class stores summary runtime statistics of the resource, including rt, thread count, qps + * and so on. Same resource shares the same {@link ClusterNode} globally, no matter in which + * {@link com.alibaba.csp.sentinel.context.Context}. + *

    + *

    + * To distinguish invocation from different origin (declared in + * {@link ContextUtil#enter(String name, String origin)}), + * one {@link ClusterNode} holds an {@link #originCountMap}, this map holds {@link StatisticNode} + * of different origin. Use {@link #getOrCreateOriginNode(String)} to get {@link Node} of the specific + * origin.
    + * Note that 'origin' usually is Service Consumer's app name. + *

    + * + * @author qinan.qn + * @author jialiang.linjl + */ +public class ClusterNode extends StatisticNode { + + private final String name; + private final int resourceType; + + public ClusterNode(String name) { + this(name, ResourceTypeConstants.COMMON); + } + + public ClusterNode(String name, int resourceType) { + AssertUtil.notEmpty(name, "name cannot be empty"); + this.name = name; + this.resourceType = resourceType; + } + + /** + *

    The origin map holds the pair: (origin, originNode) for one specific resource.

    + *

    + * The longer the application runs, the more stable this mapping will become. + * So we didn't use concurrent map here, but a lock, as this lock only happens + * at the very beginning while concurrent map will hold the lock all the time. + *

    + */ + private Map originCountMap = new HashMap<>(); + + private final ReentrantLock lock = new ReentrantLock(); + + /** + * Get resource name of the resource node. + * + * @return resource name + * @since 1.7.0 + */ + public String getName() { + return name; + } + + /** + * Get classification (type) of the resource. + * + * @return resource type + * @since 1.7.0 + */ + public int getResourceType() { + return resourceType; + } + + /** + *

    Get {@link Node} of the specific origin. Usually the origin is the Service Consumer's app name.

    + *

    If the origin node for given origin is absent, then a new {@link StatisticNode} + * for the origin will be created and returned.

    + * + * @param origin The caller's name, which is designated in the {@code parameter} parameter + * {@link ContextUtil#enter(String name, String origin)}. + * @return the {@link Node} of the specific origin + */ + public Node getOrCreateOriginNode(String origin) { + StatisticNode statisticNode = originCountMap.get(origin); + if (statisticNode == null) { + lock.lock(); + try { + statisticNode = originCountMap.get(origin); + if (statisticNode == null) { + // The node is absent, create a new node for the origin. + statisticNode = new StatisticNode(); + HashMap newMap = new HashMap<>(originCountMap.size() + 1); + newMap.putAll(originCountMap); + newMap.put(origin, statisticNode); + originCountMap = newMap; + } + } finally { + lock.unlock(); + } + } + return statisticNode; + } + + public Map getOriginCountMap() { + return originCountMap; + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/DefaultNode.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/DefaultNode.java new file mode 100755 index 00000000..e9ea6a55 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/DefaultNode.java @@ -0,0 +1,172 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import java.util.HashSet; +import java.util.Set; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.SphO; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.SphU; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.EntranceNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.StatisticNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; + +/** + *

    + * A {@link Node} used to hold statistics for specific resource name in the specific context. + * Each distinct resource in each distinct {@link Context} will corresponding to a {@link DefaultNode}. + *

    + *

    + * This class may have a list of sub {@link DefaultNode}s. Child nodes will be created when + * calling {@link SphU}#entry() or {@link SphO}@entry() multiple times in the same {@link Context}. + *

    + * + * @author qinan.qn + * @see NodeSelectorSlot + */ +public class DefaultNode extends StatisticNode { + + /** + * The resource associated with the node. + */ + private ResourceWrapper id; + + /** + * The list of all child nodes. + */ + private volatile Set childList = new HashSet<>(); + + /** + * Associated cluster node. + */ + private ClusterNode clusterNode; + + public DefaultNode(ResourceWrapper id, ClusterNode clusterNode) { + this.id = id; + this.clusterNode = clusterNode; + } + + public ResourceWrapper getId() { + return id; + } + + public ClusterNode getClusterNode() { + return clusterNode; + } + + public void setClusterNode(ClusterNode clusterNode) { + this.clusterNode = clusterNode; + } + + /** + * Add child node to current node. + * + * @param node valid child node + */ + public void addChild(Node node) { + if (node == null) { + RecordLog.warn("Trying to add null child to node <{}>, ignored", id.getName()); + return; + } + if (!childList.contains(node)) { + synchronized (this) { + if (!childList.contains(node)) { + Set newSet = new HashSet<>(childList.size() + 1); + newSet.addAll(childList); + newSet.add(node); + childList = newSet; + } + } + RecordLog.info("Add child <{}> to node <{}>", ((DefaultNode)node).id.getName(), id.getName()); + } + } + + /** + * Reset the child node list. + */ + public void removeChildList() { + this.childList = new HashSet<>(); + } + + public Set getChildList() { + return childList; + } + + @Override + public void increaseBlockQps(int count) { + super.increaseBlockQps(count); + this.clusterNode.increaseBlockQps(count); + } + + @Override + public void increaseExceptionQps(int count) { + super.increaseExceptionQps(count); + this.clusterNode.increaseExceptionQps(count); + } + + @Override + public void addRtAndSuccess(long rt, int successCount) { + super.addRtAndSuccess(rt, successCount); + this.clusterNode.addRtAndSuccess(rt, successCount); + } + + @Override + public void increaseThreadNum() { + super.increaseThreadNum(); + this.clusterNode.increaseThreadNum(); + } + + @Override + public void decreaseThreadNum() { + super.decreaseThreadNum(); + this.clusterNode.decreaseThreadNum(); + } + + @Override + public void addPassRequest(int count) { + super.addPassRequest(count); + this.clusterNode.addPassRequest(count); + } + + public void printDefaultNode() { + visitTree(0, this); + } + + private void visitTree(int level, DefaultNode node) { + for (int i = 0; i < level; ++i) { + System.out.print("-"); + } + if (!(node instanceof EntranceNode)) { + System.out.println( + String.format("%s(thread:%s pq:%s bq:%s tq:%s rt:%s 1mp:%s 1mb:%s 1mt:%s)", node.id.getShowName(), + node.curThreadNum(), node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(), + node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest())); + } else { + System.out.println( + String.format("Entry-%s(t:%s pq:%s bq:%s tq:%s rt:%s 1mp:%s 1mb:%s 1mt:%s)", node.id.getShowName(), + node.curThreadNum(), node.passQps(), node.blockQps(), node.totalQps(), node.avgRt(), + node.totalRequest() - node.blockRequest(), node.blockRequest(), node.totalRequest())); + } + for (Node n : node.getChildList()) { + DefaultNode dn = (DefaultNode)n; + visitTree(level + 1, dn); + } + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/EntranceNode.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/EntranceNode.java new file mode 100755 index 00000000..9318acc6 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/EntranceNode.java @@ -0,0 +1,127 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; + +/** + *

    + * A {@link Node} represents the entrance of the invocation tree. + *

    + *

    + * One {@link Context} will related to a {@link EntranceNode}, + * which represents the entrance of the invocation tree. New {@link EntranceNode} will be created if + * current context does't have one. Note that same context name will share same {@link EntranceNode} + * globally. + *

    + * + * @author qinan.qn + * @see ContextUtil + * @see ContextUtil#enter(String, String) + * @see NodeSelectorSlot + */ +public class EntranceNode extends DefaultNode { + + public EntranceNode(ResourceWrapper id, ClusterNode clusterNode) { + super(id, clusterNode); + } + + @Override + public double avgRt() { + double total = 0; + double totalQps = 0; + for (Node node : getChildList()) { + total += node.avgRt() * node.passQps(); + totalQps += node.passQps(); + } + return total / (totalQps == 0 ? 1 : totalQps); + } + + @Override + public double blockQps() { + double blockQps = 0; + for (Node node : getChildList()) { + blockQps += node.blockQps(); + } + return blockQps; + } + + @Override + public long blockRequest() { + long r = 0; + for (Node node : getChildList()) { + r += node.blockRequest(); + } + return r; + } + + @Override + public int curThreadNum() { + int r = 0; + for (Node node : getChildList()) { + r += node.curThreadNum(); + } + return r; + } + + @Override + public double totalQps() { + double r = 0; + for (Node node : getChildList()) { + r += node.totalQps(); + } + return r; + } + + @Override + public double successQps() { + double r = 0; + for (Node node : getChildList()) { + r += node.successQps(); + } + return r; + } + + @Override + public double passQps() { + double r = 0; + for (Node node : getChildList()) { + r += node.passQps(); + } + return r; + } + + @Override + public long totalRequest() { + long r = 0; + for (Node node : getChildList()) { + r += node.totalRequest(); + } + return r; + } + + @Override + public long totalPass() { + long r = 0; + for (Node node : getChildList()) { + r += node.totalPass(); + } + return r; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/IntervalProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/IntervalProperty.java new file mode 100755 index 00000000..91f07b9f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/IntervalProperty.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SimplePropertyListener; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; + +/** + * QPS statistics interval. + * + * @author youji.zj + * @author jialiang.linjl + * @author Carpenter Lee + * @author Eric Zhao + */ +public class IntervalProperty { + + /** + *

    Interval in milliseconds. This variable determines sensitivity of the QPS calculation.

    + *

    + * DO NOT MODIFY this value directly, use {@link #updateInterval(int)}, otherwise the modification will not + * take effect. + *

    + */ + public static volatile int INTERVAL = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS; + + public static void register2Property(SentinelProperty property) { + property.addListener(new SimplePropertyListener() { + @Override + public void configUpdate(Integer value) { + if (value != null) { + updateInterval(value); + } + } + }); + } + + /** + * Update the {@link #INTERVAL}, All {@link ClusterNode}s will be reset if newInterval is + * different from {@link #INTERVAL} + * + * @param newInterval New interval to set. + */ + public static void updateInterval(int newInterval) { + if (newInterval != INTERVAL) { + INTERVAL = newInterval; + ClusterBuilderSlot.resetClusterNodes(); + } + RecordLog.info("[IntervalProperty] INTERVAL updated to: {}", INTERVAL); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/Node.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/Node.java new file mode 100755 index 00000000..edfd735a --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/Node.java @@ -0,0 +1,207 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import java.util.List; +import java.util.Map; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.IntervalProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.OccupySupport; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.SampleCountProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric.MetricNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Predicate; + +/** + * Holds real-time statistics for resources. + * + * @author qinan.qn + * @author leyou + * @author Eric Zhao + */ +public interface Node extends OccupySupport, DebugSupport { + + /** + * Get incoming request per minute ({@code pass + block}). + * + * @return total request count per minute + */ + long totalRequest(); + + /** + * Get pass count per minute. + * + * @return total passed request count per minute + * @since 1.5.0 + */ + long totalPass(); + + /** + * Get {@link Entry#exit()} count per minute. + * + * @return total completed request count per minute + */ + long totalSuccess(); + + /** + * Get blocked request count per minute (totalBlockRequest). + * + * @return total blocked request count per minute + */ + long blockRequest(); + + /** + * Get exception count per minute. + * + * @return total business exception count per minute + */ + long totalException(); + + /** + * Get pass request per second. + * + * @return QPS of passed requests + */ + double passQps(); + + /** + * Get block request per second. + * + * @return QPS of blocked requests + */ + double blockQps(); + + /** + * Get {@link #passQps()} + {@link #blockQps()} request per second. + * + * @return QPS of passed and blocked requests + */ + double totalQps(); + + /** + * Get {@link Entry#exit()} request per second. + * + * @return QPS of completed requests + */ + double successQps(); + + /** + * Get estimated max success QPS till now. + * + * @return max completed QPS + */ + double maxSuccessQps(); + + /** + * Get exception count per second. + * + * @return QPS of exception occurs + */ + double exceptionQps(); + + /** + * Get average rt per second. + * + * @return average response time per second + */ + double avgRt(); + + /** + * Get minimal response time. + * + * @return recorded minimal response time + */ + double minRt(); + + /** + * Get current active thread count. + * + * @return current active thread count + */ + int curThreadNum(); + + /** + * Get last second block QPS. + */ + double previousBlockQps(); + + /** + * Last window QPS. + */ + double previousPassQps(); + + /** + * Fetch all valid metric nodes of resources. + * + * @return valid metric nodes of resources + */ + Map metrics(); + + /** + * Fetch all raw metric items that satisfies the time predicate. + * + * @param timePredicate time predicate + * @return raw metric items that satisfies the time predicate + * @since 1.7.0 + */ + List rawMetricsInMin(Predicate timePredicate); + + /** + * Add pass count. + * + * @param count count to add pass + */ + void addPassRequest(int count); + + /** + * Add rt and success count. + * + * @param rt response time + * @param success success count to add + */ + void addRtAndSuccess(long rt, int success); + + /** + * Increase the block count. + * + * @param count count to add + */ + void increaseBlockQps(int count); + + /** + * Add the biz exception count. + * + * @param count count to add + */ + void increaseExceptionQps(int count); + + /** + * Increase current thread count. + */ + void increaseThreadNum(); + + /** + * Decrease current thread count. + */ + void decreaseThreadNum(); + + /** + * Reset the internal counter. Reset is needed when {@link IntervalProperty#INTERVAL} or + * {@link SampleCountProperty#SAMPLE_COUNT} is changed. + */ + void reset(); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/NodeBuilder.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/NodeBuilder.java new file mode 100755 index 00000000..29c7a5ed --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/NodeBuilder.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; + +/** + * Builds new {@link DefaultNode} and {@link ClusterNode}. + * + * @author qinan.qn + */ +@Deprecated +public interface NodeBuilder { + + /** + * Create a new {@link DefaultNode} as tree node. + * + * @param id resource + * @param clusterNode the cluster node of the provided resource + * @return new created tree node + */ + DefaultNode buildTreeNode(ResourceWrapper id, ClusterNode clusterNode); + + /** + * Create a new {@link ClusterNode} as universal statistic node for a single resource. + * + * @return new created cluster node + */ + ClusterNode buildClusterNode(); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/OccupySupport.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/OccupySupport.java new file mode 100644 index 00000000..f839d50c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/OccupySupport.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.OccupyTimeoutProperty; + +/** + * @author Eric Zhao + * @since 1.5.0 + */ +public interface OccupySupport { + + /** + * Try to occupy latter time windows' tokens. If occupy success, a value less than + * {@code occupyTimeout} in {@link OccupyTimeoutProperty} will be return. + * + *

    + * Each time we occupy tokens of the future window, current thread should sleep for the + * corresponding time for smoothing QPS. We can't occupy tokens of the future with unlimited, + * the sleep time limit is {@code occupyTimeout} in {@link OccupyTimeoutProperty}. + *

    + * + * @param currentTime current time millis. + * @param acquireCount tokens count to acquire. + * @param threshold qps threshold. + * @return time should sleep. Time >= {@code occupyTimeout} in {@link OccupyTimeoutProperty} means + * occupy fail, in this case, the request should be rejected immediately. + */ + long tryOccupyNext(long currentTime, int acquireCount, double threshold); + + /** + * Get current waiting amount. Useful for debug. + * + * @return current waiting amount + */ + long waiting(); + + /** + * Add request that occupied. + * + * @param futureTime future timestamp that the acquireCount should be added on. + * @param acquireCount tokens count. + */ + void addWaitingRequest(long futureTime, int acquireCount); + + /** + * Add occupied pass request, which represents pass requests that borrow the latter windows' token. + * + * @param acquireCount tokens count. + */ + void addOccupiedPass(int acquireCount); + + /** + * Get current occupied pass QPS. + * + * @return current occupied pass QPS + */ + double occupiedPassQps(); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/OccupyTimeoutProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/OccupyTimeoutProperty.java new file mode 100644 index 00000000..b4a16586 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/OccupyTimeoutProperty.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SimplePropertyListener; + +/** + * @author jialiang.linjl + * @author Carpenter Lee + * @since 1.5.0 + */ +public class OccupyTimeoutProperty { + + /** + *

    + * Max occupy timeout in milliseconds. Requests with priority can occupy tokens of the future statistic + * window, and {@code occupyTimeout} limit the max time length that can be occupied. + *

    + *

    + * Note that the timeout value should never be greeter than {@link IntervalProperty#INTERVAL}. + *

    + * DO NOT MODIFY this value directly, use {@link #updateTimeout(int)}, + * otherwise the modification will not take effect. + */ + private static volatile int occupyTimeout = 500; + + public static void register2Property(SentinelProperty property) { + property.addListener(new SimplePropertyListener() { + @Override + public void configUpdate(Integer value) { + if (value != null) { + updateTimeout(value); + } + } + }); + } + + public static int getOccupyTimeout() { + return occupyTimeout; + } + + /** + * Update the timeout value.
    + * Note that the time out should never greeter than {@link IntervalProperty#INTERVAL}, + * or it will be ignored. + * + * @param newInterval new value. + */ + public static void updateTimeout(int newInterval) { + if (newInterval < 0) { + RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: " + occupyTimeout); + return; + } + if (newInterval > IntervalProperty.INTERVAL) { + RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: {}, should <= {}", + occupyTimeout, IntervalProperty.INTERVAL); + return; + } + if (newInterval != occupyTimeout) { + occupyTimeout = newInterval; + } + RecordLog.info("[OccupyTimeoutProperty] occupyTimeout updated to: {}", occupyTimeout); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/SampleCountProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/SampleCountProperty.java new file mode 100755 index 00000000..1fd1183d --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/SampleCountProperty.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SimplePropertyListener; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; + +/** + * Holds statistic buckets count per second. + * + * @author jialiang.linjl + * @author CarpenterLee + */ +public class SampleCountProperty { + + /** + *

    + * Statistic buckets count per second. This variable determines sensitivity of the QPS calculation. + * DO NOT MODIFY this value directly, use {@link #updateSampleCount(int)}, otherwise the modification will not + * take effect. + *

    + * Node that this value must be divisor of 1000. + */ + public static volatile int SAMPLE_COUNT = 2; + + public static void register2Property(SentinelProperty property) { + property.addListener(new SimplePropertyListener() { + @Override + public void configUpdate(Integer value) { + if (value != null) { + updateSampleCount(value); + } + } + }); + } + + /** + * Update the {@link #SAMPLE_COUNT}. All {@link ClusterNode}s will be reset if newSampleCount + * is different from {@link #SAMPLE_COUNT}. + * + * @param newSampleCount New sample count to set. This value must be divisor of 1000. + */ + public static void updateSampleCount(int newSampleCount) { + if (newSampleCount != SAMPLE_COUNT) { + SAMPLE_COUNT = newSampleCount; + ClusterBuilderSlot.resetClusterNodes(); + } + RecordLog.info("SAMPLE_COUNT updated to: {}", SAMPLE_COUNT); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/StatisticNode.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/StatisticNode.java new file mode 100755 index 00000000..97372548 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/StatisticNode.java @@ -0,0 +1,340 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.LongAdder; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.IntervalProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.OccupyTimeoutProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.SampleCountProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric.MetricNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.ArrayMetric; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.Metric; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Predicate; + +/** + *

    The statistic node keep three kinds of real-time statistics metrics:

    + *
      + *
    1. metrics in second level ({@code rollingCounterInSecond})
    2. + *
    3. metrics in minute level ({@code rollingCounterInMinute})
    4. + *
    5. thread count
    6. + *
    + * + *

    + * Sentinel use sliding window to record and count the resource statistics in real-time. + * The sliding window infrastructure behind the {@link ArrayMetric} is {@code LeapArray}. + *

    + * + *

    + * case 1: When the first request comes in, Sentinel will create a new window bucket of + * a specified time-span to store running statics, such as total response time(rt), + * incoming request(QPS), block request(bq), etc. And the time-span is defined by sample count. + *

    + *
    + * 	0      100ms
    + *  +-------+--→ Sliding Windows
    + * 	    ^
    + * 	    |
    + * 	  request
    + * 
    + *

    + * Sentinel use the statics of the valid buckets to decide whether this request can be passed. + * For example, if a rule defines that only 100 requests can be passed, + * it will sum all qps in valid buckets, and compare it to the threshold defined in rule. + *

    + * + *

    case 2: continuous requests

    + *
    + *  0    100ms    200ms    300ms
    + *  +-------+-------+-------+-----→ Sliding Windows
    + *                      ^
    + *                      |
    + *                   request
    + * 
    + * + *

    case 3: requests keeps coming, and previous buckets become invalid

    + *
    + *  0    100ms    200ms	  800ms	   900ms  1000ms    1300ms
    + *  +-------+-------+ ...... +-------+-------+ ...... +-------+-----→ Sliding Windows
    + *                                                      ^
    + *                                                      |
    + *                                                    request
    + * 
    + * + *

    The sliding window should become:

    + *
    + * 300ms     800ms  900ms  1000ms  1300ms
    + *  + ...... +-------+ ...... +-------+-----→ Sliding Windows
    + *                                                      ^
    + *                                                      |
    + *                                                    request
    + * 
    + * + * @author qinan.qn + * @author jialiang.linjl + */ +public class StatisticNode implements Node { + + /** + * Holds statistics of the recent {@code INTERVAL} milliseconds. The {@code INTERVAL} is divided into time spans + * by given {@code sampleCount}. + */ + private transient volatile Metric rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, + IntervalProperty.INTERVAL); + + /** + * Holds statistics of the recent 60 seconds. The windowLengthInMs is deliberately set to 1000 milliseconds, + * meaning each bucket per second, in this way we can get accurate statistics of each second. + */ + private transient Metric rollingCounterInMinute = new ArrayMetric(60, 60 * 1000, false); + + /** + * The counter for thread count. + */ + private LongAdder curThreadNum = new LongAdder(); + + /** + * The last timestamp when metrics were fetched. + */ + private long lastFetchTime = -1; + + @Override + public Map metrics() { + // The fetch operation is thread-safe under a single-thread scheduler pool. + long currentTime = TimeUtil.currentTimeMillis(); + currentTime = currentTime - currentTime % 1000; + Map metrics = new ConcurrentHashMap<>(); + List nodesOfEverySecond = rollingCounterInMinute.details(); + long newLastFetchTime = lastFetchTime; + // Iterate metrics of all resources, filter valid metrics (not-empty and up-to-date). + for (MetricNode node : nodesOfEverySecond) { + if (isNodeInTime(node, currentTime) && isValidMetricNode(node)) { + metrics.put(node.getTimestamp(), node); + newLastFetchTime = Math.max(newLastFetchTime, node.getTimestamp()); + } + } + lastFetchTime = newLastFetchTime; + + return metrics; + } + + @Override + public List rawMetricsInMin(Predicate timePredicate) { + return rollingCounterInMinute.detailsOnCondition(timePredicate); + } + + private boolean isNodeInTime(MetricNode node, long currentTime) { + return node.getTimestamp() > lastFetchTime && node.getTimestamp() < currentTime; + } + + private boolean isValidMetricNode(MetricNode node) { + return node.getPassQps() > 0 || node.getBlockQps() > 0 || node.getSuccessQps() > 0 + || node.getExceptionQps() > 0 || node.getRt() > 0 || node.getOccupiedPassQps() > 0; + } + + @Override + public void reset() { + rollingCounterInSecond = new ArrayMetric(SampleCountProperty.SAMPLE_COUNT, IntervalProperty.INTERVAL); + } + + @Override + public long totalRequest() { + return rollingCounterInMinute.pass() + rollingCounterInMinute.block(); + } + + @Override + public long blockRequest() { + return rollingCounterInMinute.block(); + } + + @Override + public double blockQps() { + return rollingCounterInSecond.block() / rollingCounterInSecond.getWindowIntervalInSec(); + } + + @Override + public double previousBlockQps() { + return this.rollingCounterInMinute.previousWindowBlock(); + } + + @Override + public double previousPassQps() { + return this.rollingCounterInMinute.previousWindowPass(); + } + + @Override + public double totalQps() { + return passQps() + blockQps(); + } + + @Override + public long totalSuccess() { + return rollingCounterInMinute.success(); + } + + @Override + public double exceptionQps() { + return rollingCounterInSecond.exception() / rollingCounterInSecond.getWindowIntervalInSec(); + } + + @Override + public long totalException() { + return rollingCounterInMinute.exception(); + } + + @Override + public double passQps() { + return rollingCounterInSecond.pass() / rollingCounterInSecond.getWindowIntervalInSec(); + } + + @Override + public long totalPass() { + return rollingCounterInMinute.pass(); + } + + @Override + public double successQps() { + return rollingCounterInSecond.success() / rollingCounterInSecond.getWindowIntervalInSec(); + } + + @Override + public double maxSuccessQps() { + return (double) rollingCounterInSecond.maxSuccess() * rollingCounterInSecond.getSampleCount() + / rollingCounterInSecond.getWindowIntervalInSec(); + } + + @Override + public double occupiedPassQps() { + return rollingCounterInSecond.occupiedPass() / rollingCounterInSecond.getWindowIntervalInSec(); + } + + @Override + public double avgRt() { + long successCount = rollingCounterInSecond.success(); + if (successCount == 0) { + return 0; + } + + return rollingCounterInSecond.rt() * 1.0 / successCount; + } + + @Override + public double minRt() { + return rollingCounterInSecond.minRt(); + } + + @Override + public int curThreadNum() { + return (int)curThreadNum.sum(); + } + + @Override + public void addPassRequest(int count) { + rollingCounterInSecond.addPass(count); + rollingCounterInMinute.addPass(count); + } + + @Override + public void addRtAndSuccess(long rt, int successCount) { + rollingCounterInSecond.addSuccess(successCount); + rollingCounterInSecond.addRT(rt); + + rollingCounterInMinute.addSuccess(successCount); + rollingCounterInMinute.addRT(rt); + } + + @Override + public void increaseBlockQps(int count) { + rollingCounterInSecond.addBlock(count); + rollingCounterInMinute.addBlock(count); + } + + @Override + public void increaseExceptionQps(int count) { + rollingCounterInSecond.addException(count); + rollingCounterInMinute.addException(count); + } + + @Override + public void increaseThreadNum() { + curThreadNum.increment(); + } + + @Override + public void decreaseThreadNum() { + curThreadNum.decrement(); + } + + @Override + public void debug() { + rollingCounterInSecond.debug(); + } + + @Override + public long tryOccupyNext(long currentTime, int acquireCount, double threshold) { + double maxCount = threshold * IntervalProperty.INTERVAL / 1000; + long currentBorrow = rollingCounterInSecond.waiting(); + if (currentBorrow >= maxCount) { + return OccupyTimeoutProperty.getOccupyTimeout(); + } + + int windowLength = IntervalProperty.INTERVAL / SampleCountProperty.SAMPLE_COUNT; + long earliestTime = currentTime - currentTime % windowLength + windowLength - IntervalProperty.INTERVAL; + + int idx = 0; + /* + * Note: here {@code currentPass} may be less than it really is NOW, because time difference + * since call rollingCounterInSecond.pass(). So in high concurrency, the following code may + * lead more tokens be borrowed. + */ + long currentPass = rollingCounterInSecond.pass(); + while (earliestTime < currentTime) { + long waitInMs = idx * windowLength + windowLength - currentTime % windowLength; + if (waitInMs >= OccupyTimeoutProperty.getOccupyTimeout()) { + break; + } + long windowPass = rollingCounterInSecond.getWindowPass(earliestTime); + if (currentPass + currentBorrow + acquireCount - windowPass <= maxCount) { + return waitInMs; + } + earliestTime += windowLength; + currentPass -= windowPass; + idx++; + } + + return OccupyTimeoutProperty.getOccupyTimeout(); + } + + @Override + public long waiting() { + return rollingCounterInSecond.waiting(); + } + + @Override + public void addWaitingRequest(long futureTime, int acquireCount) { + rollingCounterInSecond.addWaiting(futureTime, acquireCount); + } + + @Override + public void addOccupiedPass(int acquireCount) { + rollingCounterInMinute.addOccupiedPass(acquireCount); + rollingCounterInMinute.addPass(acquireCount); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricNode.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricNode.java new file mode 100755 index 00000000..5bb581f6 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricNode.java @@ -0,0 +1,262 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Metrics data for a specific resource at given {@code timestamp}. + * + * @author jialiang.linjl + * @author Carpenter Lee + */ +public class MetricNode { + + private String resource; + /** + * Resource classification (e.g. SQL or RPC) + * @since 1.7.0 + */ + private int classification; + + private long timestamp; + private long passQps; + private long blockQps; + private long successQps; + private long exceptionQps; + private long rt; + + /** + * @since 1.5.0 + */ + private long occupiedPassQps; + /** + * @since 1.7.0 + */ + private int concurrency; + + public long getTimestamp() { + return timestamp; + } + + public long getOccupiedPassQps() { + return occupiedPassQps; + } + + public void setOccupiedPassQps(long occupiedPassQps) { + this.occupiedPassQps = occupiedPassQps; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + public long getSuccessQps() { + return successQps; + } + + public void setSuccessQps(long successQps) { + this.successQps = successQps; + } + + public long getPassQps() { + return passQps; + } + + public void setPassQps(long passQps) { + this.passQps = passQps; + } + + public long getExceptionQps() { + return exceptionQps; + } + + public void setExceptionQps(long exceptionQps) { + this.exceptionQps = exceptionQps; + } + + public long getBlockQps() { + return blockQps; + } + + public void setBlockQps(long blockQps) { + this.blockQps = blockQps; + } + + public long getRt() { + return rt; + } + + public void setRt(long rt) { + this.rt = rt; + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public int getClassification() { + return classification; + } + + public MetricNode setClassification(int classification) { + this.classification = classification; + return this; + } + + public int getConcurrency() { + return concurrency; + } + + public MetricNode setConcurrency(int concurrency) { + this.concurrency = concurrency; + return this; + } + + @Override + public String toString() { + return "MetricNode{" + + "resource='" + resource + '\'' + + ", classification=" + classification + + ", timestamp=" + timestamp + + ", passQps=" + passQps + + ", blockQps=" + blockQps + + ", successQps=" + successQps + + ", exceptionQps=" + exceptionQps + + ", rt=" + rt + + ", concurrency=" + concurrency + + ", occupiedPassQps=" + occupiedPassQps + + '}'; + } + + /** + * To formatting string. All "|" in {@link #resource} will be replaced with + * "_", format is:
    + * + * timestamp|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps + * + * + * @return string format of this. + */ + public String toThinString() { + StringBuilder sb = new StringBuilder(); + sb.append(timestamp).append("|"); + String legalName = resource.replaceAll("\\|", "_"); + sb.append(legalName).append("|"); + sb.append(passQps).append("|"); + sb.append(blockQps).append("|"); + sb.append(successQps).append("|"); + sb.append(exceptionQps).append("|"); + sb.append(rt).append("|"); + sb.append(occupiedPassQps).append("|"); + sb.append(concurrency).append("|"); + sb.append(classification); + return sb.toString(); + } + + /** + * Parse {@link MetricNode} from thin string, see {@link #toThinString()} + * + * @param line + * @return + */ + public static MetricNode fromThinString(String line) { + MetricNode node = new MetricNode(); + String[] strs = line.split("\\|"); + node.setTimestamp(Long.parseLong(strs[0])); + node.setResource(strs[1]); + node.setPassQps(Long.parseLong(strs[2])); + node.setBlockQps(Long.parseLong(strs[3])); + node.setSuccessQps(Long.parseLong(strs[4])); + node.setExceptionQps(Long.parseLong(strs[5])); + node.setRt(Long.parseLong(strs[6])); + if (strs.length >= 8) { + node.setOccupiedPassQps(Long.parseLong(strs[7])); + } + if (strs.length >= 9) { + node.setConcurrency(Integer.parseInt(strs[8])); + } + if (strs.length == 10) { + node.setClassification(Integer.parseInt(strs[9])); + } + return node; + } + + /** + * To formatting string. All "|" in {@link MetricNode#resource} will be + * replaced with "_", format is:
    + * + * timestamp|yyyy-MM-dd HH:mm:ss|resource|passQps|blockQps|successQps|exceptionQps|rt|occupiedPassQps\n + * + * + * @return string format of this. + */ + public String toFatString() { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + StringBuilder sb = new StringBuilder(32); + sb.delete(0, sb.length()); + sb.append(getTimestamp()).append("|"); + sb.append(df.format(new Date(getTimestamp()))).append("|"); + String legalName = getResource().replaceAll("\\|", "_"); + sb.append(legalName).append("|"); + sb.append(getPassQps()).append("|"); + sb.append(getBlockQps()).append("|"); + sb.append(getSuccessQps()).append("|"); + sb.append(getExceptionQps()).append("|"); + sb.append(getRt()).append("|"); + sb.append(getOccupiedPassQps()).append("|"); + sb.append(concurrency).append("|"); + sb.append(classification); + sb.append('\n'); + return sb.toString(); + } + + /** + * Parse {@link MetricNode} from fat string, see {@link #toFatString()} + * + * @param line + * @return the {@link MetricNode} parsed. + */ + public static MetricNode fromFatString(String line) { + String[] strs = line.split("\\|"); + Long time = Long.parseLong(strs[0]); + MetricNode node = new MetricNode(); + node.setTimestamp(time); + node.setResource(strs[2]); + node.setPassQps(Long.parseLong(strs[3])); + node.setBlockQps(Long.parseLong(strs[4])); + node.setSuccessQps(Long.parseLong(strs[5])); + node.setExceptionQps(Long.parseLong(strs[6])); + node.setRt(Long.parseLong(strs[7])); + if (strs.length >= 9) { + node.setOccupiedPassQps(Long.parseLong(strs[8])); + } + if (strs.length >= 10) { + node.setConcurrency(Integer.parseInt(strs[9])); + } + if (strs.length == 11) { + node.setClassification(Integer.parseInt(strs[10])); + } + return node; + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricSearcher.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricSearcher.java new file mode 100755 index 00000000..595abd57 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricSearcher.java @@ -0,0 +1,223 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric; + +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.nio.charset.Charset; +import java.util.List; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig; + +/** + * 从指定目录下找出所有的metric文件,并按照指定时间戳进行检索,参考{@link MetricSearcher#find(long, int)}。 + * 会借助索引以提高检索效率,参考{@link MetricWriter};还会在内部缓存上一次检索的文件指针,以便下一次顺序检索时 + * 减少读盘次数。 + * + * @author leyou + */ +public class MetricSearcher { + + private static final Charset defaultCharset = Charset.forName(SentinelConfig.charset()); + private final MetricsReader metricsReader; + + private String baseDir; + private String baseFileName; + + private Position lastPosition = new Position(); + + /** + * @param baseDir metric文件所在目录 + * @param baseFileName metric文件名的关键字,比如 alihot-metrics.log + */ + public MetricSearcher(String baseDir, String baseFileName) { + this(baseDir, baseFileName, defaultCharset); + } + + /** + * @param baseDir metric文件所在目录 + * @param baseFileName metric文件名的关键字,比如 alihot-metrics.log + * @param charset + */ + public MetricSearcher(String baseDir, String baseFileName, Charset charset) { + if (baseDir == null) { + throw new IllegalArgumentException("baseDir can't be null"); + } + if (baseFileName == null) { + throw new IllegalArgumentException("baseFileName can't be null"); + } + if (charset == null) { + throw new IllegalArgumentException("charset can't be null"); + } + this.baseDir = baseDir; + if (!baseDir.endsWith(File.separator)) { + this.baseDir += File.separator; + } + this.baseFileName = baseFileName; + metricsReader = new MetricsReader(charset); + } + + /** + * 从beginTime开始,检索recommendLines条(大概)记录。同一秒中的数据是原子的,不能分割成多次查询。 + * + * @param beginTimeMs 检索的最小时间戳 + * @param recommendLines 查询最多想得到的记录条数,返回条数会尽可能不超过这个数字。但是为保证每一秒的数据不被分割,有时候 + * 返回的记录条数会大于该数字。 + * @return + * @throws Exception + */ + public synchronized List find(long beginTimeMs, int recommendLines) throws Exception { + List fileNames = MetricWriter.listMetricFiles(baseDir, baseFileName); + int i = 0; + long offsetInIndex = 0; + if (validPosition(beginTimeMs)) { + i = fileNames.indexOf(lastPosition.metricFileName); + if (i == -1) { + i = 0; + } else { + offsetInIndex = lastPosition.offsetInIndex; + } + } + for (; i < fileNames.size(); i++) { + String fileName = fileNames.get(i); + long offset = findOffset(beginTimeMs, fileName, + MetricWriter.formIndexFileName(fileName), offsetInIndex); + offsetInIndex = 0; + if (offset != -1) { + return metricsReader.readMetrics(fileNames, i, offset, recommendLines); + } + } + return null; + } + + /** + * Find metric between [beginTimeMs, endTimeMs], both side inclusive. + * When identity is null, all metric between the time intervalMs will be read, otherwise, only the specific + * identity will be read. + */ + public synchronized List findByTimeAndResource(long beginTimeMs, long endTimeMs, String identity) + throws Exception { + List fileNames = MetricWriter.listMetricFiles(baseDir, baseFileName); + //RecordLog.info("pid=" + pid + ", findByTimeAndResource([" + beginTimeMs + ", " + endTimeMs + // + "], " + identity + ")"); + int i = 0; + long offsetInIndex = 0; + if (validPosition(beginTimeMs)) { + i = fileNames.indexOf(lastPosition.metricFileName); + if (i == -1) { + i = 0; + } else { + offsetInIndex = lastPosition.offsetInIndex; + } + } else { + //RecordLog.info("lastPosition is invalidate, will re iterate all files, pid = " + pid); + } + + for (; i < fileNames.size(); i++) { + String fileName = fileNames.get(i); + long offset = findOffset(beginTimeMs, fileName, + MetricWriter.formIndexFileName(fileName), offsetInIndex); + offsetInIndex = 0; + if (offset != -1) { + return metricsReader.readMetricsByEndTime(fileNames, i, offset, beginTimeMs, endTimeMs, identity); + } + } + return null; + } + + /** + * 记录上一次读取的index文件位置和数值 + */ + private static final class Position { + String metricFileName; + String indexFileName; + /** + * 索引文件内的偏移 + */ + long offsetInIndex; + /** + * 索引文件中offsetInIndex位置上的数字,秒数。 + */ + long second; + } + + /** + * The position we cached is useful only when {@code beginTimeMs} is >= {@code lastPosition.second} + * and the index file exists and the second we cached is same as in the index file. + */ + private boolean validPosition(long beginTimeMs) { + if (beginTimeMs / 1000 < lastPosition.second) { + return false; + } + if (lastPosition.indexFileName == null) { + return false; + } + // index file dose not exits + if (!new File(lastPosition.indexFileName).exists()) { + return false; + } + FileInputStream in = null; + try { + in = new FileInputStream(lastPosition.indexFileName); + in.getChannel().position(lastPosition.offsetInIndex); + DataInputStream indexIn = new DataInputStream(in); + // timestamp(second) in the specific position == that we cached + return indexIn.readLong() == lastPosition.second; + } catch (Exception e) { + return false; + } finally { + if (in != null) { + try { + in.close(); + } catch (Exception ignore) { + } + } + } + } + + private long findOffset(long beginTime, String metricFileName, + String idxFileName, long offsetInIndex) throws Exception { + lastPosition.metricFileName = null; + lastPosition.indexFileName = null; + if (!new File(idxFileName).exists()) { + return -1; + } + long beginSecond = beginTime / 1000; + FileInputStream in = new FileInputStream(idxFileName); + in.getChannel().position(offsetInIndex); + DataInputStream indexIn = new DataInputStream(in); + long offset; + try { + long second; + lastPosition.offsetInIndex = in.getChannel().position(); + while ((second = indexIn.readLong()) < beginSecond) { + offset = indexIn.readLong(); + lastPosition.offsetInIndex = in.getChannel().position(); + } + offset = indexIn.readLong(); + lastPosition.metricFileName = metricFileName; + lastPosition.indexFileName = idxFileName; + lastPosition.second = second; + return offset; + } catch (EOFException ignore) { + return -1; + } finally { + indexIn.close(); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java new file mode 100755 index 00000000..bccf4b73 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricTimerListener.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.ClusterNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; + +/** + * @author jialiang.linjl + */ +public class MetricTimerListener implements Runnable { + + private static final MetricWriter metricWriter = new MetricWriter(SentinelConfig.singleMetricFileSize(), + SentinelConfig.totalMetricFileCount()); + + @Override + public void run() { + Map> maps = new TreeMap<>(); + for (Entry e : ClusterBuilderSlot.getClusterNodeMap().entrySet()) { + ClusterNode node = e.getValue(); + Map metrics = node.metrics(); + aggregate(maps, metrics, node); + } + aggregate(maps, Constants.ENTRY_NODE.metrics(), Constants.ENTRY_NODE); + if (!maps.isEmpty()) { + for (Entry> entry : maps.entrySet()) { + try { + metricWriter.write(entry.getKey(), entry.getValue()); + } catch (Exception e) { + RecordLog.warn("[MetricTimerListener] Write metric error", e); + } + } + } + } + + private void aggregate(Map> maps, Map metrics, ClusterNode node) { + for (Entry entry : metrics.entrySet()) { + long time = entry.getKey(); + MetricNode metricNode = entry.getValue(); + metricNode.setResource(node.getName()); + metricNode.setClassification(node.getResourceType()); + maps.computeIfAbsent(time, k -> new ArrayList()); + List nodes = maps.get(time); + nodes.add(entry.getValue()); + } + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricWriter.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricWriter.java new file mode 100755 index 00000000..95a130d8 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricWriter.java @@ -0,0 +1,402 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogBase; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.PidUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; + +/** + * This class is responsible for writing {@link MetricNode} to disk: + *
      + *
    1. metric with the same second should write to the same file;
    2. + *
    3. single file size must be controlled;
    4. + *
    5. file name is like: {@code ${appName}-metrics.log.pid${pid}.yyyy-MM-dd.[number]}
    6. + *
    7. metric of different day should in different file;
    8. + *
    9. every metric file is accompanied with an index file, which file name is {@code ${metricFileName}.idx}
    10. + *
    + * + * @author Carpenter Lee + */ +public class MetricWriter { + + private static final String CHARSET = SentinelConfig.charset(); + public static final String METRIC_BASE_DIR = LogBase.getLogBaseDir(); + /** + * Note: {@link MetricFileNameComparator}'s implementation relies on the metric file name, + * so we should be careful when changing the metric file name. + * + * @see #formMetricFileName(String, int) + */ + public static final String METRIC_FILE = "metrics.log"; + public static final String METRIC_FILE_INDEX_SUFFIX = ".idx"; + public static final Comparator METRIC_FILE_NAME_CMP = new MetricFileNameComparator(); + + private final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + /** + * 排除时差干扰 + */ + private long timeSecondBase; + private String baseDir; + private String baseFileName; + /** + * file must exist when writing + */ + private File curMetricFile; + private File curMetricIndexFile; + + private FileOutputStream outMetric; + private DataOutputStream outIndex; + private BufferedOutputStream outMetricBuf; + private long singleFileSize; + private int totalFileCount; + private boolean append = false; + private final int pid = PidUtil.getPid(); + + /** + * 秒级统计,忽略毫秒数。 + */ + private long lastSecond = -1; + + public MetricWriter(long singleFileSize) { + this(singleFileSize, 6); + } + + public MetricWriter(long singleFileSize, int totalFileCount) { + if (singleFileSize <= 0 || totalFileCount <= 0) { + throw new IllegalArgumentException(); + } + RecordLog.info("[MetricWriter] Creating new MetricWriter, singleFileSize={}, totalFileCount={}", + singleFileSize, totalFileCount); + this.baseDir = METRIC_BASE_DIR; + File dir = new File(baseDir); + if (!dir.exists()) { + dir.mkdirs(); + } + + long time = System.currentTimeMillis(); + this.lastSecond = time / 1000; + this.singleFileSize = singleFileSize; + this.totalFileCount = totalFileCount; + try { + this.timeSecondBase = df.parse("1970-01-01 00:00:00").getTime() / 1000; + } catch (Exception e) { + RecordLog.warn("[MetricWriter] Create new MetricWriter error", e); + } + } + + /** + * 如果传入了time,就认为nodes中所有的时间时间戳都是time. + * + * @param time + * @param nodes + */ + public synchronized void write(long time, List nodes) throws Exception { + if (nodes == null) { + return; + } + for (MetricNode node : nodes) { + node.setTimestamp(time); + } + + String appName = SentinelConfig.getAppName(); + if (appName == null) { + appName = ""; + } + // first write, should create file + if (curMetricFile == null) { + baseFileName = formMetricFileName(appName, pid); + closeAndNewFile(nextFileNameOfDay(time)); + } + if (!(curMetricFile.exists() && curMetricIndexFile.exists())) { + closeAndNewFile(nextFileNameOfDay(time)); + } + + long second = time / 1000; + if (second < lastSecond) { + // 时间靠前的直接忽略,不应该发生。 + } else if (second == lastSecond) { + for (MetricNode node : nodes) { + outMetricBuf.write(node.toFatString().getBytes(CHARSET)); + } + outMetricBuf.flush(); + if (!validSize()) { + closeAndNewFile(nextFileNameOfDay(time)); + } + } else { + writeIndex(second, outMetric.getChannel().position()); + if (isNewDay(lastSecond, second)) { + closeAndNewFile(nextFileNameOfDay(time)); + for (MetricNode node : nodes) { + outMetricBuf.write(node.toFatString().getBytes(CHARSET)); + } + outMetricBuf.flush(); + if (!validSize()) { + closeAndNewFile(nextFileNameOfDay(time)); + } + } else { + for (MetricNode node : nodes) { + outMetricBuf.write(node.toFatString().getBytes(CHARSET)); + } + outMetricBuf.flush(); + if (!validSize()) { + closeAndNewFile(nextFileNameOfDay(time)); + } + } + lastSecond = second; + } + } + + public synchronized void close() throws Exception { + if (outMetricBuf != null) { + outMetricBuf.close(); + } + if (outIndex != null) { + outIndex.close(); + } + } + + private void writeIndex(long time, long offset) throws Exception { + outIndex.writeLong(time); + outIndex.writeLong(offset); + outIndex.flush(); + } + + private String nextFileNameOfDay(long time) { + List list = new ArrayList(); + File baseFile = new File(baseDir); + DateFormat fileNameDf = new SimpleDateFormat("yyyy-MM-dd"); + String dateStr = fileNameDf.format(new Date(time)); + String fileNameModel = baseFileName + "." + dateStr; + for (File file : baseFile.listFiles()) { + String fileName = file.getName(); + if (fileName.contains(fileNameModel) + && !fileName.endsWith(METRIC_FILE_INDEX_SUFFIX) + && !fileName.endsWith(".lck")) { + list.add(file.getAbsolutePath()); + } + } + Collections.sort(list, METRIC_FILE_NAME_CMP); + if (list.isEmpty()) { + return baseDir + fileNameModel; + } + String last = list.get(list.size() - 1); + int n = 0; + String[] strs = last.split("\\."); + if (strs.length > 0 && strs[strs.length - 1].matches("[0-9]{1,10}")) { + n = Integer.parseInt(strs[strs.length - 1]); + } + return baseDir + fileNameModel + "." + (n + 1); + } + + /** + * A comparator for metric file name. Metric file name is like:
    + *
    +     * metrics.log.2018-03-06
    +     * metrics.log.2018-03-07
    +     * metrics.log.2018-03-07.10
    +     * metrics.log.2018-03-06.100
    +     * 
    + *

    + * File name with the early date is smaller, if date is same, the one with the small file number is smaller. + * Note that if the name is an absolute path, only the fileName({@link File#getName()}) part will be considered. + * So the above file names should be sorted as:
    + *

    +     * metrics.log.2018-03-06
    +     * metrics.log.2018-03-06.100
    +     * metrics.log.2018-03-07
    +     * metrics.log.2018-03-07.10
    +     *
    +     * 
    + *

    + */ + private static final class MetricFileNameComparator implements Comparator { + private final String pid = "pid"; + + @Override + public int compare(String o1, String o2) { + String name1 = new File(o1).getName(); + String name2 = new File(o2).getName(); + String dateStr1 = name1.split("\\.")[2]; + String dateStr2 = name2.split("\\.")[2]; + // in case of file name contains pid, skip it, like Sentinel-Admin-metrics.log.pid22568.2018-12-24 + if (dateStr1.startsWith(pid)) { + dateStr1 = name1.split("\\.")[3]; + dateStr2 = name2.split("\\.")[3]; + } + + // compare date first + int t = dateStr1.compareTo(dateStr2); + if (t != 0) { + return t; + } + + // same date, compare file number + t = name1.length() - name2.length(); + if (t != 0) { + return t; + } + return name1.compareTo(name2); + } + } + + /** + * Get all metric files' name in {@code baseDir}. The file name must like + *
    +     * baseFileName + ".yyyy-MM-dd.number"
    +     * 
    + * and not endsWith {@link #METRIC_FILE_INDEX_SUFFIX} or ".lck". + * + * @param baseDir the directory to search. + * @param baseFileName the file name pattern. + * @return the metric files' absolute path({@link File#getAbsolutePath()}) + * @throws Exception + */ + static List listMetricFiles(String baseDir, String baseFileName) throws Exception { + List list = new ArrayList(); + File baseFile = new File(baseDir); + File[] files = baseFile.listFiles(); + if (files == null) { + return list; + } + for (File file : files) { + String fileName = file.getName(); + if (file.isFile() + && fileNameMatches(fileName, baseFileName) + && !fileName.endsWith(MetricWriter.METRIC_FILE_INDEX_SUFFIX) + && !fileName.endsWith(".lck")) { + list.add(file.getAbsolutePath()); + } + } + Collections.sort(list, MetricWriter.METRIC_FILE_NAME_CMP); + return list; + } + + /** + * Test whether fileName matches baseFileName. fileName matches baseFileName when + *
    +     * fileName = baseFileName + ".yyyy-MM-dd.number"
    +     * 
    + * + * @param fileName file name + * @param baseFileName base file name. + * @return if fileName matches baseFileName return true, else return false. + */ + public static boolean fileNameMatches(String fileName, String baseFileName) { + if (fileName.startsWith(baseFileName)) { + String part = fileName.substring(baseFileName.length()); + // part is like: ".yyyy-MM-dd.number", eg. ".2018-12-24.11" + return part.matches("\\.[0-9]{4}-[0-9]{2}-[0-9]{2}(\\.[0-9]*)?"); + } else { + return false; + } + } + + private void removeMoreFiles() throws Exception { + List list = listMetricFiles(baseDir, baseFileName); + if (list == null || list.isEmpty()) { + return; + } + for (int i = 0; i < list.size() - totalFileCount + 1; i++) { + String fileName = list.get(i); + String indexFile = formIndexFileName(fileName); + new File(fileName).delete(); + RecordLog.info("[MetricWriter] Removing metric file: {}", fileName); + new File(indexFile).delete(); + RecordLog.info("[MetricWriter] Removing metric index file: {}", indexFile); + } + } + + private void closeAndNewFile(String fileName) throws Exception { + removeMoreFiles(); + if (outMetricBuf != null) { + outMetricBuf.close(); + } + if (outIndex != null) { + outIndex.close(); + } + outMetric = new FileOutputStream(fileName, append); + outMetricBuf = new BufferedOutputStream(outMetric); + curMetricFile = new File(fileName); + String idxFile = formIndexFileName(fileName); + curMetricIndexFile = new File(idxFile); + outIndex = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(idxFile, append))); + RecordLog.info("[MetricWriter] New metric file created: {}", fileName); + RecordLog.info("[MetricWriter] New metric index file created: {}", idxFile); + } + + private boolean validSize() throws Exception { + long size = outMetric.getChannel().size(); + return size < singleFileSize; + } + + private boolean isNewDay(long lastSecond, long second) { + long lastDay = (lastSecond - timeSecondBase) / 86400; + long newDay = (second - timeSecondBase) / 86400; + return newDay > lastDay; + } + + /** + * Form metric file name use the specific appName and pid. Note that only + * form the file name, not include path. + * + * Note: {@link MetricFileNameComparator}'s implementation relays on the metric file name, + * we should be careful when changing the metric file name. + * + * @param appName + * @param pid + * @return metric file name. + */ + public static String formMetricFileName(String appName, int pid) { + if (appName == null) { + appName = ""; + } + // dot is special char that should be replaced. + final String dot = "."; + final String separator = "-"; + if (appName.contains(dot)) { + appName = appName.replace(dot, separator); + } + String name = appName + separator + METRIC_FILE; + if (LogBase.isLogNameUsePid()) { + name += ".pid" + pid; + } + return name; + } + + /** + * Form index file name of the {@code metricFileName} + * + * @param metricFileName + * @return the index file name of the metricFileName + */ + public static String formIndexFileName(String metricFileName) { + return metricFileName + METRIC_FILE_INDEX_SUFFIX; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricsReader.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricsReader.java new file mode 100644 index 00000000..52e83c6c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/node/metric/MetricsReader.java @@ -0,0 +1,142 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +/** + * Reads metrics data from log file. + */ +class MetricsReader { + + /** + * Avoid OOM in any cases. + */ + private static final int MAX_LINES_RETURN = 100000; + + private final Charset charset; + + public MetricsReader(Charset charset) { + this.charset = charset; + } + + /** + * @return if should continue read, return true, else false. + */ + boolean readMetricsInOneFileByEndTime(List list, String fileName, long offset, + long beginTimeMs, long endTimeMs, String identity) throws Exception { + FileInputStream in = null; + long beginSecond = beginTimeMs / 1000; + long endSecond = endTimeMs / 1000; + try { + in = new FileInputStream(fileName); + in.getChannel().position(offset); + BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset)); + String line; + while ((line = reader.readLine()) != null) { + MetricNode node = MetricNode.fromFatString(line); + long currentSecond = node.getTimestamp() / 1000; + // currentSecond should >= beginSecond, otherwise a wrong metric file must occur + if (currentSecond < beginSecond) { + return false; + } + if (currentSecond <= endSecond) { + // read all + if (identity == null) { + list.add(node); + } else if (node.getResource().equals(identity)) { + list.add(node); + } + } else { + return false; + } + if (list.size() >= MAX_LINES_RETURN) { + return false; + } + } + } finally { + if (in != null) { + in.close(); + } + } + return true; + } + + void readMetricsInOneFile(List list, String fileName, + long offset, int recommendLines) throws Exception { + //if(list.size() >= recommendLines){ + // return; + //} + long lastSecond = -1; + if (list.size() > 0) { + lastSecond = list.get(list.size() - 1).getTimestamp() / 1000; + } + FileInputStream in = null; + try { + in = new FileInputStream(fileName); + in.getChannel().position(offset); + BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset)); + String line; + while ((line = reader.readLine()) != null) { + MetricNode node = MetricNode.fromFatString(line); + long currentSecond = node.getTimestamp() / 1000; + + if (list.size() < recommendLines) { + list.add(node); + } else if (currentSecond == lastSecond) { + list.add(node); + } else { + break; + } + lastSecond = currentSecond; + } + } finally { + if (in != null) { + in.close(); + } + } + } + + /** + * When identity is null, all metric between the time intervalMs will be read, otherwise, only the specific + * identity will be read. + */ + List readMetricsByEndTime(List fileNames, int pos, long offset, + long beginTimeMs, long endTimeMs, String identity) throws Exception { + List list = new ArrayList(1024); + if (readMetricsInOneFileByEndTime(list, fileNames.get(pos++), offset, beginTimeMs, endTimeMs, identity)) { + while (pos < fileNames.size() + && readMetricsInOneFileByEndTime(list, fileNames.get(pos++), 0, beginTimeMs, endTimeMs, identity)) { + } + } + return list; + } + + List readMetrics(List fileNames, int pos, + long offset, int recommendLines) throws Exception { + List list = new ArrayList(recommendLines); + readMetricsInOneFile(list, fileNames.get(pos++), offset, recommendLines); + while (list.size() < recommendLines && pos < fileNames.size()) { + readMetricsInOneFile(list, fileNames.get(pos++), 0, recommendLines); + } + return list; + } +} \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java new file mode 100755 index 00000000..52dd1dc2 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +public class DynamicSentinelProperty implements SentinelProperty { + + protected Set> listeners = new CopyOnWriteArraySet<>(); + private T value = null; + + public DynamicSentinelProperty() { + } + + public DynamicSentinelProperty(T value) { + super(); + this.value = value; + } + + @Override + public void addListener(PropertyListener listener) { + listeners.add(listener); + listener.configLoad(value); + } + + @Override + public void removeListener(PropertyListener listener) { + listeners.remove(listener); + } + + @Override + public boolean updateValue(T newValue) { + if (isEqual(value, newValue)) { + return false; + } + RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue); + + value = newValue; + for (PropertyListener listener : listeners) { + listener.configUpdate(newValue); + } + return true; + } + + private boolean isEqual(T oldValue, T newValue) { + if (oldValue == null && newValue == null) { + return true; + } + + if (oldValue == null) { + return false; + } + + return oldValue.equals(newValue); + } + + public void close() { + listeners.clear(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/NoOpSentinelProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/NoOpSentinelProperty.java new file mode 100755 index 00000000..395306c3 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/NoOpSentinelProperty.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property; + +/** + * A {@link SentinelProperty} that will never inform the {@link PropertyListener} on it. + * + * @author leyou + */ +public final class NoOpSentinelProperty implements SentinelProperty { + + @Override + public void addListener(PropertyListener listener) { } + + @Override + public void removeListener(PropertyListener listener) { } + + @Override + public boolean updateValue(Object newValue) { + return true; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/PropertyListener.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/PropertyListener.java new file mode 100755 index 00000000..cce9871d --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/PropertyListener.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty; + +/** + * This class holds callback method when {@link SentinelProperty#updateValue(Object)} need inform the listener + * + * @author jialiang.linjl + */ +public interface PropertyListener { + + /** + * Callback method when {@link SentinelProperty#updateValue(Object)} need inform the listener. + * + * @param value updated value. + */ + void configUpdate(T value); + + /** + * The first time of the {@code value}'s load. + * + * @param value the value loaded. + */ + void configLoad(T value); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SentinelProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SentinelProperty.java new file mode 100755 index 00000000..c2697e7f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SentinelProperty.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property; + +/** + *

    + * This class holds current value of the config, and is responsible for informing all {@link PropertyListener}s + * added on this when the config is updated. + *

    + *

    + * Note that not every {@link #updateValue(Object newValue)} invocation should inform the listeners, only when + * {@code newValue} is not Equals to the old value, informing is needed. + *

    + * + * @param the target type. + * @author Carpenter Lee + */ +public interface SentinelProperty { + + /** + *

    + * Add a {@link PropertyListener} to this {@link SentinelProperty}. After the listener is added, + * {@link #updateValue(Object)} will inform the listener if needed. + *

    + *

    + * This method can invoke multi times to add more than one listeners. + *

    + * + * @param listener listener to add. + */ + void addListener(PropertyListener listener); + + /** + * Remove the {@link PropertyListener} on this. After removing, {@link #updateValue(Object)} + * will not inform the listener. + * + * @param listener the listener to remove. + */ + void removeListener(PropertyListener listener); + + /** + * Update the {@code newValue} as the current value of this property and inform all {@link PropertyListener}s + * added on this only when new {@code newValue} is not Equals to the old value. + * + * @param newValue the new value. + * @return true if the value in property has been updated, otherwise false + */ + boolean updateValue(T newValue); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SimplePropertyListener.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SimplePropertyListener.java new file mode 100755 index 00000000..02af5a9a --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/property/SimplePropertyListener.java @@ -0,0 +1,24 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property; + +public abstract class SimplePropertyListener implements PropertyListener { + + @Override + public void configLoad(T value) { + configUpdate(value); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/AbstractLinkedProcessorSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/AbstractLinkedProcessorSlot.java new file mode 100755 index 00000000..a263d8f0 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/AbstractLinkedProcessorSlot.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; + +/** + * @author qinan.qn + * @author jialiang.linjl + */ +public abstract class AbstractLinkedProcessorSlot implements ProcessorSlot { + + private AbstractLinkedProcessorSlot next = null; + + @Override + public void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) + throws Throwable { + if (next != null) { + next.transformEntry(context, resourceWrapper, obj, count, prioritized, args); + } + } + + @SuppressWarnings("unchecked") + void transformEntry(Context context, ResourceWrapper resourceWrapper, Object o, int count, boolean prioritized, Object... args) + throws Throwable { + T t = (T)o; + entry(context, resourceWrapper, t, count, prioritized, args); + } + + @Override + public void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + if (next != null) { + next.exit(context, resourceWrapper, count, args); + } + } + + public AbstractLinkedProcessorSlot getNext() { + return next; + } + + public void setNext(AbstractLinkedProcessorSlot next) { + this.next = next; + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain.java new file mode 100755 index 00000000..119d3454 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/DefaultProcessorSlotChain.java @@ -0,0 +1,84 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; + +/** + * @author qinan.qn + * @author jialiang.linjl + */ +public class DefaultProcessorSlotChain extends ProcessorSlotChain { + + AbstractLinkedProcessorSlot first = new AbstractLinkedProcessorSlot() { + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args) + throws Throwable { + super.fireEntry(context, resourceWrapper, t, count, prioritized, args); + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + super.fireExit(context, resourceWrapper, count, args); + } + + }; + AbstractLinkedProcessorSlot end = first; + + @Override + public void addFirst(AbstractLinkedProcessorSlot protocolProcessor) { + protocolProcessor.setNext(first.getNext()); + first.setNext(protocolProcessor); + if (end == first) { + end = protocolProcessor; + } + } + + @Override + public void addLast(AbstractLinkedProcessorSlot protocolProcessor) { + end.setNext(protocolProcessor); + end = protocolProcessor; + } + + /** + * Same as {@link #addLast(AbstractLinkedProcessorSlot)}. + * + * @param next processor to be added. + */ + @Override + public void setNext(AbstractLinkedProcessorSlot next) { + addLast(next); + } + + @Override + public AbstractLinkedProcessorSlot getNext() { + return first.getNext(); + } + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, Object t, int count, boolean prioritized, Object... args) + throws Throwable { + first.transformEntry(context, resourceWrapper, t, count, prioritized, args); + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + first.exit(context, resourceWrapper, count, args); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java new file mode 100755 index 00000000..492ab071 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/MethodResourceWrapper.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import java.lang.reflect.Method; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ResourceTypeConstants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.IdUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.MethodUtil; + +/** + * Resource wrapper for method invocation. + * + * @author qinan.qn + */ +public class MethodResourceWrapper extends ResourceWrapper { + + private final transient Method method; + + public MethodResourceWrapper(Method method, EntryType e) { + this(method, e, ResourceTypeConstants.COMMON); + } + + public MethodResourceWrapper(Method method, EntryType e, int resType) { + super(MethodUtil.resolveMethodName(method), e, resType); + this.method = method; + } + + public Method getMethod() { + return method; + } + + @Override + public String getShowName() { + return name; + } + + @Override + public String toString() { + return "MethodResourceWrapper{" + + "name='" + name + '\'' + + ", entryType=" + entryType + + ", resourceType=" + resourceType + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlot.java new file mode 100755 index 00000000..3b280df9 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlot.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; + +/** + * A container of some process and ways of notification when the process is finished. + * + * @author qinan.qn + * @author jialiang.linjl + * @author leyou(lihao) + * @author Eric Zhao + */ +public interface ProcessorSlot { + + /** + * Entrance of this slot. + * + * @param context current {@link Context} + * @param resourceWrapper current resource + * @param param generics parameter, usually is a {@link com.alibaba.csp.sentinel.node.Node} + * @param count tokens needed + * @param prioritized whether the entry is prioritized + * @param args parameters of the original call + * @throws Throwable blocked exception or unexpected error + */ + void entry(Context context, ResourceWrapper resourceWrapper, T param, int count, boolean prioritized, + Object... args) throws Throwable; + + /** + * Means finish of {@link #entry(Context, ResourceWrapper, Object, int, boolean, Object...)}. + * + * @param context current {@link Context} + * @param resourceWrapper current resource + * @param obj relevant object (e.g. Node) + * @param count tokens needed + * @param prioritized whether the entry is prioritized + * @param args parameters of the original call + * @throws Throwable blocked exception or unexpected error + */ + void fireEntry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, + Object... args) throws Throwable; + + /** + * Exit of this slot. + * + * @param context current {@link Context} + * @param resourceWrapper current resource + * @param count tokens needed + * @param args parameters of the original call + */ + void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args); + + /** + * Means finish of {@link #exit(Context, ResourceWrapper, int, Object...)}. + * + * @param context current {@link Context} + * @param resourceWrapper current resource + * @param count tokens needed + * @param args parameters of the original call + */ + void fireExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotChain.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotChain.java new file mode 100755 index 00000000..f10fadcf --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotChain.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; + +/** + * Link all processor slots as a chain. + * + * @author qinan.qn + */ +public abstract class ProcessorSlotChain extends AbstractLinkedProcessorSlot { + + /** + * Add a processor to the head of this slot chain. + * + * @param protocolProcessor processor to be added. + */ + public abstract void addFirst(AbstractLinkedProcessorSlot protocolProcessor); + + /** + * Add a processor to the tail of this slot chain. + * + * @param protocolProcessor processor to be added. + */ + public abstract void addLast(AbstractLinkedProcessorSlot protocolProcessor); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotEntryCallback.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotEntryCallback.java new file mode 100644 index 00000000..6400bd5b --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotEntryCallback.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * Callback for entering {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked). + * + * @author Eric Zhao + * @since 0.2.0 + */ +public interface ProcessorSlotEntryCallback { + + void onPass(Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args) throws Exception; + + void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, T param, int count, Object... args); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotExitCallback.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotExitCallback.java new file mode 100644 index 00000000..8e323b12 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ProcessorSlotExitCallback.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; + +/** + * Callback for exiting {@link com.alibaba.csp.sentinel.slots.statistic.StatisticSlot} (passed and blocked). + * + * @author Eric Zhao + * @since 0.2.0 + */ +public interface ProcessorSlotExitCallback { + + void onExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java new file mode 100755 index 00000000..1271ed15 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/ResourceWrapper.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; + +/** + * A wrapper of resource name and type. + * + * @author qinan.qn + * @author jialiang.linjl + * @author Eric Zhao + */ +public abstract class ResourceWrapper { + + protected final String name; + + protected final EntryType entryType; + protected final int resourceType; + + public ResourceWrapper(String name, EntryType entryType, int resourceType) { + AssertUtil.notEmpty(name, "resource name cannot be empty"); + AssertUtil.notNull(entryType, "entryType cannot be null"); + this.name = name; + this.entryType = entryType; + this.resourceType = resourceType; + } + + /** + * Get the resource name. + * + * @return the resource name + */ + public String getName() { + return name; + } + + /** + * Get {@link EntryType} of this wrapper. + * + * @return {@link EntryType} of this wrapper. + */ + public EntryType getEntryType() { + return entryType; + } + + /** + * Get the classification of this resource. + * + * @return the classification of this resource + * @since 1.7.0 + */ + public int getResourceType() { + return resourceType; + } + + /** + * Get the beautified resource name to be showed. + * + * @return the beautified resource name + */ + public abstract String getShowName(); + + /** + * Only {@link #getName()} is considered. + */ + @Override + public int hashCode() { + return getName().hashCode(); + } + + /** + * Only {@link #getName()} is considered. + */ + @Override + public boolean equals(Object obj) { + if (obj instanceof ResourceWrapper) { + ResourceWrapper rw = (ResourceWrapper)obj; + return rw.getName().equals(getName()); + } + return false; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainBuilder.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainBuilder.java new file mode 100755 index 00000000..5b8ec58c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainBuilder.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; + +/** + * The builder for processor slot chain. + * + * @author qinan.qn + * @author leyou + * @author Eric Zhao + */ +public interface SlotChainBuilder { + + /** + * Build the processor slot chain. + * + * @return a processor slot that chain some slots together + */ + ProcessorSlotChain build(); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java new file mode 100644 index 00000000..3798921e --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoader; + +/** + * A provider for creating slot chains via resolved slot chain builder SPI. + * + * @author Eric Zhao + * @since 0.2.0 + */ +public final class SlotChainProvider { + + private static volatile SlotChainBuilder slotChainBuilder = null; + + /** + * The load and pick process is not thread-safe, but it's okay since the method should be only invoked + * via {@code lookProcessChain} in {@link com.alibaba.csp.sentinel.CtSph} under lock. + * + * @return new created slot chain + */ + public static ProcessorSlotChain newSlotChain() { + if (slotChainBuilder != null) { + return slotChainBuilder.build(); + } + + // Resolve the slot chain builder SPI. + slotChainBuilder = SpiLoader.of(SlotChainBuilder.class).loadFirstInstanceOrDefault(); + + if (slotChainBuilder == null) { + // Should not go through here. + RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default"); + slotChainBuilder = new DefaultSlotChainBuilder(); + } else { + RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}", + slotChainBuilder.getClass().getCanonicalName()); + } + return slotChainBuilder.build(); + } + + private SlotChainProvider() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java new file mode 100755 index 00000000..f0cbc5f4 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slotchain/StringResourceWrapper.java @@ -0,0 +1,50 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.ResourceTypeConstants; + +/** + * Common string resource wrapper. + * + * @author qinan.qn + * @author jialiang.linjl + */ +public class StringResourceWrapper extends ResourceWrapper { + + public StringResourceWrapper(String name, EntryType e) { + super(name, e, ResourceTypeConstants.COMMON); + } + + public StringResourceWrapper(String name, EntryType e, int resType) { + super(name, e, resType); + } + + @Override + public String getShowName() { + return name; + } + + @Override + public String toString() { + return "StringResourceWrapper{" + + "name='" + name + '\'' + + ", entryType=" + entryType + + ", resourceType=" + resourceType + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java new file mode 100755 index 00000000..c61fffa9 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java @@ -0,0 +1,54 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoader; + +import java.util.List; + +/** + * Builder for a default {@link ProcessorSlotChain}. + * + * @author qinan.qn + * @author leyou + */ +@Spi(isDefault = true) +public class DefaultSlotChainBuilder implements SlotChainBuilder { + + @Override + public ProcessorSlotChain build() { + ProcessorSlotChain chain = new DefaultProcessorSlotChain(); + + List sortedSlotList = SpiLoader.of(ProcessorSlot.class).loadInstanceListSorted(); + for (ProcessorSlot slot : sortedSlotList) { + if (!(slot instanceof AbstractLinkedProcessorSlot)) { + RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain"); + continue; + } + + chain.addLast((AbstractLinkedProcessorSlot) slot); + } + + return chain; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/AbstractRule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/AbstractRule.java new file mode 100755 index 00000000..666240f6 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/AbstractRule.java @@ -0,0 +1,121 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; + +/** + * Abstract rule entity. + * + * @author youji.zj + * @author Eric Zhao + */ +public abstract class AbstractRule implements Rule { + + /** + * rule id. + */ + private Long id; + + /** + * Resource name. + */ + private String resource; + + /** + *

    + * Application name that will be limited by origin. + * The default limitApp is {@code default}, which means allowing all origin apps. + *

    + *

    + * For authority rules, multiple origin name can be separated with comma (','). + *

    + */ + private String limitApp; + + public Long getId() { + return id; + } + + public AbstractRule setId(Long id) { + this.id = id; + return this; + } + + @Override + public String getResource() { + return resource; + } + + public AbstractRule setResource(String resource) { + this.resource = resource; + return this; + } + + public String getLimitApp() { + return limitApp; + } + + public AbstractRule setLimitApp(String limitApp) { + this.limitApp = limitApp; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof AbstractRule)) { + return false; + } + + AbstractRule that = (AbstractRule)o; + + if (resource != null ? !resource.equals(that.resource) : that.resource != null) { + return false; + } + if (!limitAppEquals(limitApp, that.limitApp)) { + return false; + } + return true; + } + + private boolean limitAppEquals(String str1, String str2) { + if ("".equals(str1)) { + return RuleConstant.LIMIT_APP_DEFAULT.equals(str2); + } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(str1)) { + return "".equals(str2) || str2 == null || str1.equals(str2); + } + if (str1 == null) { + return str2 == null || RuleConstant.LIMIT_APP_DEFAULT.equals(str2); + } + return str1.equals(str2); + } + + public T as(Class clazz) { + return (T)this; + } + + @Override + public int hashCode() { + int result = resource != null ? resource.hashCode() : 0; + if (!("".equals(limitApp) || RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp) || limitApp == null)) { + result = 31 * result + limitApp.hashCode(); + } + return result; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/BlockException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/BlockException.java new file mode 100755 index 00000000..a06be3fe --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/BlockException.java @@ -0,0 +1,132 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block; + +/** + * Abstract exception indicating blocked by Sentinel due to flow control, + * circuit breaking or system protection triggered. + * + * @author youji.zj + */ +public abstract class BlockException extends Exception { + + private static final int MAX_SEARCH_DEPTH = 10; + + public static final String BLOCK_EXCEPTION_FLAG = "SentinelBlockException"; + public static final String BLOCK_EXCEPTION_MSG_PREFIX = "SentinelBlockException: "; + + /** + *

    this constant RuntimeException has no stack trace, just has a message + * {@link #BLOCK_EXCEPTION_FLAG} that marks its name. + *

    + *

    + * Use {@link #isBlockException(Throwable)} to check whether one Exception + * Sentinel Blocked Exception. + *

    + */ + public static RuntimeException THROW_OUT_EXCEPTION = new RuntimeException(BLOCK_EXCEPTION_FLAG); + + public static StackTraceElement[] sentinelStackTrace = new StackTraceElement[] { + new StackTraceElement(BlockException.class.getName(), "block", "BlockException", 0) + }; + + static { + THROW_OUT_EXCEPTION.setStackTrace(sentinelStackTrace); + } + + protected AbstractRule rule; + private String ruleLimitApp; + + public BlockException(String ruleLimitApp) { + super(); + this.ruleLimitApp = ruleLimitApp; + } + + public BlockException(String ruleLimitApp, AbstractRule rule) { + super(); + this.ruleLimitApp = ruleLimitApp; + this.rule = rule; + } + + public BlockException(String message, Throwable cause) { + super(message, cause); + } + + public BlockException(String ruleLimitApp, String message) { + super(message); + this.ruleLimitApp = ruleLimitApp; + } + + public BlockException(String ruleLimitApp, String message, AbstractRule rule) { + super(message); + this.ruleLimitApp = ruleLimitApp; + this.rule = rule; + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + + public String getRuleLimitApp() { + return ruleLimitApp; + } + + public void setRuleLimitApp(String ruleLimitApp) { + this.ruleLimitApp = ruleLimitApp; + } + + public RuntimeException toRuntimeException() { + RuntimeException t = new RuntimeException(BLOCK_EXCEPTION_MSG_PREFIX + getClass().getSimpleName()); + t.setStackTrace(sentinelStackTrace); + return t; + } + + /** + * Check whether the exception is sentinel blocked exception. One exception is sentinel blocked + * exception only when: + *
      + *
    • the exception or its (sub-)cause is {@link BlockException}, or
    • + *
    • the exception's message or any of its sub-cause's message is prefixed by {@link #BLOCK_EXCEPTION_FLAG}
    • + *
    + * + * @param t the exception. + * @return return true if the exception marks sentinel blocked exception. + */ + public static boolean isBlockException(Throwable t) { + if (null == t) { + return false; + } + + int counter = 0; + Throwable cause = t; + while (cause != null && counter++ < MAX_SEARCH_DEPTH) { + if (cause instanceof BlockException) { + return true; + } + if (cause.getMessage() != null && cause.getMessage().startsWith(BLOCK_EXCEPTION_FLAG)) { + return true; + } + cause = cause.getCause(); + } + + return false; + } + + public AbstractRule getRule() { + return rule; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java new file mode 100644 index 00000000..d3971234 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/ClusterRuleConstant.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class ClusterRuleConstant { + + public static final int FLOW_CLUSTER_STRATEGY_NORMAL = 0; + public static final int FLOW_CLUSTER_STRATEGY_BORROW_REF = 1; + + public static final int FLOW_THRESHOLD_AVG_LOCAL = 0; + public static final int FLOW_THRESHOLD_GLOBAL = 1; + + public static final int DEFAULT_CLUSTER_SAMPLE_COUNT = 10; + + private ClusterRuleConstant() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/Rule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/Rule.java new file mode 100755 index 00000000..8c1bd46d --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/Rule.java @@ -0,0 +1,32 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block; + +/** + * Base interface of all rules. + * + * @author youji.zj + */ +public interface Rule { + + /** + * Get target resource of this rule. + * + * @return target resource of this rule + */ + String getResource(); + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/RuleConstant.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/RuleConstant.java new file mode 100755 index 00000000..ef657f5b --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/RuleConstant.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.IntervalProperty; + +/** + * @author youji.zj + * @author jialiang.linjl + */ +public final class RuleConstant { + + public static final int FLOW_GRADE_THREAD = 0; + public static final int FLOW_GRADE_QPS = 1; + + public static final int DEGRADE_GRADE_RT = 0; + /** + * Degrade by biz exception ratio in the current {@link IntervalProperty#INTERVAL} second(s). + */ + public static final int DEGRADE_GRADE_EXCEPTION_RATIO = 1; + /** + * Degrade by biz exception count in the last 60 seconds. + */ + public static final int DEGRADE_GRADE_EXCEPTION_COUNT = 2; + + public static final int DEGRADE_DEFAULT_SLOW_REQUEST_AMOUNT = 5; + public static final int DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT = 5; + + public static final int AUTHORITY_WHITE = 0; + public static final int AUTHORITY_BLACK = 1; + + public static final int STRATEGY_DIRECT = 0; + public static final int STRATEGY_RELATE = 1; + public static final int STRATEGY_CHAIN = 2; + + public static final int CONTROL_BEHAVIOR_DEFAULT = 0; + public static final int CONTROL_BEHAVIOR_WARM_UP = 1; + public static final int CONTROL_BEHAVIOR_RATE_LIMITER = 2; + public static final int CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER = 3; + + public static final int DEFAULT_BLOCK_STRATEGY = 0; + public static final int TRY_AGAIN_BLOCK_STRATEGY = 1; + public static final int TRY_UNTIL_SUCCESS_BLOCK_STRATEGY = 2; + + public static final int DEFAULT_RESOURCE_TIMEOUT_STRATEGY = 0; + public static final int RELEASE_RESOURCE_TIMEOUT_STRATEGY = 1; + public static final int KEEP_RESOURCE_TIMEOUT_STRATEGY = 2; + + public static final String LIMIT_APP_DEFAULT = "default"; + public static final String LIMIT_APP_OTHER = "other"; + + public static final int DEFAULT_SAMPLE_COUNT = 2; + public static final int DEFAULT_WINDOW_INTERVAL_MS = 1000; + + private RuleConstant() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/SentinelRpcException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/SentinelRpcException.java new file mode 100755 index 00000000..34333227 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/SentinelRpcException.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block; + +/** + * A {@link RuntimeException} marks sentinel RPC exception. The stack trace + * is removed for high performance. + * + * @author leyou + */ +public class SentinelRpcException extends RuntimeException { + + public SentinelRpcException(String msg) { + super(msg); + } + + public SentinelRpcException(Throwable e) { + super(e); + } + + @Override + public Throwable fillInStackTrace() { + return this; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityException.java new file mode 100755 index 00000000..b38b2260 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityException.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * Block exception for request origin access (authority) control. + * + * @author youji.zj + * @author Eric Zhao + */ +public class AuthorityException extends BlockException { + + public AuthorityException(String ruleLimitApp) { + super(ruleLimitApp); + } + + public AuthorityException(String ruleLimitApp, AuthorityRule rule) { + super(ruleLimitApp, rule); + } + + public AuthorityException(String message, Throwable cause) { + super(message, cause); + } + + public AuthorityException(String ruleLimitApp, String message) { + super(ruleLimitApp, message); + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + + /** + * Get triggered rule. + * Note: the rule result is a reference to rule map and SHOULD NOT be modified. + * + * @return triggered rule + * @since 1.4.2 + */ + @Override + public AuthorityRule getRule() { + return rule.as(AuthorityRule.class); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java new file mode 100755 index 00000000..fb624021 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.AbstractRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; + +/** + * Authority rule is designed for limiting by request origins. + * + * @author youji.zj + */ +public class AuthorityRule extends AbstractRule { + + /** + * Mode: 0 for whitelist; 1 for blacklist. + */ + private int strategy = RuleConstant.AUTHORITY_WHITE; + + public int getStrategy() { + return strategy; + } + + public AuthorityRule setStrategy(int strategy) { + this.strategy = strategy; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (!(o instanceof AuthorityRule)) { return false; } + if (!super.equals(o)) { return false; } + + AuthorityRule rule = (AuthorityRule)o; + + return strategy == rule.strategy; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + strategy; + return result; + } + + @Override + public String toString() { + return "AuthorityRule{" + + "resource=" + getResource() + + ", limitApp=" + getLimitApp() + + ", strategy=" + strategy + + "} "; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleChecker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleChecker.java new file mode 100644 index 00000000..66ec1ca5 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleChecker.java @@ -0,0 +1,68 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +/** + * Rule checker for white/black list authority. + * + * @author Eric Zhao + * @since 0.2.0 + */ +final class AuthorityRuleChecker { + + static boolean passCheck(AuthorityRule rule, Context context) { + String requester = context.getOrigin(); + + // Empty origin or empty limitApp will pass. + if (StringUtil.isEmpty(requester) || StringUtil.isEmpty(rule.getLimitApp())) { + return true; + } + + // Do exact match with origin name. + int pos = rule.getLimitApp().indexOf(requester); + boolean contain = pos > -1; + + if (contain) { + boolean exactlyMatch = false; + String[] appArray = rule.getLimitApp().split(","); + for (String app : appArray) { + if (requester.equals(app)) { + exactlyMatch = true; + break; + } + } + + contain = exactlyMatch; + } + + int strategy = rule.getStrategy(); + if (strategy == RuleConstant.AUTHORITY_BLACK && contain) { + return false; + } + + if (strategy == RuleConstant.AUTHORITY_WHITE && !contain) { + return false; + } + + return true; + } + + private AuthorityRuleChecker() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java new file mode 100755 index 00000000..5ee15fa8 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java @@ -0,0 +1,151 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.DynamicSentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.PropertyListener; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty; + +/** + * Manager for authority rules. + * + * @author youji.zj + * @author jialiang.linjl + * @author Eric Zhao + */ +public final class AuthorityRuleManager { + + private static volatile Map> authorityRules = new ConcurrentHashMap<>(); + + private static final RulePropertyListener LISTENER = new RulePropertyListener(); + private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); + + static { + currentProperty.addListener(LISTENER); + } + + public static void register2Property(SentinelProperty> property) { + AssertUtil.notNull(property, "property cannot be null"); + synchronized (LISTENER) { + if (currentProperty != null) { + currentProperty.removeListener(LISTENER); + } + property.addListener(LISTENER); + currentProperty = property; + RecordLog.info("[AuthorityRuleManager] Registering new property to authority rule manager"); + } + } + + /** + * Load the authority rules to memory. + * + * @param rules list of authority rules + */ + public static void loadRules(List rules) { + currentProperty.updateValue(rules); + } + + public static boolean hasConfig(String resource) { + return authorityRules.containsKey(resource); + } + + /** + * Get a copy of the rules. + * + * @return a new copy of the rules. + */ + public static List getRules() { + List rules = new ArrayList<>(); + if (authorityRules == null) { + return rules; + } + for (Map.Entry> entry : authorityRules.entrySet()) { + rules.addAll(entry.getValue()); + } + return rules; + } + + private static class RulePropertyListener implements PropertyListener> { + + @Override + public synchronized void configLoad(List value) { + authorityRules = loadAuthorityConf(value); + + RecordLog.info("[AuthorityRuleManager] Authority rules loaded: {}", authorityRules); + } + + @Override + public synchronized void configUpdate(List conf) { + authorityRules = loadAuthorityConf(conf); + + RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules); + } + + private Map> loadAuthorityConf(List list) { + Map> newRuleMap = new ConcurrentHashMap<>(); + + if (list == null || list.isEmpty()) { + return newRuleMap; + } + + for (AuthorityRule rule : list) { + if (!isValidRule(rule)) { + RecordLog.warn("[AuthorityRuleManager] Ignoring invalid authority rule when loading new rules: {}", rule); + continue; + } + + if (StringUtil.isBlank(rule.getLimitApp())) { + rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); + } + + String identity = rule.getResource(); + Set ruleSet = newRuleMap.get(identity); + // putIfAbsent + if (ruleSet == null) { + ruleSet = new HashSet<>(); + ruleSet.add(rule); + newRuleMap.put(identity, ruleSet); + } else { + // One resource should only have at most one authority rule, so just ignore redundant rules. + RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: {}", rule.toString()); + } + } + + return newRuleMap; + } + + } + + static Map> getAuthorityRules() { + return authorityRules; + } + + public static boolean isValidRule(AuthorityRule rule) { + return rule != null && !StringUtil.isBlank(rule.getResource()) + && rule.getStrategy() >= 0 && StringUtil.isNotBlank(rule.getLimitApp()); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java new file mode 100755 index 00000000..f57888f8 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority; + +import java.util.Map; +import java.util.Set; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority.AuthorityException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleChecker; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi; + +/** + * A {@link ProcessorSlot} that dedicates to {@link AuthorityRule} checking. + * + * @author leyou + * @author Eric Zhao + */ +@Spi(order = Constants.ORDER_AUTHORITY_SLOT) +public class AuthoritySlot extends AbstractLinkedProcessorSlot { + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) + throws Throwable { + checkBlackWhiteAuthority(resourceWrapper, context); + fireEntry(context, resourceWrapper, node, count, prioritized, args); + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + fireExit(context, resourceWrapper, count, args); + } + + void checkBlackWhiteAuthority(ResourceWrapper resource, Context context) throws AuthorityException { + Map> authorityRules = AuthorityRuleManager.getAuthorityRules(); + + if (authorityRules == null) { + return; + } + + Set rules = authorityRules.get(resource.getName()); + if (rules == null) { + return; + } + + for (AuthorityRule rule : rules) { + if (!AuthorityRuleChecker.passCheck(rule, context)) { + throw new AuthorityException(context.getOrigin(), rule); + } + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeException.java new file mode 100755 index 00000000..81f7373e --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeException.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; + +/*** + * @author youji.zj + */ +public class DegradeException extends BlockException { + + public DegradeException(String ruleLimitApp) { + super(ruleLimitApp); + } + + public DegradeException(String ruleLimitApp, DegradeRule rule) { + super(ruleLimitApp, rule); + } + + public DegradeException(String message, Throwable cause) { + super(message, cause); + } + + public DegradeException(String ruleLimitApp, String message) { + super(ruleLimitApp, message); + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + + /** + * Get triggered rule. + * Note: the rule result is a reference to rule map and SHOULD NOT be modified. + * + * @return triggered rule + * @since 1.4.2 + */ + @Override + public DegradeRule getRule() { + return rule.as(DegradeRule.class); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java new file mode 100755 index 00000000..6dfa059c --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java @@ -0,0 +1,185 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.AbstractRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; + +import java.util.Objects; + +/** + *

    + * Degrade is used when the resources are in an unstable state, these resources + * will be degraded within the next defined time window. There are two ways to + * measure whether a resource is stable or not: + *

    + *
      + *
    • + * Average response time ({@code DEGRADE_GRADE_RT}): When + * the average RT exceeds the threshold ('count' in 'DegradeRule', in milliseconds), the + * resource enters a quasi-degraded state. If the RT of next coming 5 + * requests still exceed this threshold, this resource will be downgraded, which + * means that in the next time window (defined in 'timeWindow', in seconds) all the + * access to this resource will be blocked. + *
    • + *
    • + * Exception ratio: When the ratio of exception count per second and the + * success qps exceeds the threshold, access to the resource will be blocked in + * the coming window. + *
    • + *
    + * + * @author jialiang.linjl + * @author Eric Zhao + */ +public class DegradeRule extends AbstractRule { + + public DegradeRule() {} + + public DegradeRule(String resourceName) { + setResource(resourceName); + } + + /** + * Circuit breaking strategy (0: average RT, 1: exception ratio, 2: exception count). + */ + private int grade = RuleConstant.DEGRADE_GRADE_RT; + + /** + * Threshold count. The exact meaning depends on the field of grade. + *
      + *
    • In average RT mode, it means the maximum response time(RT) in milliseconds.
    • + *
    • In exception ratio mode, it means exception ratio which between 0.0 and 1.0.
    • + *
    • In exception count mode, it means exception count
    • + *
        + */ + private double count; + + /** + * Recovery timeout (in seconds) when circuit breaker opens. After the timeout, the circuit breaker will + * transform to half-open state for trying a few requests. + */ + private int timeWindow; + + /** + * Minimum number of requests (in an active statistic time span) that can trigger circuit breaking. + * + * @since 1.7.0 + */ + private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT; + + /** + * The threshold of slow request ratio in RT mode. + * + * @since 1.8.0 + */ + private double slowRatioThreshold = 1.0d; + + /** + * The interval statistics duration in millisecond. + * + * @since 1.8.0 + */ + private int statIntervalMs = 1000; + + public int getGrade() { + return grade; + } + + public DegradeRule setGrade(int grade) { + this.grade = grade; + return this; + } + + public double getCount() { + return count; + } + + public DegradeRule setCount(double count) { + this.count = count; + return this; + } + + public int getTimeWindow() { + return timeWindow; + } + + public DegradeRule setTimeWindow(int timeWindow) { + this.timeWindow = timeWindow; + return this; + } + + public int getMinRequestAmount() { + return minRequestAmount; + } + + public DegradeRule setMinRequestAmount(int minRequestAmount) { + this.minRequestAmount = minRequestAmount; + return this; + } + + public double getSlowRatioThreshold() { + return slowRatioThreshold; + } + + public DegradeRule setSlowRatioThreshold(double slowRatioThreshold) { + this.slowRatioThreshold = slowRatioThreshold; + return this; + } + + public int getStatIntervalMs() { + return statIntervalMs; + } + + public DegradeRule setStatIntervalMs(int statIntervalMs) { + this.statIntervalMs = statIntervalMs; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + if (!super.equals(o)) { return false; } + DegradeRule rule = (DegradeRule)o; + return Double.compare(rule.count, count) == 0 && + timeWindow == rule.timeWindow && + grade == rule.grade && + minRequestAmount == rule.minRequestAmount && + Double.compare(rule.slowRatioThreshold, slowRatioThreshold) == 0 && + statIntervalMs == rule.statIntervalMs; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), count, timeWindow, grade, minRequestAmount, + slowRatioThreshold, statIntervalMs); + } + + @Override + public String toString() { + return "DegradeRule{" + + "resource=" + getResource() + + ", grade=" + grade + + ", count=" + count + + ", limitApp=" + getLimitApp() + + ", timeWindow=" + timeWindow + + ", minRequestAmount=" + minRequestAmount + + ", slowRatioThreshold=" + slowRatioThreshold + + ", statIntervalMs=" + statIntervalMs + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java new file mode 100755 index 00000000..64be18e0 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java @@ -0,0 +1,268 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.DynamicSentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.PropertyListener; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ExceptionCircuitBreaker; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ResponseTimeCircuitBreaker; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +/** + * The rule manager for circuit breaking rules ({@link DegradeRule}). + * + * @author youji.zj + * @author jialiang.linjl + * @author Eric Zhao + */ +public final class DegradeRuleManager { + + private static volatile Map> circuitBreakers = new HashMap<>(); + private static volatile Map> ruleMap = new HashMap<>(); + + private static final RulePropertyListener LISTENER = new RulePropertyListener(); + private static SentinelProperty> currentProperty + = new DynamicSentinelProperty<>(); + + static { + currentProperty.addListener(LISTENER); + } + + /** + * Listen to the {@link SentinelProperty} for {@link DegradeRule}s. The property is the source + * of {@link DegradeRule}s. Degrade rules can also be set by {@link #loadRules(List)} directly. + * + * @param property the property to listen. + */ + public static void register2Property(SentinelProperty> property) { + AssertUtil.notNull(property, "property cannot be null"); + synchronized (LISTENER) { + RecordLog.info("[DegradeRuleManager] Registering new property to degrade rule manager"); + currentProperty.removeListener(LISTENER); + property.addListener(LISTENER); + currentProperty = property; + } + } + + static List getCircuitBreakers(String resourceName) { + return circuitBreakers.get(resourceName); + } + + public static boolean hasConfig(String resource) { + if (resource == null) { + return false; + } + return circuitBreakers.containsKey(resource); + } + + /** + *

        Get existing circuit breaking rules.

        + *

        Note: DO NOT modify the rules from the returned list directly. + * The behavior is undefined.

        + * + * @return list of existing circuit breaking rules, or empty list if no rules were loaded + */ + public static List getRules() { + List rules = new ArrayList<>(); + for (Map.Entry> entry : ruleMap.entrySet()) { + rules.addAll(entry.getValue()); + } + return rules; + } + + public static Set getRulesOfResource(String resource) { + AssertUtil.assertNotBlank(resource, "resource name cannot be blank"); + return ruleMap.get(resource); + } + + /** + * Load {@link DegradeRule}s, former rules will be replaced. + * + * @param rules new rules to load. + */ + public static void loadRules(List rules) { + try { + currentProperty.updateValue(rules); + } catch (Throwable e) { + RecordLog.error("[DegradeRuleManager] Unexpected error when loading degrade rules", e); + } + } + + /** + * Set degrade rules for provided resource. Former rules of the resource will be replaced. + * + * @param resourceName valid resource name + * @param rules new rule set to load + * @return whether the rules has actually been updated + * @since 1.5.0 + */ + public static boolean setRulesForResource(String resourceName, Set rules) { + AssertUtil.notEmpty(resourceName, "resourceName cannot be empty"); + try { + Map> newRuleMap = new HashMap<>(ruleMap); + if (rules == null) { + newRuleMap.remove(resourceName); + } else { + Set newSet = new HashSet<>(); + for (DegradeRule rule : rules) { + if (isValidRule(rule) && resourceName.equals(rule.getResource())) { + newSet.add(rule); + } + } + newRuleMap.put(resourceName, newSet); + } + List allRules = new ArrayList<>(); + for (Set set : newRuleMap.values()) { + allRules.addAll(set); + } + return currentProperty.updateValue(allRules); + } catch (Throwable e) { + RecordLog.error("[DegradeRuleManager] Unexpected error when setting circuit breaking" + + " rules for resource: " + resourceName, e); + return false; + } + } + + private static CircuitBreaker getExistingSameCbOrNew(/*@Valid*/ DegradeRule rule) { + List cbs = getCircuitBreakers(rule.getResource()); + if (cbs == null || cbs.isEmpty()) { + return newCircuitBreakerFrom(rule); + } + for (CircuitBreaker cb : cbs) { + if (rule.equals(cb.getRule())) { + // Reuse the circuit breaker if the rule remains unchanged. + return cb; + } + } + return newCircuitBreakerFrom(rule); + } + + /** + * Create a circuit breaker instance from provided circuit breaking rule. + * + * @param rule a valid circuit breaking rule + * @return new circuit breaker based on provided rule; null if rule is invalid or unsupported type + */ + private static CircuitBreaker newCircuitBreakerFrom(/*@Valid*/ DegradeRule rule) { + switch (rule.getGrade()) { + case RuleConstant.DEGRADE_GRADE_RT: + return new ResponseTimeCircuitBreaker(rule); + case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO: + case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT: + return new ExceptionCircuitBreaker(rule); + default: + return null; + } + } + + public static boolean isValidRule(DegradeRule rule) { + boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) + && rule.getCount() >= 0 && rule.getTimeWindow() > 0; + if (!baseValid) { + return false; + } + if (rule.getMinRequestAmount() <= 0 || rule.getStatIntervalMs() <= 0) { + return false; + } + switch (rule.getGrade()) { + case RuleConstant.DEGRADE_GRADE_RT: + return rule.getSlowRatioThreshold() >= 0 && rule.getSlowRatioThreshold() <= 1; + case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO: + return rule.getCount() <= 1; + case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT: + return true; + default: + return false; + } + } + + private static class RulePropertyListener implements PropertyListener> { + + private synchronized void reloadFrom(List list) { + Map> cbs = buildCircuitBreakers(list); + Map> rm = new HashMap<>(cbs.size()); + + for (Map.Entry> e : cbs.entrySet()) { + assert e.getValue() != null && !e.getValue().isEmpty(); + + Set rules = new HashSet<>(e.getValue().size()); + for (CircuitBreaker cb : e.getValue()) { + rules.add(cb.getRule()); + } + rm.put(e.getKey(), rules); + } + + DegradeRuleManager.circuitBreakers = cbs; + DegradeRuleManager.ruleMap = rm; + } + + @Override + public void configUpdate(List conf) { + reloadFrom(conf); + RecordLog.info("[DegradeRuleManager] Degrade rules has been updated to: {}", ruleMap); + } + + @Override + public void configLoad(List conf) { + reloadFrom(conf); + RecordLog.info("[DegradeRuleManager] Degrade rules loaded: {}", ruleMap); + } + + private Map> buildCircuitBreakers(List list) { + Map> cbMap = new HashMap<>(8); + if (list == null || list.isEmpty()) { + return cbMap; + } + for (DegradeRule rule : list) { + if (!isValidRule(rule)) { + RecordLog.warn("[DegradeRuleManager] Ignoring invalid rule when loading new rules: {}", rule); + continue; + } + + if (StringUtil.isBlank(rule.getLimitApp())) { + rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); + } + CircuitBreaker cb = getExistingSameCbOrNew(rule); + if (cb == null) { + RecordLog.warn("[DegradeRuleManager] Unknown circuit breaking strategy, ignoring: {}", rule); + continue; + } + + String resourceName = rule.getResource(); + + List cbList = cbMap.get(resourceName); + if (cbList == null) { + cbList = new ArrayList<>(); + cbMap.put(resourceName, cbList); + } + cbList.add(cb); + } + return cbMap; + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java new file mode 100755 index 00000000..00e35cc0 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java @@ -0,0 +1,84 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade; + +import java.util.List; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi; + +/** + * A {@link ProcessorSlot} dedicates to circuit breaking. + * + * @author Carpenter Lee + * @author Eric Zhao + */ +@Spi(order = Constants.ORDER_DEGRADE_SLOT) +public class DegradeSlot extends AbstractLinkedProcessorSlot { + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, + boolean prioritized, Object... args) throws Throwable { + performChecking(context, resourceWrapper); + + fireEntry(context, resourceWrapper, node, count, prioritized, args); + } + + void performChecking(Context context, ResourceWrapper r) throws BlockException { + List circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName()); + if (circuitBreakers == null || circuitBreakers.isEmpty()) { + return; + } + for (CircuitBreaker cb : circuitBreakers) { + if (!cb.tryPass(context)) { + throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule()); + } + } + } + + @Override + public void exit(Context context, ResourceWrapper r, int count, Object... args) { + Entry curEntry = context.getCurEntry(); + if (curEntry.getBlockError() != null) { + fireExit(context, r, count, args); + return; + } + List circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName()); + if (circuitBreakers == null || circuitBreakers.isEmpty()) { + fireExit(context, r, count, args); + return; + } + + if (curEntry.getBlockError() == null) { + // passed request + for (CircuitBreaker circuitBreaker : circuitBreakers) { + circuitBreaker.onRequestComplete(context); + } + } + + fireExit(context, r, count, args); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java new file mode 100644 index 00000000..d35f0293 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java @@ -0,0 +1,162 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.BiConsumer; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public abstract class AbstractCircuitBreaker implements CircuitBreaker { + + protected final DegradeRule rule; + protected final int recoveryTimeoutMs; + + private final EventObserverRegistry observerRegistry; + + protected final AtomicReference currentState = new AtomicReference<>(State.CLOSED); + protected volatile long nextRetryTimestamp; + + public AbstractCircuitBreaker(DegradeRule rule) { + this(rule, EventObserverRegistry.getInstance()); + } + + AbstractCircuitBreaker(DegradeRule rule, EventObserverRegistry observerRegistry) { + AssertUtil.notNull(observerRegistry, "observerRegistry cannot be null"); + if (!DegradeRuleManager.isValidRule(rule)) { + throw new IllegalArgumentException("Invalid DegradeRule: " + rule); + } + this.observerRegistry = observerRegistry; + this.rule = rule; + this.recoveryTimeoutMs = rule.getTimeWindow() * 1000; + } + + @Override + public DegradeRule getRule() { + return rule; + } + + @Override + public State currentState() { + return currentState.get(); + } + + @Override + public boolean tryPass(Context context) { + // Template implementation. + if (currentState.get() == State.CLOSED) { + return true; + } + if (currentState.get() == State.OPEN) { + // For half-open state we allow a request for probing. + return retryTimeoutArrived() && fromOpenToHalfOpen(context); + } + return false; + } + + /** + * Reset the statistic data. + */ + abstract void resetStat(); + + protected boolean retryTimeoutArrived() { + return TimeUtil.currentTimeMillis() >= nextRetryTimestamp; + } + + protected void updateNextRetryTimestamp() { + this.nextRetryTimestamp = TimeUtil.currentTimeMillis() + recoveryTimeoutMs; + } + + protected boolean fromCloseToOpen(double snapshotValue) { + State prev = State.CLOSED; + if (currentState.compareAndSet(prev, State.OPEN)) { + updateNextRetryTimestamp(); + + notifyObservers(prev, State.OPEN, snapshotValue); + return true; + } + return false; + } + + protected boolean fromOpenToHalfOpen(Context context) { + if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) { + notifyObservers(State.OPEN, State.HALF_OPEN, null); + Entry entry = context.getCurEntry(); + entry.whenTerminate(new BiConsumer() { + @Override + public void accept(Context context, Entry entry) { + // Note: This works as a temporary workaround for https://github.com/alibaba/Sentinel/issues/1638 + // Without the hook, the circuit breaker won't recover from half-open state in some circumstances + // when the request is actually blocked by upcoming rules (not only degrade rules). + if (entry.getBlockError() != null) { + // Fallback to OPEN due to detecting request is blocked + currentState.compareAndSet(State.HALF_OPEN, State.OPEN); + notifyObservers(State.HALF_OPEN, State.OPEN, 1.0d); + } + } + }); + return true; + } + return false; + } + + private void notifyObservers(CircuitBreaker.State prevState, CircuitBreaker.State newState, Double snapshotValue) { + for (CircuitBreakerStateChangeObserver observer : observerRegistry.getStateChangeObservers()) { + observer.onStateChange(prevState, newState, rule, snapshotValue); + } + } + + protected boolean fromHalfOpenToOpen(double snapshotValue) { + if (currentState.compareAndSet(State.HALF_OPEN, State.OPEN)) { + updateNextRetryTimestamp(); + notifyObservers(State.HALF_OPEN, State.OPEN, snapshotValue); + return true; + } + return false; + } + + protected boolean fromHalfOpenToClose() { + if (currentState.compareAndSet(State.HALF_OPEN, State.CLOSED)) { + resetStat(); + notifyObservers(State.HALF_OPEN, State.CLOSED, null); + return true; + } + return false; + } + + protected void transformToOpen(double triggerValue) { + State cs = currentState.get(); + switch (cs) { + case CLOSED: + fromCloseToOpen(triggerValue); + break; + case HALF_OPEN: + fromHalfOpenToOpen(triggerValue); + break; + default: + break; + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java new file mode 100644 index 00000000..f141d9ca --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; + +/** + *

        Basic circuit breaker interface.

        + * + * @author Eric Zhao + */ +public interface CircuitBreaker { + + /** + * Get the associated circuit breaking rule. + * + * @return associated circuit breaking rule + */ + DegradeRule getRule(); + + /** + * Acquires permission of an invocation only if it is available at the time of invoking. + * + * @param context context of current invocation + * @return {@code true} if permission was acquired and {@code false} otherwise + */ + boolean tryPass(Context context); + + /** + * Get current state of the circuit breaker. + * + * @return current state of the circuit breaker + */ + State currentState(); + + /** + *

        Record a completed request with the context and handle state transformation of the circuit breaker.

        + *

        Called when a passed invocation finished.

        + * + * @param context context of current invocation + */ + void onRequestComplete(Context context); + + /** + * Circuit breaker state. + */ + enum State { + /** + * In {@code OPEN} state, all requests will be rejected until the next recovery time point. + */ + OPEN, + /** + * In {@code HALF_OPEN} state, the circuit breaker will allow a "probe" invocation. + * If the invocation is abnormal according to the strategy (e.g. it's slow), the circuit breaker + * will re-transform to the {@code OPEN} state and wait for the next recovery time point; + * otherwise the resource will be regarded as "recovered" and the circuit breaker + * will cease cutting off requests and transform to {@code CLOSED} state. + */ + HALF_OPEN, + /** + * In {@code CLOSED} state, all requests are permitted. When current metric value exceeds the threshold, + * the circuit breaker will transform to {@code OPEN} state. + */ + CLOSED + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java new file mode 100644 index 00000000..85fea2c2 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public interface CircuitBreakerStateChangeObserver { + + /** + *

        Observer method triggered when circuit breaker state changed. The transformation could be:

        + *
          + *
        • From {@code CLOSED} to {@code OPEN} (with the triggered metric)
        • + *
        • From {@code OPEN} to {@code HALF_OPEN}
        • + *
        • From {@code OPEN} to {@code CLOSED}
        • + *
        • From {@code HALF_OPEN} to {@code OPEN} (with the triggered metric)
        • + *
        + * + * @param prevState previous state of the circuit breaker + * @param newState new state of the circuit breaker + * @param rule associated rule + * @param snapshotValue triggered value on circuit breaker opens (null if the new state is CLOSED or HALF_OPEN) + */ + void onStateChange(CircuitBreaker.State prevState, CircuitBreaker.State newState, DegradeRule rule, + Double snapshotValue); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java new file mode 100644 index 00000000..fe89971b --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public enum CircuitBreakerStrategy { + + /** + * Circuit breaker opens (cuts off) when slow request ratio exceeds the threshold. + */ + SLOW_REQUEST_RATIO(0), + /** + * Circuit breaker opens (cuts off) when error ratio exceeds the threshold. + */ + ERROR_RATIO(1), + /** + * Circuit breaker opens (cuts off) when error count exceeds the threshold. + */ + ERROR_COUNT(2); + + private int type; + + CircuitBreakerStrategy(int type) { + this.type = type; + } + + public int getType() { + return type; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java new file mode 100644 index 00000000..1f408d36 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; + +/** + *

        Registry for circuit breaker event observers.

        + * + * @author Eric Zhao + * @since 1.8.0 + */ +public class EventObserverRegistry { + + private final Map stateChangeObserverMap = new HashMap<>(); + + /** + * Register a circuit breaker state change observer. + * + * @param name observer name + * @param observer a valid observer + */ + public void addStateChangeObserver(String name, CircuitBreakerStateChangeObserver observer) { + AssertUtil.notNull(name, "name cannot be null"); + AssertUtil.notNull(observer, "observer cannot be null"); + stateChangeObserverMap.put(name, observer); + } + + public boolean removeStateChangeObserver(String name) { + AssertUtil.notNull(name, "name cannot be null"); + return stateChangeObserverMap.remove(name) != null; + } + + /** + * Get all registered state chane observers. + * + * @return all registered state chane observers + */ + public List getStateChangeObservers() { + return new ArrayList<>(stateChangeObserverMap.values()); + } + + public static EventObserverRegistry getInstance() { + return InstanceHolder.instance; + } + + private static class InstanceHolder { + private static EventObserverRegistry instance = new EventObserverRegistry(); + } + + EventObserverRegistry() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java new file mode 100644 index 00000000..fcc129b7 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java @@ -0,0 +1,166 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import java.util.List; +import java.util.concurrent.atomic.LongAdder; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; + +import static com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT; +import static com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public class ExceptionCircuitBreaker extends AbstractCircuitBreaker { + + private final int strategy; + private final int minRequestAmount; + private final double threshold; + + private final LeapArray stat; + + public ExceptionCircuitBreaker(DegradeRule rule) { + this(rule, new SimpleErrorCounterLeapArray(1, rule.getStatIntervalMs())); + } + + ExceptionCircuitBreaker(DegradeRule rule, LeapArray stat) { + super(rule); + this.strategy = rule.getGrade(); + boolean modeOk = strategy == DEGRADE_GRADE_EXCEPTION_RATIO || strategy == DEGRADE_GRADE_EXCEPTION_COUNT; + AssertUtil.isTrue(modeOk, "rule strategy should be error-ratio or error-count"); + AssertUtil.notNull(stat, "stat cannot be null"); + this.minRequestAmount = rule.getMinRequestAmount(); + this.threshold = rule.getCount(); + this.stat = stat; + } + + @Override + protected void resetStat() { + // Reset current bucket (bucket count = 1). + stat.currentWindow().value().reset(); + } + + @Override + public void onRequestComplete(Context context) { + Entry entry = context.getCurEntry(); + if (entry == null) { + return; + } + Throwable error = entry.getError(); + SimpleErrorCounter counter = stat.currentWindow().value(); + if (error != null) { + counter.getErrorCount().add(1); + } + counter.getTotalCount().add(1); + + handleStateChangeWhenThresholdExceeded(error); + } + + private void handleStateChangeWhenThresholdExceeded(Throwable error) { + if (currentState.get() == State.OPEN) { + return; + } + + if (currentState.get() == State.HALF_OPEN) { + // In detecting request + if (error == null) { + fromHalfOpenToClose(); + } else { + fromHalfOpenToOpen(1.0d); + } + return; + } + + List counters = stat.values(); + long errCount = 0; + long totalCount = 0; + for (SimpleErrorCounter counter : counters) { + errCount += counter.errorCount.sum(); + totalCount += counter.totalCount.sum(); + } + if (totalCount < minRequestAmount) { + return; + } + double curCount = errCount; + if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) { + // Use errorRatio + curCount = errCount * 1.0d / totalCount; + } + if (curCount > threshold) { + transformToOpen(curCount); + } + } + + static class SimpleErrorCounter { + private LongAdder errorCount; + private LongAdder totalCount; + + public SimpleErrorCounter() { + this.errorCount = new LongAdder(); + this.totalCount = new LongAdder(); + } + + public LongAdder getErrorCount() { + return errorCount; + } + + public LongAdder getTotalCount() { + return totalCount; + } + + public SimpleErrorCounter reset() { + errorCount.reset(); + totalCount.reset(); + return this; + } + + @Override + public String toString() { + return "SimpleErrorCounter{" + + "errorCount=" + errorCount + + ", totalCount=" + totalCount + + '}'; + } + } + + static class SimpleErrorCounterLeapArray extends LeapArray { + + public SimpleErrorCounterLeapArray(int sampleCount, int intervalInMs) { + super(sampleCount, intervalInMs); + } + + @Override + public SimpleErrorCounter newEmptyBucket(long timeMillis) { + return new SimpleErrorCounter(); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { + // Update the start time and reset value. + w.resetTo(startTime); + w.value().reset(); + return w; + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java new file mode 100644 index 00000000..b270ea61 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java @@ -0,0 +1,170 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import java.util.List; +import java.util.concurrent.atomic.LongAdder; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Entry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker { + + private static final double SLOW_REQUEST_RATIO_MAX_VALUE = 1.0d; + + private final long maxAllowedRt; + private final double maxSlowRequestRatio; + private final int minRequestAmount; + + private final LeapArray slidingCounter; + + public ResponseTimeCircuitBreaker(DegradeRule rule) { + this(rule, new SlowRequestLeapArray(1, rule.getStatIntervalMs())); + } + + ResponseTimeCircuitBreaker(DegradeRule rule, LeapArray stat) { + super(rule); + AssertUtil.isTrue(rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT, "rule metric type should be RT"); + AssertUtil.notNull(stat, "stat cannot be null"); + this.maxAllowedRt = Math.round(rule.getCount()); + this.maxSlowRequestRatio = rule.getSlowRatioThreshold(); + this.minRequestAmount = rule.getMinRequestAmount(); + this.slidingCounter = stat; + } + + @Override + public void resetStat() { + // Reset current bucket (bucket count = 1). + slidingCounter.currentWindow().value().reset(); + } + + @Override + public void onRequestComplete(Context context) { + SlowRequestCounter counter = slidingCounter.currentWindow().value(); + Entry entry = context.getCurEntry(); + if (entry == null) { + return; + } + long completeTime = entry.getCompleteTimestamp(); + if (completeTime <= 0) { + completeTime = TimeUtil.currentTimeMillis(); + } + long rt = completeTime - entry.getCreateTimestamp(); + if (rt > maxAllowedRt) { + counter.slowCount.add(1); + } + counter.totalCount.add(1); + + handleStateChangeWhenThresholdExceeded(rt); + } + + private void handleStateChangeWhenThresholdExceeded(long rt) { + if (currentState.get() == State.OPEN) { + return; + } + + if (currentState.get() == State.HALF_OPEN) { + // In detecting request + // TODO: improve logic for half-open recovery + if (rt > maxAllowedRt) { + fromHalfOpenToOpen(1.0d); + } else { + fromHalfOpenToClose(); + } + return; + } + + List counters = slidingCounter.values(); + long slowCount = 0; + long totalCount = 0; + for (SlowRequestCounter counter : counters) { + slowCount += counter.slowCount.sum(); + totalCount += counter.totalCount.sum(); + } + if (totalCount < minRequestAmount) { + return; + } + double currentRatio = slowCount * 1.0d / totalCount; + if (currentRatio > maxSlowRequestRatio) { + transformToOpen(currentRatio); + } + if (Double.compare(currentRatio, maxSlowRequestRatio) == 0 && + Double.compare(maxSlowRequestRatio, SLOW_REQUEST_RATIO_MAX_VALUE) == 0) { + transformToOpen(currentRatio); + } + } + + static class SlowRequestCounter { + private LongAdder slowCount; + private LongAdder totalCount; + + public SlowRequestCounter() { + this.slowCount = new LongAdder(); + this.totalCount = new LongAdder(); + } + + public LongAdder getSlowCount() { + return slowCount; + } + + public LongAdder getTotalCount() { + return totalCount; + } + + public SlowRequestCounter reset() { + slowCount.reset(); + totalCount.reset(); + return this; + } + + @Override + public String toString() { + return "SlowRequestCounter{" + + "slowCount=" + slowCount + + ", totalCount=" + totalCount + + '}'; + } + } + + static class SlowRequestLeapArray extends LeapArray { + + public SlowRequestLeapArray(int sampleCount, int intervalInMs) { + super(sampleCount, intervalInMs); + } + + @Override + public SlowRequestCounter newEmptyBucket(long timeMillis) { + return new SlowRequestCounter(); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { + w.resetTo(startTime); + w.value().reset(); + return w; + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java new file mode 100644 index 00000000..91aff4dc --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java @@ -0,0 +1,233 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; + +import java.util.Objects; + +/** + * Flow rule config in cluster mode. + * + * @author Eric Zhao + * @since 1.4.0 + */ +public class ClusterFlowConfig { + + /** + * Global unique ID. + */ + private Long flowId; + + /** + * Threshold type (average by local value or global value). + */ + private int thresholdType = ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL; + private boolean fallbackToLocalWhenFail = true; + + /** + * 0: normal. + */ + private int strategy = ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL; + + private int sampleCount = ClusterRuleConstant.DEFAULT_CLUSTER_SAMPLE_COUNT; + /** + * The time interval length of the statistic sliding window (in milliseconds) + */ + private int windowIntervalMs = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS; + + /** + * if the client keep the token for more than resourceTimeout,resourceTimeoutStrategy will work. + */ + private long resourceTimeout = 2000; + + /** + * 0:ignore,1:release the token. + */ + private int resourceTimeoutStrategy = RuleConstant.DEFAULT_RESOURCE_TIMEOUT_STRATEGY; + + /** + * if the request(prioritized=true) is block,acquireRefuseStrategy will work.. + * 0:ignore and block. + * 1:try again . + * 2:try until success. + */ + private int acquireRefuseStrategy = RuleConstant.DEFAULT_BLOCK_STRATEGY; + + /** + * if a client is offline,the server will delete all the token the client holds after clientOfflineTime. + */ + private long clientOfflineTime = 2000; + + public long getResourceTimeout() { + return resourceTimeout; + } + + public void setResourceTimeout(long resourceTimeout) { + this.resourceTimeout = resourceTimeout; + } + + public int getResourceTimeoutStrategy() { + return resourceTimeoutStrategy; + } + + public void setResourceTimeoutStrategy(int resourceTimeoutStrategy) { + this.resourceTimeoutStrategy = resourceTimeoutStrategy; + } + + public int getAcquireRefuseStrategy() { + return acquireRefuseStrategy; + } + + public void setAcquireRefuseStrategy(int acquireRefuseStrategy) { + this.acquireRefuseStrategy = acquireRefuseStrategy; + } + + public long getClientOfflineTime() { + return clientOfflineTime; + } + + public void setClientOfflineTime(long clientOfflineTime) { + this.clientOfflineTime = clientOfflineTime; + } + + public Long getFlowId() { + return flowId; + } + + public ClusterFlowConfig setFlowId(Long flowId) { + this.flowId = flowId; + return this; + } + + public int getThresholdType() { + return thresholdType; + } + + public ClusterFlowConfig setThresholdType(int thresholdType) { + this.thresholdType = thresholdType; + return this; + } + + public int getStrategy() { + return strategy; + } + + public ClusterFlowConfig setStrategy(int strategy) { + this.strategy = strategy; + return this; + } + + public boolean isFallbackToLocalWhenFail() { + return fallbackToLocalWhenFail; + } + + public ClusterFlowConfig setFallbackToLocalWhenFail(boolean fallbackToLocalWhenFail) { + this.fallbackToLocalWhenFail = fallbackToLocalWhenFail; + return this; + } + + public int getSampleCount() { + return sampleCount; + } + + public ClusterFlowConfig setSampleCount(int sampleCount) { + this.sampleCount = sampleCount; + return this; + } + + public int getWindowIntervalMs() { + return windowIntervalMs; + } + + public ClusterFlowConfig setWindowIntervalMs(int windowIntervalMs) { + this.windowIntervalMs = windowIntervalMs; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ClusterFlowConfig that = (ClusterFlowConfig) o; + + if (thresholdType != that.thresholdType) { + return false; + } + if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { + return false; + } + if (strategy != that.strategy) { + return false; + } + if (sampleCount != that.sampleCount) { + return false; + } + if (windowIntervalMs != that.windowIntervalMs) { + return false; + } + if (resourceTimeout != that.resourceTimeout) { + return false; + } + if (clientOfflineTime != that.clientOfflineTime) { + return false; + } + if (resourceTimeoutStrategy != that.resourceTimeoutStrategy) { + return false; + } + if (acquireRefuseStrategy != that.acquireRefuseStrategy) { + return false; + } + return Objects.equals(flowId, that.flowId); + } + + @Override + public int hashCode() { + int result = flowId != null ? flowId.hashCode() : 0; + result = 31 * result + thresholdType; + result = 31 * result + (fallbackToLocalWhenFail ? 1 : 0); + result = 31 * result + strategy; + result = 31 * result + sampleCount; + result = 31 * result + windowIntervalMs; + result = (int) (31 * result + resourceTimeout); + result = (int) (31 * result + clientOfflineTime); + result = 31 * result + resourceTimeoutStrategy; + result = 31 * result + acquireRefuseStrategy; + return result; + } + + @Override + public String toString() { + return "ClusterFlowConfig{" + + "flowId=" + flowId + + ", thresholdType=" + thresholdType + + ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail + + ", strategy=" + strategy + + ", sampleCount=" + sampleCount + + ", windowIntervalMs=" + windowIntervalMs + + ", resourceTimeout=" + resourceTimeout + + ", resourceTimeoutStrategy=" + resourceTimeoutStrategy + + ", acquireRefuseStrategy=" + acquireRefuseStrategy + + ", clientOfflineTime=" + clientOfflineTime + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ColdFactorProperty.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ColdFactorProperty.java new file mode 100755 index 00000000..ff18b8cd --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/ColdFactorProperty.java @@ -0,0 +1,26 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig; + +/** + * @author jialiang.linjl + */ +class ColdFactorProperty { + + public static volatile int coldFactor = SentinelConfig.coldFactor(); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowException.java new file mode 100755 index 00000000..37c106b9 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowException.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; + +/*** + * @author youji.zj + */ +public class FlowException extends BlockException { + + public FlowException(String ruleLimitApp) { + super(ruleLimitApp); + } + + public FlowException(String ruleLimitApp, FlowRule rule) { + super(ruleLimitApp, rule); + } + + public FlowException(String message, Throwable cause) { + super(message, cause); + } + + public FlowException(String ruleLimitApp, String message) { + super(ruleLimitApp, message); + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + + /** + * Get triggered rule. + * Note: the rule result is a reference to rule map and SHOULD NOT be modified. + * + * @return triggered rule + * @since 1.4.2 + */ + @Override + public FlowRule getRule() { + return rule.as(FlowRule.class); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java new file mode 100755 index 00000000..6e1bc0ff --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java @@ -0,0 +1,242 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.AbstractRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; + +/** + *

        + * Each flow rule is mainly composed of three factors: grade, + * strategy and controlBehavior: + *

        + *
          + *
        • The {@link #grade} represents the threshold type of flow control (by QPS or thread count).
        • + *
        • The {@link #strategy} represents the strategy based on invocation relation.
        • + *
        • The {@link #controlBehavior} represents the QPS shaping behavior (actions on incoming request when QPS + * exceeds the threshold).
        • + *
        + * + * @author jialiang.linjl + * @author Eric Zhao + */ +public class FlowRule extends AbstractRule { + + public FlowRule() { + super(); + setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); + } + + public FlowRule(String resourceName) { + super(); + setResource(resourceName); + setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); + } + + /** + * The threshold type of flow control (0: thread count, 1: QPS). + */ + private int grade = RuleConstant.FLOW_GRADE_QPS; + + /** + * Flow control threshold count. + */ + private double count; + + /** + * Flow control strategy based on invocation chain. + * + * {@link RuleConstant#STRATEGY_DIRECT} for direct flow control (by origin); + * {@link RuleConstant#STRATEGY_RELATE} for relevant flow control (with relevant resource); + * {@link RuleConstant#STRATEGY_CHAIN} for chain flow control (by entrance resource). + */ + private int strategy = RuleConstant.STRATEGY_DIRECT; + + /** + * Reference resource in flow control with relevant resource or context. + */ + private String refResource; + + /** + * Rate limiter control behavior. + * 0. default(reject directly), 1. warm up, 2. rate limiter, 3. warm up + rate limiter + */ + private int controlBehavior = RuleConstant.CONTROL_BEHAVIOR_DEFAULT; + + private int warmUpPeriodSec = 10; + + /** + * Max queueing time in rate limiter behavior. + */ + private int maxQueueingTimeMs = 500; + + private boolean clusterMode; + /** + * Flow rule config for cluster mode. + */ + private ClusterFlowConfig clusterConfig; + + /** + * The traffic shaping (throttling) controller. + */ + private TrafficShapingController controller; + + public int getControlBehavior() { + return controlBehavior; + } + + public FlowRule setControlBehavior(int controlBehavior) { + this.controlBehavior = controlBehavior; + return this; + } + + public int getMaxQueueingTimeMs() { + return maxQueueingTimeMs; + } + + public FlowRule setMaxQueueingTimeMs(int maxQueueingTimeMs) { + this.maxQueueingTimeMs = maxQueueingTimeMs; + return this; + } + + FlowRule setRater(TrafficShapingController rater) { + this.controller = rater; + return this; + } + + TrafficShapingController getRater() { + return controller; + } + + public int getWarmUpPeriodSec() { + return warmUpPeriodSec; + } + + public FlowRule setWarmUpPeriodSec(int warmUpPeriodSec) { + this.warmUpPeriodSec = warmUpPeriodSec; + return this; + } + + public int getGrade() { + return grade; + } + + public FlowRule setGrade(int grade) { + this.grade = grade; + return this; + } + + public double getCount() { + return count; + } + + public FlowRule setCount(double count) { + this.count = count; + return this; + } + + public int getStrategy() { + return strategy; + } + + public FlowRule setStrategy(int strategy) { + this.strategy = strategy; + return this; + } + + public String getRefResource() { + return refResource; + } + + public FlowRule setRefResource(String refResource) { + this.refResource = refResource; + return this; + } + + public boolean isClusterMode() { + return clusterMode; + } + + public FlowRule setClusterMode(boolean clusterMode) { + this.clusterMode = clusterMode; + return this; + } + + public ClusterFlowConfig getClusterConfig() { + return clusterConfig; + } + + public FlowRule setClusterConfig(ClusterFlowConfig clusterConfig) { + this.clusterConfig = clusterConfig; + return this; + } + + @Override + public boolean equals(Object o) { + if (this == o) { return true; } + if (o == null || getClass() != o.getClass()) { return false; } + if (!super.equals(o)) { return false; } + + FlowRule rule = (FlowRule)o; + + if (grade != rule.grade) { return false; } + if (Double.compare(rule.count, count) != 0) { return false; } + if (strategy != rule.strategy) { return false; } + if (controlBehavior != rule.controlBehavior) { return false; } + if (warmUpPeriodSec != rule.warmUpPeriodSec) { return false; } + if (maxQueueingTimeMs != rule.maxQueueingTimeMs) { return false; } + if (clusterMode != rule.clusterMode) { return false; } + if (refResource != null ? !refResource.equals(rule.refResource) : rule.refResource != null) { return false; } + return clusterConfig != null ? clusterConfig.equals(rule.clusterConfig) : rule.clusterConfig == null; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + long temp; + result = 31 * result + grade; + temp = Double.doubleToLongBits(count); + result = 31 * result + (int)(temp ^ (temp >>> 32)); + result = 31 * result + strategy; + result = 31 * result + (refResource != null ? refResource.hashCode() : 0); + result = 31 * result + controlBehavior; + result = 31 * result + warmUpPeriodSec; + result = 31 * result + maxQueueingTimeMs; + result = 31 * result + (clusterMode ? 1 : 0); + result = 31 * result + (clusterConfig != null ? clusterConfig.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "FlowRule{" + + "resource=" + getResource() + + ", limitApp=" + getLimitApp() + + ", grade=" + grade + + ", count=" + count + + ", strategy=" + strategy + + ", refResource=" + refResource + + ", controlBehavior=" + controlBehavior + + ", warmUpPeriodSec=" + warmUpPeriodSec + + ", maxQueueingTimeMs=" + maxQueueingTimeMs + + ", clusterMode=" + clusterMode + + ", clusterConfig=" + clusterConfig + + ", controller=" + controller + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java new file mode 100644 index 00000000..2bd681a0 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleChecker.java @@ -0,0 +1,211 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import java.util.Collection; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.ClusterStateManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.server.EmbeddedClusterTokenServerProvider; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.client.TokenClientProvider; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.TokenResultStatus; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.TokenResult; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.cluster.TokenService; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Function; + +/** + * Rule checker for flow control rules. + * + * @author Eric Zhao + */ +public class FlowRuleChecker { + + public void checkFlow(Function> ruleProvider, ResourceWrapper resource, + Context context, DefaultNode node, int count, boolean prioritized) throws BlockException { + if (ruleProvider == null || resource == null) { + return; + } + Collection rules = ruleProvider.apply(resource.getName()); + if (rules != null) { + for (FlowRule rule : rules) { + if (!canPassCheck(rule, context, node, count, prioritized)) { + throw new FlowException(rule.getLimitApp(), rule); + } + } + } + } + + public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, + int acquireCount) { + return canPassCheck(rule, context, node, acquireCount, false); + } + + public boolean canPassCheck(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { + String limitApp = rule.getLimitApp(); + if (limitApp == null) { + return true; + } + + if (rule.isClusterMode()) { + return passClusterCheck(rule, context, node, acquireCount, prioritized); + } + + return passLocalCheck(rule, context, node, acquireCount, prioritized); + } + + private static boolean passLocalCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { + Node selectedNode = selectNodeByRequesterAndStrategy(rule, context, node); + if (selectedNode == null) { + return true; + } + + return rule.getRater().canPass(selectedNode, acquireCount, prioritized); + } + + static Node selectReferenceNode(FlowRule rule, Context context, DefaultNode node) { + String refResource = rule.getRefResource(); + int strategy = rule.getStrategy(); + + if (StringUtil.isEmpty(refResource)) { + return null; + } + + if (strategy == RuleConstant.STRATEGY_RELATE) { + return ClusterBuilderSlot.getClusterNode(refResource); + } + + if (strategy == RuleConstant.STRATEGY_CHAIN) { + if (!refResource.equals(context.getName())) { + return null; + } + return node; + } + // No node. + return null; + } + + private static boolean filterOrigin(String origin) { + // Origin cannot be `default` or `other`. + return !RuleConstant.LIMIT_APP_DEFAULT.equals(origin) && !RuleConstant.LIMIT_APP_OTHER.equals(origin); + } + + static Node selectNodeByRequesterAndStrategy(/*@NonNull*/ FlowRule rule, Context context, DefaultNode node) { + // The limit app should not be empty. + String limitApp = rule.getLimitApp(); + int strategy = rule.getStrategy(); + String origin = context.getOrigin(); + + if (limitApp.equals(origin) && filterOrigin(origin)) { + if (strategy == RuleConstant.STRATEGY_DIRECT) { + // Matches limit origin, return origin statistic node. + return context.getOriginNode(); + } + + return selectReferenceNode(rule, context, node); + } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(limitApp)) { + if (strategy == RuleConstant.STRATEGY_DIRECT) { + // Return the cluster node. + return node.getClusterNode(); + } + + return selectReferenceNode(rule, context, node); + } else if (RuleConstant.LIMIT_APP_OTHER.equals(limitApp) + && FlowRuleManager.isOtherOrigin(origin, rule.getResource())) { + if (strategy == RuleConstant.STRATEGY_DIRECT) { + return context.getOriginNode(); + } + + return selectReferenceNode(rule, context, node); + } + + return null; + } + + private static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { + try { + TokenService clusterService = pickClusterService(); + if (clusterService == null) { + return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); + } + long flowId = rule.getClusterConfig().getFlowId(); + TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized); + return applyTokenResult(result, rule, context, node, acquireCount, prioritized); + // If client is absent, then fallback to local mode. + } catch (Throwable ex) { + RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex); + } + // Fallback to local flow control when token client or server for this rule is not available. + // If fallback is not enabled, then directly pass. + return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); + } + + private static boolean fallbackToLocalOrPass(FlowRule rule, Context context, DefaultNode node, int acquireCount, + boolean prioritized) { + if (rule.getClusterConfig().isFallbackToLocalWhenFail()) { + return passLocalCheck(rule, context, node, acquireCount, prioritized); + } else { + // The rule won't be activated, just pass. + return true; + } + } + + private static TokenService pickClusterService() { + if (ClusterStateManager.isClient()) { + return TokenClientProvider.getClient(); + } + if (ClusterStateManager.isServer()) { + return EmbeddedClusterTokenServerProvider.getServer(); + } + return null; + } + + private static boolean applyTokenResult(/*@NonNull*/ TokenResult result, FlowRule rule, Context context, + DefaultNode node, + int acquireCount, boolean prioritized) { + switch (result.getStatus()) { + case TokenResultStatus.OK: + return true; + case TokenResultStatus.SHOULD_WAIT: + // Wait for next tick. + try { + Thread.sleep(result.getWaitInMs()); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + case TokenResultStatus.NO_RULE_EXISTS: + case TokenResultStatus.BAD_REQUEST: + case TokenResultStatus.FAIL: + case TokenResultStatus.TOO_MANY_REQUEST: + return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized); + case TokenResultStatus.BLOCKED: + default: + return false; + } + } +} \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java new file mode 100755 index 00000000..b51047f8 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleComparator.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import java.util.Comparator; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; + +/** + * Comparator for flow rules. + * + * @author jialiang.linjl + */ +public class FlowRuleComparator implements Comparator { + + @Override + public int compare(FlowRule o1, FlowRule o2) { + // the FlowRule in Clustered mode will be put at the end. + if (o1.isClusterMode() && !o2.isClusterMode()) { + return 1; + } + + if (!o1.isClusterMode() && o2.isClusterMode()) { + return -1; + } + + if (o1.getLimitApp() == null) { + return 0; + } + + if (o1.getLimitApp().equals(o2.getLimitApp())) { + return 0; + } + + if (RuleConstant.LIMIT_APP_DEFAULT.equals(o1.getLimitApp())) { + return 1; + } else if (RuleConstant.LIMIT_APP_DEFAULT.equals(o2.getLimitApp())) { + return -1; + } else { + return 0; + } + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java new file mode 100755 index 00000000..4df8d24e --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java @@ -0,0 +1,171 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric.MetricTimerListener; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.DynamicSentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.PropertyListener; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + *

        + * One resources can have multiple rules. And these rules take effects in the following order: + *

          + *
        1. requests from specified caller
        2. + *
        3. no specified caller
        4. + *
        + *

        + * + * @author jialiang.linjl + * @author Eric Zhao + * @author Weihua + */ +public class FlowRuleManager { + + private static volatile Map> flowRules = new HashMap<>(); + + private static final FlowPropertyListener LISTENER = new FlowPropertyListener(); + private static SentinelProperty> currentProperty = new DynamicSentinelProperty>(); + + /** the corePool size of SCHEDULER must be set at 1, so the two task ({@link #startMetricTimerListener()} can run orderly by the SCHEDULER **/ + @SuppressWarnings("PMD.ThreadPoolCreationRule") + private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("sentinel-metrics-record-task", true)); + + static { + currentProperty.addListener(LISTENER); + startMetricTimerListener(); + } + + /** + *

        Start the MetricTimerListener + *

          + *
        1. If the flushInterval more than 0, + * the timer will run with the flushInterval as the rate
        2. . + *
        3. If the flushInterval less than 0(include) or value is not valid, + * then means the timer will not be started
        4. + *

            + */ + private static void startMetricTimerListener() { + long flushInterval = SentinelConfig.metricLogFlushIntervalSec(); + if (flushInterval <= 0) { + RecordLog.info("[FlowRuleManager] The MetricTimerListener isn't started. If you want to start it, " + + "please change the value(current: {}) of config({}) more than 0 to start it.", flushInterval, + SentinelConfig.METRIC_FLUSH_INTERVAL); + return; + } + SCHEDULER.scheduleAtFixedRate(new MetricTimerListener(), 0, flushInterval, TimeUnit.SECONDS); + } + + /** + * Listen to the {@link SentinelProperty} for {@link FlowRule}s. The property is the source of {@link FlowRule}s. + * Flow rules can also be set by {@link #loadRules(List)} directly. + * + * @param property the property to listen. + */ + public static void register2Property(SentinelProperty> property) { + AssertUtil.notNull(property, "property cannot be null"); + synchronized (LISTENER) { + RecordLog.info("[FlowRuleManager] Registering new property to flow rule manager"); + currentProperty.removeListener(LISTENER); + property.addListener(LISTENER); + currentProperty = property; + } + } + + /** + * Get a copy of the rules. + * + * @return a new copy of the rules. + */ + public static List getRules() { + List rules = new ArrayList(); + for (Map.Entry> entry : flowRules.entrySet()) { + rules.addAll(entry.getValue()); + } + return rules; + } + + /** + * Load {@link FlowRule}s, former rules will be replaced. + * + * @param rules new rules to load. + */ + public static void loadRules(List rules) { + currentProperty.updateValue(rules); + } + + static Map> getFlowRuleMap() { + return flowRules; + } + + public static boolean hasConfig(String resource) { + return flowRules.containsKey(resource); + } + + public static boolean isOtherOrigin(String origin, String resourceName) { + if (StringUtil.isEmpty(origin)) { + return false; + } + + List rules = flowRules.get(resourceName); + + if (rules != null) { + for (FlowRule rule : rules) { + if (origin.equals(rule.getLimitApp())) { + return false; + } + } + } + + return true; + } + + private static final class FlowPropertyListener implements PropertyListener> { + + @Override + public synchronized void configUpdate(List value) { + Map> rules = FlowRuleUtil.buildFlowRuleMap(value); + if (rules != null) { + flowRules = rules; + } + RecordLog.info("[FlowRuleManager] Flow rules received: {}", rules); + } + + @Override + public synchronized void configLoad(List conf) { + Map> rules = FlowRuleUtil.buildFlowRuleMap(conf); + if (rules != null) { + flowRules = rules; + } + RecordLog.info("[FlowRuleManager] Flow rules loaded: {}", rules); + } + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java new file mode 100644 index 00000000..e8a9df99 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java @@ -0,0 +1,266 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.ColdFactorProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowRuleComparator; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpRateLimiterController; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Function; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Predicate; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Eric Zhao + * @since 1.4.0 + */ +public final class FlowRuleUtil { + + /** + * Build the flow rule map from raw list of flow rules, grouping by resource name. + * + * @param list raw list of flow rules + * @return constructed new flow rule map; empty map if list is null or empty, or no valid rules + */ + public static Map> buildFlowRuleMap(List list) { + return buildFlowRuleMap(list, null); + } + + /** + * Build the flow rule map from raw list of flow rules, grouping by resource name. + * + * @param list raw list of flow rules + * @param filter rule filter + * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules + */ + public static Map> buildFlowRuleMap(List list, Predicate filter) { + return buildFlowRuleMap(list, filter, true); + } + + /** + * Build the flow rule map from raw list of flow rules, grouping by resource name. + * + * @param list raw list of flow rules + * @param filter rule filter + * @param shouldSort whether the rules should be sorted + * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules + */ + public static Map> buildFlowRuleMap(List list, Predicate filter, + boolean shouldSort) { + return buildFlowRuleMap(list, extractResource, filter, shouldSort); + } + + /** + * Build the flow rule map from raw list of flow rules, grouping by provided group function. + * + * @param list raw list of flow rules + * @param groupFunction grouping function of the map (by key) + * @param filter rule filter + * @param shouldSort whether the rules should be sorted + * @param type of key + * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules + */ + public static Map> buildFlowRuleMap(List list, Function groupFunction, + Predicate filter, boolean shouldSort) { + Map> newRuleMap = new ConcurrentHashMap<>(); + if (list == null || list.isEmpty()) { + return newRuleMap; + } + Map> tmpMap = new ConcurrentHashMap<>(); + + for (FlowRule rule : list) { + if (!isValidRule(rule)) { + RecordLog.warn("[FlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule); + continue; + } + if (filter != null && !filter.test(rule)) { + continue; + } + if (StringUtil.isBlank(rule.getLimitApp())) { + rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); + } + TrafficShapingController rater = generateRater(rule); + rule.setRater(rater); + + K key = groupFunction.apply(rule); + if (key == null) { + continue; + } + Set flowRules = tmpMap.get(key); + + if (flowRules == null) { + // Use hash set here to remove duplicate rules. + flowRules = new HashSet<>(); + tmpMap.put(key, flowRules); + } + + flowRules.add(rule); + } + Comparator comparator = new FlowRuleComparator(); + for (Entry> entries : tmpMap.entrySet()) { + List rules = new ArrayList<>(entries.getValue()); + if (shouldSort) { + // Sort the rules. + Collections.sort(rules, comparator); + } + newRuleMap.put(entries.getKey(), rules); + } + + return newRuleMap; + } + + private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) { + if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) { + switch (rule.getControlBehavior()) { + case RuleConstant.CONTROL_BEHAVIOR_WARM_UP: + return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(), + ColdFactorProperty.coldFactor); + case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: + return new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount()); + case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER: + return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(), + rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor); + case RuleConstant.CONTROL_BEHAVIOR_DEFAULT: + default: + // Default mode or unknown mode: default traffic shaping controller (fast-reject). + } + } + return new DefaultController(rule.getCount(), rule.getGrade()); + } + + /** + * Check whether provided ID can be a valid cluster flow ID. + * + * @param id flow ID to check + * @return true if valid, otherwise false + */ + public static boolean validClusterRuleId(Long id) { + return id != null && id > 0; + } + + /** + * Check whether provided flow rule is valid. + * + * @param rule flow rule to check + * @return true if valid, otherwise false + */ + public static boolean isValidRule(FlowRule rule) { + boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 + && rule.getGrade() >= 0 && rule.getStrategy() >= 0 && rule.getControlBehavior() >= 0; + if (!baseValid) { + return false; + } + if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) { + // Check strategy and control (shaping) behavior. + return checkClusterField(rule) && checkStrategyField(rule) && checkControlBehaviorField(rule); + } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_THREAD) { + return checkClusterConcurrentField(rule); + } else { + return false; + } + + } + + public static boolean checkClusterConcurrentField(/*@NonNull*/ FlowRule rule) { + if (!rule.isClusterMode()) { + return true; + } + ClusterFlowConfig clusterConfig = rule.getClusterConfig(); + if (clusterConfig == null) { + return false; + } + if (clusterConfig.getClientOfflineTime() <= 0 || clusterConfig.getResourceTimeout() <= 0) { + return false; + } + + if (clusterConfig.getAcquireRefuseStrategy() < 0 || clusterConfig.getResourceTimeoutStrategy() < 0) { + return false; + } + + if (!validClusterRuleId(clusterConfig.getFlowId())) { + return false; + } + + return isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs()); + } + + private static boolean checkClusterField(/*@NonNull*/ FlowRule rule) { + if (!rule.isClusterMode()) { + return true; + } + ClusterFlowConfig clusterConfig = rule.getClusterConfig(); + if (clusterConfig == null) { + return false; + } + if (!validClusterRuleId(clusterConfig.getFlowId())) { + return false; + } + if (!isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())) { + return false; + } + switch (clusterConfig.getStrategy()) { + case ClusterRuleConstant.FLOW_CLUSTER_STRATEGY_NORMAL: + return true; + default: + return false; + } + } + + public static boolean isWindowConfigValid(int sampleCount, int windowIntervalMs) { + return sampleCount > 0 && windowIntervalMs > 0 && windowIntervalMs % sampleCount == 0; + } + + private static boolean checkStrategyField(/*@NonNull*/ FlowRule rule) { + if (rule.getStrategy() == RuleConstant.STRATEGY_RELATE || rule.getStrategy() == RuleConstant.STRATEGY_CHAIN) { + return StringUtil.isNotBlank(rule.getRefResource()); + } + return true; + } + + private static boolean checkControlBehaviorField(/*@NonNull*/ FlowRule rule) { + switch (rule.getControlBehavior()) { + case RuleConstant.CONTROL_BEHAVIOR_WARM_UP: + return rule.getWarmUpPeriodSec() > 0; + case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: + return rule.getMaxQueueingTimeMs() > 0; + case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER: + return rule.getWarmUpPeriodSec() > 0 && rule.getMaxQueueingTimeMs() > 0; + default: + return true; + } + } + + private static final Function extractResource = new Function() { + @Override + public String apply(FlowRule rule) { + return rule.getResource(); + } + }; + + private FlowRuleUtil() { + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java new file mode 100755 index 00000000..d7067214 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java @@ -0,0 +1,189 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Function; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + *

            + * Combined the runtime statistics collected from the previous + * slots (NodeSelectorSlot, ClusterNodeBuilderSlot, and StatisticSlot), FlowSlot + * will use pre-set rules to decide whether the incoming requests should be + * blocked. + *

            + * + *

            + * {@code SphU.entry(resourceName)} will throw {@code FlowException} if any rule is + * triggered. Users can customize their own logic by catching {@code FlowException}. + *

            + * + *

            + * One resource can have multiple flow rules. FlowSlot traverses these rules + * until one of them is triggered or all rules have been traversed. + *

            + * + *

            + * Each {@link FlowRule} is mainly composed of these factors: grade, strategy, path. We + * can combine these factors to achieve different effects. + *

            + * + *

            + * The grade is defined by the {@code grade} field in {@link FlowRule}. Here, 0 for thread + * isolation and 1 for request count shaping (QPS). Both thread count and request + * count are collected in real runtime, and we can view these statistics by + * following command: + *

            + * + *
            + * curl http://localhost:8719/tree
            + *
            + * idx id    thread pass  blocked   success total aRt   1m-pass   1m-block   1m-all   exception
            + * 2   abc647 0      460    46          46   1    27      630       276        897      0
            + * 
            + * + *
              + *
            • {@code thread} for the count of threads that is currently processing the resource
            • + *
            • {@code pass} for the count of incoming request within one second
            • + *
            • {@code blocked} for the count of requests blocked within one second
            • + *
            • {@code success} for the count of the requests successfully handled by Sentinel within one second
            • + *
            • {@code RT} for the average response time of the requests within a second
            • + *
            • {@code total} for the sum of incoming requests and blocked requests within one second
            • + *
            • {@code 1m-pass} is for the count of incoming requests within one minute
            • + *
            • {@code 1m-block} is for the count of a request blocked within one minute
            • + *
            • {@code 1m-all} is the total of incoming and blocked requests within one minute
            • + *
            • {@code exception} is for the count of business (customized) exceptions in one second
            • + *
            + * + * This stage is usually used to protect resources from occupying. If a resource + * takes long time to finish, threads will begin to occupy. The longer the + * response takes, the more threads occupy. + * + * Besides counter, thread pool or semaphore can also be used to achieve this. + * + * - Thread pool: Allocate a thread pool to handle these resource. When there is + * no more idle thread in the pool, the request is rejected without affecting + * other resources. + * + * - Semaphore: Use semaphore to control the concurrent count of the threads in + * this resource. + * + * The benefit of using thread pool is that, it can walk away gracefully when + * time out. But it also bring us the cost of context switch and additional + * threads. If the incoming requests is already served in a separated thread, + * for instance, a Servlet HTTP request, it will almost double the threads count if + * using thread pool. + * + *

            Traffic Shaping

            + *

            + * When QPS exceeds the threshold, Sentinel will take actions to control the incoming request, + * and is configured by {@code controlBehavior} field in flow rules. + *

            + *
              + *
            1. Immediately reject ({@code RuleConstant.CONTROL_BEHAVIOR_DEFAULT})
            2. + *

              + * This is the default behavior. The exceeded request is rejected immediately + * and the FlowException is thrown + *

              + * + *
            3. Warmup ({@code RuleConstant.CONTROL_BEHAVIOR_WARM_UP})
            4. + *

              + * If the load of system has been low for a while, and a large amount of + * requests comes, the system might not be able to handle all these requests at + * once. However if we steady increase the incoming request, the system can warm + * up and finally be able to handle all the requests. + * This warmup period can be configured by setting the field {@code warmUpPeriodSec} in flow rules. + *

              + * + *
            5. Uniform Rate Limiting ({@code RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER})
            6. + *

              + * This strategy strictly controls the interval between requests. + * In other words, it allows requests to pass at a stable, uniform rate. + *

              + * + *

              + * This strategy is an implement of leaky bucket. + * It is used to handle the request at a stable rate and is often used in burst traffic (e.g. message handling). + * When a large number of requests beyond the system’s capacity arrive + * at the same time, the system using this strategy will handle requests and its + * fixed rate until all the requests have been processed or time out. + *

              + *
            + * + * @author jialiang.linjl + * @author Eric Zhao + */ +@Spi(order = Constants.ORDER_FLOW_SLOT) +public class FlowSlot extends AbstractLinkedProcessorSlot { + + private final FlowRuleChecker checker; + + public FlowSlot() { + this(new FlowRuleChecker()); + } + + /** + * Package-private for test. + * + * @param checker flow rule checker + * @since 1.6.1 + */ + FlowSlot(FlowRuleChecker checker) { + AssertUtil.notNull(checker, "flow checker should not be null"); + this.checker = checker; + } + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, + boolean prioritized, Object... args) throws Throwable { + checkFlow(resourceWrapper, context, node, count, prioritized); + + fireEntry(context, resourceWrapper, node, count, prioritized, args); + } + + void checkFlow(ResourceWrapper resource, Context context, DefaultNode node, int count, boolean prioritized) + throws BlockException { + checker.checkFlow(ruleProvider, resource, context, node, count, prioritized); + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + fireExit(context, resourceWrapper, count, args); + } + + private final Function> ruleProvider = new Function>() { + @Override + public Collection apply(String resource) { + // Flow rule map should not be null. + Map> flowRules = FlowRuleManager.getFlowRuleMap(); + return flowRules.get(resource); + } + }; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/PriorityWaitException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/PriorityWaitException.java new file mode 100644 index 00000000..4ad3d482 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/PriorityWaitException.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +/** + * An exception that marks previous prioritized request has been waiting till now, then should pass. + * + * @author jialiang.linjl + * @since 1.5.0 + */ +public class PriorityWaitException extends RuntimeException { + + private final long waitInMs; + + public PriorityWaitException(long waitInMs) { + this.waitInMs = waitInMs; + } + + public long getWaitInMs() { + return waitInMs; + } + + @Override + public Throwable fillInStackTrace() { + return this; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java new file mode 100755 index 00000000..8aeb474b --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/TrafficShapingController.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; + +/** + * A universal interface for traffic shaping controller. + * + * @author jialiang.linjl + */ +public interface TrafficShapingController { + + /** + * Check whether given resource entry can pass with provided count. + * + * @param node resource node + * @param acquireCount count to acquire + * @param prioritized whether the request is prioritized + * @return true if the resource entry can pass; false if it should be blocked + */ + boolean canPass(Node node, int acquireCount, boolean prioritized); + + /** + * Check whether given resource entry can pass with provided count. + * + * @param node resource node + * @param acquireCount count to acquire + * @return true if the resource entry can pass; false if it should be blocked + */ + boolean canPass(Node node, int acquireCount); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java new file mode 100755 index 00000000..caebfeda --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/DefaultController.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.OccupyTimeoutProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; + +/** + * Default throttling controller (immediately reject strategy). + * + * @author jialiang.linjl + * @author Eric Zhao + */ +public class DefaultController implements TrafficShapingController { + + private static final int DEFAULT_AVG_USED_TOKENS = 0; + + private double count; + private int grade; + + public DefaultController(double count, int grade) { + this.count = count; + this.grade = grade; + } + + @Override + public boolean canPass(Node node, int acquireCount) { + return canPass(node, acquireCount, false); + } + + @Override + public boolean canPass(Node node, int acquireCount, boolean prioritized) { + int curCount = avgUsedTokens(node); + if (curCount + acquireCount > count) { + if (prioritized && grade == RuleConstant.FLOW_GRADE_QPS) { + long currentTime; + long waitInMs; + currentTime = TimeUtil.currentTimeMillis(); + waitInMs = node.tryOccupyNext(currentTime, acquireCount, count); + if (waitInMs < OccupyTimeoutProperty.getOccupyTimeout()) { + node.addWaitingRequest(currentTime + waitInMs, acquireCount); + node.addOccupiedPass(acquireCount); + sleep(waitInMs); + + // PriorityWaitException indicates that the request will pass after waiting for {@link @waitInMs}. + throw new PriorityWaitException(waitInMs); + } + } + return false; + } + return true; + } + + private int avgUsedTokens(Node node) { + if (node == null) { + return DEFAULT_AVG_USED_TOKENS; + } + return grade == RuleConstant.FLOW_GRADE_THREAD ? node.curThreadNum() : (int)(node.passQps()); + } + + private void sleep(long timeMillis) { + try { + Thread.sleep(timeMillis); + } catch (InterruptedException e) { + // Ignore. + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java new file mode 100755 index 00000000..c0cc4b99 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterController.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller; + +import java.util.concurrent.atomic.AtomicLong; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; + +/** + * @author jialiang.linjl + */ +public class RateLimiterController implements TrafficShapingController { + + private final int maxQueueingTimeMs; + private final double count; + + private final AtomicLong latestPassedTime = new AtomicLong(-1); + + public RateLimiterController(int timeOut, double count) { + this.maxQueueingTimeMs = timeOut; + this.count = count; + } + + @Override + public boolean canPass(Node node, int acquireCount) { + return canPass(node, acquireCount, false); + } + + @Override + public boolean canPass(Node node, int acquireCount, boolean prioritized) { + // Pass when acquire count is less or equal than 0. + if (acquireCount <= 0) { + return true; + } + // Reject when count is less or equal than 0. + // Otherwise,the costTime will be max of long and waitTime will overflow in some cases. + if (count <= 0) { + return false; + } + + long currentTime = TimeUtil.currentTimeMillis(); + // Calculate the interval between every two requests. + long costTime = Math.round(1.0 * (acquireCount) / count * 1000); + + // Expected pass time of this request. + long expectedTime = costTime + latestPassedTime.get(); + + if (expectedTime <= currentTime) { + // Contention may exist here, but it's okay. + latestPassedTime.set(currentTime); + return true; + } else { + // Calculate the time to wait. + long waitTime = costTime + latestPassedTime.get() - TimeUtil.currentTimeMillis(); + if (waitTime > maxQueueingTimeMs) { + return false; + } else { + long oldTime = latestPassedTime.addAndGet(costTime); + try { + waitTime = oldTime - TimeUtil.currentTimeMillis(); + if (waitTime > maxQueueingTimeMs) { + latestPassedTime.addAndGet(-costTime); + return false; + } + // in race condition waitTime may <= 0 + if (waitTime > 0) { + Thread.sleep(waitTime); + } + return true; + } catch (InterruptedException e) { + } + } + } + return false; + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java new file mode 100755 index 00000000..f28c7c63 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpController.java @@ -0,0 +1,177 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller; + +import java.util.concurrent.atomic.AtomicLong; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.TrafficShapingController; + +/** + *

            + * The principle idea comes from Guava. However, the calculation of Guava is + * rate-based, which means that we need to translate rate to QPS. + *

            + * + *

            + * Requests arriving at the pulse may drag down long idle systems even though it + * has a much larger handling capability in stable period. It usually happens in + * scenarios that require extra time for initialization, e.g. DB establishes a connection, + * connects to a remote service, and so on. That’s why we need “warm up”. + *

            + * + *

            + * Sentinel's "warm-up" implementation is based on the Guava's algorithm. + * However, Guava’s implementation focuses on adjusting the request interval, + * which is similar to leaky bucket. Sentinel pays more attention to + * controlling the count of incoming requests per second without calculating its interval, + * which resembles token bucket algorithm. + *

            + * + *

            + * The remaining tokens in the bucket is used to measure the system utility. + * Suppose a system can handle b requests per second. Every second b tokens will + * be added into the bucket until the bucket is full. And when system processes + * a request, it takes a token from the bucket. The more tokens left in the + * bucket, the lower the utilization of the system; when the token in the token + * bucket is above a certain threshold, we call it in a "saturation" state. + *

            + * + *

            + * Base on Guava’s theory, there is a linear equation we can write this in the + * form y = m * x + b where y (a.k.a y(x)), or qps(q)), is our expected QPS + * given a saturated period (e.g. 3 minutes in), m is the rate of change from + * our cold (minimum) rate to our stable (maximum) rate, x (or q) is the + * occupied token. + *

            + * + * @author jialiang.linjl + */ +public class WarmUpController implements TrafficShapingController { + + protected double count; + private int coldFactor; + protected int warningToken = 0; + private int maxToken; + protected double slope; + + protected AtomicLong storedTokens = new AtomicLong(0); + protected AtomicLong lastFilledTime = new AtomicLong(0); + + public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) { + construct(count, warmUpPeriodInSec, coldFactor); + } + + public WarmUpController(double count, int warmUpPeriodInSec) { + construct(count, warmUpPeriodInSec, 3); + } + + private void construct(double count, int warmUpPeriodInSec, int coldFactor) { + + if (coldFactor <= 1) { + throw new IllegalArgumentException("Cold factor should be larger than 1"); + } + + this.count = count; + + this.coldFactor = coldFactor; + + // thresholdPermits = 0.5 * warmupPeriod / stableInterval. + // warningToken = 100; + warningToken = (int)(warmUpPeriodInSec * count) / (coldFactor - 1); + // / maxPermits = thresholdPermits + 2 * warmupPeriod / + // (stableInterval + coldInterval) + // maxToken = 200 + maxToken = warningToken + (int)(2 * warmUpPeriodInSec * count / (1.0 + coldFactor)); + + // slope + // slope = (coldIntervalMicros - stableIntervalMicros) / (maxPermits + // - thresholdPermits); + slope = (coldFactor - 1.0) / count / (maxToken - warningToken); + + } + + @Override + public boolean canPass(Node node, int acquireCount) { + return canPass(node, acquireCount, false); + } + + @Override + public boolean canPass(Node node, int acquireCount, boolean prioritized) { + long passQps = (long) node.passQps(); + + long previousQps = (long) node.previousPassQps(); + syncToken(previousQps); + + // 开始计算它的斜率 + // 如果进入了警戒线,开始调整他的qps + long restToken = storedTokens.get(); + if (restToken >= warningToken) { + long aboveToken = restToken - warningToken; + // 消耗的速度要比warning快,但是要比慢 + // current interval = restToken*slope+1/count + double warningQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count)); + if (passQps + acquireCount <= warningQps) { + return true; + } + } else { + if (passQps + acquireCount <= count) { + return true; + } + } + + return false; + } + + protected void syncToken(long passQps) { + long currentTime = TimeUtil.currentTimeMillis(); + currentTime = currentTime - currentTime % 1000; + long oldLastFillTime = lastFilledTime.get(); + if (currentTime <= oldLastFillTime) { + return; + } + + long oldValue = storedTokens.get(); + long newValue = coolDownTokens(currentTime, passQps); + + if (storedTokens.compareAndSet(oldValue, newValue)) { + long currentValue = storedTokens.addAndGet(0 - passQps); + if (currentValue < 0) { + storedTokens.set(0L); + } + lastFilledTime.set(currentTime); + } + + } + + private long coolDownTokens(long currentTime, long passQps) { + long oldValue = storedTokens.get(); + long newValue = oldValue; + + // 添加令牌的判断前提条件: + // 当令牌的消耗程度远远低于警戒线的时候 + if (oldValue < warningToken) { + newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000); + } else if (oldValue > warningToken) { + if (passQps < (int)count / coldFactor) { + newValue = (long)(oldValue + (currentTime - lastFilledTime.get()) * count / 1000); + } + } + return Math.min(newValue, maxToken); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java new file mode 100644 index 00000000..2420ab8f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/block/flow/controller/WarmUpRateLimiterController.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.controller; + +import java.util.concurrent.atomic.AtomicLong; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; + +/** + * @author jialiang.linjl + * @since 1.4.0 + */ +public class WarmUpRateLimiterController extends WarmUpController { + + private final int timeoutInMs; + private final AtomicLong latestPassedTime = new AtomicLong(-1); + + public WarmUpRateLimiterController(double count, int warmUpPeriodSec, int timeOutMs, int coldFactor) { + super(count, warmUpPeriodSec, coldFactor); + this.timeoutInMs = timeOutMs; + } + + @Override + public boolean canPass(Node node, int acquireCount) { + return canPass(node, acquireCount, false); + } + + @Override + public boolean canPass(Node node, int acquireCount, boolean prioritized) { + long previousQps = (long) node.previousPassQps(); + syncToken(previousQps); + + long currentTime = TimeUtil.currentTimeMillis(); + + long restToken = storedTokens.get(); + long costTime = 0; + long expectedTime = 0; + if (restToken >= warningToken) { + long aboveToken = restToken - warningToken; + + // current interval = restToken*slope+1/count + double warmingQps = Math.nextUp(1.0 / (aboveToken * slope + 1.0 / count)); + costTime = Math.round(1.0 * (acquireCount) / warmingQps * 1000); + } else { + costTime = Math.round(1.0 * (acquireCount) / count * 1000); + } + expectedTime = costTime + latestPassedTime.get(); + + if (expectedTime <= currentTime) { + latestPassedTime.set(currentTime); + return true; + } else { + long waitTime = costTime + latestPassedTime.get() - currentTime; + if (waitTime > timeoutInMs) { + return false; + } else { + long oldTime = latestPassedTime.addAndGet(costTime); + try { + waitTime = oldTime - TimeUtil.currentTimeMillis(); + if (waitTime > timeoutInMs) { + latestPassedTime.addAndGet(-costTime); + return false; + } + if (waitTime > 0) { + Thread.sleep(waitTime); + } + return true; + } catch (InterruptedException e) { + } + } + } + return false; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java new file mode 100755 index 00000000..e1fba5d3 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java @@ -0,0 +1,165 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.clusterbuilder; + +import java.util.HashMap; +import java.util.Map; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.ClusterNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.IntervalProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.SampleCountProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi; + +/** + *

            + * This slot maintains resource running statistics (response time, qps, thread + * count, exception), and a list of callers as well which is marked by + * {@link ContextUtil#enter(String origin)} + *

            + *

            + * One resource has only one cluster node, while one resource can have multiple + * default nodes. + *

            + * + * @author jialiang.linjl + */ +@Spi(isSingleton = false, order = Constants.ORDER_CLUSTER_BUILDER_SLOT) +public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot { + + /** + *

            + * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share + * the same {@link ProcessorSlotChain} globally, no matter in which context. So if + * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, boolean, Object...)}, + * the resource name must be same but context name may not. + *

            + *

            + * To get total statistics of the same resource in different context, same resource + * shares the same {@link ClusterNode} globally. All {@link ClusterNode}s are cached + * in this map. + *

            + *

            + * The longer the application runs, the more stable this mapping will + * become. so we don't concurrent map but a lock. as this lock only happens + * at the very beginning while concurrent map will hold the lock all the time. + *

            + */ + private static volatile Map clusterNodeMap = new HashMap<>(); + + private static final Object lock = new Object(); + + private volatile ClusterNode clusterNode = null; + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, + boolean prioritized, Object... args) + throws Throwable { + if (clusterNode == null) { + synchronized (lock) { + if (clusterNode == null) { + // Create the cluster node. + clusterNode = new ClusterNode(resourceWrapper.getName(), resourceWrapper.getResourceType()); + HashMap newMap = new HashMap<>(Math.max(clusterNodeMap.size(), 16)); + newMap.putAll(clusterNodeMap); + newMap.put(node.getId(), clusterNode); + + clusterNodeMap = newMap; + } + } + } + node.setClusterNode(clusterNode); + + /* + * if context origin is set, we should get or create a new {@link Node} of + * the specific origin. + */ + if (!"".equals(context.getOrigin())) { + Node originNode = node.getClusterNode().getOrCreateOriginNode(context.getOrigin()); + context.getCurEntry().setOriginNode(originNode); + } + + fireEntry(context, resourceWrapper, node, count, prioritized, args); + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + fireExit(context, resourceWrapper, count, args); + } + + /** + * Get {@link ClusterNode} of the resource of the specific type. + * + * @param id resource name. + * @param type invoke type. + * @return the {@link ClusterNode} + */ + public static ClusterNode getClusterNode(String id, EntryType type) { + return clusterNodeMap.get(new StringResourceWrapper(id, type)); + } + + /** + * Get {@link ClusterNode} of the resource name. + * + * @param id resource name. + * @return the {@link ClusterNode}. + */ + public static ClusterNode getClusterNode(String id) { + if (id == null) { + return null; + } + ClusterNode clusterNode = null; + + for (EntryType nodeType : EntryType.values()) { + clusterNode = clusterNodeMap.get(new StringResourceWrapper(id, nodeType)); + if (clusterNode != null) { + break; + } + } + + return clusterNode; + } + + /** + * Get {@link ClusterNode}s map, this map holds all {@link ClusterNode}s, it's key is resource name, + * value is the related {@link ClusterNode}.
            + * DO NOT MODIFY the map returned. + * + * @return all {@link ClusterNode}s + */ + public static Map getClusterNodeMap() { + return clusterNodeMap; + } + + /** + * Reset all {@link ClusterNode}s. Reset is needed when {@link IntervalProperty#INTERVAL} or + * {@link SampleCountProperty#SAMPLE_COUNT} is changed. + */ + public static void resetClusterNodes() { + for (ClusterNode node : clusterNodeMap.values()) { + node.reset(); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtil.java new file mode 100755 index 00000000..c7edf405 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtil.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.logger; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.EagleEye; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.eagleeye.StatLogger; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.LogBase; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +public class EagleEyeLogUtil { + + public static final String FILE_NAME = "sentinel-block.log"; + + private static StatLogger statLogger; + + static { + String path = LogBase.getLogBaseDir() + FILE_NAME; + + statLogger = EagleEye.statLoggerBuilder("sentinel-block-log") + .intervalSeconds(1) + .entryDelimiter('|') + .keyDelimiter(',') + .valueDelimiter(',') + .maxEntryCount(6000) + .configLogFilePath(path) + .maxFileSizeMB(300) + .maxBackupIndex(3) + .buildSingleton(); + } + + public static void log(String resource, String exceptionName, String ruleLimitApp, String origin, Long ruleId, int count) { + String ruleIdString = StringUtil.EMPTY; + if (ruleId != null) { + ruleIdString = String.valueOf(ruleId); + } + statLogger.stat(resource, exceptionName, ruleLimitApp, origin, ruleIdString).count(count); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/LogSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/LogSlot.java new file mode 100755 index 00000000..35c1445a --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/logger/LogSlot.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.logger; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.logger.EagleEyeLogUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi; + +/** + * A {@link com.alibaba.csp.sentinel.slotchain.ProcessorSlot} that is response for logging block exceptions + * to provide concrete logs for troubleshooting. + */ +@Spi(order = Constants.ORDER_LOG_SLOT) +public class LogSlot extends AbstractLinkedProcessorSlot { + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode obj, int count, boolean prioritized, Object... args) + throws Throwable { + try { + fireEntry(context, resourceWrapper, obj, count, prioritized, args); + } catch (BlockException e) { + EagleEyeLogUtil.log(resourceWrapper.getName(), e.getClass().getSimpleName(), e.getRuleLimitApp(), + context.getOrigin(), e.getRule().getId(), count); + throw e; + } catch (Throwable e) { + RecordLog.warn("Unexpected entry exception", e); + } + + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + try { + fireExit(context, resourceWrapper, count, args); + } catch (Throwable e) { + RecordLog.warn("Unexpected entry exit exception", e); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java new file mode 100755 index 00000000..f79dee1a --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java @@ -0,0 +1,181 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.nodeselector; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.ContextUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.ClusterNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.EntranceNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi; + +import java.util.HashMap; +import java.util.Map; + +/** + *

            + * This class will try to build the calling traces via + *
              + *
            1. adding a new {@link DefaultNode} if needed as the last child in the context. + * The context's last node is the current node or the parent node of the context.
            2. + *
            3. setting itself to the context current node.
            4. + *
            + *

            + * + *

            It works as follow:

            + *
            + * ContextUtil.enter("entrance1", "appA");
            + * Entry nodeA = SphU.entry("nodeA");
            + * if (nodeA != null) {
            + *     nodeA.exit();
            + * }
            + * ContextUtil.exit();
            + * 
            + * + * Above code will generate the following invocation structure in memory: + * + *
            + *
            + *              machine-root
            + *                  /
            + *                 /
            + *           EntranceNode1
            + *               /
            + *              /
            + *        DefaultNode(nodeA)- - - - - -> ClusterNode(nodeA);
            + * 
            + * + *

            + * Here the {@link EntranceNode} represents "entrance1" given by + * {@code ContextUtil.enter("entrance1", "appA")}. + *

            + *

            + * Both DefaultNode(nodeA) and ClusterNode(nodeA) holds statistics of "nodeA", which is given + * by {@code SphU.entry("nodeA")} + *

            + *

            + * The {@link ClusterNode} is uniquely identified by the ResourceId; the {@link DefaultNode} + * is identified by both the resource id and {@link Context}. In other words, one resource + * id will generate multiple {@link DefaultNode} for each distinct context, but only one + * {@link ClusterNode}. + *

            + *

            + * the following code shows one resource id in two different context: + *

            + * + *
            + *    ContextUtil.enter("entrance1", "appA");
            + *    Entry nodeA = SphU.entry("nodeA");
            + *    if (nodeA != null) {
            + *        nodeA.exit();
            + *    }
            + *    ContextUtil.exit();
            + *
            + *    ContextUtil.enter("entrance2", "appA");
            + *    nodeA = SphU.entry("nodeA");
            + *    if (nodeA != null) {
            + *        nodeA.exit();
            + *    }
            + *    ContextUtil.exit();
            + * 
            + * + * Above code will generate the following invocation structure in memory: + * + *
            + *
            + *                  machine-root
            + *                  /         \
            + *                 /           \
            + *         EntranceNode1   EntranceNode2
            + *               /               \
            + *              /                 \
            + *      DefaultNode(nodeA)   DefaultNode(nodeA)
            + *             |                    |
            + *             +- - - - - - - - - - +- - - - - - -> ClusterNode(nodeA);
            + * 
            + * + *

            + * As we can see, two {@link DefaultNode} are created for "nodeA" in two context, but only one + * {@link ClusterNode} is created. + *

            + * + *

            + * We can also check this structure by calling:
            + * {@code curl http://localhost:8719/tree?type=root} + *

            + * + * @author jialiang.linjl + * @see EntranceNode + * @see ContextUtil + */ +@Spi(isSingleton = false, order = Constants.ORDER_NODE_SELECTOR_SLOT) +public class NodeSelectorSlot extends AbstractLinkedProcessorSlot { + + /** + * {@link DefaultNode}s of the same resource in different context. + */ + private volatile Map map = new HashMap(10); + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, Object obj, int count, boolean prioritized, Object... args) + throws Throwable { + /* + * It's interesting that we use context name rather resource name as the map key. + * + * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share + * the same {@link ProcessorSlotChain} globally, no matter in which context. So if + * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, Object...)}, + * the resource name must be same but context name may not. + * + * If we use {@link com.alibaba.csp.sentinel.SphU#entry(String resource)} to + * enter same resource in different context, using context name as map key can + * distinguish the same resource. In this case, multiple {@link DefaultNode}s will be created + * of the same resource name, for every distinct context (different context name) each. + * + * Consider another question. One resource may have multiple {@link DefaultNode}, + * so what is the fastest way to get total statistics of the same resource? + * The answer is all {@link DefaultNode}s with same resource name share one + * {@link ClusterNode}. See {@link ClusterBuilderSlot} for detail. + */ + DefaultNode node = map.get(context.getName()); + if (node == null) { + synchronized (this) { + node = map.get(context.getName()); + if (node == null) { + node = new DefaultNode(resourceWrapper, null); + HashMap cacheMap = new HashMap(map.size()); + cacheMap.putAll(map); + cacheMap.put(context.getName(), node); + map = cacheMap; + // Build invocation tree + ((DefaultNode) context.getLastNode()).addChild(node); + } + + } + } + + context.setCurNode(node); + fireEntry(context, resourceWrapper, node, count, prioritized, args); + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + fireExit(context, resourceWrapper, count, args); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java new file mode 100644 index 00000000..42a9772d --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/MetricEvent.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic; + +/** + * @author Eric Zhao + */ +public enum MetricEvent { + + /** + * Normal pass. + */ + PASS, + /** + * Normal block. + */ + BLOCK, + EXCEPTION, + SUCCESS, + RT, + + /** + * Passed in future quota (pre-occupied, since 1.5.0). + */ + OCCUPIED_PASS +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java new file mode 100755 index 00000000..a4e6adea --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java @@ -0,0 +1,167 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic; + +import java.util.Collection; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.Node; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.StatisticSlotCallbackRegistry; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.ClusterNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + *

            + * A processor slot that dedicates to real time statistics. + * When entering this slot, we need to separately count the following + * information: + *

              + *
            • {@link ClusterNode}: total statistics of a cluster node of the resource ID.
            • + *
            • Origin node: statistics of a cluster node from different callers/origins.
            • + *
            • {@link DefaultNode}: statistics for specific resource name in the specific context.
            • + *
            • Finally, the sum statistics of all entrances.
            • + *
            + *

            + * + * @author jialiang.linjl + * @author Eric Zhao + */ +@Spi(order = Constants.ORDER_STATISTIC_SLOT) +public class StatisticSlot extends AbstractLinkedProcessorSlot { + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, + boolean prioritized, Object... args) throws Throwable { + try { + // Do some checking. + fireEntry(context, resourceWrapper, node, count, prioritized, args); + + // Request passed, add thread count and pass count. + node.increaseThreadNum(); + node.addPassRequest(count); + + if (context.getCurEntry().getOriginNode() != null) { + // Add count for origin node. + context.getCurEntry().getOriginNode().increaseThreadNum(); + context.getCurEntry().getOriginNode().addPassRequest(count); + } + + if (resourceWrapper.getEntryType() == EntryType.IN) { + // Add count for global inbound entry node for global statistics. + Constants.ENTRY_NODE.increaseThreadNum(); + Constants.ENTRY_NODE.addPassRequest(count); + } + + // Handle pass event with registered entry callback handlers. + for (ProcessorSlotEntryCallback handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { + handler.onPass(context, resourceWrapper, node, count, args); + } + } catch (PriorityWaitException ex) { + node.increaseThreadNum(); + if (context.getCurEntry().getOriginNode() != null) { + // Add count for origin node. + context.getCurEntry().getOriginNode().increaseThreadNum(); + } + + if (resourceWrapper.getEntryType() == EntryType.IN) { + // Add count for global inbound entry node for global statistics. + Constants.ENTRY_NODE.increaseThreadNum(); + } + // Handle pass event with registered entry callback handlers. + for (ProcessorSlotEntryCallback handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { + handler.onPass(context, resourceWrapper, node, count, args); + } + } catch (BlockException e) { + // Blocked, set block exception to current entry. + context.getCurEntry().setBlockError(e); + + // Add block count. + node.increaseBlockQps(count); + if (context.getCurEntry().getOriginNode() != null) { + context.getCurEntry().getOriginNode().increaseBlockQps(count); + } + + if (resourceWrapper.getEntryType() == EntryType.IN) { + // Add count for global inbound entry node for global statistics. + Constants.ENTRY_NODE.increaseBlockQps(count); + } + + // Handle block event with registered entry callback handlers. + for (ProcessorSlotEntryCallback handler : StatisticSlotCallbackRegistry.getEntryCallbacks()) { + handler.onBlocked(e, context, resourceWrapper, node, count, args); + } + + throw e; + } catch (Throwable e) { + // Unexpected internal error, set error to current entry. + context.getCurEntry().setError(e); + + throw e; + } + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + Node node = context.getCurNode(); + + if (context.getCurEntry().getBlockError() == null) { + // Calculate response time (use completeStatTime as the time of completion). + long completeStatTime = TimeUtil.currentTimeMillis(); + context.getCurEntry().setCompleteTimestamp(completeStatTime); + long rt = completeStatTime - context.getCurEntry().getCreateTimestamp(); + + Throwable error = context.getCurEntry().getError(); + + // Record response time and success count. + recordCompleteFor(node, count, rt, error); + recordCompleteFor(context.getCurEntry().getOriginNode(), count, rt, error); + if (resourceWrapper.getEntryType() == EntryType.IN) { + recordCompleteFor(Constants.ENTRY_NODE, count, rt, error); + } + } + + // Handle exit event with registered exit callback handlers. + Collection exitCallbacks = StatisticSlotCallbackRegistry.getExitCallbacks(); + for (ProcessorSlotExitCallback handler : exitCallbacks) { + handler.onExit(context, resourceWrapper, count, args); + } + + // fix bug https://github.com/alibaba/Sentinel/issues/2374 + fireExit(context, resourceWrapper, count, args); + } + + private void recordCompleteFor(Node node, int batchCount, long rt, Throwable error) { + if (node == null) { + return; + } + node.addRtAndSuccess(rt, batchCount); + node.decreaseThreadNum(); + + if (error != null && !(error instanceof BlockException)) { + node.increaseExceptionQps(batchCount); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlotCallbackRegistry.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlotCallbackRegistry.java new file mode 100644 index 00000000..8092bf43 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/StatisticSlotCallbackRegistry.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; + +/** + *

            + * Callback registry for {@link StatisticSlot}. Now two kind of callbacks are supported: + *

              + *
            • {@link ProcessorSlotEntryCallback}: callback for entry (passed and blocked)
            • + *
            • {@link ProcessorSlotExitCallback}: callback for exiting {@link StatisticSlot}
            • + *
            + *

            + * + * @author Eric Zhao + * @since 0.2.0 + */ +public final class StatisticSlotCallbackRegistry { + + private static final Map> entryCallbackMap + = new ConcurrentHashMap>(); + + private static final Map exitCallbackMap + = new ConcurrentHashMap(); + + public static void clearEntryCallback() { + entryCallbackMap.clear(); + } + + public static void clearExitCallback() { + exitCallbackMap.clear(); + } + + public static void addEntryCallback(String key, ProcessorSlotEntryCallback callback) { + entryCallbackMap.put(key, callback); + } + + public static void addExitCallback(String key, ProcessorSlotExitCallback callback) { + exitCallbackMap.put(key, callback); + } + + public static ProcessorSlotEntryCallback removeEntryCallback(String key) { + if (key == null) { + return null; + } + return entryCallbackMap.remove(key); + } + + public static ProcessorSlotExitCallback removeExitCallback(String key) { + if (key == null) { + return null; + } + return exitCallbackMap.remove(key); + } + + public static Collection> getEntryCallbacks() { + return entryCallbackMap.values(); + } + + public static Collection getExitCallbacks() { + return exitCallbackMap.values(); + } + + private StatisticSlotCallbackRegistry() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java new file mode 100755 index 00000000..97f4e043 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java @@ -0,0 +1,421 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReferenceArray; +import java.util.concurrent.locks.ReentrantLock; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.TimeUtil; + +/** + *

            + * Basic data structure for statistic metrics in Sentinel. + *

            + *

            + * Leap array use sliding window algorithm to count data. Each bucket cover {@code windowLengthInMs} time span, + * and the total time span is {@link #intervalInMs}, so the total bucket amount is: + * {@code sampleCount = intervalInMs / windowLengthInMs}. + *

            + * + * @param type of statistic data + * @author jialiang.linjl + * @author Eric Zhao + * @author Carpenter Lee + */ +public abstract class LeapArray { + + protected int windowLengthInMs; + protected int sampleCount; + protected int intervalInMs; + private double intervalInSecond; + + protected final AtomicReferenceArray> array; + + /** + * The conditional (predicate) update lock is used only when current bucket is deprecated. + */ + private final ReentrantLock updateLock = new ReentrantLock(); + + /** + * The total bucket count is: {@code sampleCount = intervalInMs / windowLengthInMs}. + * + * @param sampleCount bucket count of the sliding window + * @param intervalInMs the total time interval of this {@link LeapArray} in milliseconds + */ + public LeapArray(int sampleCount, int intervalInMs) { + AssertUtil.isTrue(sampleCount > 0, "bucket count is invalid: " + sampleCount); + AssertUtil.isTrue(intervalInMs > 0, "total time interval of the sliding window should be positive"); + AssertUtil.isTrue(intervalInMs % sampleCount == 0, "time span needs to be evenly divided"); + + this.windowLengthInMs = intervalInMs / sampleCount; + this.intervalInMs = intervalInMs; + this.intervalInSecond = intervalInMs / 1000.0; + this.sampleCount = sampleCount; + + this.array = new AtomicReferenceArray<>(sampleCount); + } + + /** + * Get the bucket at current timestamp. + * + * @return the bucket at current timestamp + */ + public WindowWrap currentWindow() { + return currentWindow(TimeUtil.currentTimeMillis()); + } + + /** + * Create a new statistic value for bucket. + * + * @param timeMillis current time in milliseconds + * @return the new empty bucket + */ + public abstract T newEmptyBucket(long timeMillis); + + /** + * Reset given bucket to provided start time and reset the value. + * + * @param startTime the start time of the bucket in milliseconds + * @param windowWrap current bucket + * @return new clean bucket at given start time + */ + protected abstract WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime); + + private int calculateTimeIdx(/*@Valid*/ long timeMillis) { + long timeId = timeMillis / windowLengthInMs; + // Calculate current index so we can map the timestamp to the leap array. + return (int)(timeId % array.length()); + } + + protected long calculateWindowStart(/*@Valid*/ long timeMillis) { + return timeMillis - timeMillis % windowLengthInMs; + } + + /** + * Get bucket item at provided timestamp. + * + * @param timeMillis a valid timestamp in milliseconds + * @return current bucket item at provided timestamp if the time is valid; null if time is invalid + */ + public WindowWrap currentWindow(long timeMillis) { + if (timeMillis < 0) { + return null; + } + + int idx = calculateTimeIdx(timeMillis); + // Calculate current bucket start time. + long windowStart = calculateWindowStart(timeMillis); + + /* + * Get bucket item at given time from the array. + * + * (1) Bucket is absent, then just create a new bucket and CAS update to circular array. + * (2) Bucket is up-to-date, then just return the bucket. + * (3) Bucket is deprecated, then reset current bucket. + */ + while (true) { + WindowWrap old = array.get(idx); + if (old == null) { + /* + * B0 B1 B2 NULL B4 + * ||_______|_______|_______|_______|_______||___ + * 200 400 600 800 1000 1200 timestamp + * ^ + * time=888 + * bucket is empty, so create new and update + * + * If the old bucket is absent, then we create a new bucket at {@code windowStart}, + * then try to update circular array via a CAS operation. Only one thread can + * succeed to update, while other threads yield its time slice. + */ + WindowWrap window = new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); + if (array.compareAndSet(idx, null, window)) { + // Successfully updated, return the created bucket. + return window; + } else { + // Contention failed, the thread will yield its time slice to wait for bucket available. + Thread.yield(); + } + } else if (windowStart == old.windowStart()) { + /* + * B0 B1 B2 B3 B4 + * ||_______|_______|_______|_______|_______||___ + * 200 400 600 800 1000 1200 timestamp + * ^ + * time=888 + * startTime of Bucket 3: 800, so it's up-to-date + * + * If current {@code windowStart} is equal to the start timestamp of old bucket, + * that means the time is within the bucket, so directly return the bucket. + */ + return old; + } else if (windowStart > old.windowStart()) { + /* + * (old) + * B0 B1 B2 NULL B4 + * |_______||_______|_______|_______|_______|_______||___ + * ... 1200 1400 1600 1800 2000 2200 timestamp + * ^ + * time=1676 + * startTime of Bucket 2: 400, deprecated, should be reset + * + * If the start timestamp of old bucket is behind provided time, that means + * the bucket is deprecated. We have to reset the bucket to current {@code windowStart}. + * Note that the reset and clean-up operations are hard to be atomic, + * so we need a update lock to guarantee the correctness of bucket update. + * + * The update lock is conditional (tiny scope) and will take effect only when + * bucket is deprecated, so in most cases it won't lead to performance loss. + */ + if (updateLock.tryLock()) { + try { + // Successfully get the update lock, now we reset the bucket. + return resetWindowTo(old, windowStart); + } finally { + updateLock.unlock(); + } + } else { + // Contention failed, the thread will yield its time slice to wait for bucket available. + Thread.yield(); + } + } else if (windowStart < old.windowStart()) { + // Should not go through here, as the provided time is already behind. + return new WindowWrap(windowLengthInMs, windowStart, newEmptyBucket(timeMillis)); + } + } + } + + /** + * Get the previous bucket item before provided timestamp. + * + * @param timeMillis a valid timestamp in milliseconds + * @return the previous bucket item before provided timestamp + */ + public WindowWrap getPreviousWindow(long timeMillis) { + if (timeMillis < 0) { + return null; + } + int idx = calculateTimeIdx(timeMillis - windowLengthInMs); + timeMillis = timeMillis - windowLengthInMs; + WindowWrap wrap = array.get(idx); + + if (wrap == null || isWindowDeprecated(wrap)) { + return null; + } + + if (wrap.windowStart() + windowLengthInMs < (timeMillis)) { + return null; + } + + return wrap; + } + + /** + * Get the previous bucket item for current timestamp. + * + * @return the previous bucket item for current timestamp + */ + public WindowWrap getPreviousWindow() { + return getPreviousWindow(TimeUtil.currentTimeMillis()); + } + + /** + * Get statistic value from bucket for provided timestamp. + * + * @param timeMillis a valid timestamp in milliseconds + * @return the statistic value if bucket for provided timestamp is up-to-date; otherwise null + */ + public T getWindowValue(long timeMillis) { + if (timeMillis < 0) { + return null; + } + int idx = calculateTimeIdx(timeMillis); + + WindowWrap bucket = array.get(idx); + + if (bucket == null || !bucket.isTimeInWindow(timeMillis)) { + return null; + } + + return bucket.value(); + } + + /** + * Check if a bucket is deprecated, which means that the bucket + * has been behind for at least an entire window time span. + * + * @param windowWrap a non-null bucket + * @return true if the bucket is deprecated; otherwise false + */ + public boolean isWindowDeprecated(/*@NonNull*/ WindowWrap windowWrap) { + return isWindowDeprecated(TimeUtil.currentTimeMillis(), windowWrap); + } + + public boolean isWindowDeprecated(long time, WindowWrap windowWrap) { + return time - windowWrap.windowStart() > intervalInMs; + } + + /** + * Get valid bucket list for entire sliding window. + * The list will only contain "valid" buckets. + * + * @return valid bucket list for entire sliding window. + */ + public List> list() { + return list(TimeUtil.currentTimeMillis()); + } + + public List> list(long validTime) { + int size = array.length(); + List> result = new ArrayList>(size); + + for (int i = 0; i < size; i++) { + WindowWrap windowWrap = array.get(i); + if (windowWrap == null || isWindowDeprecated(validTime, windowWrap)) { + continue; + } + result.add(windowWrap); + } + + return result; + } + + /** + * Get all buckets for entire sliding window including deprecated buckets. + * + * @return all buckets for entire sliding window + */ + public List> listAll() { + int size = array.length(); + List> result = new ArrayList>(size); + + for (int i = 0; i < size; i++) { + WindowWrap windowWrap = array.get(i); + if (windowWrap == null) { + continue; + } + result.add(windowWrap); + } + + return result; + } + + /** + * Get aggregated value list for entire sliding window. + * The list will only contain value from "valid" buckets. + * + * @return aggregated value list for entire sliding window + */ + public List values() { + return values(TimeUtil.currentTimeMillis()); + } + + public List values(long timeMillis) { + if (timeMillis < 0) { + return new ArrayList(); + } + int size = array.length(); + List result = new ArrayList(size); + + for (int i = 0; i < size; i++) { + WindowWrap windowWrap = array.get(i); + if (windowWrap == null || isWindowDeprecated(timeMillis, windowWrap)) { + continue; + } + result.add(windowWrap.value()); + } + return result; + } + + /** + * Get the valid "head" bucket of the sliding window for provided timestamp. + * Package-private for test. + * + * @param timeMillis a valid timestamp in milliseconds + * @return the "head" bucket if it exists and is valid; otherwise null + */ + WindowWrap getValidHead(long timeMillis) { + // Calculate index for expected head time. + int idx = calculateTimeIdx(timeMillis + windowLengthInMs); + + WindowWrap wrap = array.get(idx); + if (wrap == null || isWindowDeprecated(wrap)) { + return null; + } + + return wrap; + } + + /** + * Get the valid "head" bucket of the sliding window at current timestamp. + * + * @return the "head" bucket if it exists and is valid; otherwise null + */ + public WindowWrap getValidHead() { + return getValidHead(TimeUtil.currentTimeMillis()); + } + + /** + * Get sample count (total amount of buckets). + * + * @return sample count + */ + public int getSampleCount() { + return sampleCount; + } + + /** + * Get total interval length of the sliding window in milliseconds. + * + * @return interval in second + */ + public int getIntervalInMs() { + return intervalInMs; + } + + /** + * Get total interval length of the sliding window. + * + * @return interval in second + */ + public double getIntervalInSecond() { + return intervalInSecond; + } + + public void debug(long time) { + StringBuilder sb = new StringBuilder(); + List> lists = list(time); + sb.append("Thread_").append(Thread.currentThread().getId()).append("_"); + for (WindowWrap window : lists) { + sb.append(window.windowStart()).append(":").append(window.value().toString()); + } + System.out.println(sb.toString()); + } + + public long currentWaiting() { + // TODO: default method. Should remove this later. + return 0; + } + + public void addWaiting(long time, int acquireCount) { + // Do nothing by default. + throw new UnsupportedOperationException(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/UnaryLeapArray.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/UnaryLeapArray.java new file mode 100644 index 00000000..6f097d42 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/UnaryLeapArray.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base; + +import java.util.concurrent.atomic.LongAdder; + +/** + * @author Eric Zhao + */ +public class UnaryLeapArray extends LeapArray { + + public UnaryLeapArray(int sampleCount, int intervalInMs) { + super(sampleCount, intervalInMs); + } + + @Override + public LongAdder newEmptyBucket(long time) { + return new LongAdder(); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime) { + windowWrap.resetTo(startTime); + windowWrap.value().reset(); + return windowWrap; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java new file mode 100755 index 00000000..efe02b60 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/base/WindowWrap.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base; + +/** + * Wrapper entity class for a period of time window. + * + * @param data type + * @author jialiang.linjl + * @author Eric Zhao + */ +public class WindowWrap { + + /** + * Time length of a single window bucket in milliseconds. + */ + private final long windowLengthInMs; + + /** + * Start timestamp of the window in milliseconds. + */ + private long windowStart; + + /** + * Statistic data. + */ + private T value; + + /** + * @param windowLengthInMs a single window bucket's time length in milliseconds. + * @param windowStart the start timestamp of the window + * @param value statistic data + */ + public WindowWrap(long windowLengthInMs, long windowStart, T value) { + this.windowLengthInMs = windowLengthInMs; + this.windowStart = windowStart; + this.value = value; + } + + public long windowLength() { + return windowLengthInMs; + } + + public long windowStart() { + return windowStart; + } + + public T value() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + /** + * Reset start timestamp of current bucket to provided time. + * + * @param startTime valid start timestamp + * @return bucket after reset + */ + public WindowWrap resetTo(long startTime) { + this.windowStart = startTime; + return this; + } + + /** + * Check whether given timestamp is in current bucket. + * + * @param timeMillis valid timestamp in ms + * @return true if the given time is in current bucket, otherwise false + * @since 1.5.0 + */ + public boolean isTimeInWindow(long timeMillis) { + return windowStart <= timeMillis && timeMillis < windowStart + windowLengthInMs; + } + + @Override + public String toString() { + return "WindowWrap{" + + "windowLengthInMs=" + windowLengthInMs + + ", windowStart=" + windowStart + + ", value=" + value + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java new file mode 100755 index 00000000..5b7bcd78 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/data/MetricBucket.java @@ -0,0 +1,139 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.MetricEvent; +import java.util.concurrent.atomic.LongAdder; + +/** + * Represents metrics data in a period of time span. + * + * @author jialiang.linjl + * @author Eric Zhao + */ +public class MetricBucket { + + private final LongAdder[] counters; + + private volatile long minRt; + + public MetricBucket() { + MetricEvent[] events = MetricEvent.values(); + this.counters = new LongAdder[events.length]; + for (MetricEvent event : events) { + counters[event.ordinal()] = new LongAdder(); + } + initMinRt(); + } + + public MetricBucket reset(MetricBucket bucket) { + for (MetricEvent event : MetricEvent.values()) { + counters[event.ordinal()].reset(); + counters[event.ordinal()].add(bucket.get(event)); + } + initMinRt(); + return this; + } + + private void initMinRt() { + this.minRt = SentinelConfig.statisticMaxRt(); + } + + /** + * Reset the adders. + * + * @return new metric bucket in initial state + */ + public MetricBucket reset() { + for (MetricEvent event : MetricEvent.values()) { + counters[event.ordinal()].reset(); + } + initMinRt(); + return this; + } + + public long get(MetricEvent event) { + return counters[event.ordinal()].sum(); + } + + public MetricBucket add(MetricEvent event, long n) { + counters[event.ordinal()].add(n); + return this; + } + + public long pass() { + return get(MetricEvent.PASS); + } + + public long occupiedPass() { + return get(MetricEvent.OCCUPIED_PASS); + } + + public long block() { + return get(MetricEvent.BLOCK); + } + + public long exception() { + return get(MetricEvent.EXCEPTION); + } + + public long rt() { + return get(MetricEvent.RT); + } + + public long minRt() { + return minRt; + } + + public long success() { + return get(MetricEvent.SUCCESS); + } + + public void addPass(int n) { + add(MetricEvent.PASS, n); + } + + public void addOccupiedPass(int n) { + add(MetricEvent.OCCUPIED_PASS, n); + } + + public void addException(int n) { + add(MetricEvent.EXCEPTION, n); + } + + public void addBlock(int n) { + add(MetricEvent.BLOCK, n); + } + + public void addSuccess(int n) { + add(MetricEvent.SUCCESS, n); + } + + public void addRT(long rt) { + add(MetricEvent.RT, rt); + + // Not thread-safe, but it's okay. + if (rt < minRt) { + minRt = rt; + } + } + + @Override + public String toString() { + return "p: " + pass() + ", b: " + block() + ", w: " + occupiedPass(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java new file mode 100755 index 00000000..40131061 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/ArrayMetric.java @@ -0,0 +1,338 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric; + +import java.util.ArrayList; +import java.util.List; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric.MetricNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.MetricEvent; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.BucketLeapArray; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.occupy.OccupiableBucketLeapArray; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Predicate; + +/** + * The basic metric class in Sentinel using a {@link BucketLeapArray} internal. + * + * @author jialiang.linjl + * @author Eric Zhao + */ +public class ArrayMetric implements Metric { + + private final LeapArray data; + + public ArrayMetric(int sampleCount, int intervalInMs) { + this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs); + } + + public ArrayMetric(int sampleCount, int intervalInMs, boolean enableOccupy) { + if (enableOccupy) { + this.data = new OccupiableBucketLeapArray(sampleCount, intervalInMs); + } else { + this.data = new BucketLeapArray(sampleCount, intervalInMs); + } + } + + /** + * For unit test. + */ + public ArrayMetric(LeapArray array) { + this.data = array; + } + + @Override + public long success() { + data.currentWindow(); + long success = 0; + + List list = data.values(); + for (MetricBucket window : list) { + success += window.success(); + } + return success; + } + + @Override + public long maxSuccess() { + data.currentWindow(); + long success = 0; + + List list = data.values(); + for (MetricBucket window : list) { + if (window.success() > success) { + success = window.success(); + } + } + return Math.max(success, 1); + } + + @Override + public long exception() { + data.currentWindow(); + long exception = 0; + List list = data.values(); + for (MetricBucket window : list) { + exception += window.exception(); + } + return exception; + } + + @Override + public long block() { + data.currentWindow(); + long block = 0; + List list = data.values(); + for (MetricBucket window : list) { + block += window.block(); + } + return block; + } + + @Override + public long pass() { + data.currentWindow(); + long pass = 0; + List list = data.values(); + + for (MetricBucket window : list) { + pass += window.pass(); + } + return pass; + } + + @Override + public long occupiedPass() { + data.currentWindow(); + long pass = 0; + List list = data.values(); + for (MetricBucket window : list) { + pass += window.occupiedPass(); + } + return pass; + } + + @Override + public long rt() { + data.currentWindow(); + long rt = 0; + List list = data.values(); + for (MetricBucket window : list) { + rt += window.rt(); + } + return rt; + } + + @Override + public long minRt() { + data.currentWindow(); + long rt = SentinelConfig.statisticMaxRt(); + List list = data.values(); + for (MetricBucket window : list) { + if (window.minRt() < rt) { + rt = window.minRt(); + } + } + + return Math.max(1, rt); + } + + @Override + public List details() { + List details = new ArrayList<>(); + data.currentWindow(); + List> list = data.list(); + for (WindowWrap window : list) { + if (window == null) { + continue; + } + + details.add(fromBucket(window)); + } + + return details; + } + + @Override + public List detailsOnCondition(Predicate timePredicate) { + List details = new ArrayList<>(); + data.currentWindow(); + List> list = data.list(); + for (WindowWrap window : list) { + if (window == null) { + continue; + } + if (timePredicate != null && !timePredicate.test(window.windowStart())) { + continue; + } + + details.add(fromBucket(window)); + } + + return details; + } + + private MetricNode fromBucket(WindowWrap wrap) { + MetricNode node = new MetricNode(); + node.setBlockQps(wrap.value().block()); + node.setExceptionQps(wrap.value().exception()); + node.setPassQps(wrap.value().pass()); + long successQps = wrap.value().success(); + node.setSuccessQps(successQps); + if (successQps != 0) { + node.setRt(wrap.value().rt() / successQps); + } else { + node.setRt(wrap.value().rt()); + } + node.setTimestamp(wrap.windowStart()); + node.setOccupiedPassQps(wrap.value().occupiedPass()); + return node; + } + + @Override + public MetricBucket[] windows() { + data.currentWindow(); + return data.values().toArray(new MetricBucket[0]); + } + + @Override + public void addException(int count) { + WindowWrap wrap = data.currentWindow(); + wrap.value().addException(count); + } + + @Override + public void addBlock(int count) { + WindowWrap wrap = data.currentWindow(); + wrap.value().addBlock(count); + } + + @Override + public void addWaiting(long time, int acquireCount) { + data.addWaiting(time, acquireCount); + } + + @Override + public void addOccupiedPass(int acquireCount) { + WindowWrap wrap = data.currentWindow(); + wrap.value().addOccupiedPass(acquireCount); + } + + @Override + public void addSuccess(int count) { + WindowWrap wrap = data.currentWindow(); + wrap.value().addSuccess(count); + } + + @Override + public void addPass(int count) { + WindowWrap wrap = data.currentWindow(); + wrap.value().addPass(count); + } + + @Override + public void addRT(long rt) { + WindowWrap wrap = data.currentWindow(); + wrap.value().addRT(rt); + } + + @Override + public void debug() { + data.debug(System.currentTimeMillis()); + } + + @Override + public long previousWindowBlock() { + data.currentWindow(); + WindowWrap wrap = data.getPreviousWindow(); + if (wrap == null) { + return 0; + } + return wrap.value().block(); + } + + @Override + public long previousWindowPass() { + data.currentWindow(); + WindowWrap wrap = data.getPreviousWindow(); + if (wrap == null) { + return 0; + } + return wrap.value().pass(); + } + + public void add(MetricEvent event, long count) { + data.currentWindow().value().add(event, count); + } + + public long getCurrentCount(MetricEvent event) { + return data.currentWindow().value().get(event); + } + + /** + * Get total sum for provided event in {@code intervalInSec}. + * + * @param event event to calculate + * @return total sum for event + */ + public long getSum(MetricEvent event) { + data.currentWindow(); + long sum = 0; + + List buckets = data.values(); + for (MetricBucket bucket : buckets) { + sum += bucket.get(event); + } + return sum; + } + + /** + * Get average count for provided event per second. + * + * @param event event to calculate + * @return average count per second for event + */ + public double getAvg(MetricEvent event) { + return getSum(event) / data.getIntervalInSecond(); + } + + @Override + public long getWindowPass(long timeMillis) { + MetricBucket bucket = data.getWindowValue(timeMillis); + if (bucket == null) { + return 0L; + } + return bucket.pass(); + } + + @Override + public long waiting() { + return data.currentWaiting(); + } + + @Override + public double getWindowIntervalInSec() { + return data.getIntervalInSecond(); + } + + @Override + public int getSampleCount() { + return data.getSampleCount(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/BucketLeapArray.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/BucketLeapArray.java new file mode 100755 index 00000000..07cc55fc --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/BucketLeapArray.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; + +/** + * The fundamental data structure for metric statistics in a time span. + * + * @author jialiang.linjl + * @author Eric Zhao + * @see LeapArray + */ +public class BucketLeapArray extends LeapArray { + + public BucketLeapArray(int sampleCount, int intervalInMs) { + super(sampleCount, intervalInMs); + } + + @Override + public MetricBucket newEmptyBucket(long time) { + return new MetricBucket(); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { + // Update the start time and reset value. + w.resetTo(startTime); + w.value().reset(); + return w; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/DebugSupport.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/DebugSupport.java new file mode 100644 index 00000000..1ef31252 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/DebugSupport.java @@ -0,0 +1,28 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric; + +/** + * @author Eric Zhao + * @since 1.5.0 + */ +public interface DebugSupport { + + /** + * For debug; + */ + void debug(); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java new file mode 100755 index 00000000..3d771394 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/Metric.java @@ -0,0 +1,203 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric; + +import java.util.List; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.metric.MetricNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.DebugSupport; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Predicate; + +/** + * Represents a basic structure recording invocation metrics of protected resources. + * + * @author jialiang.linjl + * @author Eric Zhao + */ +public interface Metric extends DebugSupport { + + /** + * Get total success count. + * + * @return success count + */ + long success(); + + /** + * Get max success count. + * + * @return max success count + */ + long maxSuccess(); + + /** + * Get total exception count. + * + * @return exception count + */ + long exception(); + + /** + * Get total block count. + * + * @return block count + */ + long block(); + + /** + * Get total pass count. not include {@link #occupiedPass()} + * + * @return pass count + */ + long pass(); + + /** + * Get total response time. + * + * @return total RT + */ + long rt(); + + /** + * Get the minimal RT. + * + * @return minimal RT + */ + long minRt(); + + /** + * Get aggregated metric nodes of all resources. + * + * @return metric node list of all resources + */ + List details(); + + /** + * Generate aggregated metric items that satisfies the time predicate. + * + * @param timePredicate time predicate + * @return aggregated metric items + * @since 1.7.0 + */ + List detailsOnCondition(Predicate timePredicate); + + /** + * Get the raw window array. + * + * @return window metric array + */ + MetricBucket[] windows(); + + /** + * Add current exception count. + * + * @param n count to add + */ + void addException(int n); + + /** + * Add current block count. + * + * @param n count to add + */ + void addBlock(int n); + + /** + * Add current completed count. + * + * @param n count to add + */ + void addSuccess(int n); + + /** + * Add current pass count. + * + * @param n count to add + */ + void addPass(int n); + + /** + * Add given RT to current total RT. + * + * @param rt RT + */ + void addRT(long rt); + + /** + * Get the sliding window length in seconds. + * + * @return the sliding window length + */ + double getWindowIntervalInSec(); + + /** + * Get sample count of the sliding window. + * + * @return sample count of the sliding window. + */ + int getSampleCount(); + + /** + * Note: this operation will not perform refreshing, so will not generate new buckets. + * + * @param timeMillis valid time in ms + * @return pass count of the bucket exactly associated to provided timestamp, or 0 if the timestamp is invalid + * @since 1.5.0 + */ + long getWindowPass(long timeMillis); + + // Occupy-based (@since 1.5.0) + + /** + * Add occupied pass, which represents pass requests that borrow the latter windows' token. + * + * @param acquireCount tokens count. + * @since 1.5.0 + */ + void addOccupiedPass(int acquireCount); + + /** + * Add request that occupied. + * + * @param futureTime future timestamp that the acquireCount should be added on. + * @param acquireCount tokens count. + * @since 1.5.0 + */ + void addWaiting(long futureTime, int acquireCount); + + /** + * Get waiting pass account + * + * @return waiting pass count + * @since 1.5.0 + */ + long waiting(); + + /** + * Get occupied pass count. + * + * @return occupied pass count + * @since 1.5.0 + */ + long occupiedPass(); + + // Tool methods. + + long previousWindowBlock(); + + long previousWindowPass(); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/FutureBucketLeapArray.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/FutureBucketLeapArray.java new file mode 100644 index 00000000..a4d7965f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/FutureBucketLeapArray.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.occupy; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; + +/** + * A kind of {@code BucketLeapArray} that only reserves for future buckets. + * + * @author jialiang.linjl + * @since 1.5.0 + */ +public class FutureBucketLeapArray extends LeapArray { + + public FutureBucketLeapArray(int sampleCount, int intervalInMs) { + // This class is the original "BorrowBucketArray". + super(sampleCount, intervalInMs); + } + + @Override + public MetricBucket newEmptyBucket(long time) { + return new MetricBucket(); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { + // Update the start time and reset value. + w.resetTo(startTime); + w.value().reset(); + return w; + } + + @Override + public boolean isWindowDeprecated(long time, WindowWrap windowWrap) { + // Tricky: will only calculate for future. + return time >= windowWrap.windowStart(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/OccupiableBucketLeapArray.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/OccupiableBucketLeapArray.java new file mode 100644 index 00000000..62c3f78f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/statistic/metric/occupy/OccupiableBucketLeapArray.java @@ -0,0 +1,101 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.metric.occupy; + +import java.util.List; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.MetricEvent; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.data.MetricBucket; + +/** + * @author jialiang.linjl + * @since 1.5.0 + */ +public class OccupiableBucketLeapArray extends LeapArray { + + private final FutureBucketLeapArray borrowArray; + + public OccupiableBucketLeapArray(int sampleCount, int intervalInMs) { + // This class is the original "CombinedBucketArray". + super(sampleCount, intervalInMs); + this.borrowArray = new FutureBucketLeapArray(sampleCount, intervalInMs); + } + + @Override + public MetricBucket newEmptyBucket(long time) { + MetricBucket newBucket = new MetricBucket(); + + MetricBucket borrowBucket = borrowArray.getWindowValue(time); + if (borrowBucket != null) { + newBucket.reset(borrowBucket); + } + + return newBucket; + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap w, long time) { + // Update the start time and reset value. + w.resetTo(time); + MetricBucket borrowBucket = borrowArray.getWindowValue(time); + if (borrowBucket != null) { + w.value().reset(); + w.value().addPass((int)borrowBucket.pass()); + } else { + w.value().reset(); + } + + return w; + } + + @Override + public long currentWaiting() { + borrowArray.currentWindow(); + long currentWaiting = 0; + List list = borrowArray.values(); + + for (MetricBucket window : list) { + currentWaiting += window.pass(); + } + return currentWaiting; + } + + @Override + public void addWaiting(long time, int acquireCount) { + WindowWrap window = borrowArray.currentWindow(time); + window.value().add(MetricEvent.PASS, acquireCount); + } + + @Override + public void debug(long time) { + StringBuilder sb = new StringBuilder(); + List> lists = listAll(); + sb.append("a_Thread_").append(Thread.currentThread().getId()).append(" time=").append(time).append("; "); + for (WindowWrap window : lists) { + sb.append(window.windowStart()).append(":").append(window.value().toString()).append(";"); + } + sb.append("\n"); + + lists = borrowArray.listAll(); + sb.append("b_Thread_").append(Thread.currentThread().getId()).append(" time=").append(time).append("; "); + for (WindowWrap window : lists) { + sb.append(window.windowStart()).append(":").append(window.value().toString()).append(";"); + } + System.out.println(sb.toString()); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemBlockException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemBlockException.java new file mode 100755 index 00000000..b0ef0c0f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemBlockException.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * @author jialiang.linjl + */ +public class SystemBlockException extends BlockException { + + private final String resourceName; + + public SystemBlockException(String resourceName, String message, Throwable cause) { + super(message, cause); + this.resourceName = resourceName; + } + + public SystemBlockException(String resourceName, String limitType) { + super(limitType); + this.resourceName = resourceName; + } + + public String getResourceName() { + return resourceName; + } + + @Override + public Throwable fillInStackTrace() { + return this; + } + + /** + * Return the limit type of system rule. + * + * @return the limit type + * @since 1.4.2 + */ + public String getLimitType() { + return getRuleLimitApp(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemRule.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemRule.java new file mode 100755 index 00000000..8e4a715b --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemRule.java @@ -0,0 +1,194 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.AbstractRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemRuleManager; + +/** + *

            + * Sentinel System Rule makes the inbound traffic and capacity meet. It takes + * average RT, QPS and thread count of requests into account. And it also + * provides a measurement of system's load, but only available on Linux. + *

            + *

            + * We recommend to coordinate {@link #highestSystemLoad}, {@link #qps}, {@link #avgRt} + * and {@link #maxThread} to make sure your system run in safety level. + *

            + *

            + * To set the threshold appropriately, performance test may be needed. + *

            + * + * @author jialiang.linjl + * @author Carpenter Lee + * @see SystemRuleManager + */ +public class SystemRule extends AbstractRule { + + /** + * negative value means no threshold checking. + */ + private double highestSystemLoad = -1; + /** + * cpu usage, between [0, 1] + */ + private double highestCpuUsage = -1; + private double qps = -1; + private long avgRt = -1; + private long maxThread = -1; + + public double getQps() { + return qps; + } + + /** + * Set max total QPS. In a high concurrency condition, real passed QPS may be greater than max QPS set. + * The real passed QPS will nearly satisfy the following formula:
            + * + *
            real passed QPS = QPS set + concurrent thread number
            + * + * @param qps max total QOS, values <= 0 are special for clearing the threshold. + */ + public void setQps(double qps) { + this.qps = qps; + } + + public long getMaxThread() { + return maxThread; + } + + /** + * Set max PARALLEL working thread. When concurrent thread number is greater than {@code maxThread} only + * maxThread will run in parallel. + * + * @param maxThread max parallel thread number, values <= 0 are special for clearing the threshold. + */ + public void setMaxThread(long maxThread) { + this.maxThread = maxThread; + } + + public long getAvgRt() { + return avgRt; + } + + /** + * Set max average RT(response time) of all passed requests. + * + * @param avgRt max average response time, values <= 0 are special for clearing the threshold. + */ + public void setAvgRt(long avgRt) { + this.avgRt = avgRt; + } + + public double getHighestSystemLoad() { + return highestSystemLoad; + } + + /** + *

            + * Set highest load. The load is not same as Linux system load, which is not sensitive enough. + * To calculate the load, both Linux system load, current global response time and global QPS will be considered, + * which means that we need to coordinate with {@link #setAvgRt(long)} and {@link #setQps(double)} + *

            + *

            + * Note that this parameter is only available on Unix like system. + *

            + * + * @param highestSystemLoad highest system load, values <= 0 are special for clearing the threshold. + * @see SystemRuleManager + */ + public void setHighestSystemLoad(double highestSystemLoad) { + this.highestSystemLoad = highestSystemLoad; + } + + /** + * Get highest cpu usage. Cpu usage is between [0, 1] + * + * @return highest cpu usage + */ + public double getHighestCpuUsage() { + return highestCpuUsage; + } + + /** + * set highest cpu usage. Cpu usage is between [0, 1] + * + * @param highestCpuUsage the value to set. + */ + public void setHighestCpuUsage(double highestCpuUsage) { + this.highestCpuUsage = highestCpuUsage; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SystemRule)) { + return false; + } + if (!super.equals(o)) { + return false; + } + + SystemRule that = (SystemRule)o; + + if (Double.compare(that.highestSystemLoad, highestSystemLoad) != 0) { + return false; + } + if (Double.compare(that.highestCpuUsage, highestCpuUsage) != 0) { + return false; + } + + if (Double.compare(that.qps, qps) != 0) { + return false; + } + + if (avgRt != that.avgRt) { + return false; + } + return maxThread == that.maxThread; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + long temp; + temp = Double.doubleToLongBits(highestSystemLoad); + result = 31 * result + (int)(temp ^ (temp >>> 32)); + + temp = Double.doubleToLongBits(highestCpuUsage); + result = 31 * result + (int)(temp ^ (temp >>> 32)); + + temp = Double.doubleToLongBits(qps); + result = 31 * result + (int)(temp ^ (temp >>> 32)); + + result = 31 * result + (int)(avgRt ^ (avgRt >>> 32)); + result = 31 * result + (int)(maxThread ^ (maxThread >>> 32)); + return result; + } + + @Override + public String toString() { + return "SystemRule{" + + "highestSystemLoad=" + highestSystemLoad + + ", highestCpuUsage=" + highestCpuUsage + + ", qps=" + qps + + ", avgRt=" + avgRt + + ", maxThread=" + maxThread + + "}"; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemRuleManager.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemRuleManager.java new file mode 100755 index 00000000..177550ca --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemRuleManager.java @@ -0,0 +1,351 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.EntryType; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.DynamicSentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SentinelProperty; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.property.SimplePropertyListener; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemBlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemStatusListener; + +/** + *

            + * Sentinel System Rule makes the inbound traffic and capacity meet. It takes + * average rt, qps, thread count of incoming requests into account. And it also + * provides a measurement of system's load, but only available on Linux. + *

            + *

            + * rt, qps, thread count is easy to understand. If the incoming requests' + * rt,qps, thread count exceeds its threshold, the requests will be + * rejected.however, we use a different method to calculate the load. + *

            + *

            + * Consider the system as a pipeline,transitions between constraints result in + * three different regions (traffic-limited, capacity-limited and danger area) + * with qualitatively different behavior. When there isn’t enough request in + * flight to fill the pipe, RTprop determines behavior; otherwise, the system + * capacity dominates. Constraint lines intersect at inflight = Capacity × + * RTprop. Since the pipe is full past this point, the inflight –capacity excess + * creates a queue, which results in the linear dependence of RTT on inflight + * traffic and an increase in system load.In danger area, system will stop + * responding.
            + * Referring to BBR algorithm to learn more. + *

            + *

            + * Note that {@link SystemRule} only effect on inbound requests, outbound traffic + * will not limit by {@link SystemRule} + *

            + * + * @author jialiang.linjl + * @author leyou + */ +public final class SystemRuleManager { + + private static volatile double highestSystemLoad = Double.MAX_VALUE; + /** + * cpu usage, between [0, 1] + */ + private static volatile double highestCpuUsage = Double.MAX_VALUE; + private static volatile double qps = Double.MAX_VALUE; + private static volatile long maxRt = Long.MAX_VALUE; + private static volatile long maxThread = Long.MAX_VALUE; + /** + * mark whether the threshold are set by user. + */ + private static volatile boolean highestSystemLoadIsSet = false; + private static volatile boolean highestCpuUsageIsSet = false; + private static volatile boolean qpsIsSet = false; + private static volatile boolean maxRtIsSet = false; + private static volatile boolean maxThreadIsSet = false; + + private static AtomicBoolean checkSystemStatus = new AtomicBoolean(false); + + private static SystemStatusListener statusListener = null; + private final static SystemPropertyListener listener = new SystemPropertyListener(); + private static SentinelProperty> currentProperty = new DynamicSentinelProperty>(); + + @SuppressWarnings("PMD.ThreadPoolCreationRule") + private final static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("sentinel-system-status-record-task", true)); + + static { + checkSystemStatus.set(false); + statusListener = new SystemStatusListener(); + scheduler.scheduleAtFixedRate(statusListener, 0, 1, TimeUnit.SECONDS); + currentProperty.addListener(listener); + } + + /** + * Listen to the {@link SentinelProperty} for {@link SystemRule}s. The property is the source + * of {@link SystemRule}s. System rules can also be set by {@link #loadRules(List)} directly. + * + * @param property the property to listen. + */ + public static void register2Property(SentinelProperty> property) { + synchronized (listener) { + RecordLog.info("[SystemRuleManager] Registering new property to system rule manager"); + currentProperty.removeListener(listener); + property.addListener(listener); + currentProperty = property; + } + } + + /** + * Load {@link SystemRule}s, former rules will be replaced. + * + * @param rules new rules to load. + */ + public static void loadRules(List rules) { + currentProperty.updateValue(rules); + } + + /** + * Get a copy of the rules. + * + * @return a new copy of the rules. + */ + public static List getRules() { + + List result = new ArrayList(); + if (!checkSystemStatus.get()) { + return result; + } + + if (highestSystemLoadIsSet) { + SystemRule loadRule = new SystemRule(); + loadRule.setHighestSystemLoad(highestSystemLoad); + result.add(loadRule); + } + + if (highestCpuUsageIsSet) { + SystemRule rule = new SystemRule(); + rule.setHighestCpuUsage(highestCpuUsage); + result.add(rule); + } + + if (maxRtIsSet) { + SystemRule rtRule = new SystemRule(); + rtRule.setAvgRt(maxRt); + result.add(rtRule); + } + + if (maxThreadIsSet) { + SystemRule threadRule = new SystemRule(); + threadRule.setMaxThread(maxThread); + result.add(threadRule); + } + + if (qpsIsSet) { + SystemRule qpsRule = new SystemRule(); + qpsRule.setQps(qps); + result.add(qpsRule); + } + + return result; + } + + public static double getInboundQpsThreshold() { + return qps; + } + + public static long getRtThreshold() { + return maxRt; + } + + public static long getMaxThreadThreshold() { + return maxThread; + } + + static class SystemPropertyListener extends SimplePropertyListener> { + + @Override + public synchronized void configUpdate(List rules) { + restoreSetting(); + // systemRules = rules; + if (rules != null && rules.size() >= 1) { + for (SystemRule rule : rules) { + loadSystemConf(rule); + } + } else { + checkSystemStatus.set(false); + } + + RecordLog.info(String.format("[SystemRuleManager] Current system check status: %s, " + + "highestSystemLoad: %e, " + + "highestCpuUsage: %e, " + + "maxRt: %d, " + + "maxThread: %d, " + + "maxQps: %e", + checkSystemStatus.get(), + highestSystemLoad, + highestCpuUsage, + maxRt, + maxThread, + qps)); + } + + protected void restoreSetting() { + checkSystemStatus.set(false); + + // should restore changes + highestSystemLoad = Double.MAX_VALUE; + highestCpuUsage = Double.MAX_VALUE; + maxRt = Long.MAX_VALUE; + maxThread = Long.MAX_VALUE; + qps = Double.MAX_VALUE; + + highestSystemLoadIsSet = false; + highestCpuUsageIsSet = false; + maxRtIsSet = false; + maxThreadIsSet = false; + qpsIsSet = false; + } + + } + + public static Boolean getCheckSystemStatus() { + return checkSystemStatus.get(); + } + + public static double getSystemLoadThreshold() { + return highestSystemLoad; + } + + public static double getCpuUsageThreshold() { + return highestCpuUsage; + } + + public static void loadSystemConf(SystemRule rule) { + boolean checkStatus = false; + // Check if it's valid. + + if (rule.getHighestSystemLoad() >= 0) { + highestSystemLoad = Math.min(highestSystemLoad, rule.getHighestSystemLoad()); + highestSystemLoadIsSet = true; + checkStatus = true; + } + + if (rule.getHighestCpuUsage() >= 0) { + if (rule.getHighestCpuUsage() > 1) { + RecordLog.warn(String.format("[SystemRuleManager] Ignoring invalid SystemRule: " + + "highestCpuUsage %.3f > 1", rule.getHighestCpuUsage())); + } else { + highestCpuUsage = Math.min(highestCpuUsage, rule.getHighestCpuUsage()); + highestCpuUsageIsSet = true; + checkStatus = true; + } + } + + if (rule.getAvgRt() >= 0) { + maxRt = Math.min(maxRt, rule.getAvgRt()); + maxRtIsSet = true; + checkStatus = true; + } + if (rule.getMaxThread() >= 0) { + maxThread = Math.min(maxThread, rule.getMaxThread()); + maxThreadIsSet = true; + checkStatus = true; + } + + if (rule.getQps() >= 0) { + qps = Math.min(qps, rule.getQps()); + qpsIsSet = true; + checkStatus = true; + } + + checkSystemStatus.set(checkStatus); + + } + + /** + * Apply {@link SystemRule} to the resource. Only inbound traffic will be checked. + * + * @param resourceWrapper the resource. + * @throws BlockException when any system rule's threshold is exceeded. + */ + public static void checkSystem(ResourceWrapper resourceWrapper, int count) throws BlockException { + if (resourceWrapper == null) { + return; + } + // Ensure the checking switch is on. + if (!checkSystemStatus.get()) { + return; + } + + // for inbound traffic only + if (resourceWrapper.getEntryType() != EntryType.IN) { + return; + } + + // total qps + double currentQps = Constants.ENTRY_NODE.passQps(); + if (currentQps + count > qps) { + throw new SystemBlockException(resourceWrapper.getName(), "qps"); + } + + // total thread + int currentThread = Constants.ENTRY_NODE.curThreadNum(); + if (currentThread > maxThread) { + throw new SystemBlockException(resourceWrapper.getName(), "thread"); + } + + double rt = Constants.ENTRY_NODE.avgRt(); + if (rt > maxRt) { + throw new SystemBlockException(resourceWrapper.getName(), "rt"); + } + + // load. BBR algorithm. + if (highestSystemLoadIsSet && getCurrentSystemAvgLoad() > highestSystemLoad) { + if (!checkBbr(currentThread)) { + throw new SystemBlockException(resourceWrapper.getName(), "load"); + } + } + + // cpu usage + if (highestCpuUsageIsSet && getCurrentCpuUsage() > highestCpuUsage) { + throw new SystemBlockException(resourceWrapper.getName(), "cpu"); + } + } + + private static boolean checkBbr(int currentThread) { + if (currentThread > 1 && + currentThread > Constants.ENTRY_NODE.maxSuccessQps() * Constants.ENTRY_NODE.minRt() / 1000) { + return false; + } + return true; + } + + public static double getCurrentSystemAvgLoad() { + return statusListener.getSystemAverageLoad(); + } + + public static double getCurrentCpuUsage() { + return statusListener.getCpuUsage(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemSlot.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemSlot.java new file mode 100755 index 00000000..f6368830 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemSlot.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.context.Context; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.node.DefaultNode; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.Spi; + +/** + * A {@link ProcessorSlot} that dedicates to {@link SystemRule} checking. + * + * @author jialiang.linjl + * @author leyou + */ +@Spi(order = Constants.ORDER_SYSTEM_SLOT) +public class SystemSlot extends AbstractLinkedProcessorSlot { + + @Override + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, + boolean prioritized, Object... args) throws Throwable { + SystemRuleManager.checkSystem(resourceWrapper, count); + fireEntry(context, resourceWrapper, node, count, prioritized, args); + } + + @Override + public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + fireExit(context, resourceWrapper, count, args); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java new file mode 100755 index 00000000..bb9d1b35 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java @@ -0,0 +1,100 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.util.concurrent.TimeUnit; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.Constants; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +import com.sun.management.OperatingSystemMXBean; + +/** + * @author jialiang.linjl + */ +public class SystemStatusListener implements Runnable { + + volatile double currentLoad = -1; + volatile double currentCpuUsage = -1; + + volatile String reason = StringUtil.EMPTY; + + volatile long processCpuTime = 0; + volatile long processUpTime = 0; + + public double getSystemAverageLoad() { + return currentLoad; + } + + public double getCpuUsage() { + return currentCpuUsage; + } + + @Override + public void run() { + try { + OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class); + currentLoad = osBean.getSystemLoadAverage(); + + /* + * Java Doc copied from {@link OperatingSystemMXBean#getSystemCpuLoad()}:
            + * Returns the "recent cpu usage" for the whole system. This value is a double in the [0.0,1.0] interval. + * A value of 0.0 means that all CPUs were idle during the recent period of time observed, while a value + * of 1.0 means that all CPUs were actively running 100% of the time during the recent period being + * observed. All values between 0.0 and 1.0 are possible depending of the activities going on in the + * system. If the system recent cpu usage is not available, the method returns a negative value. + */ + double systemCpuUsage = osBean.getSystemCpuLoad(); + + // calculate process cpu usage to support application running in container environment + RuntimeMXBean runtimeBean = ManagementFactory.getPlatformMXBean(RuntimeMXBean.class); + long newProcessCpuTime = osBean.getProcessCpuTime(); + long newProcessUpTime = runtimeBean.getUptime(); + int cpuCores = osBean.getAvailableProcessors(); + long processCpuTimeDiffInMs = TimeUnit.NANOSECONDS + .toMillis(newProcessCpuTime - processCpuTime); + long processUpTimeDiffInMs = newProcessUpTime - processUpTime; + double processCpuUsage = (double) processCpuTimeDiffInMs / processUpTimeDiffInMs / cpuCores; + processCpuTime = newProcessCpuTime; + processUpTime = newProcessUpTime; + + currentCpuUsage = Math.max(processCpuUsage, systemCpuUsage); + + if (currentLoad > SystemRuleManager.getSystemLoadThreshold()) { + writeSystemStatusLog(); + } + } catch (Throwable e) { + RecordLog.warn("[SystemStatusListener] Failed to get system metrics from JMX", e); + } + } + + private void writeSystemStatusLog() { + StringBuilder sb = new StringBuilder(); + sb.append("Load exceeds the threshold: "); + sb.append("load:").append(String.format("%.4f", currentLoad)).append("; "); + sb.append("cpuUsage:").append(String.format("%.4f", currentCpuUsage)).append("; "); + sb.append("qps:").append(String.format("%.4f", Constants.ENTRY_NODE.passQps())).append("; "); + sb.append("rt:").append(String.format("%.4f", Constants.ENTRY_NODE.avgRt())).append("; "); + sb.append("thread:").append(Constants.ENTRY_NODE.curThreadNum()).append("; "); + sb.append("success:").append(String.format("%.4f", Constants.ENTRY_NODE.successQps())).append("; "); + sb.append("minRt:").append(String.format("%.2f", Constants.ENTRY_NODE.minRt())).append("; "); + sb.append("maxSuccess:").append(String.format("%.2f", Constants.ENTRY_NODE.maxSuccessQps())).append("; "); + RecordLog.info(sb.toString()); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/spi/Spi.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/spi/Spi.java new file mode 100644 index 00000000..401707b8 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/spi/Spi.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoader; + +import java.lang.annotation.*; + +/** + * Annotation for Provider class of SPI. + * + * @see SpiLoader + * @author cdfive + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@Documented +public @interface Spi { + + /** + * Alias name of Provider class + */ + String value() default ""; + + /** + * Whether create singleton instance + */ + boolean isSingleton() default true; + + /** + * Whether is the default Provider + */ + boolean isDefault() default false; + + /** + * Order priority of Provider class + */ + int order() default 0; + + int ORDER_HIGHEST = Integer.MIN_VALUE; + + int ORDER_LOWEST = Integer.MAX_VALUE; +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/spi/SpiLoader.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/spi/SpiLoader.java new file mode 100644 index 00000000..45c2dc36 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/spi/SpiLoader.java @@ -0,0 +1,538 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi.SpiLoaderException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.AssertUtil; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +import java.io.*; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A simple SPI loading facility (refactored since 1.8.1). + * + *

            SPI is short for Service Provider Interface.

            + * + *

            + * Service is represented by a single type, that is, a single interface or an abstract class. + * Provider is implementations of Service, that is, some classes which implement the interface or extends the abstract class. + *

            + * + *

            + * For Service type: + * Must interface or abstract class. + *

            + * + *

            + * For Provider class: + * Must have a zero-argument constructor so that they can be instantiated during loading. + *

            + * + *

            + * For Provider configuration file: + * 1. The file contains a list of fully-qualified binary names of concrete provider classes, one per line. + * 2. Space and tab characters surrounding each name, as well as blank lines, are ignored. + * 3. The comment line character is #, all characters following it are ignored. + *

            + * + * + *

            {@code SpiLoader} provide common functions, such as:

            + *
              + *
            • Load all Provider instance unsorted/sorted list.
            • + *
            • Load highest/lowest order priority instance.
            • + *
            • Load first-found or default instance.
            • + *
            • Load instance by alias name or provider class.
            • + *
            + * + * @author Eric Zhao + * @author cdfive + * @since 1.4.0 + * @see com.alibaba.csp.sentinel.spi.Spi + * @see ServiceLoader + */ +public final class SpiLoader { + + // Default path for the folder of Provider configuration file + private static final String SPI_FILE_PREFIX = "META-INF/services/"; + + // Cache the SpiLoader instances, key: classname of Service, value: SpiLoader instance + private static final ConcurrentHashMap SPI_LOADER_MAP = new ConcurrentHashMap<>(); + + // Cache the classes of Provider + private final List> classList = Collections.synchronizedList(new ArrayList>()); + + // Cache the sorted classes of Provider + private final List> sortedClassList = Collections.synchronizedList(new ArrayList>()); + + /** + * Cache the classes of Provider, key: aliasName, value: class of Provider. + * Note: aliasName is the value of {@link Spi} when the Provider class has {@link Spi} annotation and value is not empty, + * otherwise use classname of the Provider. + */ + private final ConcurrentHashMap> classMap = new ConcurrentHashMap<>(); + + // Cache the singleton instance of Provider, key: classname of Provider, value: Provider instance + private final ConcurrentHashMap singletonMap = new ConcurrentHashMap<>(); + + // Whether this SpiLoader has been loaded, that is, loaded the Provider configuration file + private final AtomicBoolean loaded = new AtomicBoolean(false); + + // Default provider class + private Class defaultClass = null; + + // The Service class, must be interface or abstract class + private Class service; + + /** + * Create SpiLoader instance via Service class + * Cached by className, and load from cache first + * + * @param service Service class + * @param Service type + * @return SpiLoader instance + */ + public static SpiLoader of(Class service) { + AssertUtil.notNull(service, "SPI class cannot be null"); + AssertUtil.isTrue(service.isInterface() || Modifier.isAbstract(service.getModifiers()), + "SPI class[" + service.getName() + "] must be interface or abstract class"); + + String className = service.getName(); + SpiLoader spiLoader = SPI_LOADER_MAP.get(className); + if (spiLoader == null) { + synchronized (SpiLoader.class) { + spiLoader = SPI_LOADER_MAP.get(className); + if (spiLoader == null) { + SPI_LOADER_MAP.putIfAbsent(className, new SpiLoader<>(service)); + spiLoader = SPI_LOADER_MAP.get(className); + } + } + } + + return spiLoader; + } + + /** + * Reset and clear all SpiLoader instances. + * Package privilege, used only in test cases. + */ + synchronized static void resetAndClearAll() { + Set> entries = SPI_LOADER_MAP.entrySet(); + for (Map.Entry entry : entries) { + SpiLoader spiLoader = entry.getValue(); + spiLoader.resetAndClear(); + } + SPI_LOADER_MAP.clear(); + } + + // Private access + private SpiLoader(Class service) { + this.service = service; + } + + /** + * Load all Provider instances of the specified Service + * + * @return Provider instances list + */ + public List loadInstanceList() { + load(); + + return createInstanceList(classList); + } + + /** + * Load all Provider instances of the specified Service, sorted by order value in class's {@link Spi} annotation + * + * @return Sorted Provider instances list + */ + public List loadInstanceListSorted() { + load(); + + return createInstanceList(sortedClassList); + } + + /** + * Load highest order priority instance, order value is defined in class's {@link Spi} annotation + * + * @return Provider instance of highest order priority + */ + public S loadHighestPriorityInstance() { + load(); + + if (sortedClassList.size() == 0) { + return null; + } + + Class highestClass = sortedClassList.get(0); + return createInstance(highestClass); + } + + /** + * Load lowest order priority instance, order value is defined in class's {@link Spi} annotation + * + * @return Provider instance of lowest order priority + */ + public S loadLowestPriorityInstance() { + load(); + + if (sortedClassList.size() == 0) { + return null; + } + + Class lowestClass = sortedClassList.get(sortedClassList.size() - 1); + return createInstance(lowestClass); + } + + /** + * Load the first-found Provider instance + * + * @return Provider instance of first-found specific + */ + public S loadFirstInstance() { + load(); + + if (classList.size() == 0) { + return null; + } + + Class serviceClass = classList.get(0); + S instance = createInstance(serviceClass); + return instance; + } + + /** + * Load the first-found Provider instance,if not found, return default Provider instance + * + * @return Provider instance + */ + public S loadFirstInstanceOrDefault() { + load(); + + for (Class clazz : classList) { + if (defaultClass == null || clazz != defaultClass) { + return createInstance(clazz); + } + } + + return loadDefaultInstance(); + } + + /** + * Load default Provider instance + * Provider class with @Spi(isDefault = true) + * + * @return default Provider instance + */ + public S loadDefaultInstance() { + load(); + + if (defaultClass == null) { + return null; + } + + return createInstance(defaultClass); + } + + /** + * Load instance by specific class type + * + * @param clazz class type + * @return Provider instance + */ + public S loadInstance(Class clazz) { + AssertUtil.notNull(clazz, "SPI class cannot be null"); + + if (clazz.equals(service)) { + fail(clazz.getName() + " is not subtype of " + service.getName()); + } + + load(); + + if (!classMap.containsValue(clazz)) { + fail(clazz.getName() + " is not Provider class of " + service.getName() + ",check if it is in the SPI configuration file?"); + } + + return createInstance(clazz); + } + + /** + * Load instance by aliasName of Provider class + * + * @param aliasName aliasName of Provider class + * @return Provider instance + */ + public S loadInstance(String aliasName) { + AssertUtil.notEmpty(aliasName, "aliasName cannot be empty"); + + load(); + + Class clazz = classMap.get(aliasName); + if (clazz == null) { + fail("no Provider class's aliasName is " + aliasName); + } + + return createInstance(clazz); + } + + /** + * Reset and clear all fields of current SpiLoader instance and remove instance in SPI_LOADER_MAP + */ + synchronized void resetAndClear() { + SPI_LOADER_MAP.remove(service.getName()); + classList.clear(); + sortedClassList.clear(); + classMap.clear(); + singletonMap.clear(); + defaultClass = null; + loaded.set(false); + } + + /** + * Load the Provider class from Provider configuration file + */ + public void load() { + if (!loaded.compareAndSet(false, true)) { + return; + } + + String fullFileName = SPI_FILE_PREFIX + service.getName(); + ClassLoader classLoader; + if (SentinelConfig.shouldUseContextClassloader()) { + classLoader = Thread.currentThread().getContextClassLoader(); + } else { + classLoader = service.getClassLoader(); + } + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + Enumeration urls = null; + try { + urls = classLoader.getResources(fullFileName); + } catch (IOException e) { + fail("Error locating SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader, e); + } + + if (urls == null || !urls.hasMoreElements()) { + RecordLog.warn("No SPI configuration file, filename=" + fullFileName + ", classloader=" + classLoader); + return; + } + + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + + InputStream in = null; + BufferedReader br = null; + try { + in = url.openStream(); + br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + String line; + while ((line = br.readLine()) != null) { + if (StringUtil.isBlank(line)) { + // Skip blank line + continue; + } + + line = line.trim(); + int commentIndex = line.indexOf("#"); + if (commentIndex == 0) { + // Skip comment line + continue; + } + + if (commentIndex > 0) { + line = line.substring(0, commentIndex); + } + line = line.trim(); + + Class clazz = null; + try { + clazz = (Class) Class.forName(line, false, classLoader); + } catch (ClassNotFoundException e) { + fail("class " + line + " not found", e); + } + + if (!service.isAssignableFrom(clazz)) { + fail("class " + clazz.getName() + "is not subtype of " + service.getName() + ",SPI configuration file=" + fullFileName); + } + + classList.add(clazz); + Spi spi = clazz.getAnnotation(Spi.class); + String aliasName = spi == null || "".equals(spi.value()) ? clazz.getName() : spi.value(); + if (classMap.containsKey(aliasName)) { + Class existClass = classMap.get(aliasName); + fail("Found repeat alias name for " + clazz.getName() + " and " + + existClass.getName() + ",SPI configuration file=" + fullFileName); + } + classMap.put(aliasName, clazz); + + if (spi != null && spi.isDefault()) { + if (defaultClass != null) { + fail("Found more than one default Provider, SPI configuration file=" + fullFileName); + } + defaultClass = clazz; + } + + RecordLog.info("[SpiLoader] Found SPI implementation for SPI {}, provider={}, aliasName={}" + + ", isSingleton={}, isDefault={}, order={}", + service.getName(), line, aliasName + , spi == null ? true : spi.isSingleton() + , spi == null ? false : spi.isDefault() + , spi == null ? 0 : spi.order()); + } + } catch (IOException e) { + fail("error reading SPI configuration file", e); + } finally { + closeResources(in, br); + } + } + + sortedClassList.addAll(classList); + Collections.sort(sortedClassList, new Comparator>() { + @Override + public int compare(Class o1, Class o2) { + Spi spi1 = o1.getAnnotation(Spi.class); + int order1 = spi1 == null ? 0 : spi1.order(); + + Spi spi2 = o2.getAnnotation(Spi.class); + int order2 = spi2 == null ? 0 : spi2.order(); + + return Integer.compare(order1, order2); + } + }); + } + + @Override + public String toString() { + return "com.alibaba.csp.sentinel.spi.SpiLoader[" + service.getName() + "]"; + } + + /** + * Create Provider instance list + * + * @param clazzList class types of Providers + * @return Provider instance list + */ + private List createInstanceList(List> clazzList) { + if (clazzList == null || clazzList.size() == 0) { + return Collections.emptyList(); + } + + List instances = new ArrayList<>(clazzList.size()); + for (Class clazz : clazzList) { + S instance = createInstance(clazz); + instances.add(instance); + } + return instances; + } + + /** + * Create Provider instance + * + * @param clazz class type of Provider + * @return Provider class + */ + private S createInstance(Class clazz) { + Spi spi = clazz.getAnnotation(Spi.class); + boolean singleton = true; + if (spi != null) { + singleton = spi.isSingleton(); + } + return createInstance(clazz, singleton); + } + + /** + * Create Provider instance + * + * @param clazz class type of Provider + * @param singleton if instance is singleton or prototype + * @return Provider instance + */ + private S createInstance(Class clazz, boolean singleton) { + S instance = null; + try { + if (singleton) { + instance = singletonMap.get(clazz.getName()); + if (instance == null) { + synchronized (this) { + instance = singletonMap.get(clazz.getName()); + if (instance == null) { + instance = service.cast(clazz.newInstance()); + singletonMap.put(clazz.getName(), instance); + } + } + } + } else { + instance = service.cast(clazz.newInstance()); + } + } catch (Throwable e) { + fail(clazz.getName() + " could not be instantiated"); + } + return instance; + } + + /** + * Close all resources + * + * @param closeables {@link Closeable} resources + */ + private void closeResources(Closeable... closeables) { + if (closeables == null || closeables.length == 0) { + return; + } + + Exception firstException = null; + for (Closeable closeable : closeables) { + try { + closeable.close(); + } catch (Exception e) { + if (firstException == null) { + firstException = e; + } + } + } + if (firstException != null) { + fail("error closing resources", firstException); + } + } + + /** + * Throw {@link SpiLoaderException} with message + * + * @param msg error message + */ + private void fail(String msg) { + RecordLog.error(msg); + throw new SpiLoaderException("[" + service.getName() + "]" + msg); + } + + /** + * Throw {@link SpiLoaderException} with message and Throwable + * + * @param msg error message + */ + private void fail(String msg, Throwable e) { + RecordLog.error(msg, e); + throw new SpiLoaderException("[" + service.getName() + "]" + msg, e); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/spi/SpiLoaderException.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/spi/SpiLoaderException.java new file mode 100644 index 00000000..a07fc56b --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/spi/SpiLoaderException.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.spi; + +/** + * Error thrown when something goes wrong while loading Provider via {@link SpiLoader}. + * + * @author cdfive + */ +public class SpiLoaderException extends RuntimeException { + + public SpiLoaderException() { + super(); + } + + public SpiLoaderException(String message) { + super(message); + } + + public SpiLoaderException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/AppNameUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/AppNameUtil.java new file mode 100755 index 00000000..493641fc --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/AppNameUtil.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.config.SentinelConfig; + +/** + * @author Eric Zhao + * @author leyou + */ + +public final class AppNameUtil { + + + private AppNameUtil() { + } + + public static String getAppName() { + return SentinelConfig.getAppName(); + } + +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/AssertUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/AssertUtil.java new file mode 100644 index 00000000..a208edff --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/AssertUtil.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +import java.util.Collection; + +/** + * Util class for checking arguments. + * + * @author Eric Zhao + */ +public class AssertUtil { + + private AssertUtil(){} + + public static void notEmpty(String string, String message) { + if (StringUtil.isEmpty(string)) { + throw new IllegalArgumentException(message); + } + } + + public static void assertNotEmpty(Collection collection, String message) { + if (collection == null || collection.isEmpty()) { + throw new IllegalArgumentException(message); + } + } + + public static void assertNotBlank(String string, String message) { + if (StringUtil.isBlank(string)) { + throw new IllegalArgumentException(message); + } + } + + public static void notNull(Object object, String message) { + if (object == null) { + throw new IllegalArgumentException(message); + } + } + + public static void isTrue(boolean value, String message) { + if (!value) { + throw new IllegalArgumentException(message); + } + } + + public static void assertState(boolean condition, String message) { + if (!condition) { + throw new IllegalStateException(message); + } + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/ConfigUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/ConfigUtil.java new file mode 100644 index 00000000..f93a03f0 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/ConfigUtil.java @@ -0,0 +1,156 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.StringUtil; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; + +/** + *

            + * Util class for loading configuration from file or command arguments. + *

            + * + * @author lianglin + * @since 1.7.0 + */ +public final class ConfigUtil { + + public static final String CLASSPATH_FILE_FLAG = "classpath:"; + + /** + *

            Load the properties from provided file.

            + *

            Currently it supports reading from classpath file or local file.

            + * + * @param fileName valid file path + * @return the retrieved properties from the file; null if the file not exist + */ + public static Properties loadProperties(String fileName) { + if (StringUtil.isNotBlank(fileName)) { + if (absolutePathStart(fileName)) { + return loadPropertiesFromAbsoluteFile(fileName); + } else if (fileName.startsWith(CLASSPATH_FILE_FLAG)) { + return loadPropertiesFromClasspathFile(fileName); + } else { + return loadPropertiesFromRelativeFile(fileName); + } + } else { + return null; + } + } + + private static Properties loadPropertiesFromAbsoluteFile(String fileName) { + Properties properties = null; + try { + + File file = new File(fileName); + if (!file.exists()) { + return null; + } + + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(new FileInputStream(file), getCharset()))) { + properties = new Properties(); + properties.load(bufferedReader); + } + } catch (Throwable e) { + e.printStackTrace(); + } + return properties; + } + + private static boolean absolutePathStart(String path) { + File[] files = File.listRoots(); + for (File file : files) { + if (path.startsWith(file.getPath())) { + return true; + } + } + return false; + } + + + private static Properties loadPropertiesFromClasspathFile(String fileName) { + fileName = fileName.substring(CLASSPATH_FILE_FLAG.length()).trim(); + + List list = new ArrayList<>(); + try { + Enumeration urls = getClassLoader().getResources(fileName); + list = new ArrayList<>(); + while (urls.hasMoreElements()) { + list.add(urls.nextElement()); + } + } catch (Throwable e) { + e.printStackTrace(); + } + + if (list.isEmpty()) { + return null; + } + + Properties properties = new Properties(); + for (URL url : list) { + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(url.openStream(), getCharset()))) { + Properties p = new Properties(); + p.load(bufferedReader); + properties.putAll(p); + } catch (Throwable e) { + e.printStackTrace(); + } + } + return properties; + } + + private static Properties loadPropertiesFromRelativeFile(String fileName) { + String userDir = System.getProperty("user.dir"); + String realFilePath = addSeparator(userDir) + fileName; + return loadPropertiesFromAbsoluteFile(realFilePath); + } + + private static ClassLoader getClassLoader() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + if (classLoader == null) { + classLoader = ConfigUtil.class.getClassLoader(); + } + return classLoader; + } + + private static Charset getCharset() { + // avoid static loop dependencies: SentinelConfig -> SentinelConfigLoader -> ConfigUtil -> SentinelConfig + // so not use SentinelConfig.charset() + return Charset.forName(System.getProperty("csp.sentinel.charset", StandardCharsets.UTF_8.name())); + } + + public static String addSeparator(String dir) { + if (!dir.endsWith(File.separator)) { + dir += File.separator; + } + return dir; + } + + private ConfigUtil() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/HostNameUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/HostNameUtil.java new file mode 100755 index 00000000..1c05b1cb --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/HostNameUtil.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Enumeration; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; + +/** + * Get host name and ip of the host. + * + * @author leyou + */ +public final class HostNameUtil { + + private static String ip; + private static String hostName; + + static { + try { + // Init the host information. + resolveHost(); + } catch (Exception e) { + RecordLog.info("Failed to get local host", e); + } + } + + private static void resolveHost() throws Exception { + InetAddress addr = InetAddress.getLocalHost(); + hostName = addr.getHostName(); + ip = addr.getHostAddress(); + if (addr.isLoopbackAddress()) { + // find the first IPv4 Address that not loopback + Enumeration interfaces = NetworkInterface.getNetworkInterfaces(); + while (interfaces.hasMoreElements()) { + NetworkInterface in = interfaces.nextElement(); + Enumeration addrs = in.getInetAddresses(); + while (addrs.hasMoreElements()) { + InetAddress address = addrs.nextElement(); + if (!address.isLoopbackAddress() && address instanceof Inet4Address) { + ip = address.getHostAddress(); + } + } + } + } + } + + public static String getIp() { + return ip; + } + + public static String getHostName() { + return hostName; + } + + public static String getConfigString() { + return "{\n" + + "\t\"machine\": \"" + hostName + "\",\n" + + "\t\"ip\": \"" + ip + "\"\n" + + "}"; + } + + private HostNameUtil() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/IdUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/IdUtil.java new file mode 100755 index 00000000..96d373c1 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/IdUtil.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +/** + * @author qinan.qn + */ +public final class IdUtil { + + public static String truncate(String id) { + IdLexer lexer = new IdLexer(id); + StringBuilder sb = new StringBuilder(); + String r; + String temp = ""; + while ((r = lexer.nextToken()) != null) { + if ("(".equals(r) || ")".equals(r) || ",".equals(r)) { + sb.append(temp).append(r); + temp = ""; + } else if (!".".equals(r)) { + temp = r; + } + } + + return sb.toString(); + } + + private static class IdLexer { + private String id; + private int idx = 0; + + IdLexer(String id) { + this.id = id; + } + + String nextToken() { + int oldIdx = idx; + String result = null; + while (idx != id.length()) { + char curChar = id.charAt(idx); + if (curChar == '.' || curChar == '(' || curChar == ')' || curChar == ',') { + if (idx == oldIdx) { + result = String.valueOf(curChar); + ++idx; + break; + } else { + result = id.substring(oldIdx, idx); + break; + } + } + ++idx; + } + return result; + } + } + + private IdUtil() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/MethodUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/MethodUtil.java new file mode 100755 index 00000000..f865a242 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/MethodUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/*** + * Util class for processing {@link Method}. + * + * @author youji.zj + */ +public final class MethodUtil { + + private static final Map methodNameMap = new ConcurrentHashMap(); + + private static final Object LOCK = new Object(); + + /** + * Parse and resolve the method name, then cache to the map. + * + * @param method method instance + * @return resolved method name + */ + public static String resolveMethodName(Method method) { + if (method == null) { + throw new IllegalArgumentException("Null method"); + } + String methodName = methodNameMap.get(method); + if (methodName == null) { + synchronized (LOCK) { + methodName = methodNameMap.get(method); + if (methodName == null) { + StringBuilder sb = new StringBuilder(); + + String className = method.getDeclaringClass().getName(); + String name = method.getName(); + Class[] params = method.getParameterTypes(); + sb.append(className).append(":").append(name); + sb.append("("); + + int paramPos = 0; + for (Class clazz : params) { + sb.append(clazz.getCanonicalName()); + if (++paramPos < params.length) { + sb.append(","); + } + } + sb.append(")"); + methodName = sb.toString(); + + methodNameMap.put(method, methodName); + } + } + } + return methodName; + } + + /** + * For test. + */ + static void clearMethodMap() { + methodNameMap.clear(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/PidUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/PidUtil.java new file mode 100755 index 00000000..dbcfc00a --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/PidUtil.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +import java.lang.management.ManagementFactory; + +/** + * Util class providing pid of current process. + */ +public final class PidUtil { + + /** + * Resolve and get current process ID. + * + * @return current process ID + */ + public static int getPid() { + // Note: this will trigger local host resolve, which might be slow. + String name = ManagementFactory.getRuntimeMXBean().getName(); + return Integer.parseInt(name.split("@")[0]); + } + + private PidUtil() {} +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/StringUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/StringUtil.java new file mode 100755 index 00000000..c7e9289e --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/StringUtil.java @@ -0,0 +1,138 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +/*** + * Util class providing operations on {@link String}. + * + * @author youji.zj + */ +public final class StringUtil { + + public static final String EMPTY = ""; + + public static boolean equalsIgnoreCase(final CharSequence str1, final CharSequence str2) { + if (str1 == null || str2 == null) { + return str1 == str2; + } else if (str1 == str2) { + return true; + } else if (str1.length() != str2.length()) { + return false; + } else { + return regionMatches(str1, true, 0, str2, 0, str1.length()); + } + } + + public static boolean equals(String str1, String str2) { + return str1 == null ? str2 == null : str1.equals(str2); + } + + public static boolean isBlank(String str) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if ((!Character.isWhitespace(str.charAt(i)))) { + return false; + } + } + return true; + } + + public static boolean isNotBlank(String str) { + return !isBlank(str); + } + + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + public static boolean isNotEmpty(String str) { + return !isEmpty(str); + } + + public static String trimToEmpty(String str) { + return str == null ? EMPTY : str.trim(); + } + + public static String trim(String str) { + return str == null ? null : str.trim(); + } + + private static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, + final CharSequence substring, final int start, final int length) { + if (cs instanceof String && substring instanceof String) { + return ((String)cs).regionMatches(ignoreCase, thisStart, (String)substring, start, length); + } + int index1 = thisStart; + int index2 = start; + int tmpLen = length; + + // Extract these first so we detect NPEs the same as the java.lang.String version + final int srcLen = cs.length() - thisStart; + final int otherLen = substring.length() - start; + + // Check for invalid parameters + if (thisStart < 0 || start < 0 || length < 0) { + return false; + } + + // Check that the regions are long enough + if (srcLen < length || otherLen < length) { + return false; + } + + while (tmpLen-- > 0) { + final char c1 = cs.charAt(index1++); + final char c2 = substring.charAt(index2++); + + if (c1 == c2) { + continue; + } + + if (!ignoreCase) { + return false; + } + + // The same check as in String.regionMatches(): + if (Character.toUpperCase(c1) != Character.toUpperCase(c2) + && Character.toLowerCase(c1) != Character.toLowerCase(c2)) { + return false; + } + } + + return true; + } + + public static String capitalize(String str) { + return changeFirstCharacterCase(str, true); + } + + private static String changeFirstCharacterCase(String str, boolean capitalize) { + if (str == null || str.length() == 0) { + return str; + } + StringBuilder buf = new StringBuilder(str.length()); + if (capitalize) { + buf.append(Character.toUpperCase(str.charAt(0))); + } else { + buf.append(Character.toLowerCase(str.charAt(0))); + } + buf.append(str.substring(1)); + return buf.toString(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/TimeUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/TimeUtil.java new file mode 100755 index 00000000..114407db --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/TimeUtil.java @@ -0,0 +1,225 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.LongAdder; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function.Tuple2; + +/** + *

            Provides millisecond-level time of OS.

            + *

            + * Here we should see that not all the time TimeUtil should + * keep looping 1_000 times every second (Actually about 800/s due to some losses). + *

            + * * In idle conditions it just acts as System.currentTimeMillis();
            + * * In busy conditions (significantly more than 1_000/s) it keeps loop to reduce costs.
            + * 
            + * For detail design and proposals please goto + * https://github.com/alibaba/Sentinel/issues/1702 + * + * @author qinan.qn + * @author jason + */ +public final class TimeUtil implements Runnable { + private static final long CHECK_INTERVAL = 3000; + private static final long HITS_LOWER_BOUNDARY = 800; + private static final long HITS_UPPER_BOUNDARY = 1200; + + public static enum STATE { + IDLE, + PREPARE, + RUNNING; + } + + private static class Statistic { + private final LongAdder writes = new LongAdder(); + private final LongAdder reads = new LongAdder(); + + public LongAdder getWrites() { + return writes; + } + + public LongAdder getReads() { + return reads; + } + } + + private static TimeUtil INSTANCE; + + private volatile long currentTimeMillis; + private volatile STATE state = STATE.IDLE; + + private LeapArray statistics; + + /** + * thread private variables + */ + private long lastCheck = 0; + + static { + INSTANCE = new TimeUtil(); + } + + public TimeUtil() { + this.statistics = new LeapArray(3, 3000) { + + @Override + public Statistic newEmptyBucket(long timeMillis) { + return new Statistic(); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap windowWrap, long startTime) { + Statistic val = windowWrap.value(); + val.getReads().reset(); + val.getWrites().reset(); + windowWrap.resetTo(startTime); + return windowWrap; + } + }; + this.currentTimeMillis = System.currentTimeMillis(); + this.lastCheck = this.currentTimeMillis; + Thread daemon = new Thread(this); + daemon.setDaemon(true); + daemon.setName("sentinel-time-tick-thread"); + daemon.start(); + } + + @Override + public void run() { + while (true) { + // Mechanism optimized since 1.8.2 + this.check(); + if (this.state == STATE.RUNNING) { + this.currentTimeMillis = System.currentTimeMillis(); + this.statistics.currentWindow(this.currentTimeMillis).value().getWrites().increment(); + try { + TimeUnit.MILLISECONDS.sleep(1); + } catch (Throwable e) { + } + continue; + } + if (this.state == STATE.IDLE) { + try { + TimeUnit.MILLISECONDS.sleep(300); + } catch (Throwable e) { + } + continue; + } + if (this.state == STATE.PREPARE) { + RecordLog.debug("TimeUtil switches to RUNNING"); + this.currentTimeMillis = System.currentTimeMillis(); + this.state = STATE.RUNNING; + continue; + } + } + } + + /** + * Current running state + * + * @return + */ + public STATE getState() { + return state; + } + + /** + * Current qps statistics (including reads and writes request) + * excluding current working time window for accurate result. + * + * @param now + * @return + */ + public Tuple2 currentQps(long now) { + List> list = this.statistics.listAll(); + long reads = 0; + long writes = 0; + int cnt = 0; + for (WindowWrap windowWrap : list) { + if (windowWrap.isTimeInWindow(now)) { + continue; + } + cnt++; + reads += windowWrap.value().getReads().longValue(); + writes += windowWrap.value().getWrites().longValue(); + } + if (cnt < 1) { + return new Tuple2(0L, 0L); + } + return new Tuple2(reads / cnt, writes / cnt); + } + + /** + * Check and operate the state if necessary. + * ATTENTION: It's called in daemon thread. + */ + private void check() { + long now = currentTime(true); + // every period + if (now - this.lastCheck < CHECK_INTERVAL) { + return; + } + this.lastCheck = now; + Tuple2 qps = currentQps(now); + if (this.state == STATE.IDLE && qps.r1 > HITS_UPPER_BOUNDARY) { + RecordLog.info("TimeUtil switches to PREPARE for better performance, reads={}/s, writes={}/s", qps.r1, qps.r2); + this.state = STATE.PREPARE; + } else if (this.state == STATE.RUNNING && qps.r1 < HITS_LOWER_BOUNDARY) { + RecordLog.info("TimeUtil switches to IDLE due to not enough load, reads={}/s, writes={}/s", qps.r1, qps.r2); + this.state = STATE.IDLE; + } + } + + private long currentTime(boolean innerCall) { + long now = this.currentTimeMillis; + Statistic val = this.statistics.currentWindow(now).value(); + if (!innerCall) { + val.getReads().increment(); + } + if (this.state == STATE.IDLE || this.state == STATE.PREPARE) { + now = System.currentTimeMillis(); + this.currentTimeMillis = now; + if (!innerCall) { + val.getWrites().increment(); + } + } + return now; + } + + /** + * Current timestamp in milliseconds. + * + * @return + */ + public long getTime() { + return this.currentTime(false); + } + + public static TimeUtil instance() { + return INSTANCE; + } + + public static long currentTimeMillis() { + return INSTANCE.getTime(); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/VersionUtil.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/VersionUtil.java new file mode 100644 index 00000000..b5c8a930 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/VersionUtil.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util; + +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.RecordLog; + +/** + * Get version of Sentinel from {@code MANIFEST.MF} file. + * + * @author jason + * @since 0.2.1 + */ +public final class VersionUtil { + + public static String getVersion(String defaultVersion) { + try { + String version = VersionUtil.class.getPackage().getImplementationVersion(); + return StringUtil.isBlank(version) ? defaultVersion : version; + } catch (Throwable e) { + RecordLog.warn("Using default version, ignore exception", e); + return defaultVersion; + } + } + + private VersionUtil() {} + + private static int parseInt(String str) { + if (str == null || str.length() < 1) { + return 0; + } + int num = 0; + for (int i = 0; i < str.length(); i ++) { + char ch = str.charAt(i); + if (ch < '0' || ch > '9') { + break; + } + num = num * 10 + (ch - '0'); + } + return num; + } + + /** + * Convert version in string like x.y.z or x.y.z.b into number
            + * Each segment has one byte space(unsigned)
            + * eg.
            + *
            +     * 1.2.3.4 => 01 02 03 04
            +     * 1.2.3   => 01 02 03 00
            +     * 1.2     => 01 02 00 00
            +     * 1       => 01 00 00 00
            +     * 
            + * + * @return + */ + public static int fromVersionString(String verStr) { + if (verStr == null || verStr.length() < 1) { + return 0; + } + int[] versions = new int[] {0, 0, 0, 0}; + int index = 0; + String segment; + int cur = 0; + int pos; + do { + if (index >= versions.length) { + // More dots than "x.y.z.b" contains + return 0; + } + pos = verStr.indexOf('.', cur); + if (pos == -1) { + segment = verStr.substring(cur); + } else if (cur < pos) { + segment = verStr.substring(cur, pos); + } else { + // Illegal format + return 0; + } + versions[index] = parseInt(segment); + if (versions[index] < 0 || versions[index] > 255) { + // Out of range [0, 255] + return 0; + } + cur = pos + 1; + index ++; + } while (pos > 0); + return ((versions[0] & 0xff) << 24) + | ((versions[1] & 0xff) << 16) + | ((versions[2] & 0xff) << 8) + | (versions[3] & 0xff); + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/BiConsumer.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/BiConsumer.java new file mode 100644 index 00000000..9e95996f --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/BiConsumer.java @@ -0,0 +1,24 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function; + +/** + * BiConsumer interface from JDK 8. + */ +public interface BiConsumer { + + void accept(T t, U u); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Consumer.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Consumer.java new file mode 100644 index 00000000..d86cc122 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Consumer.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * 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 + * + * https://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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function; + +/** + * Consumer interface from JDK 8. + */ +public interface Consumer { + + /** + * Performs this operation on the given argument. + * + * @param t the input argument + */ + void accept(T t); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Function.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Function.java new file mode 100644 index 00000000..c47d9b0b --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Function.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function; + +/** + * Function functional interface from JDK 8. + */ +public interface Function { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + */ + R apply(T t); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Predicate.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Predicate.java new file mode 100644 index 00000000..8fddd1b3 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Predicate.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function; + +/** + * Predicate functional interface from JDK 8. + */ +public interface Predicate { + + /** + * Evaluates this predicate on the given argument. + * + * @param t the input argument + * @return {@code true} if the input argument matches the predicate, + * otherwise {@code false} + */ + boolean test(T t); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Supplier.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Supplier.java new file mode 100644 index 00000000..ffca92f1 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Supplier.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * 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.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function; + +/** + * Supplier functional interface from JDK 8. + */ +public interface Supplier { + + /** + * Gets a result. + * + * @return a result + */ + T get(); +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Tuple2.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Tuple2.java new file mode 100644 index 00000000..86ab3abd --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/third/com/alibaba/csp/sentinel/util/function/Tuple2.java @@ -0,0 +1,62 @@ +package com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.util.function; + +import java.util.Objects; + +/** + * A tuple of 2 elements. + */ +public class Tuple2 { + + public final R1 r1; + public final R2 r2; + + public Tuple2(R1 r1, R2 r2) { + this.r1 = r1; + this.r2 = r2; + } + + /** + * Factory method for creating a Tuple. + * + * @return new Tuple + */ + public static Tuple2 of(C1 c1, C2 c2) { + return new Tuple2(c1, c2); + } + + /** + * Swaps the element of this Tuple. + * + * @return a new Tuple where the first element is the second element of this Tuple and the second element is the first element of this Tuple. + */ + public Tuple2 swap() { + return new Tuple2(this.r2, this.r1); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Tuple2)) { + return false; + } + Tuple2 that = (Tuple2) o; + return Objects.equals(this.r1, that.r1) && Objects.equals(this.r2, that.r2); + } + + @Override + public int hashCode() { + int result = r1 != null ? r1.hashCode() : 0; + result = 31 * result + (r2 != null ? r2.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Tuple2{" + + "r1=" + r1 + + ", r2=" + r2 + + '}'; + } +} diff --git a/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/utils/InvokeUtils.java b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/utils/InvokeUtils.java new file mode 100644 index 00000000..7895f199 --- /dev/null +++ b/yop-java-sdk-router/src/main/java/com/yeepay/yop/sdk/router/utils/InvokeUtils.java @@ -0,0 +1,128 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.utils; + +import com.yeepay.yop.sdk.invoke.Router; +import com.yeepay.yop.sdk.invoke.SimpleRetryPolicy; +import com.yeepay.yop.sdk.invoke.UriResourceRouteInvoker; +import com.yeepay.yop.sdk.invoke.UriResourceRouteInvokerWrapper; +import com.yeepay.yop.sdk.invoke.model.AnalyzedException; +import com.yeepay.yop.sdk.invoke.model.RetryPolicy; +import com.yeepay.yop.sdk.invoke.model.UriResource; +import com.yeepay.yop.sdk.router.SimpleContext; +import com.yeepay.yop.sdk.router.SimpleUriResourceBusinessLogic; +import com.yeepay.yop.sdk.router.SimpleUriResourceInvoker; +import com.yeepay.yop.sdk.router.config.YopFileRouteConfigProvider; +import com.yeepay.yop.sdk.router.config.YopRouteConfigProvider; +import com.yeepay.yop.sdk.utils.RandomUtils; + +import java.io.IOException; +import java.net.NoRouteToHostException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +/** + * title: 工具类
            + * description: 描述
            + * Copyright: Copyright (c)2014
            + * Company: 易宝支付(YeePay)
            + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/2 + */ +public class InvokeUtils { + + /** + * 发起调用 + * + * @param businessLogic 业务逻辑 + * @param router 自实现域名路由 + * @param 出参范型 + * @return 业务出参 + */ + public static Output invoke(SimpleUriResourceBusinessLogic businessLogic, + Router router) { + return invoke(businessLogic, router, YopFileRouteConfigProvider.INSTANCE, new SimpleRetryPolicy(3)); + } + + /** + * 发起调用 + * + * @param businessLogic 业务逻辑 + * @param router 自实现域名路由 + * @param routeConfigProvider 自实现路由配置加载 + * @param retryPolicy 自实现重试策略 + * @param 出参范型 + * @return 业务出参 + */ + public static Output invoke(SimpleUriResourceBusinessLogic businessLogic, + Router router, + YopRouteConfigProvider routeConfigProvider, RetryPolicy retryPolicy) { + // 业务处理、熔断操作、异常分析封装 + UriResourceRouteInvoker uriResourceRouteInvoker + = new SimpleUriResourceInvoker<>(businessLogic, new SimpleContext(), routeConfigProvider); + return invoke(uriResourceRouteInvoker, router, retryPolicy); + } + + /** + * 发起调用 + * + * @param invoker 自实现调用逻辑 + * @param router 自实现域名路由 + * @param retryPolicy 自实现重试策略 + * @param 出参范型 + * @return 业务出参 + */ + public static Output invoke(UriResourceRouteInvoker invoker, + Router router, + RetryPolicy retryPolicy) { + // 路由切换、重试策略封装 + return new UriResourceRouteInvokerWrapper<>(invoker, retryPolicy, router).invoke(); + + } + + /** + * 发起调用(模拟故障,测试用) + * + * @param businessLogic 业务逻辑 + * @param router 自实现域名路由 + * @param mockFailureConfig 模拟故障配置 + * @param retrySuccessStats 重试结果统计 + * @param 出参范型 + * @return 业务出参 + */ + public static Output mockInvoke(SimpleUriResourceBusinessLogic businessLogic, + Router router, + Map mockFailureConfig, AtomicLong retrySuccessStats) { + final YopRouteConfigProvider routeConfigProvider = YopFileRouteConfigProvider.INSTANCE; + final UriResourceRouteInvoker uriResourceRouteInvoker = + new SimpleUriResourceInvoker(businessLogic, new SimpleContext(), routeConfigProvider) { + + // 模拟域名故障 + @Override + protected void beforeBusiness() throws IOException { + final String targetServer = getUriResource().getResource().toString(); + if (RandomUtils.randomFailure(mockFailureConfig.get(targetServer))) { + throw new NoRouteToHostException("mock failure:" + targetServer); + } + super.beforeBusiness(); + } + + // 统计重试成功数 + @Override + protected void afterBusiness() throws IOException { + super.afterBusiness(); + if (getContext().getRetryCount() > 0) { + retrySuccessStats.addAndGet(1); + } + } + }; + + // 路由切换、重试策略封装 + return new UriResourceRouteInvokerWrapper<>(uriResourceRouteInvoker, new SimpleRetryPolicy(3), router).invoke(); + } + +} diff --git a/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.invoke.RouterPolicy b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.invoke.RouterPolicy new file mode 100644 index 00000000..45a9a454 --- /dev/null +++ b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.invoke.RouterPolicy @@ -0,0 +1,2 @@ +com.yeepay.yop.sdk.router.policy.AbAndFirstBlockPolicy +com.yeepay.yop.sdk.router.policy.AbAndRoundRobinPolicy \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init.InitFunc b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init.InitFunc new file mode 100644 index 00000000..229ca3b2 --- /dev/null +++ b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.init.InitFunc @@ -0,0 +1,2 @@ +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.metric.extension.MetricCallbackInit +com.yeepay.yop.sdk.router.sentinel.YopSentinelInit \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.Logger b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.Logger new file mode 100644 index 00000000..0a56b6e2 --- /dev/null +++ b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.log.Logger @@ -0,0 +1 @@ +com.yeepay.yop.sdk.router.sentinel.YopSentinelRecordLogger \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot new file mode 100644 index 00000000..e195ce05 --- /dev/null +++ b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlot @@ -0,0 +1,10 @@ +# Sentinel default ProcessorSlots +# TODO 注释掉多余slot +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.logger.LogSlot +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.statistic.StatisticSlot +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.system.SystemSlot +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.flow.FlowSlot +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.SlotChainBuilder b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.SlotChainBuilder new file mode 100644 index 00000000..b69332c5 --- /dev/null +++ b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.SlotChainBuilder @@ -0,0 +1,2 @@ +# Default slot chain builder +com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver new file mode 100644 index 00000000..b1657022 --- /dev/null +++ b/yop-java-sdk-router/src/main/resources/META-INF/services/com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver @@ -0,0 +1 @@ +com.yeepay.yop.sdk.router.sentinel.listener.YopResourceStatusListener \ No newline at end of file diff --git a/yop-java-sdk-router/src/main/resources/config/yop_route_config_default.json b/yop-java-sdk-router/src/main/resources/config/yop_route_config_default.json new file mode 100644 index 00000000..2dc7ee2a --- /dev/null +++ b/yop-java-sdk-router/src/main/resources/config/yop_route_config_default.json @@ -0,0 +1,27 @@ +{ + "retry_exceptions": [ + "java.net.UnknownHostException", + "java.net.ConnectException:No route to host (connect failed)", + "java.net.ConnectException:Connection refused (Connection refused)", + "java.net.ConnectException:Connection refused: connect", + "java.net.SocketTimeoutException:connect timed out", + "java.net.NoRouteToHostException", + "org.apache.http.conn.ConnectTimeoutException", "com.yeepay.shade.org.apache.http.conn.ConnectTimeoutException", + "org.apache.http.conn.HttpHostConnectException", "com.yeepay.shade.org.apache.http.conn.HttpHostConnectException", + "java.net.ConnectException:Connection timed out","java.net.ConnectException:连接超时" + ], + "circuit_breaker": { + "enable": true, + "yop_exclude_exceptions": [ + "com.yeepay.yop.sdk.exception.YopClientException" + ], + "rules": [ + { + "grade": 2, + "count": 1, + "time_window": 600, + "stat_interval_ms": 300000 + } + ] + } +} \ No newline at end of file diff --git a/yop-java-sdk-router/src/test/java/com/yeepay/yop/sdk/router/SimpleExample.java b/yop-java-sdk-router/src/test/java/com/yeepay/yop/sdk/router/SimpleExample.java new file mode 100644 index 00000000..bf972d0f --- /dev/null +++ b/yop-java-sdk-router/src/test/java/com/yeepay/yop/sdk/router/SimpleExample.java @@ -0,0 +1,70 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router; + +import com.google.common.collect.Lists; +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.exception.YopServiceException; +import com.yeepay.yop.sdk.invoke.model.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * title: 通用资源调用示例
            + * description: 描述
            + * Copyright: Copyright (c)2014
            + * Company: 易宝支付(YeePay)
            + * + * @author wdc + * @version 1.0.0 + * @since 2024/4/10 + */ +public class SimpleExample { + + private static final Logger LOGGER = LoggerFactory.getLogger(SimpleExample.class); + + // 1、初始化路由客户端 + // 1.1、构造每一个client时,请确保传入的一组域名之间关系对等,均能处理同类业务 + // 1.2、可以根据业务分类,合理拆分client,每个client,使用单例模式 + // 1.3、发生网络故障时,使用同一个client实例发出的多个请求,共享同一套路由切换逻辑 + private static final ResourceRouteClient ROUTE_CLIENT = new ResourceRouteClient( + Lists.newArrayList("A", "B", "C")); + + + + // 2、提供业务调用逻辑(简单示例:出参为String) + // 2.1、入参为:待请求目标域名targetResource、当前请求上下文信息simpleContext + // 2.2、根据需要补充自身业务处理逻辑 + // 2.3、根据情况抛出指定异常 + private static final ResourceInvocation INVOKE_LOGIC = + new ResourceInvocation() { + @Override + public String doInvoke(Resource targetResource, SimpleContext context) { + LOGGER.info("请求到:{}", targetResource.getResourceKey()); + // TODO 区分出客户端错误、业务处理错误等非网络故障,抛出指定异常 + //模拟逻辑:客户端错误(参数校验、转换等) + boolean clientError = false; + if (clientError) { + throw new YopClientException("xxx"); + } + + //模拟逻辑:业务处理错误(服务端返回了相关业务错误码) + boolean businessError = false; + if (businessError) { + throw new YopServiceException("xxx"); + } + + // 其他错误交给上层自动解析处理 + return "hello world"; + } + }; + + // 3、调用示例:使用路由客户端,传入业务逻辑,即可完成调用 + public static void main(String[] args) { + final String output = ROUTE_CLIENT.route(INVOKE_LOGIC); + assert "hello world".equals(output); + } + +} diff --git a/yop-java-sdk-router/src/test/java/com/yeepay/yop/sdk/router/example/MinimalExample.java b/yop-java-sdk-router/src/test/java/com/yeepay/yop/sdk/router/example/MinimalExample.java new file mode 100644 index 00000000..20b86695 --- /dev/null +++ b/yop-java-sdk-router/src/test/java/com/yeepay/yop/sdk/router/example/MinimalExample.java @@ -0,0 +1,70 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.example; + +import com.google.common.collect.Lists; +import com.yeepay.yop.sdk.exception.YopClientException; +import com.yeepay.yop.sdk.exception.YopServiceException; +import com.yeepay.yop.sdk.invoke.model.UriResource; +import com.yeepay.yop.sdk.router.SimpleContext; +import com.yeepay.yop.sdk.router.SimpleUriResourceBusinessLogic; +import com.yeepay.yop.sdk.router.SimpleUriResourceRouteClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * title: 最小化对接示例
            + * description: 描述
            + * Copyright: Copyright (c)2014
            + * Company: 易宝支付(YeePay)
            + * + * @author wdc + * @version 1.0.0 + * @since 2024/2/2 + */ +public class MinimalExample { + + private static final Logger LOGGER = LoggerFactory.getLogger(MinimalExample.class); + + // 1、初始化路由客户端 + // 1.1、构造每一个client时,请确保传入的一组域名之间关系对等,均能处理同类业务 + // 1.2、可以根据业务分类,合理拆分client,每个client,使用单例模式 + // 1.3、发生网络故障时,使用同一个client实例发出的多个请求,共享同一套路由切换逻辑 + private static final SimpleUriResourceRouteClient ROUTE_CLIENT = new SimpleUriResourceRouteClient( + Lists.newArrayList("https://baidu.com", "https://google.com")); + + // 2、提供业务调用逻辑(简单示例:出参为String) + // 2.1、入参为:待请求目标域名targetServer、当前请求上下文信息simpleContext + // 2.2、根据需要补充自身业务处理逻辑 + // 2.3、根据情况抛出指定异常 + private static final SimpleUriResourceBusinessLogic BUSINESS_LOGIC = + new SimpleUriResourceBusinessLogic() { + @Override + public String doBusiness(UriResource targetResource, SimpleContext context) { + LOGGER.info("请求到:{}", targetResource.getResource()); + // TODO 区分出客户端错误、业务处理错误等非网络故障,抛出指定异常 + //模拟逻辑:客户端错误(参数校验、转换等) + boolean clientError = false; + if (clientError) { + throw new YopClientException("xxx"); + } + + //模拟逻辑:业务处理错误(服务端返回了相关业务错误码) + boolean businessError = false; + if (businessError) { + throw new YopServiceException("xxx"); + } + + // 其他错误交给上层自动解析处理 + return "hello world"; + } + }; + + // 3、调用示例:使用路由客户端,传入业务逻辑,即可完成调用 + public static void main(String[] args) { + final String output = ROUTE_CLIENT.route(BUSINESS_LOGIC); + assert "hello world".equals(output); + } +} diff --git a/yop-java-sdk-router/src/test/java/com/yeepay/yop/sdk/router/yace/MockExample.java b/yop-java-sdk-router/src/test/java/com/yeepay/yop/sdk/router/yace/MockExample.java new file mode 100644 index 00000000..3e3b38e2 --- /dev/null +++ b/yop-java-sdk-router/src/test/java/com/yeepay/yop/sdk/router/yace/MockExample.java @@ -0,0 +1,96 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.router.yace; + +import com.google.common.collect.Lists; +import com.yeepay.yop.sdk.invoke.Router; +import com.yeepay.yop.sdk.invoke.model.UriResource; +import com.yeepay.yop.sdk.router.SimpleContext; +import com.yeepay.yop.sdk.router.SimpleUriResourceBusinessLogic; +import com.yeepay.yop.sdk.router.SimpleUriResourceRouter; +import com.yeepay.yop.sdk.router.YopRouterConstants; +import com.yeepay.yop.sdk.router.policy.RouterPolicyFactory; +import com.yeepay.yop.sdk.router.utils.InvokeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +/** + * title: 模拟故障示例
            + * description: 描述
            + * Copyright: Copyright (c)2014
            + * Company: 易宝支付(YeePay)
            + * + * @author wdc + * @version 1.0.0 + * @since 2024/1/29 + */ +public class MockExample { + + private static final Logger LOGGER = LoggerFactory.getLogger(MockExample.class); + + // 1、处理同类业务的,同一组域名,全局初始化一次即可 + private static final Router ROUTER = new SimpleUriResourceRouter<>("mock", + Lists.newArrayList("https://baidu.com", "https://google.com"), + RouterPolicyFactory.get(YopRouterConstants.ROUTER_POLICY_DEFAULT)); + + // 2、测试配置:模拟不同域名的故障率 + private static final Map MOCK_FAILURE_MAP; + static { + MOCK_FAILURE_MAP = new HashMap<>(); + MOCK_FAILURE_MAP.put("https://baidu.com", 100);//1%故障率 + MOCK_FAILURE_MAP.put("https://google.com", 2000);//20%故障率 + } + + // 3、提供业务调用逻辑(简单示例:出参String) + private static final SimpleUriResourceBusinessLogic BUSINESS_LOGIC = + new SimpleUriResourceBusinessLogic() { + @Override + public String doBusiness(UriResource targetResource, SimpleContext context) { + LOGGER.info("请求到:{}", targetResource); + return "hello world"; + } + }; + + // 4、mock域名故障示例,20个线程并发,10万笔请求 + public static void main(String[] args) throws InterruptedException { + // 模拟用户多线程并发请求 + final ExecutorService executorService = Executors.newFixedThreadPool(20); + for (int i = 0; i < 20; i++) { + executorService.submit(() -> { + for (int j = 0; j < 500; j++) { + try { + mockRequest(); + } catch (Exception e) { + FAIL_COUNT.addAndGet(1); + } finally { + TOTAL_COUNT.addAndGet(1); + } + } + }); + } + executorService.shutdown(); + final boolean finished = executorService.awaitTermination(1, TimeUnit.MINUTES); + LOGGER.info("总量:{}, 失败量:{}, 重试成功量:{}, finished:{}", TOTAL_COUNT.get(), FAIL_COUNT.get(), RETRY_SUCCESS_COUNT.get(), finished); + } + + private static void mockRequest() { + InvokeUtils.mockInvoke(BUSINESS_LOGIC, ROUTER, MOCK_FAILURE_MAP, RETRY_SUCCESS_COUNT); + // 异常分析器,用于分析当笔调用异常原因,是否可重试,是否为域名故障等等 + // 默认实现:基于用户配置的非熔断异常、可重试异常进行分析 + // 可根据需要自行扩展配置,或者调整实现 + } + + // mock结果统计 + private static final AtomicLong TOTAL_COUNT = new AtomicLong(); + private static final AtomicLong FAIL_COUNT = new AtomicLong(); + private static final AtomicLong RETRY_SUCCESS_COUNT = new AtomicLong(); +} diff --git a/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/example/YopSM2EncryptExample.java b/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/example/YopSM2EncryptExample.java new file mode 100644 index 00000000..32e3eee2 --- /dev/null +++ b/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/example/YopSM2EncryptExample.java @@ -0,0 +1,959 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.example; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Joiner; +import com.google.common.collect.*; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.client.utils.HttpClientUtils; +import org.apache.http.entity.InputStreamEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.ASN1Sequence; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.*; +import org.bouncycastle.crypto.signers.SM2Signer; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.custom.gm.SM2P256V1Curve; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.ISODateTimeFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.KeyGenerator; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.math.BigInteger; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.security.*; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.*; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * title: Sm2 加密示例
            + * description: 描述
            + * Copyright: Copyright (c)2014
            + * Company: 易宝支付(YeePay)
            + * + * @author wdc + * @version 1.0.0 + * @since 2023/9/27 + */ +public class YopSM2EncryptExample { + + private static final Logger LOGGER = LoggerFactory.getLogger(YopSM2EncryptExample.class); + static { + try { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + LOGGER.debug("BouncyCastleProvider added"); + } catch (Exception e) { + LOGGER.warn("error when add BouncyCastleProvider", e); + } + } + + private static final CloseableHttpClient httpClient = HttpClients.createDefault(); + private static final String APP_KEY = "sandbox_sm_10080041523"; + private static final BCECPrivateKey ISV_PRIVATE_KEY = string2PrivateKey("MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgr0mQ3/jjQOczWI6bnJFdqF4D/DFYHaqXftqXU/jGKpCgCgYIKoEcz1UBgi2hRANCAAQPpkZNnOnXTCXOIHJbfR+i6ea1QkM8HxkdO8KSWK8IgltHZxr5xlxiqR8inOREmmrxUQQagOH5i3oELWgXZz8G"); + private static final String YOP_PUBLIC_KEY_SERIAL_NO = "4052988765"; + private static final BCECPublicKey YOP_PUBLIC_KEY = string2PublicKey("MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE3OYUx7xC7Pzy6XFNDBAqte2tKKr/kn4N9duYeTuaSieslZD0hMoHPI3UWpni/fkpgtcluICawo7RRGtn8plSfw=="); + private static final String SERVER_ROOT = "https://sandbox.yeepay.com/yop-center"; + private static final String SLASH = "/"; + private static final String UNDER_LINE = "_"; + private static final String SEMICOLON = ";"; + private static final String EMPTY = ""; + + private static final String SM4_CBC_PKCS5PADDING = "SM4/CBC/PKCS5Padding"; + private static final byte[] SM4_IV = new SecureRandom().generateSeed(16); + private static final String STREAM = "stream"; + private static final String DEFAULT_ENCODING = "UTF-8"; + private static final String DEFAULT_YOP_PROTOCOL_VERSION = "yop-auth-v3"; + public static final String DEFAULT_AUTH_PREFIX_SM2 = "YOP-SM2-SM3"; + private static final String DEFAULT_DIGEST_ALG = "SM3"; + private static final Joiner QUERY_STRING_JOINER = Joiner.on('&'); + + // mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + // 我们采用C1C3C2 + // 根据mode不同,输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + private static final ThreadLocal engineThreadLocal = new ThreadLocal() { + @Override + protected SM2Engine initialValue() { + return new SM2Engine(SM2Engine.Mode.C1C3C2); + } + }; + + public static void main(String[] args) throws Exception { + // 加密会话密钥,可每笔调用都生成,也可以多笔公用一个,建议定时更换 + String encryptKey = encodeUrlSafeBase64(generateRandomKey()); + +// // get请求,form参数 + getFormExample(YopRequestMethod.GET, "/rest/v1.0/test/errorcode2", + YopRequestContentType.FORM_URL_ENCODE, encryptKey); + + // post请求,form参数 + postFormExample(YopRequestMethod.POST, "/rest/v1.0/test/old-api-mgr/find-api-by-uri", + YopRequestContentType.FORM_URL_ENCODE, encryptKey); + + // post请求,json参数 + postJsonExample(YopRequestMethod.POST, "/rest/v1.0/test-wdc/test/http-json/test", + YopRequestContentType.JSON, encryptKey); + + // get请求,form参数,下载文件 + downloadExample(YopRequestMethod.GET, "/yos/v1.0/test/test/ceph-download", + YopRequestContentType.FORM_URL_ENCODE, encryptKey); + } + + private static void getFormExample(YopRequestMethod requestMethod, String requestUri, + YopRequestContentType requestContentType, + String encryptKey) throws Exception { + // 请求参数 + String paramKey = "errorCode"; + String paramPlainValue = "000027", + paramEncryptValue = encryptParam(encryptKey, paramPlainValue); + Multimap formParams = ArrayListMultimap.create(); + // 参数不加密 +// formParams.put(paramKey, paramPlainValue); + + // 参数加密 + formParams.put(paramKey, paramEncryptValue); + Set encryptHeaders = Collections.emptySet(); + Set encryptParams = Sets.newHashSet(); + encryptParams.add(paramKey); + + // 请求头 + Map headers = buildHeaders(requestMethod, requestUri, formParams, + requestContentType, "", encryptKey, encryptHeaders, encryptParams); + + // 构造http请求 + HttpUriRequest request = buildHttpRequest(SERVER_ROOT + requestUri, + requestMethod, headers, formParams, null); + + // 发起http调用 + CloseableHttpResponse response = null; + try { + response = httpClient.execute(request); + handleResponse(YopRequestType.WEB, response, encryptKey); + } finally { + HttpClientUtils.closeQuietly(response); + } + } + + private static Map buildHeaders(YopRequestMethod httpMethod, String apiUri, + Multimap params, + YopRequestContentType contentType, String content, + String encryptKey, Set encryptHeaders, Set encryptParams) throws Exception { + Map headers = new HashMap<>(); + headers.put(YOP_SDK_LANGS, "java"); + headers.put(YOP_SDK_VERSION, "4.3.0"); + headers.put(YOP_APPKEY, APP_KEY); + headers.put(YOP_REQUEST_ID, UUID.randomUUID().toString()); + + // 加密头:请求参数不加密的情况, 也需设置加密头,用于响应结果加密 + headers.put(YOP_ENCRYPT, buildEncryptHeader(encryptHeaders, encryptParams, encryptKey)); + + // 摘要头 + headers.put(YOP_CONTENT_SM3, calculateContentHash(httpMethod, contentType, params, content)); + + // 签名头 + headers.put(AUTHORIZATION, signRequest(httpMethod, apiUri, headers, params)); + return headers; + } + + private static void postFormExample(YopRequestMethod requestMethod, String requestUri, + YopRequestContentType requestContentType, + String encryptKey) throws Exception { + + // 请求参数 + String paramKey = "apiUri"; + String paramPlainValue = "/rest/v1.0/test/product/find/lookatdoc", + paramEncryptValue = encryptParam(encryptKey, paramPlainValue); + Multimap params = ArrayListMultimap.create(); + // 参数不加密 +// params.put(paramKey, paramPlainValue); + + // 参数加密 + params.put(paramKey, paramEncryptValue); + Set encryptHeaders = Collections.emptySet(); + Set encryptParams = Sets.newHashSet(); + encryptParams.add(paramKey); + + // 请求头 + Map headers = buildHeaders(requestMethod, requestUri, params, + requestContentType, "", encryptKey, encryptHeaders, encryptParams); + + // 构造http请求 + HttpUriRequest request = buildHttpRequest(SERVER_ROOT + requestUri, + requestMethod, headers, params, null); + + // 发起http调用 + CloseableHttpResponse response = null; + try { + response = httpClient.execute(request); + handleResponse(YopRequestType.WEB, response, encryptKey); + } finally { + HttpClientUtils.closeQuietly(response); + } + } + + private static void postJsonExample(YopRequestMethod requestMethod, String requestUri, + YopRequestContentType requestContentType, + String encryptKey) throws Exception { + // 请求参数 + String plainJsonContent = "{\n" + + " \"arg1\" : {\n" + + " \"appId\" : \"app_1111111111\",\n" + + " \"customerNo\" : \"333333333\"\n" + + " },\n" + + " \"arg0\" : {\n" + + " \"string\" : \"hello\",\n" + + " \"array\" : [ \"test\" ]\n" + + " }\n" + + "}", + encryptJsonContent = encryptParam(encryptKey, plainJsonContent); + + String finalJsonContent + // 不加密 +// = plainJsonContent; + // 加密 + = encryptJsonContent; + Set encryptHeaders = Collections.emptySet(); + Set encryptParams = Sets.newHashSet(); + encryptParams.add("$");// 目前json推荐整体加密 + + // 请求头 + // 目前不允许json接口带有form参数,置空即可 + final ArrayListMultimap params = ArrayListMultimap.create(); + Map headers = buildHeaders(requestMethod, requestUri, params, + requestContentType, finalJsonContent, encryptKey, encryptHeaders, encryptParams); + + // 构造http请求 + HttpUriRequest request = buildHttpRequest(SERVER_ROOT + requestUri, + requestMethod, headers, params, finalJsonContent); + + // 发起http调用 + CloseableHttpResponse response = null; + try { + response = httpClient.execute(request); + handleResponse(YopRequestType.WEB, response, encryptKey); + } finally { + HttpClientUtils.closeQuietly(response); + } + } + + private static void downloadExample(YopRequestMethod requestMethod, String requestUri, + YopRequestContentType requestContentType, + String encryptKey) throws Exception { + // 根据实际情况来,也可能是post方法,请求报文可能是form,也可能是json,可参考其他方式的入参处理 + // 此处仅演示文件响应流的处理 + // 请求参数 + String paramKey = "fileName"; + String paramPlainValue = "wym-test.txt", + paramEncryptValue = encryptParam(encryptKey, paramPlainValue); + Multimap params = ArrayListMultimap.create(); + // 不加密 +// params.put(paramKey, paramPlainValue); + + // 加密 + params.put(paramKey, paramEncryptValue); + Set encryptHeaders = Collections.emptySet(); + Set encryptParams = Sets.newHashSet(); + encryptParams.add(paramKey); + + // 请求头 + Map headers = buildHeaders(requestMethod, requestUri, params, + requestContentType, "", encryptKey, encryptHeaders, encryptParams); + + // 构造http请求 + HttpUriRequest request = buildHttpRequest(SERVER_ROOT + requestUri, + requestMethod, headers, params, ""); + + // 发起请求 + CloseableHttpResponse response = null; + try { + response = httpClient.execute(request); + handleResponse(YopRequestType.FILE_DOWNLOAD, response, encryptKey); + } finally { + HttpClientUtils.closeQuietly(response); + } + } + + private static final Set DEFAULT_HEADERS_TO_SIGN = Sets.newHashSet(); + private static final Joiner HEADER_JOINER = Joiner.on('\n'); + private static final Joiner SIGNED_HEADER_STRING_JOINER = Joiner.on(';'); + + private static final String YOP_SDK_VERSION = "x-yop-sdk-version"; + private static final String YOP_SDK_LANGS = "x-yop-sdk-langs"; + private static final String YOP_REQUEST_ID = "x-yop-request-id"; + private static final String YOP_APPKEY = "x-yop-appkey"; + private static final String YOP_CONTENT_SM3 = "x-yop-content-sm3"; + private static final String YOP_ENCRYPT = "x-yop-encrypt"; + private static final String AUTHORIZATION = "Authorization"; + private static final String CONTENT_DISPOSITION = "Content-Disposition"; + private static final String CONTENT_TYPE = "Content-Type"; + + static { + DEFAULT_HEADERS_TO_SIGN.add(YOP_REQUEST_ID); + DEFAULT_HEADERS_TO_SIGN.add(YOP_APPKEY); + DEFAULT_HEADERS_TO_SIGN.add(YOP_CONTENT_SM3); + DEFAULT_HEADERS_TO_SIGN.add(YOP_ENCRYPT); + } + + private static String signRequest(YopRequestMethod requestMethod, String requestUri, + Map headers, Multimap params) throws Exception { + // A.构造认证字符串 + String authString = buildAuthString(); + + // B.获取规范请求串 + SortedMap headersToSign = getHeadersToSign(headers, DEFAULT_HEADERS_TO_SIGN); + String canonicalRequest = buildCanonicalRequest(requestMethod, requestUri, params, authString, headersToSign); + + // C.计算签名 + String signature = encodeUrlSafeBase64(sign(canonicalRequest.getBytes(DEFAULT_ENCODING))) + "$" + DEFAULT_DIGEST_ALG; + + // D.添加认证头 + return buildAuthHeader(authString, headersToSign, signature); + } + + private static String buildAuthHeader(String authString, + SortedMap headersToSign, + String signature) { + String signedHeaders = SIGNED_HEADER_STRING_JOINER.join(headersToSign.keySet()); + signedHeaders = signedHeaders.trim().toLowerCase(); + return DEFAULT_AUTH_PREFIX_SM2 + " " + authString + "/" + signedHeaders + "/" + signature; + } + + // SM2 + public static final SM2P256V1Curve CURVE = new SM2P256V1Curve(); + public final static BigInteger SM2_ECC_N = CURVE.getOrder(); + public final static BigInteger SM2_ECC_H = CURVE.getCofactor(); + public final static BigInteger SM2_ECC_GX = new BigInteger( + "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", 16); + public final static BigInteger SM2_ECC_GY = new BigInteger( + "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0", 16); + public static final ECPoint G_POINT = CURVE.createPoint(SM2_ECC_GX, SM2_ECC_GY); + public static final ECDomainParameters DOMAIN_PARAMS = new ECDomainParameters(CURVE, G_POINT, + SM2_ECC_N, SM2_ECC_H); + public static final int CURVE_LEN = getCurveLength(DOMAIN_PARAMS); + public static int getCurveLength(ECDomainParameters domainParams) { + return (domainParams.getCurve().getFieldSize() + 7) / 8; + } + + private static byte[] sign(byte[] data) { + try { + ECParameterSpec parameterSpec = ISV_PRIVATE_KEY.getParameters(); + ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(), + parameterSpec.getN(), parameterSpec.getH()); + ECPrivateKeyParameters priKeyParameters = new ECPrivateKeyParameters(ISV_PRIVATE_KEY.getD(), domainParameters); + //der编码后的签名值 + byte[] derSign = sign(priKeyParameters, null, data); + + //der解码过程 + ASN1Sequence as = DERSequence.getInstance(derSign); + byte[] rBytes = ((ASN1Integer) as.getObjectAt(0)).getValue().toByteArray(); + byte[] sBytes = ((ASN1Integer) as.getObjectAt(1)).getValue().toByteArray(); + //由于大数的补0规则,所以可能会出现33个字节的情况,要修正回32个字节 + rBytes = fixToCurveLengthBytes(rBytes); + sBytes = fixToCurveLengthBytes(sBytes); + byte[] rawSign = new byte[rBytes.length + sBytes.length]; + System.arraycopy(rBytes, 0, rawSign, 0, rBytes.length); + System.arraycopy(sBytes, 0, rawSign, rBytes.length, sBytes.length); + return rawSign; + } catch (Exception e) { + throw new RuntimeException("SystemError, Sign Fail, key:" + ISV_PRIVATE_KEY + ", ex:", e); + } + } + + private static byte[] fixToCurveLengthBytes(byte[] src) { + if (src.length == CURVE_LEN) { + return src; + } + + byte[] result = new byte[CURVE_LEN]; + if (src.length > CURVE_LEN) { + System.arraycopy(src, src.length - result.length, result, 0, result.length); + } else { + System.arraycopy(src, 0, result, result.length - src.length, src.length); + } + return result; + } + + /** + * 签名 + * + * @param priKeyParameters 私钥 + * @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() + * @param srcData 源数据 + * @return DER编码后的签名值 + * @throws CryptoException + */ + public static byte[] sign(ECPrivateKeyParameters priKeyParameters, byte[] withId, byte[] srcData) + throws CryptoException { + SM2Signer signer = new SM2Signer(); + CipherParameters param; + ParametersWithRandom pwr = new ParametersWithRandom(priKeyParameters, new SecureRandom()); + if (withId != null) { + param = new ParametersWithID(pwr, withId); + } else { + param = pwr; + } + signer.init(true, param); + signer.update(srcData, 0, srcData.length); + return signer.generateSignature(); + } + + private static BCECPrivateKey string2PrivateKey(String priKey) { + try { + return (BCECPrivateKey)KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME).generatePrivate( + new PKCS8EncodedKeySpec(decodeBase64(priKey))); + } catch (Exception e) { + throw new RuntimeException("ConfigProblem, IsvPrivateKey ParseFail, value:" + priKey + ", ex:", e); + } + } + + private static byte[] decodeBase64(String input) { + return Base64.decodeBase64(input); + } + + private static String buildCanonicalRequest(YopRequestMethod httpMethod, String apiUri, Multimap params, + String authString, + SortedMap headersToSign) { + String canonicalQueryString; + if (YopRequestMethod.GET.equals(httpMethod) && null != params) { + canonicalQueryString = getCanonicalQueryString(params, true); + } else { + canonicalQueryString = "";// post from 与json时,均为空,此处先简单处理 + } + String canonicalHeaders = getCanonicalHeaders(headersToSign); + + String canonicalURI = getCanonicalURIPath(apiUri); + return authString + "\n" + + httpMethod.name() + "\n" + + canonicalURI + "\n" + + canonicalQueryString + "\n" + + canonicalHeaders; + } + + private static String getCanonicalURIPath(String path) { + if (path == null) { + return "/"; + } else if (path.startsWith("/")) { + return normalizePath(path); + } else { + return "/" + normalizePath(path); + } + } + + private static String normalizePath(String path) { + return normalize(path).replace("%2F", "/"); + } + + private static SortedMap getHeadersToSign(Map headers, Set headersToSign) { + SortedMap ret = Maps.newTreeMap(); + if (headersToSign != null) { + Set tempSet = Sets.newHashSet(); + for (String header : headersToSign) { + tempSet.add(header.trim().toLowerCase()); + } + headersToSign = tempSet; + } + for (Map.Entry entry : headers.entrySet()) { + String key = entry.getKey(); + if (entry.getValue() != null && !entry.getValue().isEmpty()) { + if ((headersToSign != null && headersToSign.contains(key.toLowerCase()) + && !AUTHORIZATION.equalsIgnoreCase(key))) { + ret.put(key, entry.getValue()); + } + } + } + return ret; + } + + private static String getCanonicalHeaders(SortedMap headers) { + if (headers.isEmpty()) { + return ""; + } + + List headerStrings = Lists.newArrayList(); + for (Map.Entry entry : headers.entrySet()) { + String key = entry.getKey(); + if (key == null) { + continue; + } + String value = entry.getValue(); + if (value == null) { + value = ""; + } + headerStrings.add(normalize(key.trim().toLowerCase()) + ':' + normalize(value.trim())); + } + Collections.sort(headerStrings); + + return HEADER_JOINER.join(headerStrings); + } + + private static final DateTimeFormatter alternateIso8601DateFormat = + ISODateTimeFormat.dateTimeNoMillis().withZone(DateTimeZone.UTC); + + private static String buildAuthString() { + Date timestamp = new Date(); + return DEFAULT_YOP_PROTOCOL_VERSION + "/" + + APP_KEY + "/" + + alternateIso8601DateFormat.print(new DateTime(timestamp)) + "/" + + "1800"; + } + + private static String calculateContentHash(String jsonContent) throws Exception { + InputStream contentStream = getContentStream(jsonContent); + return Hex.encodeHexString((digest(contentStream))); + } + + private static String calculateContentHash(YopRequestMethod requestMethod, YopRequestContentType requestContentType, + Multimap params, String content) throws Exception { + String digestSource; + + if (requestMethod.equals(YopRequestMethod.GET)) { + digestSource = ""; + } else if (requestMethod.equals(YopRequestMethod.POST) + && requestContentType.equals(YopRequestContentType.JSON)) { + digestSource = content; + } else { + digestSource = getCanonicalQueryString(params, true); + } + InputStream contentStream = getContentStream(digestSource); + return Hex.encodeHexString((digest(contentStream))); + } + + private static ByteArrayInputStream getContentStream(Multimap params) throws Exception { + return getContentStream(getCanonicalQueryString(params, true)); + } + + private static ByteArrayInputStream getContentStream(String paramStr) throws Exception { + byte[] bytes; + if (StringUtils.isEmpty(paramStr)) { + bytes = new byte[0]; + } else { + bytes = paramStr.getBytes(DEFAULT_ENCODING); + } + return new ByteArrayInputStream(bytes); + } + + public static String getCanonicalQueryString(Multimap params, boolean forSignature) { + Map> parameters; + if (null == params || (parameters = params.asMap()).isEmpty()) { + return ""; + } + + List parameterStrings = Lists.newArrayList(); + for (Map.Entry> entry : parameters.entrySet()) { + if (forSignature && AUTHORIZATION.equalsIgnoreCase(entry.getKey())) { + continue; + } + String key = entry.getKey(); + checkNotNull(key, "parameter key should not be null"); + Collection value = entry.getValue(); + if (value == null) { + if (forSignature) { + parameterStrings.add(normalize(key) + '='); + } else { + parameterStrings.add(normalize(key)); + } + } else { + for (String item : value) { + parameterStrings.add(normalize(key) + '=' + normalize(item)); + } + } + } + Collections.sort(parameterStrings); + + return QUERY_STRING_JOINER.join(parameterStrings); + } + + private static final BitSet URI_UNRESERVED_CHARACTERS = new BitSet(); + private static final String[] PERCENT_ENCODED_STRINGS = new String[256]; + + static { + /* + * StringBuilder pattern = new StringBuilder(); + * + * pattern .append(Pattern.quote("+")) .append("|") .append(Pattern.quote("*")) .append("|") + * .append(Pattern.quote("%7E")) .append("|") .append(Pattern.quote("%2F")); + * + * ENCODED_CHARACTERS_PATTERN = Pattern.compile(pattern.toString()); + */ + for (int i = 'a'; i <= 'z'; i++) { + URI_UNRESERVED_CHARACTERS.set(i); + } + for (int i = 'A'; i <= 'Z'; i++) { + URI_UNRESERVED_CHARACTERS.set(i); + } + for (int i = '0'; i <= '9'; i++) { + URI_UNRESERVED_CHARACTERS.set(i); + } + URI_UNRESERVED_CHARACTERS.set('-'); + URI_UNRESERVED_CHARACTERS.set('.'); + URI_UNRESERVED_CHARACTERS.set('_'); + URI_UNRESERVED_CHARACTERS.set('~'); + + for (int i = 0; i < PERCENT_ENCODED_STRINGS.length; ++i) { + PERCENT_ENCODED_STRINGS[i] = String.format("%%%02X", i); + } + } + + public static String normalize(String value) { + try { + StringBuilder builder = new StringBuilder(); + for (byte b : value.getBytes(DEFAULT_ENCODING)) { + if (URI_UNRESERVED_CHARACTERS.get(b & 0xFF)) { + builder.append((char) b); + } else { + builder.append(PERCENT_ENCODED_STRINGS[b & 0xFF]); + } + } + return builder.toString(); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private static byte[] digest(InputStream input) { + try { + MessageDigest md = MessageDigest.getInstance(DEFAULT_DIGEST_ALG, BouncyCastleProvider.PROVIDER_NAME); + DigestInputStream digestInputStream = new DigestInputStream(input, md); + byte[] buffer = new byte[1024]; + while (digestInputStream.read(buffer) > -1) {} + return digestInputStream.getMessageDigest().digest(); + } catch (Exception e) { + throw new RuntimeException("SystemError, Digest Fail, alg:" + DEFAULT_DIGEST_ALG + ", ex:", e); + } + } + + /** + * 加密协议头(请求): + * + * yop-encrypt-v1/{服务端证书序列号}/{密钥类型(必填)}_{分组模式(必填)}_{填充算法(必填)}/{加密密钥值(必填)}/{IV}{;}{附加信息}/{客户端支持的大参数加密模式(必填)}/{encryptHeaders}/{encryptParams} + */ + public static String buildEncryptHeader(Set encryptHeaders, Set encryptParams, + String sm4Key) throws Exception { + + return "yop-encrypt-v1" + SLASH + + YOP_PUBLIC_KEY_SERIAL_NO + SLASH + //平台SM2证书序列号 + StringUtils.replace(SM4_CBC_PKCS5PADDING, SLASH, UNDER_LINE) + SLASH + + encodeUrlSafeBase64(encryptKey(decodeBase64(sm4Key))) + SLASH + + encodeUrlSafeBase64(SM4_IV) + SEMICOLON + EMPTY + SLASH + + STREAM + SLASH + + StringUtils.join(encryptHeaders, SEMICOLON) + SLASH + + encodeUrlSafeBase64(StringUtils.join(encryptParams, SEMICOLON).getBytes(DEFAULT_ENCODING)); + } + + public static BCECPublicKey string2PublicKey(String pubKey) { + try { + return (BCECPublicKey) KeyFactory.getInstance("EC").generatePublic( + new X509EncodedKeySpec(decodeBase64(pubKey))); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new RuntimeException("ConfigProblem, YopPublicKey ParseFail, value:" + pubKey + ", ex:", e); + } + } + + private static byte[] encryptKey(byte[] sm4Key) { + try { + SM2Engine engine = engineThreadLocal.get(); + ECPublicKeyParameters pubKeyParameters = convertPublicKeyToParameters(); + ParametersWithRandom pwr = new ParametersWithRandom(pubKeyParameters, new SecureRandom()); + engine.init(true, pwr); + return engine.processBlock(sm4Key, 0, sm4Key.length); + } catch (Throwable e) { + throw new RuntimeException("SystemError, Encrypt Fail, publicKey:" + YOP_PUBLIC_KEY, e); + } + } + + private static ECPublicKeyParameters convertPublicKeyToParameters() { + ECParameterSpec parameterSpec = YOP_PUBLIC_KEY.getParameters(); + ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(), + parameterSpec.getN(), parameterSpec.getH()); + return new ECPublicKeyParameters(YOP_PUBLIC_KEY.getQ(), domainParameters); + } + + private static String encryptParam(String sm4Key, String plain) throws Exception { + final Cipher cipher = getInitializedCipher(Cipher.ENCRYPT_MODE, sm4Key); + return encodeUrlSafeBase64(cipher.doFinal(plain.getBytes("UTF-8"))); + } + + private static String decryptParam(String sm4Key, String encryptContent) throws Exception { + final Cipher cipher = getInitializedCipher(Cipher.DECRYPT_MODE, sm4Key); + return new String(cipher.doFinal(decodeBase64(encryptContent)), "UTF-8"); + } + + private static InputStream decryptStream(String sm4Key, InputStream encryptStream) throws Exception { + final Cipher cipher = getInitializedCipher(Cipher.DECRYPT_MODE, sm4Key); + return new CipherInputStream(encryptStream, cipher); + } + + private static Cipher getInitializedCipher(int mode, String sm4Key) { + try { + byte[] key = decodeBase64(sm4Key); + Cipher cipher = Cipher.getInstance(SM4_CBC_PKCS5PADDING, BouncyCastleProvider.PROVIDER_NAME); + IvParameterSpec spec = new IvParameterSpec(SM4_IV); + Key secretKey = new SecretKeySpec(key, "SM4"); + cipher.init(mode, secretKey, spec); + return cipher; + } catch (Throwable throwable) { + throw new RuntimeException("error happened when initialize cipher", throwable); + } + } + + private static String encodeUrlSafeBase64(byte[] input) { + return Base64.encodeBase64URLSafeString(input); + } + + private static byte[] generateRandomKey() throws NoSuchAlgorithmException, NoSuchProviderException { + //实例化 + KeyGenerator generator = KeyGenerator.getInstance("SM4", BouncyCastleProvider.PROVIDER_NAME); + //设置密钥长度,SM4算法目前只支持128位(即密钥16字节) + generator.init(128); + //生成密钥 + return generator.generateKey().getEncoded(); + } + + protected static HttpUriRequest buildHttpRequest(String requestUrl, YopRequestMethod requestMethod, + Map headers, Multimap params, + String content) throws UnsupportedEncodingException { + RequestBuilder requestBuilder; + if (YopRequestMethod.POST == requestMethod) { + requestBuilder = RequestBuilder.post(); + } else if (YopRequestMethod.GET == requestMethod) { + requestBuilder = RequestBuilder.get(); + } else { + throw new RuntimeException("unsupported http method"); + } + requestBuilder.setUri(requestUrl); + + // header + for (Map.Entry entry : headers.entrySet()) { + requestBuilder.addHeader(entry.getKey(), entry.getValue()); + } + + // body + try { + if (null != params) { + for (Map.Entry> entry : params.asMap().entrySet()) { + String paramKey = entry.getKey(); + for (String value : entry.getValue()) { + requestBuilder.addParameter(paramKey, URLEncoder.encode(value, DEFAULT_ENCODING)); + } + } + } + } catch (IOException ex) { + throw new RuntimeException("unable to create http request.", ex); + } + if (YopRequestMethod.GET.equals(requestMethod)) { + requestBuilder.addHeader(CONTENT_TYPE, "application/x-www-form-urlencoded;charset=UTF-8"); + return requestBuilder.build(); + } + // json 请求 + if (StringUtils.isNotBlank(content)) { + final byte[] contentBytes = content.getBytes(DEFAULT_ENCODING); + requestBuilder.setEntity(new InputStreamEntity(new ByteArrayInputStream(contentBytes), contentBytes.length)); + requestBuilder.addHeader(CONTENT_TYPE, "application/json;charset=UTF-8"); + } + return requestBuilder.build(); + } + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static void handleResponse(YopRequestType requestType, + CloseableHttpResponse httpResponse, + String encryptKey) throws Exception { + // header + Map headers = Maps.newHashMap(); + for (Header header : httpResponse.getAllHeaders()) { + headers.put(header.getName(), header.getValue()); + } + String encryptHeader = headers.get("x-yop-encrypt"),// 加密头 + signHeader = headers.get("x-yop-sign"); //签名头 + + boolean isEncryptResponse = StringUtils.isNotBlank(encryptHeader); + + // body + HttpEntity entity = httpResponse.getEntity(); + int statusCode = httpResponse.getStatusLine().getStatusCode(); + if (statusCode / 100 == HttpStatus.SC_OK / 100 && statusCode != HttpStatus.SC_NO_CONTENT) { + //not a error + if (null != entity && entity.getContent() != null) { + if (isJsonResponse(httpResponse)) { + String content = IOUtils.toString(entity.getContent(), DEFAULT_ENCODING); + System.out.println("Request success, response:" + content); + final JsonNode bizData = OBJECT_MAPPER.readTree(content).get("result"); + if (isEncryptResponse) { + System.out.println("Response decrypt success, bizData:" + decryptParam(encryptKey, bizData.asText())); + } + return; + } else if (YopRequestType.FILE_DOWNLOAD.equals(requestType) || isDownloadResponse(httpResponse)) { + InputStream fileContent = entity.getContent(); + if (isEncryptResponse) { + fileContent = decryptStream(encryptKey, fileContent); + System.out.println("Response file decrypt success"); + } + final File file = saveFile(fileContent, headers); + System.out.println("Request success, file downloaded:" + file); + return; + } else { + throw new RuntimeException("Response Error, contentType:" + httpResponse.getEntity().getContentType()); + } + } else { + throw new RuntimeException("Response Error, contentType:" + httpResponse.getEntity().getContentType()); + } + } else if (statusCode >= HttpStatus.SC_INTERNAL_SERVER_ERROR && statusCode != HttpStatus.SC_BAD_GATEWAY) { + if (entity.getContent() != null) { + String content = IOUtils.toString(entity.getContent(), DEFAULT_ENCODING); + System.out.println("Request Fail, response:" + content); + return; + } else { + throw new RuntimeException("ResponseError, Empty Content, httpStatusCode:" + httpResponse.getStatusLine().getStatusCode()); + } + } else if (statusCode == HttpStatus.SC_BAD_GATEWAY || statusCode == HttpStatus.SC_NOT_FOUND) { + throw new RuntimeException("Response Error, statusCode:" + statusCode); + } + throw new RuntimeException("ReqParam Illegal, Bad Request, statusCode:" + statusCode); + } + + /** + * 保存文件到本地 + * + * @param content + * @param headers + * @return File + */ + public static File saveFile(InputStream content, Map headers) { + InputStream fileContent = content; + try { + String filePrefix = "yos-", fileSuffix = ".tmp"; + final String contentDisposition = headers.get(CONTENT_DISPOSITION); + try { + String fileName = getFileNameFromHeader(contentDisposition); + if (StringUtils.isNotBlank(fileName)) { + final String[] split = fileName.split("\\."); + if (split.length == 2) { + if (StringUtils.length(split[0]) > 3) { + filePrefix = split[0]; + } + if (StringUtils.isNotBlank(split[1])) { + fileSuffix = "." + split[1]; + } + } + } + } catch (Exception e) { + System.out.println(("parse Content-Disposition fail, value:" + contentDisposition + ", ex:" + e)); + } + File tmpFile = File.createTempFile(filePrefix, fileSuffix); + IOUtils.copy(fileContent, Files.newOutputStream(tmpFile.toPath())); + return tmpFile; + } catch (Throwable ex) { + throw new RuntimeException("fail to save file"); + } finally { + closeQuietly(fileContent); + } + } + + public static void closeQuietly(Closeable closeable) { + try { + if (null != closeable) { + closeable.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static String getFileNameFromHeader(String contentDisposition) { + if (StringUtils.isBlank(contentDisposition)) { + return null; + } + + final String[] parts = contentDisposition.split( "filename="); + if (parts.length == 2) { + return StringUtils.trim(parts[1]); + } + return null; + } + + private static final String CONTENT_TYPE_JSON = "application/json"; + private static final String CONTENT_TYPE_STREAM = "application/octet-stream"; + + private static boolean isJsonResponse(CloseableHttpResponse response) { + return null != response.getEntity() && StringUtils.startsWith(response.getEntity().getContentType().getValue(), CONTENT_TYPE_JSON); + } + + private static boolean isDownloadResponse(CloseableHttpResponse response) { + return null != response.getEntity() && StringUtils.startsWith(response.getEntity().getContentType().getValue(), CONTENT_TYPE_STREAM); + } + + public enum YopRequestMethod { + GET, + POST + } + + public enum YopRequestType { + WEB, + FILE_DOWNLOAD, + MULTI_FILE_UPLOAD, + } + + private static final String YOP_HTTP_CONTENT_TYPE_JSON = "application/json"; + private static final String YOP_HTTP_CONTENT_TYPE_FORM = "application/x-www-form-urlencoded"; + private static final String YOP_HTTP_CONTENT_TYPE_MULTIPART_FORM = "multipart/form-data"; + private static final String YOP_HTTP_CONTENT_TYPE_STREAM = "application/octet-stream"; + private static final String YOP_HTTP_CONTENT_TYPE_TEXT = "text/plain;charset=UTF-8"; + + public enum YopRequestContentType { + FORM_URL_ENCODE(YOP_HTTP_CONTENT_TYPE_FORM), + MULTIPART_FORM(YOP_HTTP_CONTENT_TYPE_MULTIPART_FORM), + JSON(YOP_HTTP_CONTENT_TYPE_JSON), + OCTET_STREAM(YOP_HTTP_CONTENT_TYPE_STREAM), + TEXT_PLAIN(YOP_HTTP_CONTENT_TYPE_TEXT); + + private String value; + + YopRequestContentType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/example/YopSm2CallbackExample.java b/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/example/YopSm2CallbackExample.java new file mode 100644 index 00000000..e868da73 --- /dev/null +++ b/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/example/YopSm2CallbackExample.java @@ -0,0 +1,390 @@ +/* + * Copyright: Copyright (c)2014 + * Company: 易宝支付(YeePay) + */ +package com.yeepay.yop.sdk.example; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.yeepay.yop.sdk.utils.Encodes; +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.engines.SM2Engine; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.params.ParametersWithID; +import org.bouncycastle.crypto.signers.SM2Signer; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.*; + + +/** + * title: Sm2回调处理
            + * description: 描述
            + * Copyright: Copyright (c)2014
            + * Company: 易宝支付(YeePay)
            + * + * @author wdc + * @version 1.0.0 + * @since 2023/10/23 + */ +public class YopSm2CallbackExample { + + private static final Logger LOGGER = LoggerFactory.getLogger(YopSm2CallbackExample.class); + + // + private static Map isvPrivateKeyMap; + + // + private static Map yopPublicKeyMap; + + // 初始化国密算法类库,准备好商户密钥、平台公钥 + static { + try { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + LOGGER.debug("BouncyCastleProvider added"); + } catch (Exception e) { + LOGGER.warn("error when add BouncyCastleProvider", e); + } + isvPrivateKeyMap = new HashMap<>(); + isvPrivateKeyMap.put("app_15958159879157110009", string2PrivateKey("MIICBQIBADCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////v////////////////////8AAAAA//////////8wRAQg/////v////////////////////8AAAAA//////////wEICjp+p6dn140TVqeS89lCafzl4n1FauPkt28vUFNlA6TBEEEMsSuLB8ZgRlfmQRGajnJlI/jC7/yZgvhcVpFiTNMdMe8Nzai9PZ3nFm9zuNraSFT0KmHfMYqR0AC3zLlITnwoAIhAP////7///////////////9yA99rIcYFK1O79Ak51UEjAgEBBIIBDzCCAQsCAQEEICvBlu1mNV6jIA8FdkKlRSga9cwXa0m+IBx9ERwtO1ZcoIHjMIHgAgEBMCwGByqGSM49AQECIQD////+/////////////////////wAAAAD//////////zBEBCD////+/////////////////////wAAAAD//////////AQgKOn6np2fXjRNWp5Lz2UJp/OXifUVq4+S3by9QU2UDpMEQQQyxK4sHxmBGV+ZBEZqOcmUj+MLv/JmC+FxWkWJM0x0x7w3NqL09necWb3O42tpIVPQqYd8xipHQALfMuUhOfCgAiEA/////v///////////////3ID32shxgUrU7v0CTnVQSMCAQE=")); + + yopPublicKeyMap = new HashMap<>(); + // 此处key为16进制转换后的值,对应10进制的275568425014 + yopPublicKeyMap.put("4029287836", string2PublicKey("MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEd8OsYO4yIFNboF68nk1Yl9zquW/OuJSjGLz8Yu7ldV3ro9pGb5g079hWGeEZ+DqaHex3YzP7dVuQ9KV81pRa3w==")); + + } + + // mode 指定密文结构,旧标准的为C1C2C3,新的[《SM2密码算法使用规范》 GM/T 0009-2012]标准为C1C3C2 + // 我们采用C1C3C2 + // 根据mode不同,输出的密文C1C2C3排列顺序不同。C1为65字节第1字节为压缩标识,这里固定为0x04,后面64字节为xy分量各32字节。C3为32字节。C2长度与原文一致。 + private static final ThreadLocal engineThreadLocal = new ThreadLocal() { + @Override + protected SM2Engine initialValue() { + return new SM2Engine(SM2Engine.Mode.C1C3C2); + } + }; + + private static final String UTF_8 = "UTF-8"; + private static final String LF = "\n"; + private static final String SPACE = " "; + private static final String DASH_LINE = "-"; + private static final String SLASH = "/"; + private static final String SEMICOLON = ";"; + private static final String COLON = ":"; + private static final String ENCRYPT_ALG = "SM4/CBC/PKCS5Padding"; + + // TODO 解析易宝发起的http请求(post json格式),会得到如下请求头、请求体(模拟测试数据) + + // 请求地址: 商户提供的回调地址 + private static String reqUri = "/xxx"; + // 请求头 + private static Map reqHeaders; + // 请求体 + private static String reqBody = "EZgjreIx_ZW-gIM2NtHoKSk2sMQ35eolEjZ76XPcCtEqbXRfv77Z2eUJHhfoN4TcAZjPykzzDJ2pH7FC8xbhXw"; + + static { + reqHeaders = Maps.newHashMap(); + reqHeaders.put("Authorization", "YOP-SM2-SM3 yop-auth-v3/app_15958159879157110009/2022-05-17T02:33:24Z/1800/content-length;content-type;x-yop-appkey;x-yop-content-sm3;x-yop-encrypt;x-yop-request-id/fuKri2WjLqmr_gKInxstDLn6zz9XPR518TKK2iF9sMROSEcWllrAxApO4ldPrjNPPc0UsAbitCxnumA3-CJt8A$SM3"); + reqHeaders.put("x-yop-content-sm3", "eaa5391d992058fce198590bcfb7f7a4533d8ea311ac97c964513d7da080351f"); + reqHeaders.put("x-yop-encrypt", "yop-encrypt-v1/275568425014/SM4_CBC_PKCS5Padding/BEmuYglu6Y0M5jkqZN_yssw137rWIiaB0ToXJXsQytFDSwau5sMGnPKCnEe-2Bgg_ThowDqOdcGnsvzATS4ol4rk_fSPebBPMvnjyWZk5hpMYPJxCCEJ80MgHYE3pBt50LulUCaCYhYDyf4VO5rYyjQ/u3E2PbLDjeiZi9IeQm7xyA/stream//JA"); + reqHeaders.put("x-yop-request-id", "wuTest1652754804319"); + reqHeaders.put("x-yop-sign-serial-no", "275568425014"); + reqHeaders.put("x-yop-appkey", "app_15958159879157110009"); + reqHeaders.put("Content-Type", "application/json"); + } + + public static void main(String[] args) throws Exception { + // TODO 从易宝回调请求中解析 + final Map headers = reqHeaders; + Map canonicalHeaders = new HashMap<>(); + headers.forEach((k,v) -> canonicalHeaders.put(k.trim().toLowerCase(), v)); + + // 解析认证头 + String authorization = canonicalHeaders.get("authorization"); + String[] protocol = authorization.split(SPACE); + String[] authorizationHeaders = StringUtils.split(protocol[1], SLASH); + + String signature = authorizationHeaders[5].split("\\$")[0]; + String platformSerialNo = canonicalHeaders.get("x-yop-sign-serial-no"); + if (StringUtils.isBlank(platformSerialNo)) { + platformSerialNo = canonicalHeaders.get("x-yop-serial-no"); + } + platformSerialNo = parseToHex(platformSerialNo); + + // 构造待认证字符串 + String canonicalReqString = buildCanonicalReqString(reqUri, canonicalHeaders); + + // 验证签名 + verifySign(canonicalReqString, signature, yopPublicKeyMap.get(platformSerialNo)); + + // TODO 从易宝请求中解析 + String jsonReqBody = reqBody; + String yopEncrypt = canonicalHeaders.get("x-yop-encrypt"); + // 解析加密头 + String[] items = StringUtils.splitPreserveAllTokens(yopEncrypt, SLASH); + String iv = null; + if (StringUtils.isNotBlank(items[4])) { + String[] iv_AAD = StringUtils.splitPreserveAllTokens(items[4], SEMICOLON); + iv = iv_AAD[0]; + } + String appKey = canonicalHeaders.get("x-yop-appkey"); + String encryptedCredentialStr = items[3]; + assert StringUtils.isNoneBlank(appKey, encryptedCredentialStr); + // 解密会话密钥 + byte[] encryptedCredentialBytes = decodeBase64(encryptedCredentialStr); + byte[] decryptedSecretKey = decryptKey(encryptedCredentialBytes, isvPrivateKeyMap.get(appKey)); + assert null != decryptedSecretKey; + + // 解密业务参数 + final String bizContent = decryptBizContent(jsonReqBody, decryptedSecretKey, iv); + LOGGER.info(bizContent); + assert "{\"appId\":\"app_1595815987915711\",\"alias\":\"alias_0329\"}".equals(bizContent); + } + + private static byte[] decryptKey(byte[] encryptedCredentialBytes, BCECPrivateKey isvPrivateKey) { + try { + SM2Engine engine = engineThreadLocal.get(); + ECPrivateKeyParameters priKeyParameters = convertPrivateKeyToParameters(isvPrivateKey); + engine.init(false, priKeyParameters); + return engine.processBlock(encryptedCredentialBytes, 0, encryptedCredentialBytes.length); + } catch (Exception e) { + throw new RuntimeException("error when decrypt work credential, ", e); + } + } + + private static String decryptBizContent(String jsonReqBody, byte[] decryptedSecretKey, String iv) { + try { + final Cipher cipher = Cipher.getInstance(ENCRYPT_ALG, BouncyCastleProvider.PROVIDER_NAME); + Key sm4Key = new SecretKeySpec(decryptedSecretKey, "SM4"); + if (StringUtils.isNotBlank(iv)) { + byte[] ivBytes = Encodes.decodeBase64(iv); + IvParameterSpec ivParameterSpec = new IvParameterSpec(ivBytes); + cipher.init(Cipher.DECRYPT_MODE, sm4Key, ivParameterSpec); + } else { + cipher.init(Cipher.DECRYPT_MODE, sm4Key); + } + return new String(cipher.doFinal(decodeBase64(jsonReqBody)), UTF_8); + } catch (Exception e) { + throw new RuntimeException("error when decrypt bizContent, ", e); + } + } + + private static ECPrivateKeyParameters convertPrivateKeyToParameters(BCECPrivateKey ecPriKey) { + ECParameterSpec parameterSpec = ecPriKey.getParameters(); + ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(), + parameterSpec.getN(), parameterSpec.getH()); + return new ECPrivateKeyParameters(ecPriKey.getD(), domainParameters); + } + + private static String buildCanonicalReqString(String httpPath, Map canonicalHeaders) { + String authorization = canonicalHeaders.get("authorization"); + String[] protocol = authorization.split(SPACE); + String protocolPrefix = protocol[0], protocolContent = protocol[1]; + String[] parts = protocolPrefix.split(DASH_LINE); + String certType = parts[1]; + String digestAlg = parts[2]; + assert certType.equals("SM2") && digestAlg.equals("SM3"); + String[] authorizationHeaders = StringUtils.split(protocolContent, SLASH); + String protocolVersion = authorizationHeaders[0]; + assert protocolVersion.equals("yop-auth-v3"); + String appKey = authorizationHeaders[1]; + String timestamp = authorizationHeaders[2]; + long expirationInSeconds = Long.parseLong(authorizationHeaders[3]); + String signedHeaders = authorizationHeaders[4].toLowerCase(); + + //authString + String authString = new StringBuilder(protocolVersion).append(SLASH) + .append(appKey).append(SLASH) + .append(timestamp).append(SLASH) + .append(expirationInSeconds).toString(); + + // Formatting the URL with signing protocol. + String canonicalURI = getCanonicalURIPath(httpPath); + + // Formatting the query string with signing protocol. + String canonicalQueryString = getCanonicalQueryString(); + + // Sorted the headers should be signed from the request. + // Formatting the headers from the request based on signing protocol. + String canonicalHeader = getCanonicalHeaders(signedHeaders, canonicalHeaders); + return new StringBuilder(authString).append(LF) + .append(httpPath).append(LF) + .append(canonicalURI).append(LF) + .append(canonicalQueryString).append(LF) + .append(canonicalHeader).toString(); + } + + private static void verifySign(String canonicalReqString, String signature, BCECPublicKey publicKey) { + try { + byte[] signData = decodeBase64(signature); + byte[] srcData = canonicalReqString.getBytes(UTF_8); + ECParameterSpec parameterSpec = publicKey.getParameters(); + ECDomainParameters domainParameters = new ECDomainParameters(parameterSpec.getCurve(), parameterSpec.getG(), + parameterSpec.getN(), parameterSpec.getH()); + ECPublicKeyParameters pubKeyParameters = new ECPublicKeyParameters(publicKey.getQ(), domainParameters); + verify(pubKeyParameters, null, srcData, signData); + } catch (IOException e) { + throw new RuntimeException("UnexpectedError, VerifySign Fail, data:" + + canonicalReqString + ", sign:" + signature + ", key:" + publicKey + ", ex:", e); + } + + } + + /** + * 验签 + * + * @param pubKeyParameters 公钥 + * @param withId 可以为null,若为null,则默认withId为字节数组:"1234567812345678".getBytes() + * @param srcData 原文 + * @param sign DER编码的签名值 + * @return 验签成功返回true,失败返回false + */ + public static boolean verify(ECPublicKeyParameters pubKeyParameters, byte[] withId, byte[] srcData, byte[] sign) { + SM2Signer signer = new SM2Signer(); + CipherParameters param; + if (withId != null) { + param = new ParametersWithID(pubKeyParameters, withId); + } else { + param = pubKeyParameters; + } + signer.init(false, param); + signer.update(srcData, 0, srcData.length); + return signer.verifySignature(sign); + } + + private static String getCanonicalURIPath(String path) { + if (path == null) { + return "/"; + } else { + return path.startsWith("/") ? normalizePath(path) : "/" + normalizePath(path); + } + } + + private static String getCanonicalQueryString() { + return ""; + } + + private static String getCanonicalHeaders(String signedHeaders, Map canonicalHeaders) { + Set headerNames = Sets.newHashSet(signedHeaders.split(";")); + List kvs = Lists.newArrayList(); + for (String key : headerNames) { + final String canonicalKey = key.trim().toLowerCase(); + String value = canonicalHeaders.get(canonicalKey); + if (StringUtils.isBlank(value)) { + continue; + } + kvs.add(normalize(canonicalKey + COLON + normalize(value.trim()))); + } + Collections.sort(kvs); + return String.join(LF, kvs); + } + + private static String normalizePath(String path) { + return normalize(path).replace("%2F", "/"); + } + + private static String normalize(String value) { + try { + StringBuilder builder = new StringBuilder(); + for (byte b : value.getBytes(UTF_8)) { + if (URI_UNRESERVED_CHARACTERS.get(b & 0xFF)) { + builder.append((char) b); + } else { + builder.append(PERCENT_ENCODED_STRINGS[b & 0xFF]); + } + } + return builder.toString(); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + private static String parseToHex(String decimalSerialNo) { + // 10进制的证书序列号一定大于10位 + if (StringUtils.isEmpty(decimalSerialNo) || 10 >= decimalSerialNo.length()) { + return decimalSerialNo; + } + return Long.toHexString(Long.parseLong(decimalSerialNo)); + } + + private static byte[] decodeBase64(String input) { + return Base64.decodeBase64(input); + } + + private static BCECPrivateKey string2PrivateKey(String priKey) { + try { + return (BCECPrivateKey)KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME).generatePrivate( + new PKCS8EncodedKeySpec(decodeBase64(priKey))); + } catch (Exception e) { + throw new RuntimeException("ConfigProblem, IsvPrivateKey ParseFail, value:" + priKey + ", ex:", e); + } + } + + public static BCECPublicKey string2PublicKey(String pubKey) { + try { + return (BCECPublicKey) KeyFactory.getInstance("EC").generatePublic( + new X509EncodedKeySpec(decodeBase64(pubKey))); + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + throw new RuntimeException("ConfigProblem, YopPublicKey ParseFail, value:" + pubKey + ", ex:", e); + } + } + + private static final BitSet URI_UNRESERVED_CHARACTERS = new BitSet(); + private static final String[] PERCENT_ENCODED_STRINGS = new String[256]; + + // Regex which matches any of the sequences that we need to fix up after URLEncoder.encode(). + // private static final Pattern ENCODED_CHARACTERS_PATTERN; + static { + /* + * StringBuilder pattern = new StringBuilder(); + * + * pattern .append(Pattern.quote("+")) .append("|") .append(Pattern.quote("*")) .append("|") + * .append(Pattern.quote("%7E")) .append("|") .append(Pattern.quote("%2F")); + * + * ENCODED_CHARACTERS_PATTERN = Pattern.compile(pattern.toString()); + */ + for (int i = 'a'; i <= 'z'; i++) { + URI_UNRESERVED_CHARACTERS.set(i); + } + for (int i = 'A'; i <= 'Z'; i++) { + URI_UNRESERVED_CHARACTERS.set(i); + } + for (int i = '0'; i <= '9'; i++) { + URI_UNRESERVED_CHARACTERS.set(i); + } + URI_UNRESERVED_CHARACTERS.set('-'); + URI_UNRESERVED_CHARACTERS.set('.'); + URI_UNRESERVED_CHARACTERS.set('_'); + URI_UNRESERVED_CHARACTERS.set('~'); + + for (int i = 0; i < PERCENT_ENCODED_STRINGS.length; ++i) { + PERCENT_ENCODED_STRINGS[i] = String.format("%%%02X", i); + } + } + + +} diff --git a/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/sentinel/SentinelTest.java b/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/sentinel/SentinelTest.java index e39755bb..edd2f2c1 100644 --- a/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/sentinel/SentinelTest.java +++ b/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/sentinel/SentinelTest.java @@ -4,12 +4,12 @@ */ package com.yeepay.yop.sdk.sentinel; -import com.alibaba.csp.sentinel.*; -import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; -import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; -import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.*; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.BlockException; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.yeepay.yop.sdk.router.third.com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.google.common.collect.Sets; import com.yeepay.yop.sdk.service.common.YopClient; import com.yeepay.yop.sdk.service.common.YopClientBuilder; diff --git a/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/service/CustomFixedSdkConfigProvider.java b/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/service/CustomFixedSdkConfigProvider.java index f26f7a77..2f7fedf3 100644 --- a/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/service/CustomFixedSdkConfigProvider.java +++ b/yop-java-sdk-test/src/test/java/com/yeepay/yop/sdk/service/CustomFixedSdkConfigProvider.java @@ -4,6 +4,7 @@ */ package com.yeepay.yop.sdk.service; +import com.google.common.collect.Lists; import com.yeepay.yop.sdk.YopConstants; import com.yeepay.yop.sdk.base.config.provider.YopFixedSdkConfigProvider; import com.yeepay.yop.sdk.config.YopSdkConfig; @@ -27,12 +28,14 @@ protected YopSdkConfig loadSdkConfig() { yopSdkConfig.setServerRoot(YopConstants.DEFAULT_SERVER_ROOT); yopSdkConfig.setYosServerRoot(YopConstants.DEFAULT_YOS_SERVER_ROOT); yopSdkConfig.setSandboxServerRoot(YopConstants.DEFAULT_SANDBOX_SERVER_ROOT); + yopSdkConfig.setPreferredServerRoots(Lists.newArrayList( + "https://openapi-a.yeepay.com/yop-center", + "https://openapi-h.yeepay.com/yop-center")); // 连接超时时间、读取超时时间等其他配置,可根据需要setXXX即可 return yopSdkConfig; } @Override public void removeConfig(String key) { - // 可以不实现 } } diff --git a/yop-java-sdk-utils/pom.xml b/yop-java-sdk-utils/pom.xml new file mode 100644 index 00000000..6cc82a47 --- /dev/null +++ b/yop-java-sdk-utils/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + com.yeepay.yop.sdk + yop-java-sdk-parent + 4.4.11-SNAPSHOT + + + yop-java-sdk-utils + + + + com.yeepay.yop.sdk + yop-java-sdk-api + + + + com.google.guava + guava + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-joda + + + joda-time + joda-time + + + com.jayway.jsonpath + json-path + + + commons-codec + commons-codec + + + org.apache.commons + commons-lang3 + + + org.apache.commons + commons-collections4 + + + + \ No newline at end of file diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/BeanUtils.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/BeanUtils.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/BeanUtils.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/BeanUtils.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/DateUtils.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/DateUtils.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/DateUtils.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/DateUtils.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/Encodes.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/Encodes.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/Encodes.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/Encodes.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/JsonUtils.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/JsonUtils.java similarity index 73% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/JsonUtils.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/JsonUtils.java index 586ac9c9..2ef63e0c 100644 --- a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/JsonUtils.java +++ b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/JsonUtils.java @@ -8,10 +8,7 @@ import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.datatype.joda.JodaModule; -import com.google.common.collect.Sets; import com.jayway.jsonpath.Configuration; -import com.jayway.jsonpath.DocumentContext; -import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Option; import com.jayway.jsonpath.spi.json.JacksonJsonProvider; import com.jayway.jsonpath.spi.json.JsonProvider; @@ -19,25 +16,16 @@ import com.jayway.jsonpath.spi.mapper.MappingProvider; import com.yeepay.yop.sdk.exception.YopClientException; import com.yeepay.yop.sdk.utils.json.joda.DatetimeModule; -import org.apache.commons.collections4.CollectionUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Writer; import java.util.EnumSet; -import java.util.List; import java.util.Set; -import java.util.SortedSet; - -import static com.yeepay.yop.sdk.YopConstants.*; public class JsonUtils { - private static final Logger LOGGER = LoggerFactory.getLogger(JsonUtils.class); - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); static { @@ -156,41 +144,4 @@ public static ObjectWriter getPrettywriter() { return prettyWriter; } - public static boolean isTotalEncrypt(Set jsonPaths) { - boolean totalEncrypt = CollectionUtils.isSubCollection(jsonPaths, JSON_PATH_ROOT); - if (totalEncrypt) { - return true; - } - if (jsonPaths.size() > 1 && !CollectionUtils.intersection(jsonPaths, JSON_PATH_ROOT).isEmpty()) { - throw new YopClientException("illegal json paths:" + jsonPaths); - } - return false; - } - - /** - * 正序排列,保证优先加密对象 - * - * @param jsonContent - * @param jsonPathPatterns - * @return - */ - public static Set resolveAllJsonPaths(String jsonContent, Set jsonPathPatterns) { - DocumentContext pathReadCtx = JsonPath.using(Configuration.builder() - .options(Option.AS_PATH_LIST).build()).parse(jsonContent); - - SortedSet encryptPaths = Sets.newTreeSet(); - for (String encryptParam : jsonPathPatterns) { - if (JSON_PATH_ROOT.contains(encryptParam)) { - return TOTAL_ENCRYPT_PARAMS; - } - if (encryptParam.startsWith(JSON_PATH_PREFIX)) { - List pathList = pathReadCtx.read(encryptParam); - if (CollectionUtils.isNotEmpty(pathList)) { - encryptPaths.addAll(pathList); - } - } - } - encryptPaths.forEach(LOGGER::debug); - return encryptPaths; - } } diff --git a/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/RandomUtils.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/RandomUtils.java new file mode 100644 index 00000000..12aee751 --- /dev/null +++ b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/RandomUtils.java @@ -0,0 +1,76 @@ +/* + * Copyright: Copyright (c)2011 + * Company: 易宝支付(YeePay) + */ + +package com.yeepay.yop.sdk.utils; + +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * title:
            + * description: 描述
            + * Copyright: Copyright (c)2014
            + * Company: 易宝支付(YeePay)
            + * + * @author dreambt + * @version 1.0.0 + * @since 2021/5/6 17:46 + */ +public final class RandomUtils { + + private RandomUtils() { + // do nothing + } + + /** + * 使用性能更好的SHA1PRNG, Tomcat的sessionId生成也用此算法. + * 但JDK7中,需要在启动参数加入 -Djava.security=file:/dev/./urandom + */ + public static SecureRandom secureRandom() { + try { + return SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) {// NOSONAR + return new SecureRandom(); + } + } + + /** + * 随机散列列表 + * + * @param origin 原列表 + * @param 元素 + * @return List + */ + public static List randomList(List origin) { + List tmp = new ArrayList<>(origin); + Collections.shuffle(tmp, secureRandom()); + return tmp; + } + + /** + * 随机选一个 + * + * @param origin 原列表 + * @param 列表元素 + * @return T + */ + public static T randomOne(List origin) { + return origin.get(secureRandom().nextInt(origin.size())); + } + + public static boolean randomFailure(int configThreshold) { + if (configThreshold <= 0) { + return false; + } + if (configThreshold >= 10000) { + return true; + } + return RandomUtils.secureRandom().nextInt(10000) <= configThreshold; + } + +} diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/StreamUtils.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/StreamUtils.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/StreamUtils.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/StreamUtils.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/checksum/CRC64.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/checksum/CRC64.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/checksum/CRC64.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/checksum/CRC64.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/checksum/CRC64Utils.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/checksum/CRC64Utils.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/checksum/CRC64Utils.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/checksum/CRC64Utils.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/JacksonJsonMarshaller.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/JacksonJsonMarshaller.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/JacksonJsonMarshaller.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/JacksonJsonMarshaller.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/KeepAsRawStringDeserializer.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/KeepAsRawStringDeserializer.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/KeepAsRawStringDeserializer.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/KeepAsRawStringDeserializer.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DateTimeDeserializer.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DateTimeDeserializer.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DateTimeDeserializer.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DateTimeDeserializer.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DateTimeSerializer.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DateTimeSerializer.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DateTimeSerializer.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DateTimeSerializer.java diff --git a/yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DatetimeModule.java b/yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DatetimeModule.java similarity index 100% rename from yop-java-sdk-base/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DatetimeModule.java rename to yop-java-sdk-utils/src/main/java/com/yeepay/yop/sdk/utils/json/joda/DatetimeModule.java