基於個人的經驗,談談設計模式在網關中的應用。因為是經驗之談,沒有絕對的對與錯。 下麵整理的是我最常使用的設計模式,我用設計模式的前提是 讓代碼的可讀性變強 能支持日後功能擴展 單例 目的 保證全局只有一個實例,防止因為頻繁的創建、銷毀對象而造成不必要的性能開銷。 在網關項目中,單例模式是出現頻率最高 ...
基於個人的經驗,談談設計模式在網關中的應用。因為是經驗之談,沒有絕對的對與錯。
下麵整理的是我最常使用的設計模式,我用設計模式的前提是
- 讓代碼的可讀性變強
- 能支持日後功能擴展
單例
目的
保證全局只有一個實例,防止因為頻繁的創建、銷毀對象而造成不必要的性能開銷。
在網關項目中,單例模式是出現頻率最高的模式。同時,所有的單例對象被 IoC 框架 Guice 統一管理。
場景 1
網關會處理各種邏輯。一般將業務邏輯從主流程中抽取出來,封裝在一個獨立對象中。可以使用單例模式來保證全局唯一,使用註解 Singleton
來表示這個一個單例:
@Singleton
public class HttpMethodPipeline {
private List<HttpMethodHandler> handlers = new ArrayList<>();
...
}
使用註解 Inject
來註入對象
public class ApiRewriteFilter extends HttpInboundSyncFilter{
@Inject
private HttpMethodPipeline pipeline;
@Override
public HttpRequestMessage apply(HttpRequestMessage request) {
...
pipeline.process(request);
...
}
}
減少 if-else
過多的 if-else 會導致
- 可讀性變差
- 難以擴展維護
- 質量不可控,健壯性差
- 不利於單元測試
但是另一方面 if-else 是無法迴避的代碼。所以,為了讓程式變得優雅,下麵幾種模式是我使用頻次很高的模式,意在消除 if-else 代碼段帶來的負面影響。
1.表驅動法(策略)
目的
用表結構來驅動業務邏輯,減少 if-else 。這裡的表結構可以參考
HashMap
,通過對 Key 計算出 hash 從而快速獲取數據
示例
以之前的游戲項目中一段代碼舉例,需要計算出當前的英雄的級別:
- 小於 80:等級 G
- 80 至140:等級 F
- 140 至 200:等級 E
- ...
使用表驅動法來計算等級的話,非常方便,只要預先定義好表即可,整體不會出現一行 if-else 代碼,如下所示:
public static String GetRoleAttributeClass(int attributeLv99) {
Map<Integer,String> attributes = new HashMap<Integer, String>()
{
{ 080, "G" },// <=80 -> G
{ 140, "F" },// >80 && <=140 -> F
{ 200, "E" },
{ 260, "D" },
{ 320, "C" },
{ 380, "B" },
{ 440, "A" },
{ 500, "S" },
};
var attributeClass = "?";
foreach (var key in attributes.Keys.OrderBy(o=>o))
{
if (attributeLv99 <= key)
{
attributeClass = attributes[key];
break;
}
}
return attributeClass;
}
當表驅動法+策略模式組合在一起時,可以極大的擴展系統。
場景 1
開放網關最初只支持 AppId+Secret 形式校驗,但隨著業務發展,為了滿足不同的場景,需支持
- 簡單認證,即 AppId+內網
- 攜帶請求頭:X-Tsign-Open-Auth-Model=simple 來告知網關走哪種模式鑒權
- Token 認證
- 攜帶請求頭:X-Tsign-Open-Auth-Mode=token 來告知網關走哪種模式鑒權
- 簽名驗簽認證
- 攜帶請求頭:X-Tsign-Open-Auth-Mode=signature 來告知網關走哪種模式鑒權
- 預設 AppId+Secret
- 攜帶請求頭:X-Tsign-Open-Auth-Mode=signature 來告知網關走哪種模式鑒權
很顯然,這是一種典型的橫向擴展需求,鑒權模式會隨著業務的發展而擴展。如果通過 if-else 將處理邏輯雜糅在主流程中,勢必會造成越來越臃腫。
使用策略模式+表驅動法,可以有效緩解這種處境。
a.) 定義鑒權策略
public interface AuthStrategy {
Observable<HttpRequestMessage> auth(HttpRequestMessage request) throws Exception;
}
b.) 定義不同的策略實現類
- SimpleAuthStrategy
- TokenAuthStrategy
- SignatureAuthStrategy
- SecretAuthStrategy
c.)通過 Guice 來定義表,即映射關係,映射的 Key= X-Tsign-Open-Auth-Model 傳遞過來的鑒權模式,Value=具體的實現類
MapBinder<String, AbstractAuthStrategy> authStrategyMapBinder = MapBinder.newMapBinder(binder(), String.class, AbstractAuthStrategy.class);
authStrategyMapBinder.addBinding(OpenProtocol.SIMPLE_AUTH_STRATEGY).to(SimpleAuthStrategy.class);
authStrategyMapBinder.addBinding(OpenProtocol.TOKEN_AUTH_STRATEGY).to(TokenAuthStrategy.class);
authStrategyMapBinder.addBinding(OpenProtocol.SIGNATURE_AUTH_STRATEGY).to(SignatureAuthStrategy.class);
authStrategyMapBinder.addBinding(OpenProtocol.SECRET_AUTH_STRATEGY).to(SecretAuthStrategy.class);
d.) 在主流程中,根據鑒權模式,獲取到對象的策略對象
@Slf4j
@Singleton
public class OpenAuthFilter extends HttpInboundFilter implements OpenProtocol {
@Inject
private Map<String, AbstractAuthStrategy> strategies;
@Configuration("${open.auth.default.mode}")
private String AUTH_DEFAULT_MODE ="secret";
@Override
public Observable<HttpRequestMessage> applyAsync(HttpRequestMessage request) {
//獲取身份校驗模式,如果不指定則使用預設的
String mode=StringUtils.defaultIfEmpty(request.getHeaders().getFirst(AUTH_MODE), AUTH_DEFAULT_MODE).toLowerCase();
//根據模式選擇對應的策略
AbstractAuthStrategy authStrategy = strategies.get(mode);
if (authStrategy == null) {
route2Error(ctx, Problem.valueOf(Status.UNAUTHORIZED));
return Observable.just(request);
}
try {
return authStrategy.auth(request);
} catch (Exception cause) {
logger.error("authentication failed.{}", cause);
route2Error(ctx, Problem.valueOf(Status.UNAUTHORIZED));
return Observable.just(request);
}
}
}
2.職責鏈
目的
一個邏輯可能由多種處理模式,通過將這些處理模式連接成一條鏈,並且沿著這條鏈傳遞請求,直到有對象處理它為止。客戶只需要將請求發送到職責鏈上即可,無須關心請求的處理細節和請求的傳遞。
場景 1
網關需要對 HTTP Method 進行適配,比如小程式客戶端 Http Method 不支持 Put/Delete ,只支持 Get/Post。所以一般情況下使用 Post 來代替 Put/Delete,同時可能通過以下幾種方式:
請求頭
- 請求參數
請求 Body
來告訴網關真正的 Method,所以網關需要對 HTTP Method 支持適配。可以職責鏈來實現這個需求:
public class HttpMethodPipeline {
private List<HttpMethodHandler> handlers = new ArrayList<>();
@PostConstruct
private void init() {
//第一優先順序
handlers.add(new InHeaderHttpMethodHandler());
//第二優先順序
handlers.add(new InParameterHttpMethodHandler());
//預設優先順序,兜底方案
handlers.add(new DefaultHttpMethodHandler());
}
public String process(HttpRequestMessage request) {
try {
for (HttpMethodHandler handler : handlers) {
if (handler.shouldFilter(request)) {
return handler.apply(request);
}
}
} catch (Exception cause) {
logger.error("{}", cause);
}
//容錯方案
return request.getMethod();
}
}
場景 2
網關對用戶 Token 鑒權時,需要對比 Token 中授權人的 Id是否與介面入參 accountId
保持一致。同時,這個 accountId 有可能位於
- Path
- Header
- Request Parameter
- Request Body
只要滿足一個條件即可,通過職責鏈模式,可以有效解決問題,避免 if-else 帶來的擴展麻煩
private HttpRequestMessage postAuthentication(HttpRequestMessage request, MatchApi matchApi) {
if (TokenMode.USER.toString().equals(tokenMode)) {
//驗證不過
if (!validatorEngine.run(
AuthContext
.builder()
.request(request)
.matchApi(matchApi)
.build())) {
route2Error(ctx, Problem.valueOf(Status.UNAUTHORIZED));
}
}
return request;
}
定義驗證引擎,本職上是一個處理鏈
class ValidatorEngine {
private List<AuthValidator> validators=new ArrayList<>();
ScopeValidator scopeValidator=new ScopeValidator();
ValidatorEngine(){
validators.add(new PathValidator());
validators.add(new HeaderValidatorEngine());
validators.add(new BodyValidator());
validators.add(new ParameterValidator());
}
boolean run(AuthContext authContext){
boolean pass=false;
try {
if (scopeValidator.validate(authContext)){
for (AuthValidator validator : validators) {
if (validator.validate(authContext)){
pass=true;
break;
}
}
}
}catch (Exception cause){
pass=true;
logger.error("",cause);
}
return pass;
}
}
簡單工廠
目的
提供創建實例的功能,而無需關心具體實現,彼此之間互相解耦。往往和策略模式組合使用,即從工廠中獲取一個策略。
場景 1
根據灰度配置獲取灰度策略
public interface RuleStrategyFactory {
RuleStrategy getRuleStrategy(GrayRule grayRule);
}
場景 2
獲取遠程服務
public interface NettyOriginFactory {
NettyOrigin create(@Assisted("name") String name, @Assisted("vip") String vip, int defaultMaxRetry, Routing routing);
}
場景 3
根據 Uri 獲取模板
public interface UriTemplateFactory {
UriTemplate create(String name);
}
場景 4
獲取 WAF 攔截處理器
@Singleton
public class InboundRuleMatcherFactory {
public InboundRuleMatcher create(RuleDefinition definition) {
InboundRuleMatcher inboundRuleMatcher = null;
switch (definition.getRuleStage()) {
case CLIENT_IP:
inboundRuleMatcher = new ClientIPRuleMatcher(definition);
break;
case CONTENT_TYPE:
inboundRuleMatcher = new ContentTypeRuleMatcher(definition);
break;
case CONTENT_LENGTH:
inboundRuleMatcher = new ContentLengthRuleMatcher(definition);
break;
case USER_AGENT:
inboundRuleMatcher = new UserAgentRuleMatcher(definition);
break;
case REQUEST_ARGS:
inboundRuleMatcher = new RequestArgsRuleMatcher(definition);
break;
case COOKIES:
inboundRuleMatcher = new CookieRuleMatcher(definition);
break;
default:
break;
}
return inboundRuleMatcher;
}
}
簡單工廠可以和表驅動法組合使用,這樣會非常清爽:
@Singleton
@Slf4j
public class DefaultFlowStrategyFactory implements FlowStrategyFactory {
@Inject
private Injector injector;
private static final ImmutableMap<LBAlgorithmType, Class<? extends AbstractFlowStrategy>> map = ImmutableMap.of(
LBAlgorithmType.RANDOM, RandomFlowStrategy.class,
LBAlgorithmType.ROUND_ROBIN, RoundRobinFlowStrategy.class,
LBAlgorithmType.WEIGHTED, WeightedFlowStrategy.class);
@Override
public FlowStrategy getFlowStrategy(Flow flow) {
AbstractFlowStrategy strategy = null;
Class<? extends AbstractFlowStrategy> clazz = map.get(flow.getAlgorithm());
if (clazz != null) {
strategy = injector.getInstance(clazz);
}
if (strategy == null) {
//容錯機制,如果配置了非 RANDOM、ROUND_ROBIN、WEIGHTED 演算法,或者忘記設置,預設返回 RANDOM
strategy = new RandomFlowStrategy();
}
strategy.apply(flow.getValue());
return strategy;
}
}
模板方法
目的
定義處理邏輯的通用骨架,將差異化延遲到子類實現
場景 1
鑒權通過時,新老開放網關向下游傳遞的數據有差異,所有數據都會存儲在 SessionContext
中,通過模板方法定義通用骨架:
public abstract class AbstractAppPropertyStash implements AppPropertyStash {
@Override
public void apply(AppEntity appEntity, HttpRequestMessage request){
SessionContext context = request.getContext();
//記得在使用方做容錯處理:DefaultValue
context.set(APP_IP_WHITE_LIST_CTX_KEY, appEntity.getIps());
context.set(APP_THROTTLE_INTERVAL_CTX_KEY, appEntity.getInterval());
context.set(APP_THROTTLE_THRESHOLD_CTX_KEY, appEntity.getThreshold());
store(appEntity,request);
}
protected abstract void store(AppEntity appEntity, HttpRequestMessage request);
}
對於新開放網關,向下游傳遞USER_ID
public class DefaultAppPropertyStash extends AbstractAppPropertyStash {
@Override
protected void store(AppEntity appEntity, HttpRequestMessage request) {
SessionContext context = request.getContext();
request.getHeaders().set(USER_ID, appEntity.getGId());
context.set(USER_ID, appEntity.getGId());
}
}
對於老開放網關,向下游傳遞LOGIN_ID
public class DefaultAppPropertyStash extends AbstractAppPropertyStash {
@Override
protected void store(AppEntity appEntity, HttpRequestMessage request) {
String gId = appEntity.getGId();
String oId = appEntity.getOId();
if (StringUtils.isNotEmpty(oId)) {
request.getHeaders().add(X_TSIGN_LOGIN_ID, oId);
request.getContext().set(X_TSIGN_LOGIN_ID, oId);
} else {
request.getHeaders().add(X_TSIGN_LOGIN_ID, "GID$$" + gId);
request.getContext().set(X_TSIGN_LOGIN_ID, "GID$$" + gId);
}
}
所以 USER_ID
與LOGIN_ID
就是差異化的表現,由各子類負責。
場景 2
網關支持灰度發佈,即通過服務分組來將請求路由到指定分組的服務,通過定義模板,獲取分組信息:group
,然後差異化的路由由子類實現,比如:RandomFlowStrategy
,RoundRobinFlowStrategy
,WeightedFlowStrategy
等。
public abstract class AbstractFlowStrategy implements FlowStrategy {
protected List<String> groups;
public void apply(Map<String, String> value) {
groups = Arrays.asList(value.get("group").split(";"));
preHandle(value);
}
protected abstract void preHandle(Map<String, String> value);
}
觀察者
目的
定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新,彼此之間解耦。
場景 1
為了提高網關的響應,一般會將常用數據 LRU 緩存到本地。比如 WAF 攔截規則會預先從資料庫中讀取出來,同時這部分數據存在變更的可能,雖然頻次很低,但還是每隔 5min 從資料庫讀取到記憶體中。
對於構建 WAF 規則是耗時的事情,特別是它需要正則表達式編譯。故通過觀察者模式,當感知到數據發生變化時,才通知下游處理程式構建 WAF 規則,如下 WAF 攔截規則即主題:
public class DynamicValue implements Value {
private Set<Observer> observers = Collections.synchronizedSet(new HashSet<>());
private Set<RuleDefinition> value = null;
public DynamicValue() {
}
public DynamicValue(Set<RuleDefinition> value) {
super();
this.value = value;
}
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public boolean updateValue(Set<RuleDefinition> newValue) {
if (isEqual(value, newValue)) {
return false;
}
value = newValue;
//規則變化,更新
for (Observer observer : observers) {
observer.onChange(newValue);
}
return true;
}
@Override
public void clear() {
observers.clear();
}
private boolean isEqual(Set<RuleDefinition> oldValue, Set<RuleDefinition> newValue) {
if (oldValue == null && newValue == null) {
return true;
}
if (oldValue == null) {
return false;
}
return oldValue.equals(newValue);
}
}
下游的處理程式作為觀察者:
public class RuleManager {
private Value wafDynamicValue = new DynamicValue();
public void register(Observer observer){
wafDynamicValue.addObserver(observer);
}
}
場景 2
網關會將所有的 HTTP 請求、響應發送到 Kafka 中。雖然本身 Kafka Client 的 send
方法時非同步的,但 Kafka 故障或者 Kafka 生產者的記憶體滿時,會 block
主線程。雖然可以通過多線程的方式解決,但線程之間的頻繁切換以及send
方法里的同步鎖勢必會造成性能影響。
藉助 RxJava 中的生產者-消費者模式,可以有效的解決這個問題:
Observable.<GatewayApiEntity>create(
emitter -> {
consumer = emitter::onNext;
cleaner = emitter::onCompleted;
})
.onBackpressureBuffer(
BUFFER_CAPACITY, // 經調試最終Capacity:BUFFER_CAPACITY+128(預設)
() -> logger.info("Buffer is filling up now."),
BackpressureOverflow.ON_OVERFLOW_DROP_OLDEST) // 當 Buffer 滿時,Drop 舊值,並添加新值
.filter(Objects::nonNull)
.observeOn(Schedulers.io())//切換到非同步線程消費
.doOnCompleted(countDownLatch::countDown)
.subscribe(this::sendMessage);
使用非同步被壓策略好處
- Kafka 獲取元數據或者當 buffer.memory >32 時,Kafka 生產者將阻塞 max.block.ms =60000 ms ,故不能將 Send 放到 Zuul IO 線程中
- 通過生產者-消費者,將 Kafka 生產者 Send 方式並行轉變為串列,減少多線程的同步、鎖競爭等問題
- 當 Kafka 故障、吞吐量降低時,背壓的丟棄策略,可以防止 OOM
裝飾者
目的
動態地給一個對象添加一些額外的功能,能在不影響原有功能的基礎上,對其擴展功能。
場景 1
網關路由時,需要獲取遠程服務相關元數據,然後通過本地負載均衡選取具體的服務實例。預設情況下,NettyOriginManager
對象將遠程的 Origin 緩存在記憶體中:ConcurrentHashMap
。從功能上來看,這是沒問題的。但為了性能上的優化,試想一下,當網關重啟時,這些緩存數據將丟失,又需要重新去獲取一遍元數據,下游服務越多,第一次請求的性能影響越大。如果在網關重啟時,預設同步所有服務元數據下來,是不是會更好?所以,需要確定哪些服務要被初始化,這就需要在 createOrigin
方法中額外增加這個保存Origin
的邏輯。
OriginManager
的實現類 NettyOriginManager
支持對Origin
的管理,創建和獲取
Slf4j
@Singleton
public class NettyOriginManager implements OriginManager<NettyOrigin>, Closeable {
private final ConcurrentHashMap<OriginKey, NettyOrigin> originMappings = new ConcurrentHashMap<>();
@Override
public NettyOrigin getOrigin(String name, String vip, String uri, SessionContext ctx{
}
@Override
public NettyOrigin createOrigin(String name, String vip, String uri, boolean useFullVipName, SessionContext ctx) {
}
}
將元數據保存原本NettyOriginManager
對象並不關心,同時如果NettyOriginManager
有三方框架提供,是無法修改其源碼。故使用裝飾者模式,可以有效解決這個尷尬的問題,如下所示:在不侵入NettyOriginManager
的情況下,對其增強
public interface OriginManagerDecorator extends OriginManager<NettyOrigin> {
void init();
void saveOrigin(String name, String vip, Map<String, Boolean> routingEntries);
void deleteOrigin(String data);
}
以保存到 Redis 為例,新增裝飾對象:RedissonOriginManager
裝飾 NettyOriginManager
,在原有能力上具備持久化的功能
@Singleton
@Slf4j
public class RedissonOriginManager implements OriginManagerDecorator {
@Inject
private RedissonReactiveClient redissonClient;
/*
被裝飾對象
*/
@Inject
private NettyOriginManager nettyOriginManager;
@Override
@PostConstruct
public void init() {
//獲取redis namespace,初始化
}
@Override
public void saveOrigin(String name, String vip, Map<String, Boolean> routingEntries){
}
@Override
public void deleteOrigin(String data) {
}
@Override
public NettyOrigin getOrigin(String name, String vip, String uri, SessionContext ctx{
//pass through
return nettyOriginManager.getOrigin(name, vip, uri, ctx);
}
@Override
public NettyOrigin createOrigin(String name, String vip, String uri, boolean useFullVipName, SessionContext ctx) {
//pass through
NettyOrigin origin = nettyOriginManager.createOrigin(name, vip, uri, useFullVipName, ctx);
//對原有的Origin Manager 進行增強,如果存在 Origin的話,對其緩存
if (origin != null && origin instanceof SimpleNettyOrigin) {
saveOrigin(name, vip, ((SimpleNettyOrigin) origin).getRouting().getRoutingEntries());
}
return origin;
}
}
在原有功能上新增了持久化到 Redis 的功能,可以根據不同的場景,裝飾不同的實現方式:Redis、資料庫、配置中心等
場景 2
網關在處理請求時,預設情況下只列印關鍵信息到日誌,但是有時為了排查錯誤,需要列印更加豐富的日誌。這是一種動態功能的增強,以開關的形式啟用,關閉。如下,預設情況下RequestEndingHandler
將 TraceId
和 ElapseTime
返回到客戶端:
public class RequestEndingHandler implements RequestHandler {
private Set<HeaderName> headers=new HashSet<>(
Arrays.asList(HttpHeaderNames.get(Inbound.X_Tsign_Elapse_Time),
HttpHeaderNames.get(Inbound.X_Tsign_Trace_Id)));
@Override
public void handle(Object obj) {
//一些服務先走應用網關,再走開放網關,清空下開放網關的響應頭,使用應用網關的
response.getHeaders().removeIf(headerEntry -> headers.contains(headerEntry.getKey()));
//統計消耗的時間,放在響應頭,便於排查問題
response.getHeaders().add(Inbound.X_Tsign_Elapse_Time,
String.valueOf(TimeUnit.MILLISECONDS.convert(request.getDuration(), TimeUnit.NANOSECONDS)));
//trace-id,用於調用鏈跟蹤
//謹防 Null
response.getHeaders().add(Inbound.X_Tsign_Trace_Id, StringUtils
.defaultString( response.getOutboundRequest().getHeaders().getFirst(CerberusConstants.TRACE_ID), ""));
}
}
當開啟了詳細模式後,對原功能進行增強,支持所有的業務參數列印到日誌:
public class SessionContextLogHandler extends RequestLogHandleDecorator {
private final static char DELIM = '\t';
protected SessionContextLogHandler(
RequestHandler handler) {
super(handler);
}
@Override
protected void log(Object obj) {
StringBuilder sb=new StringBuilder();
sb
.append(DELIM).append(context.getOrDefault(CerberusConstants.TRACE_ID,"-"))
.append(DELIM).append(context.getOrDefault(Inbound.X_TSIGN_LOGIN_ID,"-"))
.append(DELIM).append(context.getOrDefault(OpenProtocol.USER_ID,"-"))
;
logger.info(sb.toString());
}
建造者
目的
將複雜對象構建與主業務流程分離
場景 1
網關支持將所有經過網關的 HTTP 日誌記錄在 Kafka 中,這個 Message 對象是個大對象,並且對於其中的 requestHeader
,responseBody
構建演算法複雜。
通過構建者模式,將複雜對象從業務中剝離,避免過多的 if-else 造成混亂。
private GatewayApiEntity construct(HttpResponseMessage response){
entity = GatewayApiEntity.builder()
.appId(request.getHeaders().getFirst(Inbound.X_TSIGN_APP_ID))
.clientIp(HttpUtils.getClientIP(request))
.method(request.getMethod())
.requestId(context.getUUID())
.serviceId(context.getRouteVIP())
.api((String) context.getOrDefault(CerberusConstants.ORIGINAL_API, ""))
.requestTime((Long) context.get(CerberusConstants.TIMING_START_CTX_KEY))
.source(getApplicationId())
.timestamp(System.currentTimeMillis())
.traceId((String) context.getOrDefault(CerberusConstants.TRACE_ID, ""))
.url(request.getInboundRequest().getPathAndQuery())
.userAgent(request.getHeaders().getFirst(HttpHeaders.USER_AGENT))
.status(response.getStatus())
.duration(getDuration(response))
.requestHeader(getRequestHeader(request))
.requestBody(getRequestBody(request))
.responseBody(getResponseBody(response))
.build();
}
private String getRequestHeader(HttpRequestMessage request) throws JsonProcessingException {
// 3.補充請求頭 X-Tsign
}
private String getRequestBody(HttpRequestMessage request){
//4.請求數據,如果是大包的話,不進行收集,因為 Broker 端對 Producer 發送過來的消息也有一定的大小限制,這個參數叫 message.max.bytes
}
private String getResponseBody(HttpResponseMessage response) throws IOException {
// 5.處理 Body 里的數據,如果是大包的話,不進行收集,因為 Broker 端對 Producer 發送過來的消息也有一定的大小限制,這個參數叫 message.max.bytes
// Response body 被 gzip 壓縮過
}
場景 2
網關核心功能即路由,比如對請求: v1/accounts/abcdefg/infos
路由到 v1/accounts/{accountId}/infos
後端介面上 ,所以這需要正則表達式的支持。網關通過建造者模式,構建出一個複雜的 API
對象來表示元數據。
Api.builder()
.serviceId(entity.getServiceId())
.url(url)
.originalUrl(StringUtils.prependIfMissing(entity.originalUrl, "/"))
.httpMethod(entity.getHttpMethod())
.readTimeout(readTimeout)
.uriTemplate(uriTemplateFactory.create(url))
.build();