微服务中,新版服务上线的时候,为了保证不出什么问题,可以将少量的请求转发到新的服务上,然后其他的请求还是转发到旧的服务上去,等线上的新服务测试通过以后,就可以重新平均分配请求。这种功能就称为灰度发布。
要完成灰度发布,要做的就是修改ribbon的负载均衡策略,通过一些特定的标识,比如我们针对某个接口路径/gray/publish/test。将10%的请求转发到新的服务上,将90%的请求转发到旧的服务上,诸如此类,我们可以制定各种规则进行灰度测试。
在微服务中,我们可以通过eureka的metamata进行自定义元数据,然后来修改ribbon的负载均衡策略。
在此,可以先通过代码进行简单地灰度发布,在实际应用中,可以通过数据库配置进行灰度灵活发布。
首先,我们如果要部署新版的服务user-server,我们用不同的端口启动这个服务,并配置不同的自定义元数据,配置如下:
server: port: 8181 #Eureka注册配置 eureka: client: serviceUrl: defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/ instance: metadata-map: forward: 1
server: port: 8182 #Eureka注册配置 eureka: client: serviceUrl: defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/ instance: metadata-map: forward: 2
我们启动了8181和8182两个服务,并分别定义了元数据forward: 1和forward: 2,然后,开始修改zuul网关,首先导入修改ribbon负载均衡策略的依赖
<!-- 修改Zuul的负载均衡策略,也就是进行灰度发布使用的 --> <dependency> <groupId>io.jmnarloch</groupId> <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId> <version>2.1.0</version> </dependency>
然后,开始修改ribbon的负载均衡策略
package com.dkjk.gateway.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.http.HttpServletRequest; import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.*; /** * @author: qjc * @createTime: 2020/7/29 * @Description: 接口安全验证过滤器 */ @Component @Slf4j public class ValidFilter extends ZuulFilter { @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return PRE_DECORATION_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { // 进行跨域请求的时候,并且请求头中有额外参数,比如token,客户端会先发送一个OPTIONS请求来探测后续需要发起的跨域POST请求是否安全可接受 // 所以这个请求就不需要拦截,下面是处理方式 RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); if (request.getMethod().equals(RequestMethod.OPTIONS.name())) { log.info("OPTIONS请求不做拦截操作"); return false; } return true; } @Override public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String requestURI = request.getRequestURI(); if (requestURI.contains("/gray/publish/test")){ int send = (int) (Math.random() * 100); if (send >= 0 && send < 10) { //也就是百分之10的请求转发到forward=1的服务上去 RibbonFilterContextHolder.getCurrentContext().add("forward", "1");//这句话就代表将请求路由到metadata-map里forward为1的那个服务 } else { //百分之90的请求转发到forward=2的服务上去 RibbonFilterContextHolder.getCurrentContext().add("forward", "2");//这句话就代表将请求路由到metadata-map里forward为2的那个服务 } } return null; } }
接下来就可以测试接口/gray/publish/test的转发情况了,结果就是10%的请求转发到了8181上(即forward为1的服务上),90%的请求转发到了8182(即forward为2的服务上)
PS:在生产上使用的时候,可以通过创建数据库表来动态配置负载均衡策略,比如创建一张数据表:
CREATE TABLE `gray_release_config` ( `id` int(11) NOT NULL AUTO_INCREMENT, `server_name` varchar(255) DEFAULT NULL, //服务名 `path` varchar(255) DEFAULT NULL,//需要进行灰度发布的接口路径 `percent` int(11) DEFAULT NULL,//负载均衡策略,百分之percent的请求转发到forward上 `forward` int(11) DEFAULT NULL,//自定义元数据值 PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
然后每隔一段时间同步配置表中的信息,也就是写个定时任务。