灰度发布
中游服务部分:
ribbon:
自定义部分:
1,从nacos配置中获取配置
2,根据请求获取请求的实例名---对应成相应的ip(对应的配置列表中,配置的都是灰度机map)
3,把对应的ip和ribbon中服务列表匹配有放行,需要下的灰度注掉这个实例对应的ip
应用部分:
4,将自定义的rule纳入spring
5,业务的请求换成走ribbon的服务名形式,让ribbon转发即可负载----可用拦截器做(改造没有用注册中心的项目,或者没有经过zuul的项目)(zuul有一次负载,ribbon有一次负载)
这样一来只要
开启了灰度测试开关,
配置ip列表中有灰度机器就,
灰度机器注册到nacos(配置的ip在nacos可以对应到)
用的是测试账号---测试账号ip的map的key会加:,所以解析的时候要根据:能解析出需要的是测试账号
就一直走的是灰度,之前的正式的就不会走,否则就走以前正式的服务
注入自定义的规则:
@Bean
public IRule ribbonRule() {
CustomPredicateRule rule = new CustomPredicateRule(canaryServerManager);
return rule;
}
获取nacos的配置:
package com.houbank.xloan.core.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;
/**
* @author wangdong
* @Description: TODO
* @date 2019/5/22 11:56
*/
@Component
@RefreshScope
public class CanaryConfig {
@Value("${canary.open}")
private Boolean open;
@Value("${canary.telesaleBackAddr}")
private String telesaleBackAddr;
@Value("${canary.telsaleSeatAddr}")
private String telesaleSeatAddr;
@Value("${canary.telesaleWeixinAddr}")
private String telesaleWeixinAddr;
@Value("${canary.telesaleKuaiyipaiAddr}")
private String telesaleKuaiyipaiAddr;
@Value("${canary.telesaleProduct}")
private String telesaleProduct;
@Value("${canary.sessionIds}")
private String sessionIds;
public void setOpen(Boolean open) {
this.open = open;
}
public boolean isOpen() {
return open;
}
public String getSessionIds() {
return sessionIds;
}
public String getServerHost(String key){
switch (key){
case "telesale-back":return telesaleBackAddr;
case "telsale-seat":return telesaleSeatAddr;
case "telesale-seat":return telesaleSeatAddr;
case "v1":return telesaleWeixinAddr;
case "kuaiyipai-api":return telesaleKuaiyipaiAddr;
case "telesale-product":return telesaleProduct;
default:return null;
}
}
}
选择服务的工具类:
CanaryServerManager
请求的服务实例名在配置的服务列表中就返回这,否则返回空
请求loadBalancerKey在ribbon的注册中就返回该服务转发------下架灰度机的时候就loadBalancerKey找到的主机地址和ribbon中注册的不一致即可
基于配置选择服务:
CustomPredicateRule
下架灰度机的时候就loadBalancerKey找到的主机地址和ribbon中注册的不一致即可
业务中:
将请求转化成zuul请求的url:
//建议将这块代码提出封装下
loadBalancerKey 请求的实例名,根据这个获取配置文件中的主机地址 case "telesale-back":return telesaleBackAddr;
private String buildBackHost(String serviceName){
checkCanaryRequest(serviceName);
ServiceInstance serviceInstance = loadBalancerClient.choose(serviceName);
String url = String.format("http://%s:%s/",serviceInstance.getHost(),serviceInstance.getPort());
return url;
}
//判断是否开启金丝雀测试,如果开启则构造loadbalanceKey 构成为 应用名:filter
private void checkCanaryRequest(String serviceName) {
if (!canaryServerManager.isOpen())
return;
ServletRequestAttributes requestAttributes= (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
if (requestAttributes==null)
return;
HttpServletRequest httpServletRequest = requestAttributes.getRequest();
String sessionId=httpServletRequest.getRequestedSessionId();
StringBuilder loadBalancerKey=new StringBuilder();
loadBalancerKey.append(serviceName).append(":");
if (StringUtils.isNotBlank(sessionId)&&canaryServerManager.isSpecialAcct(sessionId)) { //filter表示是否是测试账号访问
loadBalancerKey.append("filter");
}
canaryServerManager.setLoadBalancerKey(loadBalancerKey.toString());
}
中游服务部分:
feign 参照ribbon
自定义部分:
1,从nacos配置中获取配置
2,根据请求获取请求的实例名---对应成相应的ip
3,把对应的ip和ribbon中服务列表匹配有放行,需要下的灰度注掉这个实例对应的ip
应用部分:
4,将自定义的rule纳入spring
5,业务的请求换成走ribbon的服务名形式,让ribbon转发即可负载----这里用拦截器做
注意:自定义hystrix的并发策略,解决hystrix线程模式下RequestContextHolder丢失问题 (信号量模式不会),自定义的策略需要在init中注册-----用于降级使用
思路:将父的上下文属性传给子复制
引入hystrix为了实现熔断降级:
自定义策略就是为了实现包装callable,
包装的目的就是为了能调用前cpoy threadlocal变量,子线程被调用设置的就在子线程中
所以一个策略可以包装多个callable,在spring容器中,一次设置都有效---类似初始化思想
效果就是只要用了这个策略,里面的callable都是有自定义用自定义的,没有用默认的(总和就是自定义+默认的)
注册策略类的本质就是注册其中的callable:
HystrixConcurrencyStrategy target = new HystrixConcurrencyStrategyCustomize(canaryServerManager);
HystrixPlugins.getInstance().registerConcurrencyStrategy(target);
1,定义策略类:
RequestContextHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy
引用定义的callable类
2,定义策略类中包装的callable类
static class RequestAttributeAwareCallable
基于接口?????????
@Component
@Slf4j
public class HystrixConfig {
@PostConstruct
public void init() {
HystrixPlugins.getInstance().registerConcurrencyStrategy(new RequestContextHystrixConcurrencyStrategy());
}
}
ThreadLocal:线程之间不共享
普通的RequestContextHolder就是threadlocal
在多线程的世界,多半是父子线程,并行的线程少数
inheritableThreadLocal:子线程继续传播父线程的上下文
ribbon上下文就是inheritableThreadLocal
RibbonFilterContextHolder.getCurrentContext().get("TAG")
//父线程传入当前的ReuestContextHolder
@Slf4j
public class HystrixConcurrencyStrategyCustomize extends HystrixConcurrencyStrategy {
private CanaryServerManager canaryServerManager;
public HystrixConcurrencyStrategyCustomize(CanaryServerManager canaryServerManager) {
this.canaryServerManager = canaryServerManager;
}
public
HystrixCustomizeCallable hystrixCustomizeCallable = new HystrixCustomizeCallable( (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(),callable);
return hystrixCustomizeCallable;
}
}
class HystrixCustomizeCallable
private Callable
private ServletRequestAttributes attributes;
public HystrixCustomizeCallable(ServletRequestAttributes attributes, Callable
this.attributes = attributes;
this.callable = callable;
}
public HystrixCustomizeCallable(Callable
this.callable = callable;
}
//子线程复制父线程中的attribute
@Override
public T call() throws Exception {============子线程才执行这个方法============
try{
//这里是为了feign拿到源请求的头信息
if(null != this.attributes){
RequestContextHolder.setRequestAttributes(this.attributes);
}
return this.callable.call();
}finally {
RequestContextHolder.resetRequestAttributes();
//结束时清除loadbalancerkey的缓存
CanaryServerManager.resetLoadBalancerKey();
}
}
}
hystrix两种模式---线程,信号量
https://blog.csdn.net/songhaifengshuaige/article/details/80345012
api部分:
zuul
参考上一篇zuul自定义负载原理