如何巧妙设计SpringMVC API灰度发布?
- 内容介绍
- 文章标签
- 相关推荐

哎,说实话,搞API灰度发布这玩意儿,一开始真是头大。各种方案,各种配置,让人眼花缭乱。特别是咱们这种小公司, 人员有限,搞那些复杂的Istio、Service Mesh什么的,简直是mission impossible。后来想了想,与其追逐潮流,不如脚踏实地,利用现有的技术栈搞一套简单的、可控的方案。于是乎,就有了这个基于SpringMVC的API灰度发布方案。
背景与问题
在快速迭代的产品开发中,API接口的更新迭代是常态,只是,旧版本接口往往已经有大量用户依赖,无法马上停用,那么如何在不影响现有用户的前提下平滑引入新版本API?,太离谱了。
传统API版本管理方案及不足
以前我们都是怎么搞的呢?要么在URL上加版本号,要么在header里加个version参数。这种方式简单粗暴, 但问题也很多:URL一长看着就恶心; 我晕... header参数容易被忽略;最重要的是每次改版本都要上线新的代码!这对于频繁迭代的小团队来说简直是噩梦。
为什么需要灰度发布
所以说啊!我们需要一种更优雅的方式来管理API版本和进行平滑过渡。这就是灰度发布的用武之地。通过逐步将流量导向新版本API, 补救一下。 我们可以及时发现潜在的问题并进行修复,而不会影响到所有用户。
核心思想
我们的核心思想是:利用SpringMVC的底层机制对RequestMappingInfo进行定制化。简单来说就是给每个方法打个注解@PathRouterDecisionMaker,然后在请求处理过程中根据这个注解来决定走哪个版本的API,歇了吧...。
@PathRouterDecisionMaker 注解
@Target
@Retention
public @interface PathRouterDecisionMaker {
Class extends RouterDecisionMaker> decision; // 决策器类
String resourceCondition default ""; // 资源条件
int order default 0; // 施行顺序
}
实现原理
要说这个原理啊...其实挺复杂的...我尽量说清楚点吧,太治愈了。。
1、 AbstractRequestCondition:这是一个抽象类
实现了 RequestCondition 接口,并提供了一1些默认实现。它简化了自定义条件的实现过程。 2、RequestCondition:这是一个接口,定义了用于匹配请求的条件。它包含两个主要方法: - getMatchingCondition:返回与给定请求匹配的条件。- combine:将当前条件与其他条件组合。
2、 进入到AbstractHandlerMethodMapping#lookupHandlerMethod
扯后腿。 预加载是:通过MappingRegistry,将原API和灰度API的RequestMappingInfo信息,注册到mappingLookup这个Map里。 产品名称 功能 价格 某监控系统 实时监控、 告警 ¥999/年 某日志分析平台日志收集、分析¥1499/年
3、返回methodHandler
服务运行时获取methodHandler时会回调 WebRouterDeci 切记... sionCondition#getMatchingCondition 方法.
摆烂。 沿着后面的链路一直debug会回调到 WebRouterConstraintCondition#getMatchingCondition 方法
核心组件
- WebMvcRegistrationsConfig:负责注册自定义的 RequestMappingHandlerMapping
- WebRouterPathConstraintMatcher:负责提取 PathRouterDecisionMaker 注解元信息
- WebRouterDecisionMakerDetection: 负责检测方法上的PathRouterDecisionMaker注解
代码分析
public interface RouterDecisionMaker { /** * 路由决策器的到头来决策方法 * @param pathPartRequest * @return 匹配返回的资源类型 */ boolean matches;}具体步骤
public class RouterPathRequest { private final String pattern; private final String url; private final Map pathVariables; private final RouterPatternKey routerPatternKey; private final String routeCondition; private final HttpServletRequest request; public RouterPathRequest { = request; = pattern; = pathVariables; = url; = routerPatternKey; = routeCondition;} public static RouterPathRequest build { return new RouterPathRequest; } //...getter&setter} @RestControllerpublic class ConstraintController { @PathRouterDecisionMaker @GetMapping public String test { return "非灰度:老API.."; } @PathRouterDecisionMaker @GetMapping public String test2 { return "灰度:新API.."; }}遇到的问题及解决方案

哎,说实话,搞API灰度发布这玩意儿,一开始真是头大。各种方案,各种配置,让人眼花缭乱。特别是咱们这种小公司, 人员有限,搞那些复杂的Istio、Service Mesh什么的,简直是mission impossible。后来想了想,与其追逐潮流,不如脚踏实地,利用现有的技术栈搞一套简单的、可控的方案。于是乎,就有了这个基于SpringMVC的API灰度发布方案。
背景与问题
在快速迭代的产品开发中,API接口的更新迭代是常态,只是,旧版本接口往往已经有大量用户依赖,无法马上停用,那么如何在不影响现有用户的前提下平滑引入新版本API?,太离谱了。
传统API版本管理方案及不足
以前我们都是怎么搞的呢?要么在URL上加版本号,要么在header里加个version参数。这种方式简单粗暴, 但问题也很多:URL一长看着就恶心; 我晕... header参数容易被忽略;最重要的是每次改版本都要上线新的代码!这对于频繁迭代的小团队来说简直是噩梦。
为什么需要灰度发布
所以说啊!我们需要一种更优雅的方式来管理API版本和进行平滑过渡。这就是灰度发布的用武之地。通过逐步将流量导向新版本API, 补救一下。 我们可以及时发现潜在的问题并进行修复,而不会影响到所有用户。
核心思想
我们的核心思想是:利用SpringMVC的底层机制对RequestMappingInfo进行定制化。简单来说就是给每个方法打个注解@PathRouterDecisionMaker,然后在请求处理过程中根据这个注解来决定走哪个版本的API,歇了吧...。
@PathRouterDecisionMaker 注解
@Target
@Retention
public @interface PathRouterDecisionMaker {
Class extends RouterDecisionMaker> decision; // 决策器类
String resourceCondition default ""; // 资源条件
int order default 0; // 施行顺序
}
实现原理
要说这个原理啊...其实挺复杂的...我尽量说清楚点吧,太治愈了。。
1、 AbstractRequestCondition:这是一个抽象类
实现了 RequestCondition 接口,并提供了一1些默认实现。它简化了自定义条件的实现过程。 2、RequestCondition:这是一个接口,定义了用于匹配请求的条件。它包含两个主要方法: - getMatchingCondition:返回与给定请求匹配的条件。- combine:将当前条件与其他条件组合。
2、 进入到AbstractHandlerMethodMapping#lookupHandlerMethod
扯后腿。 预加载是:通过MappingRegistry,将原API和灰度API的RequestMappingInfo信息,注册到mappingLookup这个Map里。 产品名称 功能 价格 某监控系统 实时监控、 告警 ¥999/年 某日志分析平台日志收集、分析¥1499/年
3、返回methodHandler
服务运行时获取methodHandler时会回调 WebRouterDeci 切记... sionCondition#getMatchingCondition 方法.
摆烂。 沿着后面的链路一直debug会回调到 WebRouterConstraintCondition#getMatchingCondition 方法
核心组件
- WebMvcRegistrationsConfig:负责注册自定义的 RequestMappingHandlerMapping
- WebRouterPathConstraintMatcher:负责提取 PathRouterDecisionMaker 注解元信息
- WebRouterDecisionMakerDetection: 负责检测方法上的PathRouterDecisionMaker注解
代码分析
public interface RouterDecisionMaker { /** * 路由决策器的到头来决策方法 * @param pathPartRequest * @return 匹配返回的资源类型 */ boolean matches;}具体步骤
public class RouterPathRequest { private final String pattern; private final String url; private final Map pathVariables; private final RouterPatternKey routerPatternKey; private final String routeCondition; private final HttpServletRequest request; public RouterPathRequest { = request; = pattern; = pathVariables; = url; = routerPatternKey; = routeCondition;} public static RouterPathRequest build { return new RouterPathRequest; } //...getter&setter} @RestControllerpublic class ConstraintController { @PathRouterDecisionMaker @GetMapping public String test { return "非灰度:老API.."; } @PathRouterDecisionMaker @GetMapping public String test2 { return "灰度:新API.."; }}
