Skip to content
This repository has been archived by the owner on Jul 9, 2024. It is now read-only.

Commit

Permalink
make circuit breaker options configurable.
Browse files Browse the repository at this point in the history
  • Loading branch information
foxgem committed Jan 23, 2017
1 parent 18ea207 commit 3b5e74c
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 67 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dgate的主要特性:
- 支持JWT
- 支持CORS
- 灵活的login配置
- 支持断路器

详细的用户指南请访问[这里](./docs/user_guide.md)

Expand Down
40 changes: 40 additions & 0 deletions docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,43 @@ dgate会给每个发往后端服务的请求参数中添加若干参数,通过
- token,已解码的jwt token,其类型是一个Map,内容依赖于在产生token时设置的值。如:在产生时包含[sub, name, role]这几个键值,则此处就获得这3个键值。若在产生时为[sub, name, role, other],则此处就可以会有这4个键值。

注意:除了必需的几个属性,JWT Token中token本身是可以附加其他属性进来的。相当于将token本身作为信息的载体。

## 断路器设置

dgate缺省会为每个上游服务设置一个断路器,缺省的配置如下:
- 最大失败次数(maxFailures),3次
- 请求超时(timeout),5秒
- 断路器重置时间(resetTimeout),10秒

若缺省的设置不合适,dgate支持两个层次的设置:gateway级别和上游服务级别。并且,在两者都存在时,上游服务的断路器设置会覆盖gateway级别的设置。

### gateway级别的设置

这个设置将对gateway内所有的上游服务生效,例子如下:

~~~
apiGateway {
……
circuitBreaker {
maxFailures = 5
timeout = 10000
resetTimeout = 30000
}
……
}
~~~

### 上游服务级别的设置

这个设置仅对当前上游服务生效,例子如下:

~~~
……
"/url" {
upstreamURLs = [
[host: 'localhost', port: 8080, url: '/test1',
circuitBreaker: [maxFailures: 2, timeout: 3000, resetTimeout: 3000]]
]
}
……
~~~
18 changes: 15 additions & 3 deletions src/main/groovy/top/dteam/dgate/config/ApiGatewayRepository.groovy
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package top.dteam.dgate.config

import io.vertx.circuitbreaker.CircuitBreakerOptions
import io.vertx.core.http.HttpMethod

class ApiGatewayRepository {
Expand Down Expand Up @@ -39,9 +40,10 @@ class ApiGatewayRepository {
String host = body.host ?: '0.0.0.0'
LoginConfig login = body.login ? buildLogin(body.login) : null
CorsConfig cors = buildCors(body.cors)
CircuitBreakerOptions defaultCBOptions = buildCircuitBreaker(body.circuitBreaker)
List<UrlConfig> urlConfigs = new ArrayList<>()
body.urls.keySet().each { url ->
urlConfigs << buildUrl(url, body.urls[url])
urlConfigs << buildUrl(url, body.urls[url], defaultCBOptions)
}

new ApiGatewayConfig(
Expand All @@ -66,15 +68,25 @@ class ApiGatewayRepository {
}
}

private static UrlConfig buildUrl(def key, def body) {
private static CircuitBreakerOptions buildCircuitBreaker(Map circuitBreaker) {
new CircuitBreakerOptions()
.setMaxFailures(circuitBreaker?.maxFailures ?: 3)
.setTimeout(circuitBreaker?.timeout ?: 5000)
.setResetTimeout(circuitBreaker?.resetTimeout ?: 10000)
}

private static UrlConfig buildUrl(def key, def body, CircuitBreakerOptions cbOptions) {
String url = key
Object required = body.required ?: null
List<HttpMethod> methods = body.methods ?: []
Map expected = body.expected
List<UpstreamURL> upstreamURLs = new ArrayList<>()
body.upstreamURLs.each { upstreamURL ->
CircuitBreakerOptions cbOptionsForUpstreamURL =
upstreamURL.circuitBreaker? buildCircuitBreaker(upstreamURL.circuitBreaker) : cbOptions

upstreamURLs << new UpstreamURL(host: upstreamURL.host, port: upstreamURL.port, url: upstreamURL.url,
before: upstreamURL.before, after: upstreamURL.after)
before: upstreamURL.before, after: upstreamURL.after, cbOptions: cbOptionsForUpstreamURL)
}

new UrlConfig(url: url, required: required, methods: methods, expected: expected, upstreamURLs: upstreamURLs)
Expand Down
4 changes: 3 additions & 1 deletion src/main/groovy/top/dteam/dgate/config/UpstreamURL.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ package top.dteam.dgate.config

import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import io.vertx.circuitbreaker.CircuitBreakerOptions
import io.vertx.core.json.JsonObject
import top.dteam.dgate.gateway.SimpleResponse

@EqualsAndHashCode(excludes = ['before', 'after'])
@EqualsAndHashCode(excludes = ['before', 'after', 'cbOptions'])
@CompileStatic
class UpstreamURL {

String host
int port
String url
CircuitBreakerOptions cbOptions

Closure<JsonObject> before
Closure<SimpleResponse> after
Expand Down
9 changes: 4 additions & 5 deletions src/main/java/top/dteam/dgate/handler/LoginHandler.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package top.dteam.dgate.handler;

import top.dteam.dgate.config.UrlConfig;
import top.dteam.dgate.utils.JWTTokenGenerator;
import io.vertx.circuitbreaker.CircuitBreakerOptions;
import io.vertx.core.Vertx;
import io.vertx.ext.auth.jwt.JWTAuth;
import top.dteam.dgate.config.UrlConfig;
import top.dteam.dgate.utils.JWTTokenGenerator;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -14,8 +13,8 @@ public class LoginHandler extends ProxyHandler {

private JWTTokenGenerator tokenGenerator;

public LoginHandler(Vertx vertx, UrlConfig urlConfig, CircuitBreakerOptions circuitBreakerOptions, JWTAuth jwtAuth) {
super(vertx, urlConfig, circuitBreakerOptions);
public LoginHandler(Vertx vertx, UrlConfig urlConfig, JWTAuth jwtAuth) {
super(vertx, urlConfig);
this.tokenGenerator = new JWTTokenGenerator(jwtAuth);
}

Expand Down
20 changes: 11 additions & 9 deletions src/main/java/top/dteam/dgate/handler/ProxyHandler.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
package top.dteam.dgate.handler;

import top.dteam.dgate.config.UpstreamURL;
import top.dteam.dgate.config.UrlConfig;
import top.dteam.dgate.gateway.SimpleResponse;
import top.dteam.dgate.utils.RequestUtils;
import top.dteam.dgate.utils.Utils;
import groovy.lang.Closure;
import io.vertx.circuitbreaker.CircuitBreaker;
import io.vertx.circuitbreaker.CircuitBreakerOptions;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.dteam.dgate.config.UpstreamURL;
import top.dteam.dgate.config.UrlConfig;
import top.dteam.dgate.gateway.SimpleResponse;
import top.dteam.dgate.utils.RequestUtils;
import top.dteam.dgate.utils.Utils;

import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;

Expand All @@ -28,7 +30,7 @@ public class ProxyHandler extends RequestHandler {
private Map<String, CircuitBreaker> circuitBreakers;
private RequestUtils requestUtils;

public ProxyHandler(Vertx vertx, UrlConfig urlConfig, CircuitBreakerOptions circuitBreakerOptions) {
public ProxyHandler(Vertx vertx, UrlConfig urlConfig) {
super(vertx, urlConfig);

upstreamURLs = urlConfig.getUpstreamURLs();
Expand All @@ -38,7 +40,7 @@ public ProxyHandler(Vertx vertx, UrlConfig urlConfig, CircuitBreakerOptions circ
upstreamURLs.forEach(upStreamURL ->
circuitBreakers.put(upStreamURL.toString(),
CircuitBreaker.create(String.format("cb-%s-%s", urlConfig.getUrl(),
upStreamURL.toString()), vertx, circuitBreakerOptions))
upStreamURL.toString()), vertx, upStreamURL.getCbOptions()))
);
}

Expand Down
20 changes: 9 additions & 11 deletions src/main/java/top/dteam/dgate/handler/RequestHandler.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
package top.dteam.dgate.handler;

import io.vertx.ext.auth.jwt.JWTAuth;
import top.dteam.dgate.config.InvalidConfiguriationException;
import top.dteam.dgate.utils.Utils;
import top.dteam.dgate.config.UrlConfig;
import io.vertx.circuitbreaker.CircuitBreakerOptions;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
Expand All @@ -13,11 +8,17 @@
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.web.RoutingContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.dteam.dgate.config.InvalidConfiguriationException;
import top.dteam.dgate.config.UrlConfig;
import top.dteam.dgate.utils.Utils;

import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class RequestHandler implements Handler<RoutingContext> {

Expand All @@ -31,12 +32,9 @@ public static RequestHandler create(Vertx vertx, UrlConfig urlConfig, JWTAuth jw
int requestHandlerType = urlConfig.requestHandlerType();
if (requestHandlerType == UrlConfig.PROXY) {
if (jwtAuth == null) {
return new ProxyHandler(vertx, urlConfig,
new CircuitBreakerOptions().setMaxFailures(3).setTimeout(5000).setResetTimeout(10000));
return new ProxyHandler(vertx, urlConfig);
} else {
return new LoginHandler(vertx, urlConfig,
new CircuitBreakerOptions().setMaxFailures(3).setTimeout(5000).setResetTimeout(10000),
jwtAuth);
return new LoginHandler(vertx, urlConfig, jwtAuth);
}
} else if (requestHandlerType == UrlConfig.MOCK) {
return new MockHandler(vertx, urlConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ class ApiGatewayRepositorySpec extends Specification {
apiGateway2 {
port = 7001
host = 'localhost'
circuitBreaker {
maxFailures = 5
timeout = 10000
resetTimeout = 30000
}
urls {
"/mock" {
expected {
Expand All @@ -85,6 +90,17 @@ class ApiGatewayRepositorySpec extends Specification {
}
}
}
"/proxy1" {
upstreamURLs = [
[host: 'localhost', port: 8080, url: '/test1']
]
}
"/proxy2" {
upstreamURLs = [
[host: 'localhost', port: 8080, url: '/test1',
circuitBreaker: [maxFailures: 2, timeout: 3000, resetTimeout: 3000]]
]
}
}
}
apiGateway3 {
Expand Down Expand Up @@ -131,6 +147,7 @@ class ApiGatewayRepositorySpec extends Specification {
}
urlConfigs.size() == 4
with(urlConfigs[0]) {
url == '/login'
required == ["sub", "password"]
methods == [HttpMethod.GET, HttpMethod.POST]
upstreamURLs.size == 1
Expand All @@ -139,9 +156,13 @@ class ApiGatewayRepositorySpec extends Specification {
upstreamURLs[0].url == '/login'
!upstreamURLs[0].before
upstreamURLs[0].after
upstreamURLs[0].cbOptions.maxFailures == 3
upstreamURLs[0].cbOptions.timeout == 5000
upstreamURLs[0].cbOptions.resetTimeout == 10000
}
urlConfigs[1].expected == [statusCode: 200, payload: [test: true]]
with(urlConfigs[2]) {
url == '/forward'
required == [get: ['param1'], post: ['param2'], delete: ['param3']]
methods == [HttpMethod.GET, HttpMethod.POST, HttpMethod.DELETE]
upstreamURLs.size == 1
Expand All @@ -153,6 +174,7 @@ class ApiGatewayRepositorySpec extends Specification {
upstreamURLs[0].after
upstreamURLs[0].after(simpleResponse) == simpleResponse
!expected
upstreamURLs[0].cbOptions
}
with(urlConfigs[3]) {
required == ['param1', 'param2']
Expand All @@ -163,9 +185,11 @@ class ApiGatewayRepositorySpec extends Specification {
new UpstreamURL(host: 'localhost', port: 8080, url: '/test2')
]
!upstreamURLs[0].before
!upstreamURLs[0].after
upstreamURLs[0].cbOptions
!upstreamURLs[1].before
!upstreamURLs[1].after
!upstreamURLs[0].before
!upstreamURLs[1].after
upstreamURLs[1].cbOptions
!expected
}
}
Expand All @@ -175,10 +199,16 @@ class ApiGatewayRepositorySpec extends Specification {
name == 'apiGateway2'
!login
!cors
urlConfigs.size() == 1
urlConfigs.size() == 3
urlConfigs[0].expected == [get : [statusCode: 200, payload: [method: 'get']],
post : [statusCode: 200, payload: [method: 'post']],
delete: [statusCode: 200, payload: [method: 'delete']]]
urlConfigs[1].upstreamURLs[0].cbOptions.maxFailures == 5
urlConfigs[1].upstreamURLs[0].cbOptions.timeout == 10000
urlConfigs[1].upstreamURLs[0].cbOptions.resetTimeout == 30000
urlConfigs[2].upstreamURLs[0].cbOptions.maxFailures == 2
urlConfigs[2].upstreamURLs[0].cbOptions.timeout == 3000
urlConfigs[2].upstreamURLs[0].cbOptions.resetTimeout == 3000
}
with(repository[2]) {
port == 7002
Expand Down
Loading

0 comments on commit 3b5e74c

Please sign in to comment.