spring cloud 灰色发布

spring cloud 灰色发布

1.灰度发布的定义

灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B 上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
spring cloud 灰色发布

2.为什么要做灰色发布以及好处

这样做,一是为了用户体验,先让小部分人体验后得到反馈才渐渐去大范围影响;
二是技术上降低风险,在放出小范围后去发现问题解决问题;
再有就是降低讽刺压力,全量上线一款新功能很容易造成访问量突然增大,服务器压力过大的风险。

3.实现思路

3.1.springCloud灰度方案说明

本博客是在Spring cloud架构体系中基于eureka、ribbon实现灰度发布。具体原理如下:
我们知道,在eureka中注册各个服务后,如果一个服务有多个实例,那么默认会走ribbon的软负载均衡来进行分发请求。

我们要完成灰度发布,要做的就是修改ribbon的负载策略(rule),通过一些特定的标识,譬如我们可以选择header里带有foo=1的全部路由到金丝雀服务上,其他的还走原来的老版本。或者可以设置个比重,虽然roll个小于4的正数,将等于1的路由到金丝雀,这样就会有1/4的请求到达金丝雀。诸如此类,我们可以定制各种规则来进行灰度测试。

在SpringCloud体系中,完成这件事,模式比较固定,就是根据eureka的metadata进行自定义元数据,然后修改ribbon的Rule规则。

具体修改如下demo

3.2.demo

这里只发出来目标服务和zuul的代码,eureka的就不放了。eureka很简单,就是一个eureka server项目,什么也没有。

我们的目标服务是User,在User的application.yml里,由于我要启动2个,所以使用不同的端口`application.yml

server:
port: 8888
eureka:
instance:
prefer-ip-address: true
metadata-map:
lancher: 2
client:
service-url:
defaultZone: http://localhost:10000/eureka/
application-dev.yml
server:
port: 8889
eureka:
instance:
metadata-map:
lancher: 1
就是那个metadata-map元数据,这是一个map,里面就自定义一些key-value键值对。将来匹配时就用这个键值对。
然后分别启动这两个实例,启动后就有两个user注册到了eureka。

zuul配置:

在zuul项目里添加依赖,https://github.com/jmnarloch/ribbon-discovery-filter-spring-cloud-starter

io.jmnarloch ribbon-discovery-filter-spring-cloud-starter 2.1.0 这个就是做ribbon的Rule的。

package com.example.zuul_route;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder;
import org.springframework.context.annotation.Configuration;

import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.*;

/**

  • @author kzj
    */
    @Configuration
    public class PreFilter extends ZuulFilter {
    @Override
    public int filterOrder() {
    return PRE_DECORATION_FILTER_ORDER - 1;
    }

@Override
public String filterType() {
return PRE_TYPE;
}

@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
// a filter has already forwarded
// a filter has already determined serviceId
return !ctx.containsKey(FORWARD_TO_KEY)
&& !ctx.containsKey(SERVICE_ID_KEY);
}

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getParameter(“foo”) != null) {
// put the serviceId in RequestContext
RibbonFilterContextHolder.getCurrentContext()
.add(“lancher”, “1”);
} else {
RibbonFilterContextHolder.getCurrentContext()
.add(“lancher”, “2”);
}

return null;
}
}
这个是zuul的filter,关键代码在run方法,

RibbonFilterContextHolder.getCurrentContext() .add(“lancher”, “1”);
这句话就代表将请求路由到metadata-map里lancher为1的那个服务。
整个过程很简单,我们就可以在这里定制各种规则了,把符合什么条件的请求,只发送到某个实例。