本文共 7791 字,大约阅读时间需要 25 分钟。
来自百度百科
灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
灰度期:灰度发布开始到结束期间的这一段时间,称为灰度期。
1、微服务中通过配置eureka.instance.metadata-map.version来控制新老服务版本号。
2、客户端请求时通过传递token来区分用户。
3、网关过滤器,首先获取灰度相关配置,然后肯定token与规则进行匹配,满足灰度条件的走新服务,不满足的走老服务。
4、如果新服务有问题,则可通过修改灰度配置来使流量全部切回到老服务。
5、如果新服务无问题,则可通过修改灰度配置来使流量全部切到新服务。
@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;}
eureka.instance.metadata-map.version=v1
@GetMapping("/getOrderInfo")public String getOrderInfo() { return "order v1";}
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)
现在虽然实现了网关到服务请求的灰度控制,但是各个微服务之间还未实现灰度。
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"); // 获取可用服务列表 ListserverList = 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/