本文使用zuul网关实现灰度发布,包括了网关到服务、服务到服务的灰度。项目gitee:https://gitee.com/menbbo/gray-demo.git
服务部署可分为三种方式
1)蓝绿发布
蓝绿发布是通过冗余的方式来解决部署问题,生产环境为绿色配置,冗余的服务为蓝色配置。在部署服务时,首先在冗余服务器上部署最新代码,由部分用户使用,
若使用没有问题,则通过负载均衡将所有用户请求转发到冗余服务器中,即冗余的服务转变为生产环境服务。优点是无需停机部署,服务回滚方便。缺点耗费服务器资源。
2)滚动发布
滚动发布指每次只部署一个或多个服务,直到服务部署完成为止。优点:用户无感知,平滑过渡;相比蓝绿发布节省服务器资源。缺点:部署复杂,且时间长;遇到
问题回滚比较复杂。
3)灰度发布
只升级部分服务,让少量用户访问新部署的服务,其他用户使用老服务,用户反馈无误后,整个集群部署,将用户迁移到新服务上来。优点:在灰度时即可发现问题及
时处理,保证系统稳定性;如果出现问题,影响范围小;用户无感知,过渡平滑。
灰度发布实现步骤:
1)定义规则:哪些用户可以访问灰度环境,比如按百分比(10%的用户可以访问灰度),或让固定用户先体验灰度环境;
2)利用网关实现路由策略,即网关到服务的路由;
3)服务与服务之间的调用使用ribbon实现灰度规则。
代码实现
代码使用zuul网关实现,项目包括了zuul、im、search三个服务,im服务调用search服务,具体实现如下。
1.引入maven依赖,关键依赖
<dependency>
<groupId>io.jmnarloch</groupId>
<artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
2.application.properties配置文件进行配置
server.port=9090 spring.application.name=zuul #注册中心 eureka.client.service-url.defaultZone=http://localhost:8761/eureka eureka.instance.prefer-ip-address=true eureka.client.registerWithEureka=true #zuul网关路由 前缀 zuul.routes.prefix=/zuul zuul.routes.im.path=/im/** #im代表自定义服务 zuul.routes.im.service-id=im #false不会截取 true截取前缀 zuul.routes.im.stripPrefix=true #http://localhost:9090/zuul/im/index
3.GrayFilter过滤器实现网关到服务的灰度规则
@Component
public class GrayFilter extends ZuulFilter {
private static final String GRAY = "gray";
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
//是否开启过滤
return true;
}
@Override
public Object run() throws ZuulException {
//实现灰度逻辑
//前端在请求头中携带灰度标识字段
//服务注册时加入metadata数据,代表该服务节点为灰度节点
//首先从头部中获取标识
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
String header = request.getHeader("gray_header");
//将灰度的请求转发到meataData中forward为1的服务
if(StringUtils.equals(header,GRAY)){
RibbonFilterContextHolder.getCurrentContext().add("forward","1");
}else {
RibbonFilterContextHolder.getCurrentContext().add("forward","2");
}
return null;
}
}
4.im、search服务启动时注册metadata到注册中心
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ eureka.client.enabled=true #eureka.instance.hostname=localhost #eureka.instance.instance-id=im #灰度端口 server.port=8081 #生产端口 #server.port=8082 spring.application.name=im #灰度发布需要metadata #灰度为1 eureka.instance.metadata-map.forward=1
eureka.client.service-url.defaultZone=http://localhost:8761/eureka server.port=8089 spring.application.name=search #灰度为1 eureka.instance.metadata-map.forward=1
5.实现服务到服务的灰度规则
/**
* 定义服务间灰度调用规则
*/
@Component
public class GrayRule extends AbstractLoadBalancerRule {
private static final String GRAY = "gray";
private static final String GRAY_HEADER = "forward";
private static final String GRAY_VALUE = "1";
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
ILoadBalancer loadBalancer = getLoadBalancer();
return this.choose(loadBalancer);
}
private Server choose(ILoadBalancer lb){
Server server = null;
if (server==null){
//获取请求头中的参数,具体实现在gitee
Map<String, String> stringStringMap = GrayRibbonParamater.get();
String grayParmater = null;
if(stringStringMap!=null){
grayParmater = stringStringMap.get("gray_header");
}
//获得可到达的服务
List<Server> reachableServers = lb.getReachableServers();
for (Server reachableServer : reachableServers) {
//获取服务的metadata
Map<String, String> metadata = ((DiscoveryEnabledServer) reachableServer).getInstanceInfo().getMetadata();
if(StringUtils.equals(metadata.get(GRAY_HEADER),GRAY_VALUE)&&StringUtils.equals(grayParmater,GRAY)){
return reachableServer;
}
}
}
return server;
}
}
浙公网安备 33010602011771号