赞
踩
来自百度百科
灰度发布(又名金丝雀发布)是指在黑与白之间,能够平滑过渡的一种发布方式。在其上可以进行A/B testing,即让一部分用户继续用产品特性A,一部分用户开始用产品特性B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。
灰度期:灰度发布开始到结束期间的这一段时间,称为灰度期。
1、微服务中通过配置eureka.instance.metadata-map.version来控制新老服务版本号。
2、客户端请求时通过传递token来区分用户。
3、网关过滤器,首先获取灰度相关配置,然后肯定token与规则进行匹配,满足灰度条件的走新服务,不满足的走老服务。
4、如果新服务有问题,则可通过修改灰度配置来使流量全部切回到老服务。
5、如果新服务无问题,则可通过修改灰度配置来使流量全部切到新服务。
@Component
public 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();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wyl.springcloud.zuul.dao.GrayRuleMapper">
<select id="getGrayRule" resultType="com.wyl.springcloud.zuul.entity.GrayRuleEntity">
select * from gray_rule
</select>
</mapper>
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;
@Configuration
public class GrayRibbonRule extends ZoneAvoidanceRule {
@Override
public Server choose(Object key) {
HttpServletRequest request =
((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
//通过网关传过来的
String version = request.getHeader("zuulVersion");
// 获取可用服务列表
List<Server> serverList = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key);
return getServer(serverList, version);
}
/**
* 根据网关传过来的版本从可用的服务列表中找到匹配的服务。
* @param serverList
* @param version
* @return
*/
private Server getServer(List<Server> serverList, String version) {
Server toServer = null;
for (Server server : serverList) {
Map<String, String> 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;
@Component
public 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方法,但是并没有实现负载均衡,其实如果要实现一个轮询的方式也很简单,无非就是搞一个递增的数字与服务列表取模而已。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。