1、实现思路
通过zuul网关寻找下层服务之前通过拦截器处理请求头的参数信息,通过判断Redis数据当前请求是否符合灰度的要求,如果符合,走灰度服务;否则走正常服务
2、实现过程
(1)设置redis缓存信息:
switch(灰度开关)、list(灰度用户列表)、address(灰度服务地址)
(2)添加请求拦截器
/**
* 灰度过滤器
*
* @author admin
*/
@Slf4j
@Service
public class GrayFilter extends ZuulFilter {
@Autowired
private RedisUtils redisUtils;
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 1000;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String token = ctx.getRequest().getHeader(Constants.HEADER_TOKEN);
String userId = getUserId(token);
log.debug("======>根据token:{},获取到userId:{}", token, userId);
//灰度开关
Object switchValue = redisUtils.get(Constants.GRAY_SWITCH);
String isGray = switchValue != null ? switchValue.toString() : "false";
String version = null;
if (Boolean.parseBoolean(isGray)) {
List userIdList = redisUtils.lrangeAllList(Constants.GRAY_USER_LIST);
if (userIdList != null) {
version = Constants.GRAY_VERSION;
version = userIdList.contains(userId) ? version : null;
}
}
log.debug("=====>userId:{},version:{}", userId, version);
CoreHeaderInterceptor.initHystrixRequestContext(version);
ctx.addZuulRequestHeader(Constants.HEADER_VERSION, version);
return null;
}
/**
* 处理token信息
*
* @return
*/
private String getUserId(String token) {
//处理请求token数据
}
}
(3)重写ribbon路由策略
public class DefaultPropertiesFactory extends PropertiesFactory {
@Autowired
private Environment environment;
private Map<Class, String> classToProperty = new HashMap<>();
public DefaultPropertiesFactory() {
super();
classToProperty.put(ILoadBalancer.class, "NFLoadBalancerClassName");
classToProperty.put(IPing.class, "NFLoadBalancerPingClassName");
classToProperty.put(IRule.class, "NFLoadBalancerRuleClassName");
classToProperty.put(ServerList.class, "NIWSServerListClassName");
classToProperty.put(ServerListFilter.class, "NIWSServerListFilterClassName");
}
@Override
public String getClassName(Class clazz, String name) {
String className = super.getClassName(clazz, name);
// 读取全局配置
if (!StringUtils.hasText(className) && this.classToProperty.containsKey(clazz)) {
String classNameProperty = this.classToProperty.get(clazz);
className = environment.getProperty("ribbon." + classNameProperty);
}
return className;
}
}
/**
* 自定义灰度策略
*
* @author admin
*/
@Configuration
public class RuleConfig {
@Bean
public IRule grayRule() {
// 指定策略:灰度策略
return new GrayRule();
}
}
/**
* 灰度路由
*
* @author admin
*/
@Slf4j
@Service
public class GrayRule extends ZoneAvoidanceRule {
@Autowired
private RedisUtils redisUtils;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
/**
* 在choose方法中,自定义规则,返回的Server就是具体选择出来的服务
*
* @param key
* @return
*/
@Override
public Server choose(Object key) {
List<Server> serverList = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key);
if (CollectionUtils.isEmpty(serverList)) {
return null;
}
String hystrixVer = CoreHeaderInterceptor.version.get();
//灰度开关
Object switchValue = redisUtils.get(Constants.GRAY_SWITCH);
String isGray = switchValue != null ? switchValue.toString() : "false";
//灰度用户列表
List grayAddress = redisUtils.lrangeAllList(Constants.GRAY_ADDRESS);
if(grayAddress == null){
return originChoose(serverList, key);
}
//查找服务
List<Server> noMetaServerList = new ArrayList<>();
for (Server server : serverList) {
//非灰度,获取所有服务
if(!Boolean.parseBoolean(isGray)){
noMetaServerList.add(server);
continue;
}
//灰度服务并且灰度版本满足,返回灰度服务
if(grayAddress.contains(server.getHost()) && Constants.GRAY_VERSION.equals(hystrixVer)){
log.info("执行灰度服务:{}", server.getHost());
return server;
}
//灰度读物并且正常用户,返回正常服务
if (!grayAddress.contains(server.getHost()) && !Constants.GRAY_VERSION.equals(hystrixVer)) {
noMetaServerList.add(server);
}
}
if (StringUtils.isEmpty(hystrixVer) && !noMetaServerList.isEmpty()) {
return originChoose(noMetaServerList, key);
}
return null;
}
/**
* 轮询路由
*
* @param noMetaServerList
* @param key
* @return
*/
private Server originChoose(List<Server> noMetaServerList, Object key) {
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(noMetaServerList, key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}
3、采坑记录
注意Spring的版本要求。否则会报事务异常。
4、总结
用Redis处理方式的路由策略就是参考过的SpringCloud+apollo的灰度方式。用apollo的方式需要在业务工程里添加apollo的监听事件。有兴趣的可以看一下。
有需要看源码的可以私信
另附:SpringCloud+apollo参考文章:https://segmentfault.com/a/1190000017412946?utm_source=tag-newest