好家伙, 灰度发布 还这么多细节
序
这前天开个会 结果就说 灰度发布 优先级高,行, 同志们说得对。我这很迷茫啊,网关代码在别的部门那管,就没见过,只能靠猜想。。。。难受 算了,先干活
背景
其实就是优雅关机 流量切换不能有临时的接口不可用(ribbon 要有重试机制,服务通知要即时,可以看阿里云有一个 讲流量切换的 视频),然后就是 升级v2之后需要先验证一下,然后流量再切到v2,防止业务不可用的情况(具体原因没了解,应该是被用户说了。。。。)
思考题 带着问题看
看完这篇文章 你还有哪些部分是 有疑问的 我先说
- nacos 配置实时生效 你说实时就实时啊,底层根据什么做的,如果一台业务服务 网络临时不好,后面恢复了,怎么补偿的?
- aop @After @AfterReturning @AfterThrowing 你说是分开的 你怎么证明的?底下怎么执行的
- aop 中的threadlocal 你说 不清除 会对系统有影响,什么影响,有什么现象?为什么?
- 多线程 操作一个对象,会有数据一致性问题,除了你这里的 不可变类 还有别的办法么? 有什么区别呢,有什么应用场景呢?
同志们 想了解哪个 评论区告诉下 我后面整理 发出来
技术调研
灰度发布这个东西 我之前没写过,只是听说,我之前的理解 给大家画个图
百度一下 灰度发布,什么玩意,你告诉我改改gateway 改改feign,v2项目启动 带上元数据,就这?闭环呢?
基础规范
- 涉及到 版本的选择,那之后 元数据 每个都加上version: 具体版本号 这不就行了,避免了改元数据 可能导致的问题
需要确定问题
- 开发流程 后台发布灰度之后 前端先测试 灰度 再上线,确定了想法,统一了思路,才能开始干活嘛 (问前端大哥)
之前有一版方案 参考下
参考之后 感觉之前的方案有点过于复杂,有一些无用功,进行缩减
思考
其实灰度发布 变为了 配置的统一管理
现在需要解决的问题
- 配置文件的管理 从2个方案进行选择 (实现 配置实时生效, spring.cloud.nacos.config.shared-configs 如果要添加 你让我每个服务 添加重新发版 我不愿意)
- 网关的修改
- feign的修改
调研
配置实时生效 nacos
@NacosConfigurationProperties(dataId = "gray.yaml", prefix = "xxx.gray", autoRefreshed = true)
本来想用bus 但是问了一句现在的环境 还不能用,先用这个注解,让其自动更新,feign 灰度配置 实时生效
spring.cloud.nacos.config.shared-configs jar包里添加监听 公共文件
我第一印象 如果nacos 开发者想让别人扩展他 会加一个@ConditionalOnMissingBean 这样我只要定义了 @Bean 就能 覆盖旧的了。 找一找
@Bean
public NacosPropertySourceLocator nacosPropertySourceLocator(
NacosConfigManager nacosConfigManager) {
return new NacosPropertySourceLocator(nacosConfigManager);
}
@Override
public PropertySource<?> locate(Environment env) {
... 省略
CompositePropertySource composite = new CompositePropertySource(
NACOS_PROPERTY_SOURCE_NAME);
//这个是读取 公共文件的
loadSharedConfiguration(composite);
loadExtConfiguration(composite);
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
//你告诉我 直接获取的 配置文件的sharedConfigs 没给我留什么活口啊,怎么办呢
List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties
.getSharedConfigs();
办法
- 直接在我们自己 灰度发布的组件jar中 使用这个nacos 对象进行指定文件加载
- 让我们在这个nacos Config加载之前 我往sharedConfigs 属性中 添加一个 gray.yaml
- 直接用 nacos 开源的监听方式 监听
第一种办法 可以看到 这个nacos 类都是private 人家本来就没想让你自己用
第二种办法 可以看到 人家@Order(0) 本来就是让先执行的。。。没必要硬改人家
第三种办法 可行 可以搞,但是需要考虑 如果nacos 连接不上,会不会导致超时,代码走不动。
办法肯定有,设置一个超时时间,然后日志打印下,方便让开发人员 定位问题。
我是默认超时时间 5s ,想想 如果超时 没获取,会不会影响 操作,后面会自己补齐么? 这个需要看代码了
如果超时 就会报一个 NacosException 进行拦截处理
行 配置文件 nacos 这块先这样,后面再继续改
说到这了,可能有人会想
会想 @Order Ordered 接口哪个优先级更高
这里就不测试了,结论是 Ordered 接口的优先生效, 最后才是 @Order
网关的修改
feign 改进
这部分 改动的功能
- 支持请求头的传递
- 灰度发布
支持请求头的传递
百度下
@Slf4j
public class CustomFeignRequestInterceptor implements RequestInterceptor {
/**
* 这里可以实现对请求的拦截,对请求添加一些额外信息之类的
*
* @param requestTemplate
*/
@Override
public void apply(RequestTemplate requestTemplate) {
// 1. obtain request
final ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// 2. 兼容hystrix限流后,获取不到ServletRequestAttributes的问题(使拦截器直接失效)
if (Objects.isNull(attributes)) {
log.error("MyFeignRequestInterceptor is invalid!");
return;
}
HttpServletRequest request = attributes.getRequest();
// 2. obtain request headers,and put it into openFeign RequestTemplate
Enumeration<String> headerNames = request.getHeaderNames();
if (Objects.nonNull(headerNames)) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String value = request.getHeader(name);
requestTemplate.header(name, value);
}
}
}
}
feign 灰度发布 需要实现的功能
//判断灰度参数 是否存在 不存在直接return
//调用 远程 判断 灰度是否生效
//存在就继续往下走
//@FeignClient 先判断是否有 url,如果有 直接 使用url 不让负载均衡生效
//name value 都会触发负载均衡
//获取 调用的服务列表 根据元数据 进行过滤出可以调用的服务
//如果没有 直接随机选一个
//如果有 从 灰度服务列表 选一个具体服务
//进行ip:port 替换
代码之后放码云
feign 修改 需要注意
feign threadlocal 别忘了删除
因为 @After @AfterReturning @AfterThrowing 是分开的,所以3个方法都要remove
@After(value = "anyMehtod()")
public void after(){
RibbonParameters.remove();
}
@AfterReturning(pointcut="anyMehtod()",returning = "rst")
public void afterRunning(Response rst){
RibbonParameters.remove();
}
@AfterThrowing(value = "anyMehtod()")
public void afterThrowing(){
RibbonParameters.remove();
}
feign 选择服务的时候 兼容指定url 就不能走选举了
除此之外 异常情况 大不了随机拿一个 也别报错
需要解决的细节
- 配置一致性 到模块 添加一个模块的概念 方便配置的扩展性
- 多线程数据一致性问题 因为一个类有多个属性,在多线程操作的时候 可能读写同时存在的情况下,可能导致 属性 分别属于不同的对象? 怎么办呢? 使用不可变类
- 怎么知道有没有 走灰度? 目前1.0 不是关键功能 先推迟
- nacos 配置错误 直接置为false 日志打印,但是不影响系统。 外面做一层拦截 打印错误日志 如果报错 就恢复为默认的灰度配置,关闭灰度
- nacos 配置解析 有可能原来规定是map 结果你给我传int 没事,instanceof 判断
不可变类 粘贴一个例子
@Data
public final class WholeProperties {
/**
* 灰度发布 开关 true 是开启 false 是关闭
*/
private final boolean grayFlag;
/**
* 现在整体 的所有数据
*/
private final Map<String, Object> map;
/**
* 本模块 的属性
*/
private final Map<String, Object> customParamMap;
public WholeProperties(boolean grayFlag, Map<String, Object> map, Map<String, Object> customParamMap) {
this.grayFlag = ObjectUtils.isEmpty(grayFlag) ? false : grayFlag;
this.map = ObjectUtils.isEmpty(map) ? new ConcurrentHashMap<>() : map;
this.customParamMap = ObjectUtils.isEmpty(customParamMap) ? new ConcurrentHashMap<>() : customParamMap;
;
}
}
写组件的时候 额外注意的
- 版本管理 请使用 ,不要手改,求求了
- 上线的版本号 必须是releases ,哎 一言难尽啊,一句话忘说就搞事
总结
feign 的改动基本就是这些,交流可以加wx yuyezhiji
转载自:https://juejin.cn/post/7138248134215335973