admin管理员组

文章数量:1130349

一、背景

在微服务重构时,我们常遇到这个业务场景:同样是/api/test,我们实现了新逻辑和老逻辑,然后根据定制的灰度策略,通常灰度API和老API两者都需要支持用户使用。

那么是否有比较好解决方案,协助我们完成同名同方法同参数列表的API灰度动态路由的方案呢?

我们就基于SpringMVC,通过对底层RequestMappingInfo的参数定制化,实现了methodHandler的动态路由决策,从而达到API灰度动态路由目的。

二、实现原理

我们总的来说,干了两件事情:

  • 第一件事

    服务启动时,在initMethodHandler执行时,对RequestMappingInfo初始化时,就将灰度决策器RouterDecisionMaker,以@PathRouterDecisionMaker决策器注解的形式,预加载到customInfo里

  • 第二件事

    服务运行期,在路由匹配器PathMatcher里,会解析RequestMappingInfo,最终执行灰度决策器RouterDecisionMaker,并挑选最合适的RequestMappingInfo映射的methodHandler去执行响应逻辑

三、实现方案

1、定义决策器注解:@PathRouterDecisionMaker

@Target({ElementType.TYPE, ElementType.PACKAGE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PathRouterDecisionMaker {
    Class<? extends RouterDecisionMaker> decision();
    String resourceCondition() default "";
    int order() default 0;
}

分析:

  • decision():决策属性,指向路由决策器类,即RouterDecisionMaker接口

注解(Annotation):仅提供附加元数据支持,并 不能实现任何操作,需要另外的 Scanner 根据元数据执行相应操作。

2、路由决策器:RouterDecisionMaker接口

RouterDecisionMaker接口,定义了决策器的匹配策略方法

public interface RouterDecisionMaker {


    /**
     * 路由决策器的最终决策方法
     * @param pathPartRequest
     * @return 匹配返回的资源类型
     */
    boolean matches(PathPartRequest pathPartRequest);
}

2.1 灰度决策器-ApiGrayDecisionMaker(返回true)

@Component(value = "ApiGrayDecisionMaker")
public class ApiGrayDecisionMaker implements RouterDecisionMaker {


    @Override
    public boolean matches(PathPartRequest pathPartRequest) {
        return Boolean.TRUE;
    }


}

代码分析:

  • 为了方便实践,我们写死一个决策匹配策略为TRUE

2.2 灰度决策器-ApiNotGrayDecisionMaker(返回false)

@Component("ApiNotGrayDecisionMaker")
public class ApiNotGrayDecisionMaker implements RouterDecisionMaker {


    /**
     * 取反,跟 ApiGrayDecision#matches 互斥
     * @param pathPartRequest
     * @return
     */
    @Override
    public boolean matches(PathPartRequest pathPartRequest) {
        return Boolean.FALSE;
    }
}

代码分析:

  • 为了方便实践,我们写死一个决策匹配策略为FALSE

2.3 决策信息

RouterPathRequest,提供数据给决策器

public class RouterPathRequest {


    private final String pattern;
    private final String url;
    private final Map<String, String> pathVariables;
    private final RouterPatternKey routerPatternKey;
    private final String routeCondition;
    private final HttpServletRequest request;


    public RouterPathRequest(HttpServletRequest request, String pattern, String url, Map<String, String> pathVariables,
            RouterPatternKey routerPatternKey, String routeCondition) {
        this.request = request;
        this.pattern = pattern;
        this.pathVariables = pathVariables;
        this.url = url;
        this.routerPatternKey = routerPatternKey;
        this.routeCondition = routeCondition;
    }


    public static RouterPathRequest build(HttpServletRequest request, String pattern, String url,
            Map<String, String> pathVariables, RouterPatternKey routerPatternKey, String routeCondition) {
        return new RouterPathRequest(request, pattern, url, pathVariables, routerPatternKey, routeCondition);
    }
    //...getter&setter
}

代码分析:

  • 实际是对HttpServletRequest的二次封装,并提取了一些常用上下文数据到属性

2.4 决策注解探测器

WebRouterDecisionMakerDetection

/**
 *  PathRouterDecisionMaker注解提取器
 *   - 从方法注释提取注解
 */
public class WebRouterDecisionMakerDetection {
    public PathRouterDecisionMaker detect(Method handlerMethod) {
        if (Objects.isNull(handlerMethod)) {
            return null;
        }
        return AnnotatedElementUtils.findMergedAnnotation(handlerMethod, PathRouterDecisionMaker.class);
    }
}

2.5 自定义-路由匹配策略

WebRouterDecisionCondition 

继承了 AbstractRequestCondition,会在创建RequestMappingInfo的填入customCondition条件时,被回调使用。

  • 抽象类AbstractRequestCondition实现了 RequestConditi

一、背景

在微服务重构时,我们常遇到这个业务场景:同样是/api/test,我们实现了新逻辑和老逻辑,然后根据定制的灰度策略,通常灰度API和老API两者都需要支持用户使用。

那么是否有比较好解决方案,协助我们完成同名同方法同参数列表的API灰度动态路由的方案呢?

我们就基于SpringMVC,通过对底层RequestMappingInfo的参数定制化,实现了methodHandler的动态路由决策,从而达到API灰度动态路由目的。

二、实现原理

我们总的来说,干了两件事情:

  • 第一件事

    服务启动时,在initMethodHandler执行时,对RequestMappingInfo初始化时,就将灰度决策器RouterDecisionMaker,以@PathRouterDecisionMaker决策器注解的形式,预加载到customInfo里

  • 第二件事

    服务运行期,在路由匹配器PathMatcher里,会解析RequestMappingInfo,最终执行灰度决策器RouterDecisionMaker,并挑选最合适的RequestMappingInfo映射的methodHandler去执行响应逻辑

三、实现方案

1、定义决策器注解:@PathRouterDecisionMaker

@Target({ElementType.TYPE, ElementType.PACKAGE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PathRouterDecisionMaker {
    Class<? extends RouterDecisionMaker> decision();
    String resourceCondition() default "";
    int order() default 0;
}

分析:

  • decision():决策属性,指向路由决策器类,即RouterDecisionMaker接口

注解(Annotation):仅提供附加元数据支持,并 不能实现任何操作,需要另外的 Scanner 根据元数据执行相应操作。

2、路由决策器:RouterDecisionMaker接口

RouterDecisionMaker接口,定义了决策器的匹配策略方法

public interface RouterDecisionMaker {


    /**
     * 路由决策器的最终决策方法
     * @param pathPartRequest
     * @return 匹配返回的资源类型
     */
    boolean matches(PathPartRequest pathPartRequest);
}

2.1 灰度决策器-ApiGrayDecisionMaker(返回true)

@Component(value = "ApiGrayDecisionMaker")
public class ApiGrayDecisionMaker implements RouterDecisionMaker {


    @Override
    public boolean matches(PathPartRequest pathPartRequest) {
        return Boolean.TRUE;
    }


}

代码分析:

  • 为了方便实践,我们写死一个决策匹配策略为TRUE

2.2 灰度决策器-ApiNotGrayDecisionMaker(返回false)

@Component("ApiNotGrayDecisionMaker")
public class ApiNotGrayDecisionMaker implements RouterDecisionMaker {


    /**
     * 取反,跟 ApiGrayDecision#matches 互斥
     * @param pathPartRequest
     * @return
     */
    @Override
    public boolean matches(PathPartRequest pathPartRequest) {
        return Boolean.FALSE;
    }
}

代码分析:

  • 为了方便实践,我们写死一个决策匹配策略为FALSE

2.3 决策信息

RouterPathRequest,提供数据给决策器

public class RouterPathRequest {


    private final String pattern;
    private final String url;
    private final Map<String, String> pathVariables;
    private final RouterPatternKey routerPatternKey;
    private final String routeCondition;
    private final HttpServletRequest request;


    public RouterPathRequest(HttpServletRequest request, String pattern, String url, Map<String, String> pathVariables,
            RouterPatternKey routerPatternKey, String routeCondition) {
        this.request = request;
        this.pattern = pattern;
        this.pathVariables = pathVariables;
        this.url = url;
        this.routerPatternKey = routerPatternKey;
        this.routeCondition = routeCondition;
    }


    public static RouterPathRequest build(HttpServletRequest request, String pattern, String url,
            Map<String, String> pathVariables, RouterPatternKey routerPatternKey, String routeCondition) {
        return new RouterPathRequest(request, pattern, url, pathVariables, routerPatternKey, routeCondition);
    }
    //...getter&setter
}

代码分析:

  • 实际是对HttpServletRequest的二次封装,并提取了一些常用上下文数据到属性

2.4 决策注解探测器

WebRouterDecisionMakerDetection

/**
 *  PathRouterDecisionMaker注解提取器
 *   - 从方法注释提取注解
 */
public class WebRouterDecisionMakerDetection {
    public PathRouterDecisionMaker detect(Method handlerMethod) {
        if (Objects.isNull(handlerMethod)) {
            return null;
        }
        return AnnotatedElementUtils.findMergedAnnotation(handlerMethod, PathRouterDecisionMaker.class);
    }
}

2.5 自定义-路由匹配策略

WebRouterDecisionCondition 

继承了 AbstractRequestCondition,会在创建RequestMappingInfo的填入customCondition条件时,被回调使用。

  • 抽象类AbstractRequestCondition实现了 RequestConditi

本文标签: 灰度方案SpringMVCapi