主頁 > 軟體設計 > 編程思想:小談網關專案中的設計模式

編程思想:小談網關專案中的設計模式

2020-09-14 01:59:32 軟體設計

基于個人的經驗,談談設計模式在網關中的應用,因為是經驗之談,沒有絕對的對與錯,

下面整理的是我最常使用的設計模式,我用設計模式的前提是

  • 讓代碼的可讀性變強
  • 能支持日后功能擴展

單例

目的

保證全域只有一個實體,防止因為頻繁的創建、銷毀物件而造成不必要的性能開銷,

在網關專案中,單例模式是出現頻率最高的模式,同時,所有的單例物件被 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=https://www.cnblogs.com/OceanEyes/p/具體的實作類

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_IDLOGIN_ID就是差異化的表現,由各子類負責,

場景 2

網關支持灰度發布,即通過服務分組來將請求路由到指定分組的服務,通過定義模板,獲取分組資訊:group,然后差異化的路由由子類實作,比如:RandomFlowStrategyRoundRobinFlowStrategyWeightedFlowStrategy等,

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 = https://www.cnblogs.com/OceanEyes/p/null;

  public DynamicValue() {
  }

  public DynamicValue(Set  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 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  oldValue, Set newValue) {
    if (oldValue == null && newValue == null) {
      return true;
    }
    if (oldValue == null) {
      return false;
    }
    return oldValue.equals(newValue);
  }
}

下游的處理程式作為觀察者:

public class RuleManager {
  private Value wafDynamicValue = https://www.cnblogs.com/OceanEyes/p/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

網關在處理請求時,默認情況下只列印關鍵資訊到日志,但是有時為了排查錯誤,需要列印更加豐富的日志,這是一種動態功能的增強,以開關的形式啟用,關閉,如下,默認情況下RequestEndingHandlerTraceIdElapseTime 回傳到客戶端:

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 物件是個大物件,并且對于其中的 requestHeaderresponseBody 構建演算法復雜,

通過構建者模式,將復雜物件從業務中剝離,避免過多的 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();

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/29330.html

標籤:設計模式

上一篇:大話設計模式隨記

下一篇:設計原則

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more