博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于zuul和ribbon的灰度发布方案
阅读量:2497 次
发布时间:2019-05-11

本文共 7791 字,大约阅读时间需要 25 分钟。

什么是灰度发布

来自百度百科

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

灰度期:灰度发布开始到结束期间的这一段时间,称为灰度期。

通过Zuul实现,客户端到网关的灰度

流程设计

1、微服务中通过配置eureka.instance.metadata-map.version来控制新老服务版本号。

2、客户端请求时通过传递token来区分用户。

3、网关过滤器,首先获取灰度相关配置,然后肯定token与规则进行匹配,满足灰度条件的走新服务,不满足的走老服务。

4、如果新服务有问题,则可通过修改灰度配置来使流量全部切回到老服务。

5、如果新服务无问题,则可通过修改灰度配置来使流量全部切到新服务。

Zuul过滤器代码

@Componentpublic class GrayFilter extends ZuulFilter {
@Resource private GrayRuleMapper grayRuleMapper; @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 {
RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); /** * 从数据库中读取灰度配置信息,可优化从缓存中获取 */ GrayRuleEntity grayRuleEntity = grayRuleMapper.getGrayRule(); String oldVersion = grayRuleEntity.getOldVersion(); String newVersion = grayRuleEntity.getNewVersion(); String greyUser = grayRuleEntity.getGreyUser(); String ruleType = grayRuleEntity.getRuleType(); /** * 流程: * 1、根据数据库配置规则确定是,当前版本是发布老版本,还是发布新版本,还是发布灰度 * 2、传递版本号 */ if ("1".equals(ruleType)) {
//走老服务 RibbonFilterContextHolder.getCurrentContext().add("version", oldVersion); } else if ("2".equals(ruleType)) {
//灰度完成,走新服务 RibbonFilterContextHolder.getCurrentContext().add("version", newVersion); } else {
//灰度 String token = request.getHeader("token"); //根据token与数据库中greyUser列值的匹配,进行新老版本访问控制 //演示项目,规则写的比较简单,实际项目中,可以根据详细的规则配置,获取到相应的灰度目标群(比如白名单用户,某个城市范围,某种权限等等),进行服务访问控制 if (token.equalsIgnoreCase(greyUser)) {
RibbonFilterContextHolder.getCurrentContext().add("version", newVersion); } else {
RibbonFilterContextHolder.getCurrentContext().add("version", oldVersion); } } return null; }}
public interface GrayRuleMapper {
/** * 获取灰度配置 * @return */ GrayRuleEntity getGrayRule();}
public class GrayRuleEntity {
/** * 版本类别,1:全部老版本,2:全部新版本,3:灰度 */ private String ruleType; /** * 老版本 */ private String oldVersion; /** * 新版本 */ private String newVersion; /** * 灰度用户 */ private String greyUser;}

数据库信息

在这里插入图片描述

微服务order 1(老版本)

eureka.instance.metadata-map.version=v1
@GetMapping("/getOrderInfo")public String getOrderInfo() {
return "order v1";}

微服务order 2(新版本)

eureka.instance.metadata-map.version=v2
@GetMapping("/getOrderInfo")public String getOrderInfo() {
return "order v2";}

最终效果

1、当数据库ruleType为1时,无论token值是多少,都只会调用v1版本的服务。(新版本出现问题时,二配置为1)

2、当数据库ruleType为2时,无论token值是多少,都只会调用v2版本的服务。(新版本没问题时,配置为2)

3、当数据库ruleType为3时,根据greyUser列值进行判断,如果是灰度用户,则调用v2版本的服务,否则调用v1版本的服务。(灰度发布,配置为3)

通过Ribbon实现,服务到服务的灰度

现在虽然实现了网关到服务请求的灰度控制,但是各个微服务之间还未实现灰度。

流程设计

1、通过网关把版本号传递到各个微服务。

2、微服务配置自定义的Ribbon负载均衡规则,重写choose方法。

3、在choose方法中获取可用服务列表,然后根据网关传过来的版本号进行匹配。

import com.netflix.loadbalancer.Server;import com.netflix.loadbalancer.ZoneAvoidanceRule;import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;import org.apache.commons.lang.StringUtils;import org.springframework.context.annotation.Configuration;import org.springframework.web.context.request.RequestContextHolder;import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;import java.util.List;import java.util.Map;@Configurationpublic class GrayRibbonRule extends ZoneAvoidanceRule {
@Override public Server choose(Object key) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); //通过网关传过来的 String version = request.getHeader("zuulVersion"); // 获取可用服务列表 List
serverList = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key); return getServer(serverList, version); } /** * 根据网关传过来的版本从可用的服务列表中找到匹配的服务。 * @param serverList * @param version * @return */ private Server getServer(List
serverList, String version) {
Server toServer = null; for (Server server : serverList) {
Map
metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata(); String metaVersion = metadata.get("version"); if (!StringUtils.isEmpty(metaVersion)) {
if (metaVersion.equals(version)) {
toServer = server; } } } return toServer; }}

网关过滤器,新增版本传递代码

import com.netflix.zuul.ZuulFilter;import com.netflix.zuul.context.RequestContext;import com.netflix.zuul.exception.ZuulException;import com.wyl.springcloud.zuul.dao.GrayRuleMapper;import com.wyl.springcloud.zuul.entity.GrayRuleEntity;import io.jmnarloch.spring.cloud.ribbon.support.RibbonFilterContextHolder;import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;import org.springframework.stereotype.Component;import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;@Componentpublic class GrayFilter extends ZuulFilter {
@Resource private GrayRuleMapper grayRuleMapper; @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 {
RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); /** * 可优化从缓存中获取 */ GrayRuleEntity grayRuleEntity = grayRuleMapper.getGrayRule(); String oldVersion = grayRuleEntity.getOldVersion(); String newVersion = grayRuleEntity.getNewVersion(); String greyUser = grayRuleEntity.getGreyUser(); String ruleType = grayRuleEntity.getRuleType(); /** * 流程: * 1、根据数据库配置规则确定是,当前版本是发布老版本,还是发布新版本,还是发布灰度 * 2、传递版本号 */ if ("1".equals(ruleType)) {
//回滚 RibbonFilterContextHolder.getCurrentContext().add("version", oldVersion); //新增的版本传递 currentContext.addZuulRequestHeader("zuulVersion",oldVersion); } else if ("2".equals(ruleType)) {
//正是上限 RibbonFilterContextHolder.getCurrentContext().add("version", newVersion); //新增的版本传递 currentContext.addZuulRequestHeader("zuulVersion",newVersion); } else {
//灰度 String token = request.getHeader("token"); if (token.equalsIgnoreCase(greyUser)) {
RibbonFilterContextHolder.getCurrentContext().add("version", newVersion); //新增的版本传递 currentContext.addZuulRequestHeader("zuulVersion",newVersion); } else {
RibbonFilterContextHolder.getCurrentContext().add("version", oldVersion); //新增的版本传递 currentContext.addZuulRequestHeader("zuulVersion",oldVersion); } } return null; }}

其他无需任何配置,只要微服务调用底层是通过ribbon实现负载均衡就会走到我们自定义的规则中(feign自带通过ribbon负载均衡、restTemplate一般都要通过ribbon负载均衡)。

本例中基本已经完成灰度的所有实现,但是还有一个小问题要解决,就是服务之间调用,重写了choose方法,但是并没有实现负载均衡,其实如果要实现一个轮询的方式也很简单,无非就是搞一个递增的数字与服务列表取模而已。

转载地址:http://kmlrb.baihongyu.com/

你可能感兴趣的文章
PHP 实例 - AJAX RSS 阅读器
查看>>
POJ 2696 计算表达式的值
查看>>
都江堰很美-佩服古人_Crmhf的一天
查看>>
Linux系统资源查询命令(cpu、io、mem)
查看>>
(转)PowerHA完全手册(一,二,三)
查看>>
hdu 4391 Paint The Wall 线段树 +优化 2012 Multi-University Training Contest 10 )
查看>>
socket与socketServer通信
查看>>
2017.12.1T19_B2_2zuoye
查看>>
Android_Layout (一)
查看>>
vs文件上传失败--超过最大字符限制
查看>>
《过早退出是一切失败的根源》读后感
查看>>
luogu P1774 最接近神的人_NOI导刊2010提高(02)
查看>>
Dynamic Proxy
查看>>
Yii2的一些问题
查看>>
LeetCode OJ - Populating Next Right Pointers in Each Node II
查看>>
C++ wifstream读取日文方法(中文适用)
查看>>
B-树
查看>>
php计算上个月是几月份
查看>>
浅谈 trie树 及其实现
查看>>
60款很酷的 jQuery 幻灯片演示和下载
查看>>