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();
    
  • 相关阅读:
    Mysql主从复制(基于Log)
    Linux系统开机启动流程
    JS的 验证组织机构的合法性
    Linux以下基于TCP多线程聊天室(client)
    浅谈Java集合框架
    疯狂Java学习笔记(72)-----------大话程序猿面试
    Android自己定义View之组合控件 ---- LED数字时钟
    C/C++学习:函数指针
    springmvc+spring+jpa(hibernate)+redis+maven配置
    数组进行多少次OP操作,才干有序
  • 原文地址:https://www.cnblogs.com/OceanEyes/p/design_pattern_in_gateway.html
Copyright © 2011-2022 走看看