本文使用zuul网关实现灰度发布,包括了网关到服务、服务到服务的灰度。
服务部署可分为三种方式
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; } }