zoukankan      html  css  js  c++  java
  • 编程思想:小谈网关项目中的设计模式

    基于个人的经验,谈谈设计模式在网关中的应用。因为是经验之谈,没有绝对的对与错。

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

    • 让代码的可读性变强
    • 能支持日后功能扩展

    单例

    目的

    保证全局只有一个实例,防止因为频繁的创建、销毁对象而造成不必要的性能开销。

    在网关项目中,单例模式是出现频率最高的模式。同时,所有的单例对象被 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_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 = 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

    网关在处理请求时,默认情况下只打印关键信息到日志,但是有时为了排查错误,需要打印更加丰富的日志。这是一种动态功能的增强,以开关的形式启用,关闭。如下,默认情况下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 = '	';
    
      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();
    
  • 相关阅读:
    游标cursor
    SQL: EXISTS
    LeetCode Reverse Integer
    LeetCode Same Tree
    LeetCode Maximum Depth of Binary Tree
    LeetCode 3Sum Closest
    LeetCode Linked List Cycle
    LeetCode Best Time to Buy and Sell Stock II
    LeetCode Balanced Binary Tree
    LeetCode Validate Binary Search Tree
  • 原文地址:https://www.cnblogs.com/OceanEyes/p/design_pattern_in_gateway.html
Copyright © 2011-2022 走看看