如果您觉得本框架具有一定的参考价值和借鉴意义,请帮忙在页面右上角 [Star]
- Nepxion开源社区创始人
- 2020年阿里巴巴**云原生峰会出品人
- 2020年被Nacos和Spring Cloud Alibaba纳入相关开源项目
- 2021年阿里巴巴技术峰会上海站演讲嘉宾
- 2021年荣获陆奇博士主持的奇绩资本,进行风险投资的关注和调研
- 2021年入选Gitee最有价值开源项目
- 阿里巴巴官方书籍《Nacos架构与原理》作者之一
- Nacos Group Member、Spring Cloud Alibaba Member
- Spring Cloud Alibaba、Nacos、Sentinel、OpenTracing Committer & Contributor
① Discovery系列
框架名称 | 框架版本 | 支持Spring Cloud版本 | 使用许可 |
---|---|---|---|
Discovery | 1.x.x ~ 6.x.x | Camden ~ Hoxton | 开源,永久免费 |
DiscoveryX | 7.x.x | 2020 | 闭源,商业许可 |
DiscoveryX | 8.x.x | 2021 | 闭源,商业许可 |
② Polaris系列
Polaris为Discovery高级定制版,特色功能
- 基于Nepxion Discovery集成定制
- 多云、多活、多机房流量调配
- 跨云动态域名、跨环境适配
- DCN、DSU、SET单元化部署
- 组件灵活装配、配置对外屏蔽
- 极简低代码PaaS平台
框架名称 | 框架版本 | 支持Discovery版本 | 支持Spring Cloud版本 | 使用许可 |
---|---|---|---|---|
Polaris | 1.x.x | 6.x.x | Finchley ~ Hoxton | 闭源,商业许可 |
Polaris | 2.x.x | 7.x.x | 2020 | 闭源,商业许可 |
Polaris | 3.x.x | 8.x.x | 2021 | 闭源,商业许可 |
有商业版需求的企业和用户,请添加微信1394997,联系作者,洽谈合作事宜
① 快速入门
② 解决方案
③ 平台界面
④ 框架源码
⑤ 指南示例源码
⑥ 指南示例说明
- 对于入门级玩家,参考6.x.x指南示例极简版,分支为6.x.x-simple
- 对于熟练级玩家,参考6.x.x指南示例精进版,分支为6.x.x。除上述《极简版》功能外,涉及到指南篇里的绝大多数高级功能
- 对于骨灰级玩家,参考6.x.x指南示例高级版,分支为6.x.x-complex。除上述《精进版》功能外,涉及到指南篇里的ActiveMQ、MongoDB、RabbitMQ、Redis、RocketMQ、MySQL等高级调用链和蓝绿灰度调用链的整合
- 上述指南实例分支是针对Spring Cloud Finchley ~ Hoxton版本。对于Spring Cloud 202x版本,参考7.x.x ~ 8.x.x指南示例精进版,分支为master
① 解决方案
② 框架源码
③ 指南示例源码
④ 指南示例说明
- Spring Cloud Finchley ~ Hoxton版本,参考1.x.x指南示例,分支为1.x.x
- Spring Cloud 202x版本,参考2.x.x ~ 3.x.x指南示例,分支为master
Discovery【探索】和Polaris【北极星】架构体系
① Discovery【探索】和Polaris【北极星】联合架构图
② Discovery【探索】和Polaris【北极星】联合拓扑图
③ Polaris【北极星】分层架构图
Discovery【探索】微服务框架,基于Spring Cloud & Spring Cloud Alibaba,Discovery服务注册发现、Ribbon & Spring Cloud LoadBalancer负载均衡、Feign & RestTemplate & WebClient调用、Spring Cloud Gateway & Zuul过滤等组件全方位增强的企业级微服务开源解决方案,更贴近企业级需求,更具有企业级的插件引入、开箱即用特征
① 微服务框架支持的技术栈,如下
- 支持阿里巴巴Spring Cloud Alibaba中间件生态圈
- 支持阿里巴巴Nacos、Eureka、Consul和Zookeeper四个服务注册发现中心
- 支持阿里巴巴Nacos、携程Apollo、Redis、Zookeeper、Consul和Etcd六个远程配置中心
- 支持阿里巴巴Sentinel、Hystrix和Resilience4J三个熔断限流降级权限中间件
- 支持OpenTracing和OpenTelemetry规范下的调用链中间件,Jaeger、SkyWalking和Zipkin等
- 支持Prometheus Micrometer和Spring Boot Admin两个指标中间件
- 支持Java Agent解决异步跨线程ThreadLocal上下文传递
- 支持Spring Spel解决蓝绿灰度参数的驱动逻辑
- 支持Spring Matcher解决元数据匹配的通配逻辑
- 支持Spring Cloud Gateway、Zuul网关和微服务三大模块的蓝绿灰度发布等一系列功能
- 支持和兼容Spring Cloud Edgware版、Finchley版、Greenwich版、Hoxton版和202x版以及更高的Spring Cloud版本
- 支持和兼容Java8~Java17以及更高的SDK版本
② Discovery【探索】微服务框架支持的应用功能,如下
- 全链路蓝绿灰度发布
- 全链路版本、区域、 IP地址和端口匹配蓝绿发布
- 全链路版本、区域、 IP地址和端口权重灰度发布
- 全链路蓝 | 绿 | 兜底、蓝 | 兜底的蓝绿路由类型
- 全链路稳定、灰度的灰度路由类型
- 全链路网关、服务端到端混合蓝绿灰度发布
- 全链路单网关、域网关、非域网关部署
- 全链路条件驱动、非条件驱动
- 全链路前端触发后端蓝绿灰度发布
- 全局订阅式蓝绿灰度发布
- 全链路自定义网关、服务的过滤器、负载均衡策略类触发蓝绿灰度发布
- 全链路Header、Parameter、Cookie、域名、RPC Method等参数化规则策略驱动
- 全链路本地和远程、局部和全局无参数化规则策略驱动
- 全链路条件表达式、通配表达式支持
- 全链路内置Header,支持定时Job的服务调用蓝绿灰度发布
- 全链路智能编排蓝绿灰度发布
- 全链路流量管控对接DevOps运维平台
- 全链路多活单元化
- 全链路隔离路由
- 全链路组隔离路由
- 组负载均衡的消费端隔离
- 组Header传值的提供端隔离
- 全链路版本偏好路由
- 全链路区域调试路由
- 全链路环境隔离路由
- 全链路可用区亲和性隔离路由
- 全链路IP地址和端口隔离路由
- 全链路组隔离路由
- 全链路隔离准入
- 基于IP地址黑白名单注册准入
- 基于最大注册数限制注册准入
- 基于IP地址黑白名单发现准入
- 自定义注册发现准入
- 全链路故障转移
- 全链路版本故障转移
- 全链路区域故障转移
- 全链路环境故障转移
- 全链路可用区故障转移
- 全链路IP地址和端口故障转移
- 全链路服务无损下线,实时性的流量绝对无损
- 全局唯一ID屏蔽
- IP地址和端口屏蔽
- 异步场景下全链路蓝绿灰度发布
- 异步跨线程Agent插件
- Hystrix线程池隔离插件
- 网关动态路由
- 路由动态添加
- 路由动态修改
- 路由动态删除
- 路由动态批量更新
- 路由查询
- 路由动态变更后的事件通知
- 全链路服务限流熔断降级权限
- Sentinel基于服务名的防护
- Sentinel基于组的防护
- Sentinel基于版本的防护
- Sentinel基于区域的防护
- Sentinel基于环境的防护
- Sentinel基于可用区的防护
- Sentinel基于IP地址和端口的防护
- Sentinel自定义Header、Parameter、Cookie的防护
- Sentinel自定义业务参数的防护
- Sentinel自定义组合式的防护
- 全链路监控
- 蓝绿灰度埋点和熔断埋点的调用链监控
- 蓝绿灰度埋点和熔断埋点的日志监控
- 熔断埋点的指标监控
- 全链路蓝绿灰度发布编排建模和流量侦测
- 全链路蓝绿发布编排建模
- 全链路灰度发布编排建模
- 全链路蓝绿发布流量侦测
- 全链路灰度发布流量侦测
- 全链路蓝绿灰度发布混合流量侦测
- 全链路数据库和消息队列蓝绿发布
- 基于多DataSource的数据库蓝绿发布
- 基于多Queue的消息队列蓝绿发布
- 全链路服务侧注解
- 全链路服务侧API权限
- 元数据流量染色
- Git插件自动化的元数据流量染色
- 服务名前缀的元数据流量染色
- 启动参数化的元数据流量染色
- 注册中心动态化的元数据流量染色
- 用户自定义的元数据流量染色
- 全链路规则策略推送
- 基于远程配置中心的规则策略订阅推送
- 基于Swagger和Rest的规则策略推送
- 基于平台端和桌面端的规则策略推送
- 统一配置订阅执行器
- Docker容器化和Kubernetes平台无缝支持部署
- 自动化测试、压力测试
③ Discovery【探索】微服务框架支持的功能维度,如下
微服务框架支持组(Group)、版本(Version)、区域(Region)、环境(Env)、可用区(Zone)、IP地址和端口(Address)、全局唯一ID七大经典维度实施流量管控的方式,通过“并集”方式叠加作用在流量控制上。上述七个维度在功能上各有各的侧重点,如下表格主要讲述各自的区别
维度 | 概念 | 场景 | 功能侧重点 | 关键头 |
---|---|---|---|---|
组 | 服务实例的系统ID 系统逻辑分组 |
路由隔离 | ① 组负载均衡隔离 - 调用端和提供端的元数据group是否相同 ② 组Header传值策略隔离 - Header(n-d-group)和提供端的元数据group是否相同 ③ 不支持故障转移 |
n-d-group |
版本 | 服务实例的版本 适用于生产环境 |
蓝绿灰度发布 路由转移 故障转移 |
① 版本条件匹配蓝绿发布 ② 版本权重灰度发布 ③ 版本偏好 - 非蓝绿灰度发布场景下,路由到相应版本的实例 - 稳定版本策略、指定版本策略 ④ 版本故障转移 - 未找到相应版本的服务实例,路由到其它版本 - 负载均衡策略、稳定版本策略、指定版本策略 |
n-d-version n-d-version-weight n-d-version-prefer n-d-version-failover |
区域 | 服务实例的区域 适用于多活单元化 适用于多机房 适用于多环境 |
蓝绿灰度发布 同城双活/异地多活 路由转移 故障转移 |
① 区域条件匹配蓝绿发布 ② 区域权重灰度发布 ③ 区域多活单元化 ④ 区域调试路由 - 多区域路由隔离下跨区服务调用的调试手段 ⑤ 区域故障转移 - 未找到相应区域的服务实例,路由到其它区域 - 负载均衡策略、指定区域策略 |
n-d-region n-d-region-weight n-d-region-transfer n-d-region-failover |
环境 | 服务实例的环境 适用于测试环境 |
路由隔离 故障转移 |
① 环境隔离路由 - Header(n-d-env)和提供端的元数据env是否相同 ② 环境故障转移 - 未找到相应环境的服务实例,路由到其它环境 - 指定环境(未配置,默认为common)策略 |
n-d-env n-d-env-failover |
可用区 | 服务实例的可用区 适用于多机房 |
路由隔离 故障转移 |
① 可用区亲和性隔离路由 - 调用端和提供端的元数据zone是否相同 ② 可用区故障转移 - 未找到相应可用区的服务实例,路由到其它可用区 - 支持负载均衡策略、指定区可用区策略 |
n-d-zone-failover |
IP地址和端口 | 服务实例机器地址 | 蓝绿灰度发布 路由隔离 故障转移 无损下线 |
① IP地址和端口匹配蓝绿发布 ② IP地址和端口权重灰度发布 ③ IP地址和端口故障转移 - 未找到相应IP地址和端口的服务实例,路由到其它地址 - 负载均衡策略、指定区IP地址和端口策略 ④ IP地址和端口无损下线黑名单屏蔽 |
n-d-address n-d-address-failover n-d-address-blacklist |
全局唯一ID | 服务实例机器ID | 无损下线 | ① 全局唯一ID无损下线黑名单屏蔽 | n-d-id-blacklist |
- 2017年12月开始筹划
- 2018年03月开始编码
- 2018年06月在GitHub开源
- 2018年06月发布v1.0.0,支持Camden版
- 2018年06月发布v2.0.0,支持Dalston版
- 2018年07月发布v3.0.0,支持Edgware版
- 2018年07月发布v4.0.0,支持Finchley版
- 2019年04月发布v5.0.0,支持Greenwich版
- 2020年04月发布v6.0.0,支持Hoxton版
- 2021年04月完成v7.0.0,支持2020版
- 2022年04月完成v8.0.0,支持2021版
① 微服务框架版本兼容列表,如下
提醒:版本号右边,
↑
表示>=该版本号, ↓
表示<=该版本号
表示维护中 |
表示不维护,但可用,强烈建议升级 |
表示不维护,不可用,已废弃
- 8.x.x版本(适用于2021.x.x)将继续维护
- 7.x.x版本(适用于2020.x.x)将继续维护
- 6.x.x版本(同时适用于Finchley、Greenwich和Hoxton)将继续维护
- 5.x.x版本(适用于Greenwich)已废弃
- 4.x.x版本(适用于Finchley)已废弃
- 3.x.x版本(适用于Edgware)不维护,但可用,强烈建议升级
- 2.x.x版本(适用于Dalston)已废弃
- 1.x.x版本(适用于Camden)已废弃
② 相关中间件版本列表,如下
组件类型 | 组件版本 |
---|---|
基础组件 | |
注册配置组件 | |
防护组件 | |
监控组件 | |
Spring组件 |
不完全统计,目前社区开源项目(包括本框架以及关联框架或组件)已经被如下公司使用或者调研
为提供更好的专业级服务,请更多已经使用本框架的公司和企业联系我,并希望在Github Issues上登记
- 某大型银行信用卡新核心系统在生产环境接入Nepxion Discovery框架的服务实例数(包括异地双活,同城双活,多机房全部汇总)将近10000个
- 某大型互联网教育公司在生产环境接入Nepxion Discovery框架的服务实例数截至到2021年2月已达到2600多个,基本接入完毕
- 感谢阿里巴巴中间件Nacos、Sentinel和Spring Cloud Alibaba团队,尤其是Nacos负责人@彦林、@于怀,Sentinel负责人@宿何、@子衿,Spring Cloud Alibaba负责人@铖朴、@良名、@小马哥、@洛夜、@亦盏的技术支持
- 感谢携程Apollo团队,尤其是@宋顺的技术支持
- 感谢所有Committers和Contributors
- 感谢所有帮忙分析和定位问题的同学
- 感谢所有提出宝贵建议和意见的同学
- 感谢支持和使用本框架的公司和企业
微信、钉钉、公众号和文档
- 简介
- 主页链接
- 工程架构
- 准备工作
- 蓝绿灰度发布概念
- 全链路和单链路概念
- 全链路蓝绿灰度发布
- 全链路流量管控对接DevOps运维平台
- 全链路多活单元化
- 全链路隔离路由
- 全链路隔离准入
- 全链路故障转移
- 全链路服务无损下线
- 异步场景下全链路蓝绿灰度发布
- 网关动态路由
- 全链路服务限流熔断降级权限
- 全链路监控
- 全链路蓝绿灰度发布平台界面
- 全链路蓝绿灰度发布图形化桌面
- 全链路数据库和消息队列蓝绿发布
- 全链路服务侧注解
- 全链路服务侧API权限
- 元数据流量染色
- 自动扫描目录
- 统一配置订阅执行器
- 规则策略定义
- 规则策略推送
- 配置文件
- Docker容器化和Kubernetes平台支持
- 自动化测试
- 压力测试
- 附录
- Star走势图
① Discovery工程清单
② DiscoveryPlatform工程清单
③ DiscoveryAgent工程清单
④ DiscoveryUI工程清单
工程名 | 描述 |
---|---|
Nepxion Discovery 服务治理平台前端桌面版 | |
Nepxion Discovery 服务治理平台前端Web版 |
⑤ DiscoveryContrib工程清单
仓库主分支 | 代码行数 |
---|---|
① 服务注册发现依赖引入
服务注册发现中间件的四个插件,必须引入其中一个
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-register-center-starter-nacos</artifactId>
<artifactId>discovery-plugin-register-center-starter-eureka</artifactId>
<artifactId>discovery-plugin-register-center-starter-consul</artifactId>
<artifactId>discovery-plugin-register-center-starter-zookeeper</artifactId>
<version>${discovery.version}</version>
</dependency>
② 配置中心依赖引入
配置中心中间件的六个插件,选择引入其中一个
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-config-center-starter-apollo</artifactId>
<artifactId>discovery-plugin-config-center-starter-nacos</artifactId>
<artifactId>discovery-plugin-config-center-starter-redis</artifactId>
<artifactId>discovery-plugin-config-center-starter-zookeeper</artifactId>
<artifactId>discovery-plugin-config-center-starter-consul</artifactId>
<artifactId>discovery-plugin-config-center-starter-etcd</artifactId>
<version>${discovery.version}</version>
</dependency>
③ 管理中心依赖引入
选择引入
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>discovery-plugin-admin-center-starter</artifactId>
<version>${discovery.version}</version>
</dependency>
④ 路由策略依赖引入
微服务端、网关Zuul端和网关Spring Cloud Gateway端三个路由策略插件,选择引入其中一个
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-service</artifactId>
<artifactId>discovery-plugin-strategy-starter-zuul</artifactId>
<artifactId>discovery-plugin-strategy-starter-gateway</artifactId>
<version>${discovery.version}</version>
</dependency>
⑤ 防护插件依赖引入
- Sentinel防护的数据源插件
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-sentinel-datasource</artifactId>
<version>${discovery.version}</version>
</dependency>
- Sentinel防护的Sentinel Limiter高级限流熔断插件。只适用于Servlet模式
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-sentinel-limiter</artifactId>
<version>${discovery.version}</version>
</dependency>
- Hystrix防护插件。Hystrix线程池隔离模式下必须引入该插件
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-hystrix</artifactId>
<version>${discovery.version}</version>
</dependency>
⑥ 控制台依赖引入
控制台对于配置中心中间件的四个插件,选择引入其中一个
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-console-starter-apollo</artifactId>
<artifactId>discovery-console-starter-nacos</artifactId>
<artifactId>discovery-console-starter-redis</artifactId>
<artifactId>discovery-console-starter-zookeeper</artifactId>
<version>${discovery.version}</version>
</dependency>
⑦ 调用链插件依赖引入
支持微服务端、网关Zuul端和网关Spring Cloud Gateway端,选择引入其中一个。如下,该模块只支持F版或更高版本
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-sentinel-opentelemetry</artifactId>
<artifactId>discovery-plugin-strategy-starter-sentinel-opentracing</artifactId>
<artifactId>discovery-plugin-strategy-starter-sentinel-skywalking</artifactId>
<version>${discovery.version}</version>
</dependency>
⑧ 自动化测试插件依赖引入
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-test-starter</artifactId>
<version>${discovery.version}</version>
</dependency>
⑨ 异步跨线程Agent引入
-javaagent:/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.abc;com.xyz
为了更好的阐述框架的各项功能,本文围绕指南示例展开,请使用者先进行下面的准备工作。指南示例以Nacos为服务注册中心和配置中心展开介绍,使用者可自行换成其它服务注册中心和配置中心
① 下载代码,Git clone https://github.com/Nepxion/DiscoveryGuide.git,分支为6.x.x-simple
② 代码导入IDE
③ 下载Nacos服务器
- 从https://github.com/alibaba/nacos/releases获取nacos-server-x.x.x.zip,并解压
④ 启动Nacos服务器
- Windows环境下,运行bin目录下的startup.cmd
- Linux环境下,运行bin目录下的startup.sh
- 在IDE中,启动四个应用服务和两个网关服务,如下
类名 | 微服务 | 服务端口 | 版本 | 区域 | 环境 | 可用区 |
---|---|---|---|---|---|---|
DiscoveryGuideServiceA1.java | A1 | 3001 | 1.0 | dev | env1 | zone1 |
DiscoveryGuideServiceA2.java | A2 | 3002 | 1.1 | qa | common | zone2 |
DiscoveryGuideServiceB1.java | B1 | 4001 | 1.0 | qa | env1 | zone1 |
DiscoveryGuideServiceB2.java | B2 | 4002 | 1.1 | dev | common | zone2 |
DiscoveryGuideGateway.java | Gateway | 5001 | 1.0 | 无 | 无 | 无 |
DiscoveryGuideZuul.java | Zuul | 5002 | 1.0 | 无 | 无 | 无 |
- 部署拓扑图
全链路路径, 如下
API网关 -> 服务A(两个实例) -> 服务B(两个实例)
通过Postman工具验证
-
导入Postman的测试脚本postman.json(位于根目录下)
-
在Postman中执行目录结构下〔Nepxion〕->〔Discovery指南网关接口〕->〔Gateway网关调用示例〕,调用地址为http://localhost:5001/discovery-guide-service-a/invoke/gateway,相关的Header值已经预设,供开发者修改。执行通过Spring Cloud Gateway网关发起的调用,结果为如下格式
gateway
-> [ID=discovery-guide-service-a][UID=20220920-195533-038-9409-313-357][T=service][P=Nacos][H=192.168.0.107:3001][V=1.0][R=dev][E=env1][Z=zone1][G=discovery-guide-group][A=false][TID=48682.7508.15870951148324081][SID=49570.77.15870951148480000]
-> [ID=discovery-guide-service-b][UID=20220920-195533-209-3098-823-558][T=service][P=Nacos][H=192.168.0.107:4001][V=1.0][R=qa][E=env1][Z=zone2][G=discovery-guide-group][A=false][TID=48682.7508.15870951148324081][SID=49571.85.15870951189970000]
- 在Postman中执行目录结构下〔Nepxion〕->〔Discovery指南网关接口〕->〔Zuul网关调用示例〕,调用地址为http://localhost:5002/discovery-guide-service-a/invoke/zuul,相关的Header值已经预设,供开发者修改。执行通过Zuul网关发起的调用,结果为如下格式
zuul
-> [ID=discovery-guide-service-a][UID=20220920-195533-038-9409-313-357][T=service][P=Nacos][H=192.168.0.107:3001][V=1.0][R=dev][E=env1][Z=zone1][G=discovery-guide-group][A=false][TID=48682.7508.15870951148324081][SID=49570.77.15870951148480000]
-> [ID=discovery-guide-service-b][UID=20220920-195533-209-3098-823-558][T=service][P=Nacos][H=192.168.0.107:4001][V=1.0][R=qa][E=env1][Z=zone2][G=discovery-guide-group][A=false][TID=48682.7508.15870951148324081][SID=49571.85.15870951189970000]
- 在Postman中多种同步和异步的调用方式,异步方式需要增加DiscoveryAgent,才能保证蓝绿发布路由调用的成功
对于Spring Cloud 202x版,由于它采用的负载均衡Spring Cloud LoadBalancer是基于异步的WebFlux,所以必须加上DiscoveryAgent,如下
-javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar
URL | 调用方式 |
---|---|
/invoke/ | 同步调用 |
/invoke-async/ | @Async注解方式的异步调用 |
/invoke-thread/ | 单线程方式的异步调用 |
/invoke-threadpool/ | 线程池方式的异步调用 |
- 上述步骤在下面每次更改规则策略的时候执行,并验证结果和规则策略的期望值是否相同
蓝绿发布 Blue-Green Deployment
① 概念
不停机旧版本,部署新版本,通过用户标记将流量在新版本和老版本切换。属无损发布
② 优点
新版本升级和老版本回滚迅速。用户可以灵活控制流量走向
③ 缺点
成本较高,需要部署两套环境(蓝/绿)
灰度发布 Gray Release(又名金丝雀发布 Canary Release)
① 概念
不停机旧版本,部署新版本,高比例流量(例如:95%)走旧版本,低比例流量(例如:5%)切换到新版本,通过监控观察无问题,逐步扩大范围,最终把所有流量都迁移到新版本上。属无损发布
② 优点
灵活简单,不需要用户标记驱动。安全性高,新版本如果出现问题,只会发生在低比例的流量上
③ 缺点
成本较高,需要部署稳定/灰度两套环境
滚动发布 Rolling Release
① 概念
每次只升级一个或多个服务,通过观察无问题,不断执行这个过程,直到集群中的全部旧版本升级到新版本。属有损发布
② 优点
成本较低,只需要部署一套环境。出现问题影响范围,只限于发生在若干台正在滚动发布的服务上
③ 缺点
停止旧版本的过程中,无法精确计算旧版本是否已经完成它正在执行的工作,需要靠业务自身去判断。旧版本不保留,一旦全部升级完毕后才发现问题,无法快速回滚,必须重新降级部署。发布和回滚需要较长的时间周期
① 全链路
- 越级管控
- 链路隔离
- 规则策略统一编排
- 推送一次
② 单链路
- 管控下一级
- 链路不隔离
- 规则策略分散编排
- 推送多次
经典场景:当调用请求从网关或者服务发起的时候,通过Header | Parameter | Cookie一种或者几种参数进行驱动,在路由过滤中,根据这些参数,选择在配置中心配置的蓝路由 | 绿路由 | 兜底路由的规则策略(Json格式),并把命中的规则策略转化为策略路由Header(n-d-开头),实现全链路传递。每个端到端服务接收到策略路由Header后,执行负载均衡时,该Header跟注册中心的对应元数据进行相关比较,不符合条件的实例进行过滤,从而实现全链路蓝绿发布
实施概要:只涉及当前正在发布的服务,例如,对于 〔网关〕->〔A服务〕->〔B服务〕->〔C服务〕->〔D服务〕调用链来说,如果当前只是B服务和C服务正在实施发布,那么,只需要把B服务和C服务配置到规则策略中,其它则不需要配置。发布结束后,即B服务和C服务的所有实例都完全一致,例如,版本号都只有唯一一个,那么清除掉在配置中心配置的规则策略即可,从而进行下一轮全链路蓝绿发布
增加Spring Cloud Gateway的版本匹配蓝绿发布策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用全链路都走版本为1.0的服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version>1.0</version>
</strategy>
</rule>
如果希望每个服务的版本分别指定,那么策略内容如下,实现从Spring Cloud Gateway发起的调用走1.0版本的a服务,走1.1版本的b服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"}</version>
</strategy>
</rule>
当所有服务都选同一版本的时候,下面两条是等效的
<version>1.0</version>
<version>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version>
如果希望可调用的版本是多个,也可以表示成如下方式,即1.0版本和1.1版本的a服务和b服务都可以被调用到,下面两条是等效的
<version>1.0;1.1</version>
<version>{"discovery-guide-service-a":"1.0;1.1", "discovery-guide-service-b":"1.0;1.1"}</version>
如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法
* - 表示调用范围为所有版本
1.* - 表示调用范围为1开头的所有版本
例如
"discovery-guide-service-b":"1.*;1.2.?"
表示discovery-guide-service-b服务的调用范围是1开头的所有版本,或者调用范围是1.2开头的所有版本(末尾必须是1个字符),多个用分号隔开
非条件驱动下的全链路蓝绿发布跟Header驱动下的全链路蓝绿发布等效,例如
n-d-version=1.0
n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
增加Zuul的区域匹配蓝绿发布策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用全链路都走区域为dev的服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<region>dev</region>
</strategy>
</rule>
如果希望每个服务的版本分别指定,那么策略内容如下,实现从Zuul发起的调用走dev区域的a服务,走qa区域的b服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<region>{"discovery-guide-service-a":"dev", "discovery-guide-service-b":"qa"}</region>
</strategy>
</rule>
当所有服务都选同一区域的时候,下面两条是等效的
<region>dev</region>
<region>{"discovery-guide-service-a":"dev", "discovery-guide-service-b":"dev"}</region>
如果希望可调用区域是多个,也可以表示成如下方式,即dev区域和qa区域的a服务和b服务都可以被调用到,下面两条是等效的
<region>dev;qa</region>
<region>{"discovery-guide-service-a":"dev;qa", "discovery-guide-service-b":"dev;aq"}</region>
如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法
* - 表示调用范围为所有区域
d* - 表示调用范围为d开头的所有区域
例如
"discovery-guide-service-b":"d*;q?"
表示discovery-guide-service-b服务的调用范围是d开头的所有区域,或者调用范围是q开头的所有区域(末尾必须是1个字符),多个用分号隔开
非条件驱动下的全链路蓝绿发布跟Header驱动下的全链路蓝绿发布等效,例如
n-d-region=dev
n-d-region={"discovery-guide-service-a":"dev", "discovery-guide-service-b":"dev"}
地址匹配,即IP地址和端口匹配
增加Zuul的IP地址和端口匹配蓝绿发布策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用走指定IP地址和端口,或者指定IP地址,或者指定端口(下面策略以端口为例)的服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<!-- <address>127.0.0.1:3001</address> -->
<!-- <address>127.0.0.1</address> -->
<address>3001</address>
</strategy>
</rule>
如果希望每个服务的IP地址或者端口分别指定,那么策略内容如下,实现从Zuul发起的调用走3001端口的a服务,走4001端口的b服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<address>{"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4001"}</address>
</strategy>
</rule>
当所有服务都选同一端口的时候,下面两条是等效的
<address>3001</address>
<address>{"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"}</address>
如果希望可调用端口是多个,也可以表示成如下方式,即3001端口和4001端口的a服务和b服务都可以被调用到,下面两条是等效的
<address>3001;4001</address>
<address>{"discovery-guide-service-a":"3001;4001", "discovery-guide-service-b":"3001;4001"}</address>
如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法
* - 表示调用范围为所有端口
3* - 表示调用范围为3开头的所有端口
例如
"discovery-guide-service-b":"3*;400?"
表示discovery-guide-service-b服务的调用范围是3开头的所有端口,或者调用范围是400开头的所有端口(末尾必须是1个字符),多个用分号隔开
非条件驱动下的全链路蓝绿发布跟Header驱动下的全链路蓝绿发布等效,例如
n-d-address=3001
n-d-address={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"}
通过Header、Parameter、Cookie驱动参数和条件表达式结合,把业务定义的这三个驱动参数转化成全链路传递的策略路由Header,执行基于版本匹配的蓝、绿、兜底三条路由驱动,实现全链路版本条件匹配蓝绿发布
① Header、Parameter、Cookie参数传递。对于要驱动发布的参数,例如,业务参数user,可以选择Header、Parameter、Cookie其中任意一个传递,都是等效的
② Header、Parameter、Cookie参数优先级。对于要驱动发布的参数,例如,业务参数user,如果在这三者中都存在,且值不相同,那么取值优先级Parameter > Cookie > Header > 内置Header
③ Header、Parameter、Cookie参数混合。对于要驱动发布的参数,如果不止一个,例如,业务参数user、age、address,可以全部是Header或者Parameter或者Cookie,也可以是这三者混合传递:user通过Header传递,age通过Parameter传递,address通过Cookie传递
① Spring Spel的条件表达式,支持等于=、不等于!=、大于>、小于<、与&&、或||、匹配matches,以及加减乘除取模等全部标准表达式用法
通过Spring Spel的matches条件表达式
- 可通过如下表达式,判断入参是否为
邮件格式
[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}
- 可通过如下表达式,判断入参是否为
三个字母,结尾等于2
[a-z]{3}2
② Spring Spel的条件表达式,整合驱动参数
例如,驱动参数分别为a、b、c,驱动条件为a等于1,b小于等于2,c不等于3,那么表达式可以写为
#
H['a'] == '1' && #
H['b'] <= '2' && #
H['c'] != '3'
或者
#
H['a'] == '1' and #
H['b'] <= '2' and #
H['c'] != '3'
其中,#
H['a'],Spring Spel表达式用来表述驱动参数a的专有格式
H的含义:H为Http首字母,即取值Http类型的参数,包括Header、Parameter、Cookie
③ Spring Spel的逻辑表达,需要注意
- 任何值都大于null。当某个参数未传值,但又指定了该参数大于的表达逻辑,那么表达式结果为false。例如,#H['a'] > '2',但a未传递进来,a即null,则null > 2,表达式结果为false
- null满足不等于。当某个参数未传值,但又指定了该该参数不等于的表达逻辑,那么表达式结果为true。例如,#H['a'] != '2',但a未传递进来,a即null,则null != 2,表达式结果为true
④ Spring Spel的符号
符号和等价符号作用相同,任选一个,等价符号不区分大小写
符号 | 等价符号 | 含义 | 备注 |
---|---|---|---|
+ | 加 | ||
- | 减 | ||
* | 乘 | ||
/ | div | 除 | |
% | mod | 求余 | |
== | eq | 等于 | equal缩写 |
!= | ne | 不等于 | not equal缩写 |
> | gt | 大于 | greater than缩写 |
>= | ge | 大于等于 | greater than equal缩写 |
< | lt | 小于 | less than缩写 |
<= | le | 小于等于 | less than equal缩写 |
&& | and | 且 | |
|| | or | 或 | |
! | not | 非 | |
matches | 正则表达式 | #H['a'] matches '[a-z]{3}2' | |
contains | 包含 | #H['a'].contains('123') | |
between | 区间 | #H['a'] between {1, 2} | |
instanceof | 实例表达式 | #H['a'] instanceof 'T(String)' |
⑤ Spring Spel的符号转义,对Xml格式的规则策略文件,保存在配置中心的时候,需要对表达式中的特殊符号进行转义
符号 | 转义符 | 含义 | 备注 |
---|---|---|---|
& | & |
和符号 | 必须转义 |
< | < |
小于号 | 必须转义 |
" | " |
双引号 | 必须转义 |
> | > |
大于号 | |
' | ' |
单引号 |
表达式如果包含跟Xml格式冲突的字符,就必须转义,例如
#
H['a'] == '1' &&
#
H['b'] <
= '2' &&
#
H['c'] != '3'
增加Spring Cloud Gateway的版本条件匹配蓝绿发布策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<!-- 全局缺省路由,当兜底路由存在的时候,全局缺省路由不需要配置 -->
<!-- <strategy>
<version>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version>
</strategy> -->
<strategy-release>
<conditions type="blue-green">
<!-- 蓝路由,条件expression驱动 -->
<condition id="blue-condition" expression="#H['a'] == '1'" version-id="blue-route"/>
<!-- 绿路由,条件expression驱动 -->
<condition id="green-condition" expression="#H['a'] == '1' and #H['b'] == '2'" version-id="green-route"/>
<!-- 兜底路由,无条件expression驱动 -->
<condition id="basic-condition" version-id="basic-route"/>
</conditions>
<routes>
<route id="blue-route" type="version">{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}</route>
<route id="green-route" type="version">{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</route>
<route id="basic-route" type="version">{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</route>
</routes>
</strategy-release>
</rule>
为准确体现相关变量(例如上文中的 a )支持Header、Parameter、Cookie中的任意一个,表达式格式为 expression="#H['a'] == '1'"
① 当外部调用带有的Header/Parameter/Cookies中的值a=1同时b=2,执行绿路由
<condition>
节点(id="green-condition")中 expression="#H['a'] == '1' and #H['b'] == '2'" 对应的 version-id="green-route" ,找到下面<route>
节点中 id="green-route" type="version" 的那项,那么路由即为
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
② 当外部调用带有的Header/Parameter/Cookies中的值a=1,执行蓝路由
<condition>
节点(id="blue-condition")中 expression="#H['a'] == '1'" 对应的 version-id="blue-route" ,找到下面<route>
节点中 id="blue-route" type="version" 的那项,那么路由即为
{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}
③ 当外部调用带有的Header/Parameter/Cookies中的值都不命中,或者未传值,执行兜底路由
- 执行
<condition>
节点(id="basic-condition")中的兜底路由,那么路由即为
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
④ 如果兜底路由未配置
- 执行
<strategy>
节点中的全局缺省路由,那么路由即为
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
兜底路由和全局缺省路由配置一个即可
- 如果上述配置都不存在,则执行Spring Cloud Ribbon/Spring Cloud LoadBalancer轮询策略
⑤ 假如不愿意从网关外部传入Header/Parameter/Cookies,那么支持策略下内置Header来决策蓝绿发布,可以代替外部传入Header/Parameter/Cookies,参考如下配置
<header>{"a":"1", "b":"2", "c":"3"}</header>
内置Header一般使用场景为定时Job的服务定时去调用其它服务,希望实施蓝绿灰度发布。当服务侧配置了内置Header,而网关也传递给对应Header给该服务,通过开关来决定,网关传递的Header为优先还是服务侧内置的Header优先
Spring Cloud Gateway在Finchley版不支持该方式
⑥ 路由类型支持如下
- 蓝 | 绿 | 兜底,即上述提到的路由场景
- 蓝 | 兜底,即绿路由缺省,那么兜底路由则为绿路由,逻辑更加简单的路由场景
- 如果蓝路由和路由都缺省,那就只有兜底路由(全局缺省路由),即为全链路版本匹配蓝绿发布的路由场景
⑦ 策略总共支持5种,可以单独一项使用,也可以多项叠加使用
- version 版本
- region 区域
- address IP地址和端口
- version-weight 版本权重
- region-weight 区域权重
⑧ 策略支持Spring Spel的条件表达式方式
⑨ 策略支持Spring Matcher的通配方式
上述方式,可以通过全链路蓝绿发布编排建模方式执行,并通过全链路蓝绿发布流量侦测进行验证
用法相似,只需要把规则策略中
- 属性
version-id
替换成region-id
- 属性
type="version"
替换成type="region"
- 节点
route
对应的Json中版本替换成区域
地址条件匹配,即IP地址和端口条件匹配
用法相似,只需要把规则策略中
- 属性
version-id
替换成address-id
- 属性
type="version"
替换成type="address"
- 节点
route
对应的Json中版本替换成IP地址和端口
经典场景:当调用请求从网关或者服务发起的时候,在路由过滤中,根据在配置中心配置的随机权重值,执行权重算法,选择灰度路由 | 稳定路由的规则策略(Json格式),并把命中的规则策略转化为策略路由Header(n-d-开头),实现全链路传递。每个端到端服务接收到策略路由Header后,执行负载均衡时,该Header跟注册中心的对应元数据进行相关比较,不符合条件的实例进行过滤,从而实现全链路灰度发布
实施概要:只涉及当前正在发布的服务,例如,对于 〔网关〕->〔A服务〕->〔B服务〕->〔C服务〕->〔D服务〕调用链来说,如果当前只是B服务和C服务正在实施发布,那么,只需要把B服务和C服务配置到规则策略中,其它则不需要配置。发布结束后,即B服务和C服务的所有实例都完全一致,例如,版本号都只有唯一一个,那么清除掉在配置中心配置的规则策略即可,从而进行下一轮全链路灰度发布
增加Spring Cloud Gateway的版本权重灰度发布策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用全链路1.0版本流量权重为90%,1.1版本流量权重为10%
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version-weight>1.0=90;1.1=10</version-weight>
</strategy>
</rule>
如果希望每个服务的版本权重分别指定,那么策略内容如下,实现从Spring Cloud Gateway发起的调用a服务1.0版本流量权重为90%,1.1版本流量权重为10%,b服务1.0版本流量权重为80%,1.1版本流量权重为20%
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version-weight>{"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=80;1.1=20"}</version-weight>
</strategy>
</rule>
当所有服务都选相同版本流量权重分配的时候,下面两条是等效的
<version-weight>1.0=90;1.1=10</version-weight>
<version-weight>{"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=90;1.1=10"}</version-weight>
非条件驱动下的全链路灰度发布跟Header驱动下的全链路灰度发布等效,例如
n-d-version-weight=1.0=90;1.1=10
n-d-version-weight={"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=90;1.1=10"}
增加Zuul的区域权重灰度发布策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用全链路dev区域流量权重为85%,qa区域流量权重为15%
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<region-weight>dev=85;qa=15</region-weight>
</strategy>
</rule>
如果希望每个服务的区域权重分别指定,那么策略内容如下,实现从Zuul发起的调用a服务dev区域流量权重为85%,qa区域流量权重为15%,b服务dev区域流量权重为75%,qa区域流量权重为25%
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<region-weight>{"discovery-guide-service-a":"dev=85;qa=15", "discovery-guide-service-b":"dev=75;qa=25"}</region-weight>
</strategy>
</rule>
当所有服务都选相同区域流量权重分配的时候,下面两条是等效的
<region-weight>dev=85;qa=15</region-weight>
<region-weight>{"discovery-guide-service-a":"dev=85;qa=15", "discovery-guide-service-b":"dev=85;qa=15"}</region-weight>
非条件驱动下的全链路灰度发布跟Header驱动下的全链路灰度发布等效,例如
n-d-region-weight=dev=85;qa=15
n-d-region-weight={"discovery-guide-service-a":"dev=85;qa=15", "discovery-guide-service-b":"dev=85;qa=15"}
增加Zuul的版本条件权重灰度发布策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<!-- 全局缺省路由,当兜底路由存在的时候,全局缺省路由不需要配置 -->
<!-- 兜底路由如果把全局缺省路由的流量配比设置成100%,其它流量配比设置成0%,那么它等同于如下全局缺省路由 -->
<!-- <strategy>
<version>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version>
</strategy> -->
<strategy-release>
<conditions type="gray">
<!-- 灰度路由1,条件expression驱动 -->
<!-- <condition id="gray-condition-1" expression="#H['a'] == '1'" version-id="gray-route=10;stable-route=90"/> -->
<!-- 灰度路由2,条件expression驱动 -->
<!-- <condition id="gray-condition-2" expression="#H['a'] == '1' and #H['b'] == '2'" version-id="gray-route=85;stable-route=15"/> -->
<!-- 兜底路由,无条件expression驱动 -->
<condition id="basic-condition" version-id="gray-route=0;stable-route=100"/>
</conditions>
<routes>
<route id="gray-route" type="version">{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}</route>
<route id="stable-route" type="version">{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</route>
</routes>
</strategy-release>
</rule>
网关随机权重调用服务,服务链路按照版本匹配方式调用
① 稳定版本路由和灰度版本路由流量权重分配
- 稳定版本路由:a服务1.0版本向网关提供100%的流量,a服务1.0版本只能访问b服务1.0版本
- 灰度版本路由:a服务1.1版本向网关提供0%的流量,a服务1.1版本只能访问b服务1.1版本
② gray-route链路配比0%的流量,stable-route链路配比100%的流量
③ 如果带上条件驱动,可以设置在不同的条件下,给予不同的流量配比
④ 策略总共支持3种,可以单独一项使用,也可以多项叠加使用
- version 版本
- region 区域
- address IP地址和端口
上述方式,可以通过全链路灰度发布编排建模方式执行,并通过全链路灰度发布流量侦测进行验证
用法相似,只需要把规则策略中
- 属性
version-id
替换成region-id
- 属性
type="version"
替换成type="region"
- 节点
route
对应的Json中版本替换成区域
地址条件权重,即IP地址和端口条件权重
用法相似,只需要把规则策略中
- 属性
version-id
替换成address-id
- 属性
type="version"
替换成type="address"
- 节点
route
对应的Json中版本替换成IP地址和端口
① 场景
- 所有部门共享一个大网关
② 思路
- 参数化动态的简单蓝绿灰度发布方式
③ 方案
- 按原生的蓝绿灰度发布来实施
① 场景
- A部门和B部门都有各自的网关
- A部门服务访问B部门服务,必须通过B部门网关
② 思路
- 当本部门服务和其它部门服务在同一时刻实施蓝绿灰度发布的时候,会产生混乱
- 本部门服务的蓝绿灰度发布只由本部门的网关来实施,其它部门无权对本部门服务实施
③ 方案
- 域网关拒绝接受外部传入的蓝绿灰度策略。需要控制网关上
header.priority
的开关 - 域网关配置兜底策略
① 场景
- A部门和B部门都有各自的网关
- A部门服务直接访问B部门服务
② 思路
- 当本部门服务和其它部门服务在同一时刻实施蓝绿灰度发布的时候,会产生混乱
- 本部门服务的蓝绿灰度发布只由本部门的网关来实施,其它部门无权对本部门服务实施
③ 方案
- 本部门的网关不得配置其它部门服务的蓝绿灰度策略
- 所有服务开启全链路版本偏好路由的稳定版本开关
① 场景
- A部门和B部门是否有各自的网关,服务之间如何调用都不做要求
② 思路
- 非参数化动态的简单蓝绿灰度发布方式,无法动态参数化驱动
- 无需Header和外部参数传递,可以规避异步下丢失Header上下文的问题
③ 方案
- 不同部门的网关和服务订阅同一个配置
- 订阅的Group和Data Id必须配置为所有网关和服务的元数据Group值
基于性能考虑,可以考虑关闭下面的开关
# 启动和关闭核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.gateway.core.header.transmission.enabled=true
spring.application.strategy.zuul.core.header.transmission.enabled=true
spring.application.strategy.feign.core.header.transmission.enabled=true
spring.application.strategy.rest.template.core.header.transmission.enabled=true
spring.application.strategy.web.client.core.header.transmission.enabled=true
前端 -> 网关 -> 服务全链路调用中,可以实施端到端蓝绿灰度发布
① 前端 -> 网关并行实施蓝绿灰度发布
当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。需要通过如下开关做控制
# 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true
spring.application.strategy.gateway.header.priority=false
# 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true
spring.application.strategy.gateway.original.header.ignored=true
# 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true
spring.application.strategy.zuul.header.priority=false
# 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true
spring.application.strategy.zuul.original.header.ignored=true
② 网关 -> 服务并行实施蓝绿灰度发布
当网关传值Header的时候,服务也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。需要通过如下开关做控制
# 当外界传值Header的时候,服务也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以服务设置为优先,否则以外界传值为优先。缺失则默认为true
spring.application.strategy.service.header.priority=true
网关 -> 服务全链路调用中,可以混合实施蓝绿灰度发布
① 网关上实施蓝绿发布,服务上实施灰度发布
② 网关上实施灰度发布,服务上实施蓝绿发布
上述两个发布场景,可以独立实施,互不影响,前提条件,需要控制服务上header.priority
的开关
网关或者服务上的规则同时含有蓝绿灰度发布策略
本着蓝绿发布优先于灰度发布的原则,当前端传入参数a(Header、Parameter、Cookie其中一种)
- 当a等于1,执行蓝绿发布,即a服务调用1.1版本,b服务调用1.1版本
- 当a的值不命中,或者未传值,执行灰度发布,即服务a和b 1.1版本的链路流量分配为5%,服务a和b 1.0版本的链路流量分配为95%
分为如下两种方式,都可以达到相同的预期效果
① 通过蓝绿灰度混合方式
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-release>
<conditions type="blue-green">
<condition id="blue-condition" expression="#H['a'] == '1'" version-id="route-2"/>
</conditions>
<conditions type="gray">
<condition id="gray-condition" version-id="route-1=95;route-2=5"/>
</conditions>
<routes>
<route id="route-1" type="version">{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</route>
<route id="route-2" type="version">{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}</route>
</routes>
</strategy-release>
</rule>
② 通过纯灰度方式
把其中一个链路的流量分配为0%,达到蓝绿发布的效果
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-release>
<conditions type="gray">
<condition id="gray-condition-1" expression="#H['a'] == '1'" version-id="route-1=0;route-2=100"/>
<condition id="gray-condition-2" version-id="route-1=95;route-2=5"/>
</conditions>
<routes>
<route id="route-1" type="version">{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</route>
<route id="route-2" type="version">{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}</route>
</routes>
</strategy-release>
</rule>
混合蓝绿灰度发布的逻辑
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version>
</strategy>
<strategy-release>
<conditions type="blue-green">
<condition id="condition-0" expression="#H['a'] == '1'" version-id="route-0"/>
<condition id="condition-1" expression="#H['a'] == '2'" version-id="route-1"/>
<condition id="basic-condition" version-id="route-0"/>
</conditions>
<conditions type="gray">
<condition id="condition-0" expression="#H['a'] == '3'" version-id="route-0=10;route-1=90"/>
<condition id="condition-1" expression="#H['a'] == '4'" version-id="route-0=40;route-1=60"/>
<condition id="basic-condition" version-id="route-0=0;route-1=100"/>
</conditions>
<routes>
<route id="route-0" type="version">{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</route>
<route id="route-1" type="version">{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}</route>
</routes>
</strategy-release>
</rule>
规则解读
原则:蓝绿规则优先于灰度规则
if (a == 1) {
执行蓝绿发布blue-green的route-0下的路由
} else if (a == 2) {
执行蓝绿发布blue-green的route-1下的路由
} else {
执行蓝绿发布blue-green的route-0下的兜底路由
}
如果删除掉蓝绿发布的兜底策略,那么执行逻辑则变为
if (a == 1) {
执行蓝绿发布route-0下的路由
} else if (a == 2) {
执行蓝绿发布route-1下的路由
} else if (a == 3) {
执行灰度发布route-0=10;route-1=90下的流量百分比分配路由
} else if (a == 4) {
执行灰度发布route-0=40;route-1=60下的流量百分比分配路由
} else {
执行灰度发布route-0=0;route-1=100下的兜底路由
由于赋予了route-0=0,那么流量会全部打到route-1上,相当于变种的蓝绿发布
}
如果删除掉蓝绿发布和灰度发布的兜底策略,那么执行逻辑则变为
if (a == 1) {
执行蓝绿发布route-0下的路由
} else if (a == 2) {
执行蓝绿发布route-1下的路由
} else if (a == 3) {
执行灰度发布route-0=10;route-1=90下的流量百分比分配路由
} else if (a == 4) {
执行灰度发布route-0=40;route-1=60下的流量百分比分配路由
} else {
执行<strategy>下的全局兜底路由
}
当蓝绿发布存在兜底策略(basic-condition
),灰度发布永远不会被执行
前端可以直接触发后端蓝绿灰度发布,前提条件,需要控制网关和服务上header.priority
的开关
n-d-的含义:n为Nepxion首字母,d为Discovery首字母
- 版本匹配策略,Header格式如下任选一个
n-d-version=1.0
n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
- 版本权重策略,Header格式如下任选一个
n-d-version-weight=1.0=90;1.1=10
n-d-version-weight={"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=90;1.1=10"}
- 区域匹配策略,Header格式如下任选一个
n-d-region=qa
n-d-region={"discovery-guide-service-a":"qa", "discovery-guide-service-b":"qa"}
- 区域权重策略,Header格式如下任选一个
n-d-region-weight=dev=99;qa=1
n-d-region-weight={"discovery-guide-service-a":"dev=99;qa=1", "discovery-guide-service-b":"dev=99;qa=1"}
- IP地址和端口匹配策略,Header格式如下任选一个
n-d-address=3001;4002
n-d-address={"discovery-guide-service-a":"127.0.0.1:3001", "discovery-guide-service-b":"127.0.0.1:4002"}
n-d-address={"discovery-guide-service-a":"127.0.0.1", "discovery-guide-service-b":"127.0.0.1"}
n-d-address={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4002"}
- 组提供端隔离策略
n-d-group=group1
- 版本偏好路由策略,Header格式如下任选一个
n-d-version-prefer=1.0
n-d-version-prefer={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
- 区域调试路由策略,Header格式如下任选一个
n-d-region-transfer=qa
n-d-region-transfer={"discovery-guide-service-a":"qa", "discovery-guide-service-b":"qa"}
- 环境隔离路由策略
n-d-env=env1
- 版本故障转移策略,Header格式如下任选一个
n-d-version-failover=1.0
n-d-version-failover={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
- 区域故障转移策略,Header格式如下任选一个
n-d-region-failover=dev
n-d-region-failover={"discovery-guide-service-a":"dev", "discovery-guide-service-b":"dev"}
- 环境故障转移策略,Header格式如下任选一个
n-d-env-failover=common
n-d-env-failover={"discovery-guide-service-a":"common", "discovery-guide-service-b":"common"}
- 可用区故障转移策略,Header格式如下任选一个
n-d-zone-failover=zone1
n-d-zone-failover={"discovery-guide-service-a":"zone1", "discovery-guide-service-b":"zone1"}
- 环境故障转移策略,Header格式如下任选一个
n-d-address-failover=*1
n-d-address-failover={"discovery-guide-service-a":"*1", "discovery-guide-service-b":"*1"}
- 服务下线实时性的流量绝对无损,全局唯一ID屏蔽策略,Header格式如下任选一个
n-d-id-blacklist=20210601-222214-909-1146-372-698;20210601-222623-277-4978-633-279
n-d-id-blacklist={"discovery-guide-service-a":"20210601-222214-909-1146-372-698", "discovery-guide-service-b":"20210601-222623-277-4978-633-279"}
- 服务下线实时性的流量绝对无损,IP地址和端口屏蔽策略,Header格式如下任选一个
n-d-address-blacklist=3001;4002
n-d-address-blacklist={"discovery-guide-service-a":"127.0.0.1:3001", "discovery-guide-service-b":"127.0.0.1:4002"}
n-d-address-blacklist={"discovery-guide-service-a":"127.0.0.1", "discovery-guide-service-b":"127.0.0.1"}
n-d-address-blacklist={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4002"}
全链路前端触发后端蓝绿灰度发布全景功能
① Header参数策略
基于标准Http传值方式
框架会默认把相关的Header,进行全链路传递,可以通过如下配置进行。除此之外,凡是以n-d-开头的任何Header,框架都会默认全链路传递
# 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true
spring.application.strategy.rest.intercept.enabled=true
# 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.rest.intercept.debug.enabled=true
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.context.request.headers=trace-id;span-id
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于业务系统自定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.business.request.headers=user;mobile;location
② Parameter参数策略
基于标准Http传值方式
http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=1
http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=2
Parameter不会全链路传递,只会发生在第一层传递
③ Cookie参数策略
基于标准Http传值方式
Cookie不会全链路传递,只会发生在第一层传递
④ 域名参数策略
基于取值域名前缀等方式
参考如下示例
本地测试,为验证结果,请事先在hosts文件中配置如下
127.0.0.1 common.nepxion.com
127.0.0.1 env1.nepxion.com
127.0.0.1 env2.nepxion.com
- 根据env1.nepxion.com域名路由到env1环境
- 根据common.nepxion.com域名路由到common环境
参考全链路过滤器触发蓝绿灰度发布示例,以根据域名全链路环境隔离为例,根据域名前缀中的环境名路由到相应的全链路环境中
⑤ RPC-Method参数策略
基于取值RPC调用中的方法入参等方式,只适用于服务侧
下面代码既适用于Zuul和Spring Cloud Gateway网关,也适用于微服务。继承DefaultGatewayStrategyRouteFilter、DefaultZuulStrategyRouteFilter和DefaultServiceStrategyRouteFilter,覆盖掉如下方法中的一个或者多个,通过@Bean方式覆盖框架内置的过滤类
public String getRouteVersion();
public String getRouteRegion();
public String getRouteEnvironment();
public String getRouteAddress();
public String getRouteVersionWeight();
public String getRouteRegionWeight();
public String getRouteIdBlacklist();
public String getRouteAddressBlacklist();
GatewayStrategyRouteFilter示例
public class MyGatewayStrategyRouteFilter extends DefaultGatewayStrategyRouteFilter {
private static final Logger LOG = LoggerFactory.getLogger(MyGatewayStrategyRouteFilter.class);
private static final String DEFAULT_A_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.0\", \"discovery-guide-service-b\":\"1.1\"}";
private static final String DEFAULT_B_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.1\", \"discovery-guide-service-b\":\"1.0\"}";
private static final String DEFAULT_A_ROUTE_REGION = "{\"discovery-guide-service-a\":\"dev\", \"discovery-guide-service-b\":\"qa\"}";
private static final String DEFAULT_B_ROUTE_REGION = "{\"discovery-guide-service-a\":\"qa\", \"discovery-guide-service-b\":\"dev\"}";
private static final String DEFAULT_A_ROUTE_ADDRESS = "{\"discovery-guide-service-a\":\"3001\", \"discovery-guide-service-b\":\"4002\"}";
private static final String DEFAULT_B_ROUTE_ADDRESS = "{\"discovery-guide-service-a\":\"3002\", \"discovery-guide-service-b\":\"4001\"}";
@Value("${a.route.version:" + DEFAULT_A_ROUTE_VERSION + "}")
private String aRouteVersion;
@Value("${b.route.version:" + DEFAULT_B_ROUTE_VERSION + "}")
private String bRouteVersion;
@Value("${a.route.region:" + DEFAULT_A_ROUTE_REGION + "}")
private String aRouteRegion;
@Value("${b.route.region:" + DEFAULT_B_ROUTE_REGION + "}")
private String bRouteRegion;
@Value("${a.route.address:" + DEFAULT_A_ROUTE_ADDRESS + "}")
private String aRouteAddress;
@Value("${b.route.address:" + DEFAULT_B_ROUTE_ADDRESS + "}")
private String bRouteAddress;
// 自定义根据Header全链路版本匹配路由
@Override
public String getRouteVersion() {
String user = strategyContextHolder.getHeader("user");
LOG.info("自定义根据Header全链路版本匹配路由, Header user={}", user);
if (StringUtils.equals(user, "zhangsan")) {
LOG.info("执行全链路版本匹配路由={}", aRouteVersion);
return aRouteVersion;
} else if (StringUtils.equals(user, "lisi")) {
LOG.info("执行全链路版本匹配路由={}", bRouteVersion);
return bRouteVersion;
}
return super.getRouteVersion();
}
// 自定义根据Parameter全链路区域匹配路由
@Override
public String getRouteRegion() {
String user = strategyContextHolder.getParameter("user");
LOG.info("自定义根据Parameter全链路区域匹配路由, Parameter user={}", user);
if (StringUtils.equals(user, "zhangsan")) {
LOG.info("执行全链路区域匹配路由={}", aRouteRegion);
return aRouteRegion;
} else if (StringUtils.equals(user, "lisi")) {
LOG.info("执行全链路区域匹配路由={}", bRouteRegion);
return bRouteRegion;
}
return super.getRouteRegion();
}
// 自定义根据Cookie全链路IP地址和端口匹配路由
@Override
public String getRouteAddress() {
String user = strategyContextHolder.getCookie("user");
LOG.info("自定义根据Cookie全链路IP地址和端口匹配路由, Cookie user={}", user);
if (StringUtils.equals(user, "zhangsan")) {
LOG.info("执行全链路IP地址和端口匹配路由={}", aRouteAddress);
return aRouteAddress;
} else if (StringUtils.equals(user, "lisi")) {
LOG.info("执行全链路IP地址和端口匹配路由={}", bRouteAddress);
return bRouteAddress;
}
return super.getRouteAddress();
}
@Autowired
private GatewayStrategyContextHolder gatewayStrategyContextHolder;
// 自定义根据域名全链路环境隔离
@Override
public String getRouteEnvironment() {
String host = gatewayStrategyContextHolder.getURI().getHost();
if (host.contains("nepxion.com")) {
LOG.info("自定义根据域名全链路环境隔离, URL={}", host);
String environment = host.substring(0, host.indexOf("."));
LOG.info("执行全链路环境隔离={}", environment);
return environment;
}
return super.getRouteEnvironment();
}
// 自定义全链路版本权重路由
/*@Override
public String getRouteVersion() {
LOG.info("自定义全链路版本权重路由");
List<Pair<String, Integer>> weightList = new ArrayList<Pair<String, Integer>>();
weightList.add(new ImmutablePair<String, Integer>(aRouteVersion, 30));
weightList.add(new ImmutablePair<String, Integer>(bRouteVersion, 70));
MapWeightRandom<String, Integer> weightRandom = new MapWeightRandom<String, Integer>(weightList);
return weightRandom.random();
}*/
}
在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类
@Bean
public GatewayStrategyRouteFilter gatewayStrategyRouteFilter() {
return new MyGatewayStrategyRouteFilter();
}
ZuulStrategyRouteFilter示例
public class MyZuulStrategyRouteFilter extends DefaultZuulStrategyRouteFilter {
private static final Logger LOG = LoggerFactory.getLogger(MyZuulStrategyRouteFilter.class);
private static final String DEFAULT_A_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.0\", \"discovery-guide-service-b\":\"1.1\"}";
private static final String DEFAULT_B_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.1\", \"discovery-guide-service-b\":\"1.0\"}";
private static final String DEFAULT_A_ROUTE_REGION = "{\"discovery-guide-service-a\":\"dev\", \"discovery-guide-service-b\":\"qa\"}";
private static final String DEFAULT_B_ROUTE_REGION = "{\"discovery-guide-service-a\":\"qa\", \"discovery-guide-service-b\":\"dev\"}";
private static final String DEFAULT_A_ROUTE_ADDRESS = "{\"discovery-guide-service-a\":\"3001\", \"discovery-guide-service-b\":\"4002\"}";
private static final String DEFAULT_B_ROUTE_ADDRESS = "{\"discovery-guide-service-a\":\"3002\", \"discovery-guide-service-b\":\"4001\"}";
@Value("${a.route.version:" + DEFAULT_A_ROUTE_VERSION + "}")
private String aRouteVersion;
@Value("${b.route.version:" + DEFAULT_B_ROUTE_VERSION + "}")
private String bRouteVersion;
@Value("${a.route.region:" + DEFAULT_A_ROUTE_REGION + "}")
private String aRouteRegion;
@Value("${b.route.region:" + DEFAULT_B_ROUTE_REGION + "}")
private String bRouteRegion;
@Value("${a.route.address:" + DEFAULT_A_ROUTE_ADDRESS + "}")
private String aRouteAddress;
@Value("${b.route.address:" + DEFAULT_B_ROUTE_ADDRESS + "}")
private String bRouteAddress;
// 自定义根据Header全链路版本匹配路由
@Override
public String getRouteVersion() {
String user = strategyContextHolder.getHeader("user");
LOG.info("自定义根据Header全链路版本匹配路由, Header user={}", user);
if (StringUtils.equals(user, "zhangsan")) {
LOG.info("执行全链路版本匹配路由={}", aRouteVersion);
return aRouteVersion;
} else if (StringUtils.equals(user, "lisi")) {
LOG.info("执行全链路版本匹配路由={}", bRouteVersion);
return bRouteVersion;
}
return super.getRouteVersion();
}
// 自定义根据Parameter全链路区域匹配路由
@Override
public String getRouteRegion() {
String user = strategyContextHolder.getParameter("user");
LOG.info("自定义根据Parameter全链路区域匹配路由, Parameter user={}", user);
if (StringUtils.equals(user, "zhangsan")) {
LOG.info("执行全链路区域匹配路由={}", aRouteRegion);
return aRouteRegion;
} else if (StringUtils.equals(user, "lisi")) {
LOG.info("执行全链路区域匹配路由={}", bRouteRegion);
return bRouteRegion;
}
return super.getRouteRegion();
}
// 自定义根据Cookie全链路IP地址和端口匹配路由
@Override
public String getRouteAddress() {
String user = strategyContextHolder.getCookie("user");
LOG.info("自定义根据Cookie全链路IP地址和端口匹配路由, Cookie user={}", user);
if (StringUtils.equals(user, "zhangsan")) {
LOG.info("执行全链路IP地址和端口匹配路由={}", aRouteAddress);
return aRouteAddress;
} else if (StringUtils.equals(user, "lisi")) {
LOG.info("执行全链路IP地址和端口匹配路由={}", bRouteAddress);
return bRouteAddress;
}
return super.getRouteEnvironment();
}
@Autowired
private ZuulStrategyContextHolder zuulStrategyContextHolder;
// 自定义根据域名全链路环境隔离
@Override
public String getRouteEnvironment() {
String requestURL = zuulStrategyContextHolder.getRequestURL();
if (requestURL.contains("nepxion.com")) {
LOG.info("自定义根据域名全链路环境隔离, URL={}", requestURL);
String host = requestURL.substring("http://".length(), requestURL.length());
String environment = host.substring(0, host.indexOf("."));
LOG.info("执行全链路环境隔离={}", environment);
return environment;
}
return super.getRouteEnvironment();
}
// 自定义全链路版本权重路由
/*@Override
public String getRouteVersion() {
LOG.info("自定义全链路版本权重路由");
List<Pair<String, Integer>> weightList = new ArrayList<Pair<String, Integer>>();
weightList.add(new ImmutablePair<String, Integer>(aRouteVersion, 30));
weightList.add(new ImmutablePair<String, Integer>(bRouteVersion, 70));
MapWeightRandom<String, Integer> weightRandom = new MapWeightRandom<String, Integer>(weightList);
return weightRandom.random();
}*/
}
在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类
@Bean
public ZuulStrategyRouteFilter zuulStrategyRouteFilter() {
return new MyZuulStrategyRouteFilter();
}
ServiceStrategyRouteFilter示例
public class MyServiceStrategyRouteFilter extends DefaultServiceStrategyRouteFilter {
private static final Logger LOG = LoggerFactory.getLogger(MyServiceStrategyRouteFilter.class);
private static final String DEFAULT_A_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.0\", \"discovery-guide-service-b\":\"1.1\"}";
private static final String DEFAULT_B_ROUTE_VERSION = "{\"discovery-guide-service-a\":\"1.1\", \"discovery-guide-service-b\":\"1.0\"}";
private static final String DEFAULT_A_ROUTE_REGION = "{\"discovery-guide-service-a\":\"dev\", \"discovery-guide-service-b\":\"qa\"}";
private static final String DEFAULT_B_ROUTE_REGION = "{\"discovery-guide-service-a\":\"qa\", \"discovery-guide-service-b\":\"dev\"}";
private static final String DEFAULT_A_ROUTE_ADDRESS = "{\"discovery-guide-service-a\":\"3001\", \"discovery-guide-service-b\":\"4002\"}";
private static final String DEFAULT_B_ROUTE_ADDRESS = "{\"discovery-guide-service-a\":\"3002\", \"discovery-guide-service-b\":\"4001\"}";
@Value("${a.route.version:" + DEFAULT_A_ROUTE_VERSION + "}")
private String aRouteVersion;
@Value("${b.route.version:" + DEFAULT_B_ROUTE_VERSION + "}")
private String bRouteVersion;
@Value("${a.route.region:" + DEFAULT_A_ROUTE_REGION + "}")
private String aRouteRegion;
@Value("${b.route.region:" + DEFAULT_B_ROUTE_REGION + "}")
private String bRouteRegion;
@Value("${a.route.address:" + DEFAULT_A_ROUTE_ADDRESS + "}")
private String aRouteAddress;
@Value("${b.route.address:" + DEFAULT_B_ROUTE_ADDRESS + "}")
private String bRouteAddress;
// 自定义根据Header全链路版本匹配路由
@Override
public String getRouteVersion() {
String user = strategyContextHolder.getHeader("user");
LOG.info("自定义根据Header全链路版本匹配路由, Header user={}", user);
if (StringUtils.equals(user, "zhangsan")) {
LOG.info("执行全链路版本匹配路由={}", aRouteVersion);
return aRouteVersion;
} else if (StringUtils.equals(user, "lisi")) {
LOG.info("执行全链路版本匹配路由={}", bRouteVersion);
return bRouteVersion;
}
return super.getRouteVersion();
}
// 自定义根据Parameter全链路区域匹配路由
@Override
public String getRouteRegion() {
String user = strategyContextHolder.getParameter("user");
LOG.info("自定义根据Parameter全链路区域匹配路由, Parameter user={}", user);
if (StringUtils.equals(user, "zhangsan")) {
LOG.info("执行全链路区域匹配路由={}", aRouteRegion);
return aRouteRegion;
} else if (StringUtils.equals(user, "lisi")) {
LOG.info("执行全链路区域匹配路由={}", bRouteRegion);
return bRouteRegion;
}
return super.getRouteRegion();
}
// 自定义根据Cookie全链路IP地址和端口匹配路由
@Override
public String getRouteAddress() {
String user = strategyContextHolder.getCookie("user");
LOG.info("自定义根据Cookie全链路IP地址和端口匹配路由, Cookie user={}", user);
if (StringUtils.equals(user, "zhangsan")) {
LOG.info("执行全链路IP地址和端口匹配路由={}", aRouteAddress);
return aRouteAddress;
} else if (StringUtils.equals(user, "lisi")) {
LOG.info("执行全链路IP地址和端口匹配路由={}", bRouteAddress);
return bRouteAddress;
}
return super.getRouteEnvironment();
}
@Autowired
private ServiceStrategyContextHolder serviceStrategyContextHolder;
// 自定义根据域名全链路环境隔离
@Override
public String getRouteEnvironment() {
String requestURL = serviceStrategyContextHolder.getRequestURL();
if (requestURL.contains("nepxion.com")) {
LOG.info("自定义根据域名全链路环境隔离, URL={}", requestURL);
String host = requestURL.substring("http://".length(), requestURL.length());
String environment = host.substring(0, host.indexOf("."));
LOG.info("执行全链路环境隔离={}", environment);
return environment;
}
return super.getRouteEnvironment();
}
// 自定义全链路版本权重路由
/*@Override
public String getRouteVersion() {
LOG.info("自定义全链路版本权重路由");
List<Pair<String, Integer>> weightList = new ArrayList<Pair<String, Integer>>();
weightList.add(new ImmutablePair<String, Integer>(aRouteVersion, 30));
weightList.add(new ImmutablePair<String, Integer>(bRouteVersion, 70));
MapWeightRandom<String, Integer> weightRandom = new MapWeightRandom<String, Integer>(weightList);
return weightRandom.random();
}*/
}
在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类
@Bean
public ServiceStrategyRouteFilter serviceStrategyRouteFilter() {
return new MyServiceStrategyRouteFilter();
}
对于Spring Cloud 202x版,由于它已经移除了Ribbon,所以apply(Server server)方法上的入参,com.netflix.loadbalancer.Server需要改成org.springframework.cloud.client.ServiceInstance
下面代码既适用于Zuul和Spring Cloud Gateway网关,也适用于微服务。继承DefaultDiscoveryEnabledStrategy,可以有多个,通过@Bean方式注入
public class MyDiscoveryEnabledStrategy extends DefaultDiscoveryEnabledStrategy {
private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledStrategy.class);
// 对REST调用传来的Header参数(例如:mobile)做策略
@Override
public boolean apply(Server server) {
String mobile = strategyContextHolder.getHeader("mobile");
String serviceId = pluginAdapter.getServerServiceId(server);
String version = pluginAdapter.getServerVersion(server);
String region = pluginAdapter.getServerRegion(server);
String environment = pluginAdapter.getServerEnvironment(server);
String address = server.getHost() + ":" + server.getPort();
LOG.info("负载均衡用户定制触发:mobile={}, serviceId={}, version={}, region={}, env={}, address={}", mobile, serviceId, version, region, environment, address);
if (StringUtils.isNotEmpty(mobile)) {
// 手机号以移动138开头,路由到1.0版本的服务上
if (mobile.startsWith("138") && StringUtils.equals(version, "1.0")) {
return true;
// 手机号以联通133开头,路由到1.1版本的服务上
} else if (mobile.startsWith("133") && StringUtils.equals(version, "1.1")) {
return true;
} else {
// 其它情况,直接拒绝请求
return false;
}
}
return true;
}
}
在配置类里@Bean方式进行策略类创建
@Bean
public DiscoveryEnabledStrategy discoveryEnabledStrategy() {
return new MyDiscoveryEnabledStrategy();
}
服务除了支持网关那种基于Rest参数的方式之外,还支持基于Rpc方法参数的方式,它包括接口名、方法名、参数名或参数值等多种形式
public class MyDiscoveryEnabledStrategy implements DiscoveryEnabledStrategy {
private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledStrategy.class);
@Autowired
private PluginAdapter pluginAdapter;
@Autowired
private ServiceStrategyContextHolder serviceStrategyContextHolder;
@Override
public boolean apply(Server server) {
boolean enabled = applyFromHeader(server);
if (!enabled) {
return false;
}
return applyFromMethod(server);
}
// 根据REST调用传来的Header参数(例如:mobile),选取执行调用请求的服务实例
private boolean applyFromHeader(Server server) {
String mobile = serviceStrategyContextHolder.getHeader("mobile");
String serviceId = pluginAdapter.getServerServiceId(server);
String version = pluginAdapter.getServerVersion(server);
String region = pluginAdapter.getServerRegion(server);
String environment = pluginAdapter.getServerEnvironment(server);
String address = server.getHost() + ":" + server.getPort();
LOG.info("负载均衡用户定制触发:mobile={}, serviceId={}, version={}, region={}, env={}, address={}", mobile, serviceId, version, region, environment, address);
if (StringUtils.isNotEmpty(mobile)) {
// 手机号以移动138开头,路由到1.0版本的服务上
if (mobile.startsWith("138") && StringUtils.equals(version, "1.0")) {
return true;
// 手机号以联通133开头,路由到2.0版本的服务上
} else if (mobile.startsWith("133") && StringUtils.equals(version, "1.1")) {
return true;
} else {
// 其它情况,直接拒绝请求
return false;
}
}
return true;
}
// 根据RPC调用传来的方法参数(例如接口名、方法名、参数名或参数值等),选取执行调用请求的服务实例
// 本示例只作用在discovery-guide-service-a服务上
@SuppressWarnings("unchecked")
private boolean applyFromMethod(Server server) {
Map<String, Object> attributes = serviceStrategyContextHolder.getRpcAttributes();
String serviceId = pluginAdapter.getServerServiceId(server);
String version = pluginAdapter.getServerVersion(server);
String region = pluginAdapter.getServerRegion(server);
String environment = pluginAdapter.getServerEnvironment(server);
String address = server.getHost() + ":" + server.getPort();
LOG.info("负载均衡用户定制触发:attributes={}, serviceId={}, version={}, region={}, env={}, address={}", attributes, serviceId, version, region, environment, address);
if (attributes.containsKey(DiscoveryConstant.PARAMETER_MAP)) {
Map<String, Object> parameterMap = (Map<String, Object>) attributes.get(DiscoveryConstant.PARAMETER_MAP);
String value = parameterMap.get("value").toString();
if (StringUtils.isNotEmpty(value)) {
// 输入值包含dev,路由到dev区域的服务上
if (value.contains("dev") && StringUtils.equals(region, "dev")) {
return true;
// 输入值包含qa,路由到qa区域的服务上
} else if (value.contains("qa") && StringUtils.equals(region, "qa")) {
return true;
} else {
// 其它情况,直接通过请求
return true;
}
}
}
return true;
}
}
需要通过如下开关开启该功能
# 启动和关闭路由策略的时候,对RPC方式的调用拦截。缺失则默认为false
spring.application.strategy.rpc.intercept.enabled=true
全链路侦测,用来侦测调试蓝绿灰度发布、路由隔离等一系列流量管控手段是否符合预期
使用侦测功能,服务必须引入discovery-plugin-admin-center-starter依赖
① 全链路侦测方式
请求URL
http://localhost:6001/inspector/inspect
请求类型
POST
请求参数
InspectorDebugEntity
格式
{
"protocol": "http",
"portal": "",
"path": "",
"service": [],
"header": {}
}
protocol
协议类型,取值http
或者https
portal
入口服务名,支持网关和服务两种入口方式path
网关路由转发路径或者服务调用上下文路径- 当以网关为入口时,
path
为网关路由转发路径。如果网关未对它后面的第一个服务配置网关路由转发路径,则path
由该服务名来代替 - 当以服务为入口时,
path
为服务调用上下文路径ContextPath
。如果服务未配置调用上下文路径,则path
留空
- 当以网关为入口时,
service
侦测服务列表,可以留空或者删除。网关为入口,例如,想侦测服务A和服务B的链路,侦测服务列表只需要包含服务B,网关路由转发路径已经包含服务Aheader
Http头的哈希表,可以留空或者删除
② 全链路侦测示例
- 以网关为入口进行侦测调试
输入InspectorDebugEntity
对象内容
如果网关对服务discovery-guide-service-a
的路由转发路径配置为/discovery-guide-service-a/**
{
"protocol": "http",
"portal": "discovery-guide-gateway",
"path": "discovery-guide-service-a",
"service": ["discovery-guide-service-b"],
"header": {"xyz": "1"}
}
返回
[ID=discovery-guide-gateway][UID=20220927-111127-875-1354-367-132][T=gateway][P=Nacos][H=192.168.31.237:5001][V=1.0][R=default][E=default][Z=default][G=discovery-guide-group][A=false][TID=30186d59ee6e0633][SID=5c2094901666a0ee]
-> [ID=discovery-guide-service-a][UID=20220927-111127-898-7896-282-453][T=service][P=Nacos][H=192.168.31.237:3001][V=1.0][R=dev][E=env1][Z=zone1][G=discovery-guide-group][A=true][TID=30186d59ee6e0633][SID=65bcb084e10fe9d0]
-> [ID=discovery-guide-service-b][UID=20220927-111127-603-9037-746-402][T=service][P=Nacos][H=192.168.31.237:4001][V=1.0][R=qa][E=env1][Z=zone1][G=discovery-guide-group][A=false][TID=30186d59ee6e0633][SID=d032cb9f094bf411]
从返回结果查看,在入口服务discovery-guide-gateway
上侦测调试服务discovery-guide-service-a
和服务discovery-guide-service-b
的蓝绿灰度发布、路由隔离等一系列流量管控手段是否符合预期
- 以服务为入口进行侦测调试
输入InspectorDebugEntity
对象内容
{
"protocol": "http",
"portal": "discovery-guide-service-a",
"path": "",
"service": ["discovery-guide-service-b"],
"header": {"xyz": "1"}
}
返回
[ID=discovery-guide-service-a][UID=20220927-111127-898-7896-282-453][T=service][P=Nacos][H=192.168.31.237:3001][V=1.0][R=dev][E=env1][Z=zone1][G=discovery-guide-group][A=true][TID=f953d0cb282ce160][SID=cb45020a6275e68d]
-> [ID=discovery-guide-service-b][UID=20220927-111127-603-9037-746-402][T=service][P=Nacos][H=192.168.31.237:4001][V=1.0][R=qa][E=env1][Z=zone1][G=discovery-guide-group][A=false][TID=f953d0cb282ce160][SID=ee2ae1d457cd8c98]
从返回结果查看,在入口服务discovery-guide-service-a
上侦测调试服务discovery-guide-service-b
的蓝绿灰度发布、路由隔离等一系列流量管控手段是否符合预期
① 全链路侦测方式
请求URL
- 网关为入口
http://网关地址:网关端口/网关路由转发路径/inspector/inspect-service
- 服务为入口
http://服务地址:服务端口/服务调用上下文路径/inspector/inspect-service
请求类型
POST
请求参数
侦测服务列表,参考控制台为起点的流量侦测调试
② 全链路侦测示例
示例,参考控制台为起点的流量侦测调试
链路智能编排的方式,即路由链路在后台会智能化编排,用户不再需要关心服务实例的版本情况而进行手工编排,只需要配置跟业务参数有关的条件表达式即可,让蓝绿灰度发布变的更简单更易用
- 线上所有的服务,每个服务实例版本号支持排序,时间戳方式或者数字递增方式都可以
- 线上所有的服务,每个服务至少有一个版本
- 线上所有的服务,每个服务如果有两个版本,第一个划归为旧版本,第二个划归为新版本
- 线上所有的服务,每个服务如果有三个及以上版本,第一个划归为旧版本,第二个到最后划归为新版本。通过逗号分隔的方式放入,例如,2.0;3.0;4.0
通过向控制台发送请求,控制台根据Json格式规则策略,根据新旧版本的判断,智能编排出两条新旧路由链路,并给它们赋予不同的条件表达式,最终创建出完整的Xml格式规则策略,保存到配置中心
① 创建版本蓝绿灰度发布
- 兜底规则策略
{
"service": ["discovery-guide-service-a", "discovery-guide-service-b"]
}
经过链路智能编排,等效于
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version>{"discovery-guide-service-a":"1.0","discovery-guide-service-b":"1.0"}</version>
</strategy>
</rule>
- 蓝绿规则策略
{
"service": ["discovery-guide-service-a", "discovery-guide-service-b"],
"blueGreen": [
{
"expression": "#H['xyz'] == '1'",
"route": "green"
},
{
"expression": "#H['xyz'] == '2'",
"route": "blue"
}
]
}
经过链路智能编排,等效于
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version>{"discovery-guide-service-a":"1.0","discovery-guide-service-b":"1.0"}</version>
</strategy>
<strategy-release>
<conditions type="blue-green">
<condition id="condition-0" expression="#H['xyz'] == '1'" version-id="route-0"/>
<condition id="condition-1" expression="#H['xyz'] == '2'" version-id="route-1"/>
</conditions>
<routes>
<route id="route-0" type="version">{"discovery-guide-service-a":"1.0","discovery-guide-service-b":"1.0"}</route>
<route id="route-1" type="version">{"discovery-guide-service-a":"1.1","discovery-guide-service-b":"1.1"}</route>
</routes>
</strategy-release>
</rule>
- 灰度规则策略
{
"service": ["discovery-guide-service-a", "discovery-guide-service-b"],
"gray": [
{
"expression": "#H['xyz'] == '3'",
"weight": [90, 10]
},
{
"expression": "#H['xyz'] == '4'",
"weight": [70, 30]
},
{
"weight": [100, 0]
}
]
}
经过链路智能编排,等效于
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version>{"discovery-guide-service-a":"1.0","discovery-guide-service-b":"1.0"}</version>
</strategy>
<strategy-release>
<conditions type="gray">
<condition id="condition-0" expression="#H['xyz'] == '3'" version-id="route-0=90;route-1=10"/>
<condition id="condition-1" expression="#H['xyz'] == '4'" version-id="route-0=70;route-1=30"/>
<condition id="condition-2" version-id="route-0=100;route-1=0"/>
</conditions>
<routes>
<route id="route-0" type="version">{"discovery-guide-service-a":"1.0","discovery-guide-service-b":"1.0"}</route>
<route id="route-1" type="version">{"discovery-guide-service-a":"1.1","discovery-guide-service-b":"1.1"}</route>
</routes>
</strategy-release>
</rule>
- 混合蓝绿灰度 + 内置Header规则策略
使用时候,请删除中文注释,否则会报错
{
"service": ["discovery-guide-service-a", "discovery-guide-service-b"],
"blueGreen": [
{
"expression": "#H['xyz'] == '1'",
// 绿(旧版本)路由链路
"route": "green"
},
{
"expression": "#H['xyz'] == '2'",
// 蓝(新版本)路由链路
"route": "blue"
}
],
"gray": [
{
"expression": "#H['xyz'] == '3'",
// 稳定(旧版本)路由链路权重,灰度(新版本)路由链路权重
"weight": [10, 90]
},
{
"expression": "#H['xyz'] == '4'",
"weight": [40, 60]
},
{
"weight": [0, 100]
}
],
"header": {"xyz": "1"}
}
经过链路智能编排,等效于
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version>{"discovery-guide-service-a":"1.0","discovery-guide-service-b":"1.0"}</version>
</strategy>
<strategy-release>
<conditions type="blue-green">
<condition id="condition-0" expression="#H['xyz'] == '1'" version-id="route-0"/>
<condition id="condition-1" expression="#H['xyz'] == '2'" version-id="route-1"/>
</conditions>
<conditions type="gray">
<condition id="condition-0" expression="#H['xyz'] == '3'" version-id="route-0=10;route-1=90"/>
<condition id="condition-1" expression="#H['xyz'] == '4'" version-id="route-0=40;route-1=60"/>
<condition id="condition-2" version-id="route-0=0;route-1=100"/>
</conditions>
<routes>
<route id="route-0" type="version">{"discovery-guide-service-a":"1.0","discovery-guide-service-b":"1.0"}</route>
<route id="route-1" type="version">{"discovery-guide-service-a":"1.1","discovery-guide-service-b":"1.1"}</route>
</routes>
<header>{"xyz":"1"}</header>
</strategy-release>
</rule>
② 清除蓝绿灰度发布
③ 校验版本蓝绿灰度发布
④ 校验条件表达式
具体用法,请参考对接DevOps运维平台公共接口
① 控制台需要连接注册中心和配置中心
② 控制台建议实现高可用架构,控制台前面部署API网关和运维平台对接
① 运维平台调用控制台的Open API,控制台进行链路智能编排
② 控制台把最终蓝绿灰度规则策略推送到配置中心
① 下载代码,Git clone https://github.com/Nepxion/DiscoveryGuide.git,分支为6.x.x-simple。代码很简洁,建议复制出来形成独立工程进行部署
② 运行discovery-guide-console
下面的DiscoveryGuideConsole.java
③ 控制台需要实现高可用,做集群部署,可以前置API网关或者Nginx
最佳实践采用举例说明,使用者需要依据实际情况来确认版本号、业务参数名和值等
生产环境上,全链路调用路径,如下
API网关 -> 服务A -> 服务B
2021年6月1日,运维平台已经上线了服务A和服务B各1个实例,进行如下染色
① 它们的组通过流量染色
步骤,都赋予为nepxion
(如果整个企业只有一个网关,可以不设置组,缺省为default
)
② 它们的版本号通过流量染色
步骤,都赋予为20210601-0001
在新版本服务上线之前,通过故障转移
步骤实施,启动故障转移功能
可以通过运维平台实施故障转移的管控
在API网关上,通过蓝绿灰度发布
的创建版本蓝绿灰度发布
步骤,创建兜底规则策略,避免流量进入新服务实例
{
"service": ["a", "b"]
}``
以Nacos和Apollo配置中心为例,举例配置方式。下同
① Nacos配置中心Group为nepxion
或者default
,Data Id为API网关的服务名
② Apollo配置中心Key为API网关的服务名-nepxion
或者API网关的服务名-default
2021年7月1日,运维平台上线新的服务A和服务B各1个实例,进行如下染色,两个新服务实例都启动成功
① 它们的组通过流量染色
步骤,都赋予为nepxion
(如果整个企业只有一个网关,可以不设置组,缺省为default
)
② 它们的版本号通过流量染色
步骤,都赋予为20210701-0001
在API网关上,通过蓝绿灰度发布
的创建版本蓝绿灰度发布
步骤,创建蓝绿灰度发布规则策略
通过在调用API网关的URL上增加基于Header/Parameter/Cookie
的业务参数xyz
① 蓝绿发布
xyz
为1
,切换到蓝路由(旧版本链路)xyz
为2
,切换到绿路由(新版本链路)xyz
缺失,切换到兜底路由(旧版本链路)
{
"service": ["a", "b"],
"blueGreen": [
{
"expression": "#H['xyz'] == '1'",
"route": "green"
},
{
"expression": "#H['xyz'] == '2'",
"route": "blue"
}
]
}
② 灰度发布
xyz
为3
,稳定路由(旧版本链路)和灰度路由(新版本链路)的流量配比是90:10xyz
为4
,稳定路由(旧版本链路)和灰度路由(新版本链路)的流量配比是70:30xyz
缺失,稳定路由(旧版本链路)和灰度路由(新版本链路)的流量配比是100:0,即流量不会进行新版本服务的链路
{
"service": ["discovery-guide-service-a", "discovery-guide-service-b"],
"gray": [
{
"expression": "#H['xyz'] == '3'",
"weight": [90, 10]
},
{
"expression": "#H['xyz'] == '4'",
"weight": [70, 30]
},
{
"weight": [100, 0]
}
]
}
蓝绿灰度执行结果处理
- 蓝绿灰度发布成功,新版本实例测试通过,流量全部切到新版本实例,下线老版本服务实例
- 蓝绿灰度发布失败,新版本实例测试失败,流量全部切到旧版本实例,下线新版本服务实例,待问题解决后重新上线新服务
在旧版本服务实例下线之前,在API网关上,执行无损下线
的添加下线的服务实例到黑名单
步骤,保证流量不会进入要下线的老版本实例
运维平台停止旧版本的服务实例
等待一段时间后,待旧服务实例彻底下线,在API网关上,执行无损下线
的从黑名单清除所有过期的服务实例
步骤
在API网关上,通过蓝绿灰度发布
的清除蓝绿灰度发布
步骤,清除蓝绿灰度发布规则策略
整个流程过程,示意如下,故障转移
和无损下线
步骤可以省略
运维平台通过命令行java -jar启动应用,加入启动参数-Dmetadata.group=xxx
和-Dmetadata.version=yyy
,表示给服务实例进行组维度和版本维度的流量染色,即
java -jar -Dmetadata.group=xxx -Dmetadata.version=yyy abc.jar
基于时间戳格式的全局唯一版本号流量染色,有如下两种最佳实践,使用者根据企业现状,选择一种
① Git插件创建版本号(把版本号创建权力下放给基础架构)
服务通过集成插件git-commit-id-plugin产生git信息文件的方式,获取git.commit.time(最后一次代码提交日期)加{git.total.commit.count}(总提交次数)来自动创建版本号,例如,20210601-567。运维平台不再需要加入启动参数-Dmetadata.version=yyy
- 增加Git编译插件
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- 必须配置,并指定为true -->
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<!-- 指定日期格式 -->
<dateFormat>yyyyMMdd</dateFormat>
<!-- <dateFormat>yyyy-MM-dd-HH:mm:ss</dateFormat> -->
</configuration>
</plugin>
- 开启Git插件产生版本号的开关
# 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false
spring.application.git.generator.enabled=true
② 运维平台创建版本号(把版本号创建权力下放给运维平台)
运维平台决定版本号可以采用日期戳-序号的格式
- 日期戳表示当天的日期
- 序号表示当天的发布次数,一般定义为四位,即从0001-9999。序号由运维平台来维护,当天每发布一个版本,序号自加1
这种表示方式具有很强的可读性意义,例如,20210601-0003
,表示某一组服务实例蓝绿灰度的版本为2021年6月1日发布的第三个版本
在网关和服务上开启如下开关
# 启动和关闭版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.enabled=true
运维平台对接控制台,在网关上实施故障转移
① 创建故障转移
网关和服务上有默认故障转移方式,如果通过运维平台来实施管控,则默认故障转移方式失效
② 清除故障转移
运维平台取消实施故障转移方式的管控
具体用法,请参考对接DevOps运维平台公共接口
运维平台对接控制台,通过链路在后台智能编排的方式,在网关上实施蓝绿灰度发布
① 创建版本蓝绿灰度发布
运维平台通过创建兜底、蓝绿、灰度、混合蓝绿灰度等四种方式实施流量管控
② 清除蓝绿灰度发布
运维平台完成蓝绿灰度发布
③ 校验版本蓝绿灰度发布
运维平台预验证蓝绿灰度对象是否合法,链路智能编排结果是否正确
④ 校验条件表达式
运维平台预验证条件表达式是否正确
⑤ 获取蓝绿灰度发布
运维平台调用配置接口
的获取规则配置对象
步骤,获取其中的蓝绿灰度发布规则策略
具体用法,请参考对接DevOps运维平台公共接口
运维平台对接控制台,在服务实例下线之前,在网关上实施服务实例的黑名单屏蔽,在服务实例下线一段时间后,再解除黑名单屏蔽
① 添加下线的服务实例到黑名单
运维平台下线服务实例之前,通过IP地址和端口添加入黑名单(转化成UUId存储)进行单个屏蔽,返回全局唯一的该服务实例的UUId,即可实现实时无损下线
② 通配添加下线的服务实例到黑名单
运维平台下线服务实例之前,通过UUId前缀的日期或者时间(标识服务实例上线的时间戳)以通配符方式加入黑名单进行批量屏蔽,即可实现实时无损下线
例如:
- A服务有两个实例,实例1的UUId为
20220920-113301-033-4289-533-056
,实例2的UUId为20220920-113259-190-5762-550-884
,代表它们同一天2022年09月20日
上线 - 通过
20220920*
通配符的方式,表示屏蔽2022年09月20日
上线的A服务的所有实例,如果希望更精确,20220920-11*
,表示屏蔽2022年09月20日11点
上线的A服务的所有实例
③ 从黑名单清除所有过期的服务实例
运维平台下线服务实例一段时间之后(大于负载均衡3
个时钟周期,推荐5
分钟),从黑名单清除所有过期的服务实例
UUId全局唯一,同样的服务实例重启注册后,UUId会重新产生,不会重复。添加过多的UUId,虽然不会影响功能,但UUId堆积过多,使规则配置文本变得臃肿,可能会影响配置订阅的响应效率
④ 获取下线的服务实例的黑名单
运维平台调用配置接口
的获取规则配置对象
步骤,获取其中的黑名单规则策略
具体用法,请参考对接DevOps运维平台公共接口
- Github Wiki :如何使用DevOps运维平台对接的公共接口
- Gitee Wiki :如何使用DevOps运维平台对接的公共接口
异地多活,主要是为了提升系统的容灾能力,比如,单机房遭遇地震、火灾、网络故障、断电等不可抗因素,都有可能造成整个机房瘫痪
基于向外提供数据和服务实时性和连续性的要求,需要在不同城市建立独立的数据中心,并搭建配套的网关和服务集群,消息队列,数据库等,当某个城市的机房崩溃,则通过SLB等最高层的设施执行流量调拨,从一个城市切换到另一个城市,让外界感知服务永远处于有效状态
要进行多活建设,需要梳理企业内的服务
下文提到的,单元和区域,一般来说等同于机房概念
① 中心单元
- 部署在核心机房,机器性能,承载能力高
- 中心单元部署全局服务、核心服务和共享服务
- 中心单元是普通单元的特殊形式,限制一个
② 普通单元
- 部署在一般机房,机器性能,承载能力一般
- 中心单元部署核心服务和共享服务
- 普通单元可以水平扩容为N个
① 全局服务
- 具有数据强一致性和实时性高要求
- 多活单元化拆分存在很大的难度
- 数据在中心单元写,中心单元读
② 共享服务
- 全局服务的代理服务,读服务
- 共享服务和全局服务实现读写分离。全局服务的最终一致性数据由共享服务暴露,在各自单元被核心服务读
③ 核心服务
- 多活单元化分片,按地域划分,就近原则访问
- 数据在各自单元写,各自单元读
- 全局服务的的强一致性数据,由全局服务直接暴露,被核心服务读
① 部署方案
- 中心单元区域只有一个,部署在机器性能,网络性能较好的机房内
- 普通单元区域可以有很多个,进行对等镜像部署方式,部署在机器性能,网络性能一般的机房内
最古典的多活方案,不建议出现全局服务的单机房部署。受制于历史包袱或者企业现状,全局服务无法进行多活单元化拆分,或者对数据一致性和实时性要求很高,故而出现全局服务的单机房架构。所以,需要保持中心单元机房内全局服务集群的高可用性是非常必要的
② 注册中心方案
- 所有API网关、全局服务、核心服务和共享服务都注册到同一个物理空间下的注册中心
- 不同物理空间下的注册中心需要双向同步
③ 配置中心方案
- 一个单元区域配置一个配置中心,不同的单元区域的配置中心上是隔离的。每增/删/改一条配置数据,需要在不同单元区域的配置中心上重复操作一遍
- 一个单元区域配置一个配置中心,不同的配置中心跟注册中心一样双向同步。在遇到重复数据时候,同步的原则是时间更新的数据覆盖时间更老的数据
- 所有API网关、全局服务、核心服务和共享服务都订阅同一个物理空间下的配置中心
④ 数据库方案
- 中心单元区域拥有全局数据库,它具有强一致性,被全局服务写,被所有单元区域的共享服务读
- 每个单元区域都拥有有各自的分片数据库,它们之间双向同步,每个分片数据库被各自单元区域的核心服务读/写
⑤ 网关方案
- API网关属于单元区域的范畴,一个单元区域需要部署一个API网关的集群
- API网关具有跨区域路由的功能
⑥ 调用方案
- 不同单元区域之间服务调用是隔离的,两个单元区域的服务不能跨区域调用
- 普通单元区域的服务调用全局服务,通过路由(故障)转移方式访问中心单元区域
- 全局服务有回溯功能,例如,当调用链为“核心服务 -> 全局服务 -> 核心服务”,全局服务再调回核心服务的时候,仍旧选择发起调用的那个单元区域,即不会出现类似“中心单元核心服务 -> 中心单元全局服务 -> 普通单元核心服务”的情况,原则是从“哪里来回哪里去”
一般来说,回溯功能很少被用到,从多活架构上,全局服务是调用链最后一个环节,全局服务基本上不会出现在调用链头部和中部(不存在全局服务再去调用其它服务的情形)。本方案,为了考虑特殊性,支持回溯功能
⑦ 分流方案
- 前置的SLB或者下级Nginx根据请求IP进行二级域名分发
- API网关配置多活切换的路由配置,映射出区域,并赋值给Header
n-d-region
全链路传递,实现区域隔离路由
⑧ 切换方案
配置多活切换的路由配置
- 域名前缀映射区域策略
- 用户Id范围映射区域策略
① 多活服务(主要是核心服务和共享服务),执行如下操作
- 开启故障转移开关
# 启动和关闭区域故障转移。缺失则默认为false
spring.application.strategy.region.failover.enabled=true
- 标记元数据为多活属性,两种方式如下任选一个
spring.cloud.discovery.metadata.active=true
-Dmetadata.active=true
② 全局服务如果在调用链中部(例如,全局服务回溯调用核心服务),全局服务执行如下操作
- 开启故障转移开关
# 启动和关闭区域故障转移。缺失则默认为false
spring.application.strategy.region.failover.enabled=true
③ 全局服务如果在调用链头部(例如,API网关直接调用全局服务),API网关执行如下操作
- 开启故障转移开关
# 启动和关闭区域故障转移。缺失则默认为false
spring.application.strategy.region.failover.enabled=true
① 域名前缀映射区域策略
API网关过滤器实现域名前缀和区域映射逻辑
public class MyGatewayStrategyRouteFilter extends DefaultGatewayStrategyRouteFilter {
@Autowired
private GatewayStrategyContextHolder gatewayStrategyContextHolder;
@Value("${active.strategy.domain}")
private String activeStrategyDomain;
@Override
public String getRouteRegion() {
String host = gatewayStrategyContextHolder.getURI().getHost();
String region = host.substring(0, host.indexOf("."));
Map<String, String> map = JsonUtil.fromJson(activeStrategyDomain, Map.class);
return map.get("active.unit." + region);
}
}
通过配置中心添加如下Json格式的配置
active.strategy.domain={"active.unit.shanghai":"shanghai", "active.unit.hangzhou":"hangzhou"}
表示域名前缀为shanghai的请求路由到shanghai单元区域,域名前缀为hangzhou的请求路由到hangzhou单元区域。如果hangzhou单元区域遭遇故障,转移到shanghai,修改"active.unit.hangzhou":"shanghai"
,完成多活切换
② 用户Id范围映射区域策略
API网关过滤器实现用户ID范围和区域映射逻辑(伪代码)
public class MyGatewayStrategyRouteFilter extends DefaultGatewayStrategyRouteFilter {
@Autowired
private GatewayStrategyContextHolder gatewayStrategyContextHolder;
@Value("${active.strategy.userId}")
private String activeStrategyUserId;
@Override
public String getRouteRegion() {
String userId = strategyContextHolder.getHeader("userId");
Map<String, String> map = JsonUtil.fromJson(activeStrategyUserId, Map.class);
String region = 轮询map,搜索userId是否落在map的value配置用户Id范围区间里
return region;
}
}
通过配置中心添加如下Json格式的配置
active.strategy.userId={"active.unit.shanghai":"0~1999", "active.unit.hangzhou":"2000~9999"}
表示用户Id范围为0~1999
的请求路由到shanghai单元区域,用户Id范围为2000~9999
的请求路由到hangzhou单元区域。如果hangzhou单元区域遭遇故障,转移到shanghai,修改"active.unit.shanghai":"0~9999"
,并删除"active.unit.hangzhou":"2000~9999"
,完成多活切换
③ 自定义映射区域策略
使用者只需要继承实现DefaultGatewayStrategyRouteFilter
的public String getRouteRegion()
方法,并结合配置中心的配置,可扩展出更多映射区域的策略
例如,要对核心区的服务实施蓝绿灰度发布,假设核心区有A和B两个服务,分别有1.0和1.1两个版本,则可以通过如下规则策略实施
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-release>
<conditions type="blue-green">
<!-- 蓝路由,条件expression驱动 -->
<condition id="blue-condition" expression="#H['a'] == '1'" version-id="blue-route"/>
<!-- 绿路由,条件expression驱动 -->
<condition id="green-condition" expression="#H['a'] == '2'" version-id="green-route"/>
<!-- 兜底路由,无条件expression驱动 -->
<condition id="basic-condition" version-id="basic-route"/>
</conditions>
<routes>
<route id="blue-route" type="version">{"core-service-a":"1.1", "core-service-b":"1.1"}</route>
<route id="green-route" type="version">{"core-service-a":"1.0", "core-service-b":"1.0"}</route>
<route id="basic-route" type="version">{"core-service-a":"1.0", "core-b":"1.0"}</route>
</routes>
</strategy-release>
</rule>
一般来说,一个单元区域在执行蓝绿灰度发布的时候,另外一个单元区域不会同步执行,所以两个单元区域在某一个时刻,服务镜像是不对等的(例如,中心单元区域的核心服务里有核心区有A和B两个服务,分别有1.0和1.1两个版本,而普通单元区域里的核心服务,只有A和B服务的1.0版本,没有1.1版本)
基于上述情况,当实施单元区域切换的时候,需要清掉蓝绿灰度规则策略
元数据中的Group在一定意义上代表着系统ID或者系统逻辑分组,基于Group策略意味着只有同一个系统中的服务才能调用
基于Group是否相同的策略,即消费端拿到的提供端列表,两者的Group必须相同。只需要在网关或者服务端,开启如下配置即可
# 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.consumer.isolation.enabled=true
通过修改discovery-guide-service-b的Group名为其它名称,执行Postman调用,将发现从discovery-guide-service-a无法拿到discovery-guide-service-b的任何实例,意味着在discovery-guide-service-a消费端进行了隔离
元数据中的Group在一定意义上代表着系统ID或者系统逻辑分组,基于Group策略意味着只有同一个系统中的服务才能调用
基于Group是否相同的策略,即服务端被消费端调用,两者的Group必须相同,否则拒绝调用,异构系统可以通过Header方式传递n-d-group值进行匹配。只需要在服务端(不适用网关),开启如下配置即可
# 启动和关闭提供端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.provider.isolation.enabled=true
# 路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截和消费端的服务隔离两项工作
spring.application.strategy.scan.packages=com.nepxion.discovery.guide.service.feign
在Postman调用,执行http://localhost:4001/invoke/abc,去调用discovery-guide-service-b服务,将出现如下异常。意味着在discovery-guide-service-b提供端进行了隔离
Reject to invoke because of isolation with different group
如果加上n-d-group=discovery-guide-group的Header,那么两者保持Group相同,则调用通过。这是解决异构系统调用微服务被隔离的一种手段
版本偏好,即非蓝绿灰度发布场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施蓝绿灰度版本发布产生混乱,对处于非蓝绿灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;蓝绿灰度状态的服务,还是根据传递的Header版本号进行匹配
版本偏好有两种策略:
- 如果“version-prefer”值已配置,指定版本的偏好,即不管存在多少版本,直接路由到该版本实例
- 如果“version-prefer”值未配置,版本列表排序策略的(取最老的稳定版本的实例)偏好,即不管存在多少版本,直接路由到最老的稳定版本的实例
两种策略的区别:
- 指定版本策略,需要在配置文件里手工写死目标路由版本,适合版本无序的落地场景
- 版本列表排序策略,对版本号进行排序,此解决方案的前置条件是版本号必须是规律的有次序,例如,以时间戳的方式。如果所有服务实例的版本号未设置,那么将转移到未设置版本号的实例上。适合版本有序的落地场景,不需要人工干预
通过在配置中心修改版本偏好值,可以达到动态版本偏好的效果
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-failover>
<!-- 版本偏好,非蓝绿灰度发布场景下,路由到指定版本的实例 -->
<version-prefer>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version-prefer>
</strategy-failover>
</rule>
需要通过如下开关开启该功能
# 启动和关闭版本偏好。缺失则默认为false
spring.application.strategy.version.prefer.enabled=true
在区域调试路由执行的时候,当未对服务指定访问区域的时候,路由到事先指定的区域。该功能属于静态隔离和动态路由结合在一起的灵活方案,适用于开发环境(个人电脑环境)在测试环境(线上环境)进行联调,同时当多套个人环境接入时候,可以保护不同的个人环境间不会彼此调用
在下面的全链路调用路径中
A服务 -> B服务 -> C服务 -> D服务
其中,A服务和B服务在开发环境上,C服务和D服务在测试环境上,希望A服务调用B服务的时候,只会走本地电脑,不会去访问测试环境的B服务,也不会去访问其它本地电脑的B服务;B服务调用C服务的时候,只会去访问测试环境的C服务,C服务调用D服务的时候,也只是在测试环境的区域内
服务实例的元数据设置如下:
① A服务和B服务的区域(Region)元数据配置为MyDEV(本地电脑的名称或者可以区别其它电脑的特征值),如下
spring.cloud.discovery.metadata.region=MyDEV
② C服务和D服务的区域(Region)元数据配置为FAT(测试环境),如下
spring.cloud.discovery.metadata.region=FAT
只需要通过如下步骤:
① 打开启动和关闭区域调试转移
开关
# 启动和关闭区域调试转移。缺失则默认为false
spring.application.strategy.region.transfer.enabled=true
② 设置区域调试转移值
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-failover>
<!-- 区域调试转移,跨区调试路由到指定区域的实例 -->
<region-transfer>FAT</region-transfer>
</strategy-failover>
</rule>
通过在配置中心修改版本偏好值,可以达到动态区域调试路由的效果
③ 前端传入B服务的区域Header。由于A服务是调用起点,所以不需要配置A服务的值
n-d-region={"service-b":"MyDEV"}
扩展场景:
如果希望C服务访问的是开发环境上的D服务,那么变成
A服务(本地环境) -> B服务(本地环境) -> C服务(测试环境) -> D服务(本地环境)
前端传入区域Header改为
n-d-region={"service-b":"MyDEV", "service-d":"MyDEV"}
- 要调用测试环境中的服务,包括开发环境调用测试环境和测试环境中的服务间调用,必须打开
启动和关闭区域调试转移
开关和设置区域调试转移值
- 要调用开发环境中的服务,包括测试环境回调开发环境和开发环境中的服务间调用,必须加上
n-d-region
的Header进行动态路由
基于服务实例的元数据Metadata的env参数和全链路传递的环境Header值进行对比实现隔离,当从网关传递来的环境Header(n-d-env)值和提供端实例的元数据Metadata环境配置值相等才能调用
在网关或者服务端,配置环境元数据,在同一套环境下,env值必须是一样的,这样才能达到在同一个注册中心下,环境隔离的目的
spring.cloud.discovery.metadata.env=env1
通过环境Header(n-d-env)值的传递实现全链路环境隔离路由
基于调用端实例和提供端实例的元数据Metadata的zone配置值进行对比实现隔离
spring.cloud.discovery.metadata.zone=zone1
通过如下开关进行开启和关闭
# 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false
spring.application.strategy.zone.affinity.enabled=true
- 不归属任何可用区,含义是服务实例未设置任何zone元数据值。可用区亲和性路由功能,是为了尽量保证流量不损失
- 本框架提供的可用区亲和性功能适用于一切注册中心
- 如果采用Eureka注册中心,Ribbon在Eureka Client上会自动开启可用区亲和性功能,跟本框架提供的功能相似。它不提供禁止“可用区亲和性失败后的路由”,如果使用者希望实现“找不到相同可用区,直接调用失败”的功能,可以结合本框架上述两个开关来实现
基于服务实例的IP地址或者端口参数和全链路传递的环境Header值进行对比实现隔离,当从网关传递来的环境Header(n-d-address)值和提供端实例的IP地址或者端口值相等才能调用
该方案是一种细粒度隔离路由方案,需要注意,容器化下的服务实例在重启后IP地址变化的情况
微服务启动的时候,禁止指定的IP地址注册到注册中心。支持黑/白名单,白名单表示只允许指定IP地址前缀注册,黑名单表示不允许指定IP地址前缀注册
- 全局过滤,指注册到服务注册发现中心的所有微服务,只有IP地址包含在全局过滤字段的前缀中,都允许注册(对于白名单而言),或者不允许注册(对于黑名单而言)
- 局部过滤,指专门针对某个微服务而言,那么真正的过滤条件是全局过滤 + 局部过滤结合在一起
微服务启动的时候,一旦微服务集群下注册的实例数目已经达到上限(可配置),将禁止后续的微服务进行注册
- 全局配置值,只下面配置所有的微服务集群,最多能注册多少个
- 局部配置值,指专门针对某个微服务而言,如果该值如存在,全局配置值失效
微服务启动的时候,禁止指定的IP地址被服务发现。它使用的方式和基于IP地址黑白名单注册准入一致
- 集成AbstractRegisterListener,实现自定义禁止注册
- 集成AbstractDiscoveryListener,实现自定义禁止被发现。需要注意,在Consul下,同时会触发service和management两个实例的事件,需要区别判断
- 集成AbstractLoadBalanceListener,实现自定义禁止被负载均衡
故障转移,即在实施蓝绿灰度发布或者路由时候,消费端调用提供端,无法在提供端找到相应条件的服务实例,转移到指定的服务实例。支持版本、区域、环境、可用区、IP地址和端口五个维度的故障转移
五大维度的故障转移逻辑是可以并行叠加的,有两种实施方式:
- 通过在配置中心修改添加如下规则
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-failover>
<!-- 版本偏好,非蓝绿灰度发布场景下,路由到指定版本的实例 -->
<version-prefer>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version-prefer>
<!-- 版本故障转移,无法找到相应版本的服务实例,路由到指定版本的实例 -->
<version-failover>{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}</version-failover>
<!-- 区域调试转移,跨区调试路由到指定区域的实例 -->
<region-transfer>qa</region-transfer>
<!-- 区域故障转移,无法找到相应区域的服务实例,路由到指定区域的实例 -->
<region-failover>dev</region-failover>
<!-- 环境故障转移,无法找到相应环境的服务实例,路由到指定环境的实例 -->
<env-failover>common</env-failover>
<!-- 可用区故障转移,无法找到相应可用区的服务实例,路由到指定可用区的实例 -->
<zone-failover>zone1</zone-failover>
<!-- IP地址和端口故障转移,无法找到相应IP地址和端口的服务实例,路由到指定IP地址和端口的实例 -->
<address-failover>*1</address-failover>
</strategy-failover>
</rule>
- 通过如下Header传递
n-d-version-prefer={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
n-d-version-failover={"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}
n-d-region-transfer=qa
n-d-region-failover=dev
n-d-env-failover=common
n-d-zone-failover=zone1
n-d-address-failover=*1
动态改变上述值,可以达到动态故障转移的效果
上述规则配置跟蓝绿灰度发布的链路配置用法相似,以版本为例
对于配置项
<version-prefer>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version-prefer>
当所有服务都选同一版本的时候,下面两条是等效的
<version-prefer>1.0</version-prefer>
<version-prefer>{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}</version-prefer>
如果希望可调用的版本是多个,也可以表示成如下方式,即1.0版本和1.1版本的a服务和b服务都可以被调用到,下面两条是等效的
<version-prefer>1.0;1.1</version-prefer>
<version-prefer>{"discovery-guide-service-a":"1.0;1.1", "discovery-guide-service-b":"1.0;1.1"}</version-prefer>
如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法
* - 表示调用范围为所有版本
1.* - 表示调用范围为1开头的所有版本
例如
"discovery-guide-service-b":"1.*;1.2.?"
表示discovery-guide-service-b服务的调用范围是1开头的所有版本,或者调用范围是1.2开头的所有版本(末尾必须是1个字符),多个用分号隔开
五大维度故障转移的逻辑,有相同点和不同点。下面进行细化阐述
版本故障转移,即无法找到相应版本的服务实例,转移到指定版本的服务实例。其作用是防止蓝绿灰度版本发布人为设置错误,或者对应版本的服务实例发生灾难性的全部下线,导致流量有损
故障转移有三种策略:
- 如果“version-failover”值已配置,指定版本的故障转移,即找不到实例的时候,直接路由到该版本实例
- 如果“version-failover”值未配置
- 开启“version.failover.stable.enabled”开关,版本列表排序策略的(取最老的稳定版本的实例)故障转移,即找不到实例的时候,直接路由到最老的稳定版本的实例
- 关闭“version.failover.stable.enabled”开关,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
三种策略的区别:
- 负载均衡策略,找不到指定版本后,在其它的版本列表里进行负载均衡轮询调用
- 版本列表排序策略,对版本号进行排序,此解决方案的前置条件是版本号必须是规律的有次序,例如,以时间戳的方式。如果所有服务实例的版本号未设置,那么将转移到未设置版本号的实例上。适合版本有序的落地场景,不需要人工干预
- 指定版本策略,需要在配置文件里手工写死目标路由版本,适合版本无序的落地场景
通过在配置中心修改版本偏好值,可以达到动态版本故障转移的效果
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-failover>
<!-- 版本故障转移,无法找到相应版本的服务实例,路由到指定版本的实例 -->
<version-failover>{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}</version-failover>
</strategy-failover>
</rule>
需要通过如下开关开启该功能
# 启动和关闭版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.enabled=true
# 开启和关闭版本列表排序策略下取稳定版本的版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.stable.enabled=true
区域故障转移,即无法找到相应区域的服务实例,转移到指定区域的服务实例。其作用是防止路由时候区域人为设置错误,或者对应区域的服务实例发生灾难性的全部下线,导致流量有损
故障转移有两种策略:
- 如果“region-failover”值已配置,指定区域的故障转移,即找不到实例的时候,直接路由到该区域实例
- 如果“region-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
通过在配置中心修改区域故障转移值,可以达到动态区域故障转移的效果
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-failover>
<!-- 区域故障转移,无法找到相应区域的服务实例,路由到指定区域的实例 -->
<region-failover>dev</region-failover>
</strategy-failover>
</rule>
需要通过如下开关开启该功能
# 启动和关闭区域故障转移。缺失则默认为false
spring.application.strategy.region.failover.enabled=true
环境故障转移,即无法找到相应环境的服务实例,转移到指定环境的实例(未指定,默认路由到common环境)。其作用是防止路由时候环境人为设置错误,或者对应环境的服务实例发生灾难性的全部下线,导致流量有损
通过在配置中心修改环境故障转移值,可以达到动态环境故障转移的效果
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-failover>
<!-- 环境故障转移,无法找到相应环境的服务实例,路由到指定环境的实例 -->
<env-failover>common</env-failover>
</strategy-failover>
</rule>
需要通过如下开关开启该功能
# 启动和关闭环境故障转移。缺失则默认为false
# 如果“env-failover”值未配置,则默认为common
spring.application.strategy.environment.failover.enabled=true
可用区故障转移,即无法找到相应可用区的服务实例,转移到指定可用区的服务实例。其作用是防止路由时候可用区人为设置错误,或者对应可用区的服务实例发生灾难性的全部下线,导致流量有损
故障转移有两种策略:
- 如果“zone-failover”值已配置,指定可用区的故障转移,即找不到实例的时候,直接路由到该可用区实例
- 如果“zone-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
通过在配置中心修改可用区故障转移值,可以达到动态可用区故障转移的效果
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-failover>
<!-- 可用区故障转移,无法找到相应可用区的服务实例,路由到指定可用区的实例 -->
<zone-failover>zone1</zone-failover>
</strategy-failover>
</rule>
需要通过如下开关开启该功能
# 启动和关闭可用区故障转移。缺失则默认为false
spring.application.strategy.zone.failover.enabled=true
IP地址和端口转移,即无法找到相应IP地址和端口的服务实例,转移到指定IP地址和端口的服务实例。其作用是防止路由时候IP地址和端口人为设置错误,或者对应IP地址和端口的服务实例发生灾难性的全部下线,导致流量有损
故障转移有两种策略:
- 如果“address-failover”值已配置,指定IP地址或者端口的故障转移,即找不到实例的时候,直接路由到该IP地址或者端口实例
- 如果“address-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
通过在配置中心修改IP地址和端口故障转移值,可以达到动态IP地址和端口故障转移的效果
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-failover>
<!-- IP地址和端口故障转移,无法找到相应IP地址和端口的服务实例,路由到指定IP地址和端口的实例 -->
<address-failover>3*2</address-failover>
</strategy-failover>
</rule>
需要通过如下开关开启该功能
# 启动和关闭IP地址和端口故障转移。缺失则默认为false
spring.application.strategy.address.failover.enabled=true
服务下线场景下,由于Ribbon/Spring Cloud LoadBalancer负载均衡组件存在着缓存机制,当被提供端服务实例已经下线,而消费端服务实例还暂时缓存着它,直到下个心跳周期才会把已下线的服务实例剔除,在此期间,如果发生调用,会造成流量有损
框架提供流量的实时性绝对无损策略。采用下线之前,把服务实例添加到屏蔽名单中,负载均衡不会去寻址该服务实例。下线之后,清除该名单。实现该方式,需要通过DevOps调用配置中心的Open API推送或者在配置中心界面手工修改
全局唯一ID对应于元数据spring.application.uuid字段,框架会自动把该ID注册到注册中心,不需要用户自己配置,支持通配表达式方式
全局唯一ID的格式为
年月日(8位)-小时分钟秒(6位)-毫秒(3位)-随机数(4位)-随机数(3位)-随机数(3位)
前半部分精确到毫秒的设计,基本能保证ID的全局唯一,后半部分三重随机数,完全能保证ID的全局唯一。全局唯一失效的前提是,两个服务实例必须是毫秒级的同时启动,同时三次随机碰撞下来,得到完全三个相同的随机数后
增加Spring Cloud Gateway的全局唯一ID屏蔽策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用屏蔽指定全局唯一ID的服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-blacklist>
<id>20210601-222214-909-1146-372-698</id>
</strategy-blacklist>
</rule>
如果希望每个服务的全局唯一ID分别指定,那么策略内容如下,实现从Spring Cloud Gateway发起的调用屏蔽ID为20210601-222214-909-1146-372-698的a服务,屏蔽ID为20210601-222623-277-4978-633-279的b服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-blacklist>
<id>{"discovery-guide-service-a":"20210601-222214-909-1146-372-698", "discovery-guide-service-b":"20210601-222623-277-4978-633-279"}</id>
</strategy-blacklist>
</rule>
如果忽略服务名,也可以表示成如下方式,即ID为20210601-222214-909-1146-372-698和20210601-222623-277-4978-633-279的服务都被屏蔽
<id>20210601-222214-909-1146-372-698;20210601-222623-277-4978-633-279</id>
如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法
20210601* - 表示屏蔽范围是2021年06月01日注册的实例口
20210601-222214-909-1146-372-69? - 表示屏蔽范围是20210601-222214-909-1146-372-69开头ID的服务
例如
"discovery-guide-service-b":"20210601*;20210601-222214-909-1146-372-69?"
表示discovery-guide-service-b服务的屏蔽范围是2021年06月01日注册的实例,或者屏蔽范围是20210601-222214-909-1146-372-69开头ID的服务(末尾必须是1个字符),多个用分号隔开
跟Header驱动下的IP地址和端口屏蔽策略等效,例如
n-d-id-blacklist=20210601-222214-909-1146-372-698;20210601-222623-277-4978-633-279
n-d-id-blacklist={"discovery-guide-service-a":"20210601-222214-909-1146-372-698", "discovery-guide-service-b":"20210601-222623-277-4978-633-279"}
利用通配符方式实现对指定日期上线的服务实例做屏蔽,示例内容如下,表示2021年6月1日(也可以精确到小时或者分钟)上线的a服务实例和b服务实例都会被屏蔽。该场景的使用意义是,在服务下线之前,使用者担心流量有损,同时使用者知道上一次服务发布的日期,只要该屏蔽策略一生效,负载均衡将实时过滤掉指定日期的服务实例。那么,使用者对这些服务实例无论是优雅停机,还是暴力下线,都不会造成任何流量有损,例如
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-blacklist>
<id>{"discovery-guide-service-a":"20210601*", "discovery-guide-service-b":"20210601*"}</id>
</strategy-blacklist>
</rule>
通过IP地址或者端口或者IP地址+端口进行屏蔽,支持通配表达式方式
增加Zuul的IP地址和端口屏蔽策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用屏蔽指定IP地址和端口,或者指定IP地址,或者指定端口(下面策略以端口为例)的服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-blacklist>
<!-- <address>127.0.0.1:3001</address> -->
<!-- <address>127.0.0.1</address> -->
<address>3001</address>
</strategy-blacklist>
</rule>
如果希望每个服务的IP地址或者端口分别指定,那么策略内容如下,实现从Zuul发起的屏蔽屏蔽3001端口的a服务,屏蔽4001端口的b服务
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy-blacklist>
<address>{"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4001"}</address>
</strategy-blacklist>
</rule>
如果忽略服务名,也可以表示成如下方式,即3001和4001端口的服务都被屏蔽
<address>3001;4001</address>
当所有服务都选同一端口的时候,下面两条是等效的
<address>3001</address>
<address>{"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"}</address>
如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法
* - 表示屏蔽为所有端口
3* - 表示屏蔽范围为3开头的所有端口
例如
"discovery-guide-service-b":"3*;400?"
表示discovery-guide-service-b服务的屏蔽范围是3开头的所有端口,或者屏蔽范围是400开头的所有端口(末尾必须是1个字符),多个用分号隔开
跟Header驱动下的IP地址和端口屏蔽策略等效,例如
n-d-address-blacklist=3001
n-d-address-blacklist={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"}
Discovery框架存在着如下全链路传递上下文的场景,包括
- 策略路由Header全链路从网关传递到服务
- 调用链埋点全链路从网关传递到服务
- 业务自定义的上下文的传递
上述上下文会在如下异步场景中丢失,包括
- WebFlux Reactor响应式异步
- Spring异步,@Async注解异步
- Hystrix线程池隔离模式异步
- 线程,线程池异步
- SLF4J日志异步
通过DiscoveryAgent,解决上述痛点。Discovery框架利用DiscoveryAgent字节码增强技术,完美解决各种调用场景下的异步,包括
- Spring Cloud Gateway过滤器中的上下文传递
- Zuul过滤器中的上下文传递
- Feign拦截器中的上下文转发
- RestTemplate拦截器中的上下文转发
- WebClient拦截器中的上下文转发
DiscoveryAgent不仅适用于Discovery框架,也适用于一切具有类似使用场景的基础框架(例如:Dubbo)和业务系统
ThreadLocal的作用是提供线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立。在异步场景下,由于出现线程切换的问题,例如,主线程切换到子线程,会导致线程ThreadLocal上下文丢失。DiscoveryAgent通过Java Agent方式解决这些痛点
涵盖所有Java框架的异步场景,解决如下8个异步场景下丢失线程ThreadLocal上下文的问题
- WebFlux Reactor
@
Async- Hystrix Thread Pool Isolation
- Runnable
- Callable
- Single Thread
- Thread Pool
- SLF4J MDC
DiscoveryAgent不支持含有Lambda语法的异步代码。使用Lambda去实现的Runnable类会生成一个匿名内部类,这个匿名内部类和DiscoveryAgent使用的是不同的类加载器,导致DiscoveryAgent无法去修改Lambda表达式生成的Runnable的实现类
插件获取方式有两种方式
- 通过https://github.com/Nepxion/DiscoveryAgent/releases下载最新版本的Discovery Agent
- 编译https://github.com/Nepxion/DiscoveryAgent产生discovery-agent目录
① discovery-agent-starter-$
{discovery.version}.jar为Agent引导启动程序,JVM启动时进行加载
② agent.config为基准扫描目录配置文件
绝大多数情况下不需要修改,当然使用者也可以增加和删除agent.config的基准扫描目录。默认配置如下
# Base thread scan packages
agent.plugin.thread.scan.packages=reactor.core.publisher;org.springframework.aop.interceptor;com.netflix.hystrix
基准扫描目录,含义如下
- WebFlux Reactor异步场景下的扫描目录对应为reactor.core.publisher
@
Async场景下的扫描目录对应为org.springframework.aop.interceptor- Hystrix线程池隔离场景下的扫描目录对应为com.netflix.hystrix
③ plugin/discovery-agent-starter-plugin-strategy-$
{discovery.version}.jar插件,解决Nepxion Discovery上下文异步场景
④ plugin/discovery-agent-starter-plugin-mdc-$
{discovery.version}.jar插件,解决SLF4J MDC日志上下文异步场景
⑤ 业务系统可以自定义plugin,解决业务自己定义的上下文异步场景
① 使用示例
- 通过如下-javaagent启动,基本格式,如下
-javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.nepxion.discovery.guide.service.feign
② 参数说明
- C:/opt/discovery-agent:Agent所在的目录,需要对应到实际的目录上
-D
thread.scan.packages:Runnable/Callable/Thread/ThreadPool等异步类所在的扫描目录,该目录下的异步类都会被装饰- 扫描目录最好精细和准确,目录越详细,越可以减少被装饰的对象数,从一定程度上可以提高性能
- 扫描目录如果有多个,用“;”分隔
- 扫描目录如果含有“;”,可能会在某些操作系统中无法被识别,请用
""
进行引入,例如,-Dthread.scan.packages="com.abc;com.xyz" - 扫描目录下没有Runnable/Callable/Thread/ThreadPool等异步类存在,那么thread.scan.packages也不需要配置,最终启动命令行简化为-javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar
-D
thread.gateway.enabled:Spring Cloud Gateway端策略Header输出到异步子线程。默认开启-D
thread.zuul.enabled:Zuul端策略Header输出到异步子线程。默认开启-D
thread.service.enabled:服务端策略Header输出到异步子线程。默认开启-D
thread.mdc.enabled:SLF4J MDC日志输出到异步子线程。默认开启-D
thread.request.decorator.enabled:异步调用场景下在服务端的Request请求的装饰,当主线程先于子线程执行完的时候,Request会被Destory,导致Header仍旧拿不到,开启装饰,就可以确保拿到。默认为开启,根据实践经验,大多数场景下,需要开启该开关
- 根据规范开发一个插件,插件提供了钩子函数,在某个类被加载的时候,可以注册一个事件到线程上下文切换事件当中,实现业务自定义ThreadLocal的跨线程传递
- plugin目录为放置需要在线程切换时进行ThreadLocal传递的自定义插件。业务自定义插件开发完后,放入到plugin目录下即可
具体步骤介绍,如下
① SDK侧工作
- 新建ThreadLocal上下文类
public class MyContext {
private static final ThreadLocal<MyContext> THREAD_LOCAL = new ThreadLocal<MyContext>() {
@Override
protected MyContext initialValue() {
return new MyContext();
}
};
public static MyContext getCurrentContext() {
return THREAD_LOCAL.get();
}
public static void clearCurrentContext() {
THREAD_LOCAL.remove();
}
private Map<String, String> attributes = new HashMap<>();
public Map<String, String> getAttributes() {
return attributes;
}
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
}
② Agent侧工作
- 新建一个模块,引入如下依赖
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-agent-starter</artifactId>
<version>${discovery.agent.version}</version>
<scope>provided</scope>
</dependency>
- 新建一个ThreadLocalHook类继承AbstractThreadLocalHook
public class MyContextHook extends AbstractThreadLocalHook {
@Override
public Object create() {
// 从主线程的ThreadLocal里获取并返回上下文对象
return MyContext.getCurrentContext().getAttributes();
}
@Override
public void before(Object object) {
// 把create方法里获取到的上下文对象放置到子线程的ThreadLocal里
if (object instanceof Map) {
MyContext.getCurrentContext().setAttributes((Map<String, String>) object);
}
}
@Override
public void after() {
// 线程结束,销毁上下文对象
MyContext.clearCurrentContext();
}
}
- 新建一个Plugin类继承AbstractPlugin
public class MyContextPlugin extends AbstractPlugin {
private Boolean threadMyPluginEnabled = Boolean.valueOf(System.getProperty("thread.myplugin.enabled", "false"));
@Override
protected String getMatcherClassName() {
// 返回存储ThreadLocal对象的类名,由于插件是可以插拔的,所以必须是字符串形式,不允许是显式引入类
return "com.nepxion.discovery.example.sdk.MyContext";
}
@Override
protected String getHookClassName() {
// 返回ThreadLocalHook类名
return MyContextHook.class.getName();
}
@Override
protected boolean isEnabled() {
// 通过外部-Dthread.myplugin.enabled=true/false的运行参数来控制当前Plugin是否生效。该方法在父类中定义的返回值为true,即缺省为生效
return threadMyPluginEnabled;
}
}
- 定义SPI扩展,在src/main/resources/META-INF/services目录下定义SPI文件
名称为固定如下格式
com.nepxion.discovery.agent.plugin.Plugin
内容为Plugin类的全路径
com.nepxion.discovery.example.agent.MyContextPlugin
-
执行Maven编译,把编译后的包放在discovery-agent/plugin目录下
-
给服务增加启动参数并启动,如下
-javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.nepxion.discovery.example.application -Dthread.myplugin.enabled=true
③ Application侧工作
- 执行MyApplication,它模拟在主线程ThreadLocal放入Map数据,子线程通过DiscoveryAgent获取到该Map数据,并打印出来
@SpringBootApplication
@RestController
public class MyApplication {
private static final Logger LOG = LoggerFactory.getLogger(MyApplication.class);
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
invoke();
}
public static void invoke() {
RestTemplate restTemplate = new RestTemplate();
for (int i = 1; i <= 10; i++) {
restTemplate.getForEntity("http://localhost:8080/index/" + i, String.class).getBody();
}
}
@GetMapping("/index/{value}")
public String index(@PathVariable(value = "value") String value) throws InterruptedException {
Map<String, String> attributes = new HashMap<String, String>();
attributes.put(value, "MyContext");
MyContext.getCurrentContext().setAttributes(attributes);
LOG.info("【主】线程ThreadLocal:{}", MyContext.getCurrentContext().getAttributes());
new Thread(new Runnable() {
@Override
public void run() {
LOG.info("【子】线程ThreadLocal:{}", MyContext.getCurrentContext().getAttributes());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOG.info("Sleep 5秒之后,【子】线程ThreadLocal:{} ", MyContext.getCurrentContext().getAttributes());
}
}).start();
return "";
}
}
输出结果,如下
2020-11-09 00:08:14.330 INFO 16588 --- [nio-8080-exec-1] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{1=MyContext}
2020-11-09 00:08:14.381 INFO 16588 --- [ Thread-4] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{1=MyContext}
2020-11-09 00:08:14.402 INFO 16588 --- [nio-8080-exec-2] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{2=MyContext}
2020-11-09 00:08:14.403 INFO 16588 --- [ Thread-5] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{2=MyContext}
2020-11-09 00:08:14.405 INFO 16588 --- [nio-8080-exec-3] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{3=MyContext}
2020-11-09 00:08:14.406 INFO 16588 --- [ Thread-6] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{3=MyContext}
2020-11-09 00:08:14.414 INFO 16588 --- [nio-8080-exec-4] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{4=MyContext}
2020-11-09 00:08:14.414 INFO 16588 --- [ Thread-7] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{4=MyContext}
2020-11-09 00:08:14.417 INFO 16588 --- [nio-8080-exec-5] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{5=MyContext}
2020-11-09 00:08:14.418 INFO 16588 --- [ Thread-8] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{5=MyContext}
2020-11-09 00:08:14.421 INFO 16588 --- [nio-8080-exec-6] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{6=MyContext}
2020-11-09 00:08:14.422 INFO 16588 --- [ Thread-9] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{6=MyContext}
2020-11-09 00:08:14.424 INFO 16588 --- [nio-8080-exec-7] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{7=MyContext}
2020-11-09 00:08:14.425 INFO 16588 --- [ Thread-10] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{7=MyContext}
2020-11-09 00:08:14.427 INFO 16588 --- [nio-8080-exec-8] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{8=MyContext}
2020-11-09 00:08:14.428 INFO 16588 --- [ Thread-11] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{8=MyContext}
2020-11-09 00:08:14.430 INFO 16588 --- [nio-8080-exec-9] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{9=MyContext}
2020-11-09 00:08:14.431 INFO 16588 --- [ Thread-12] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{9=MyContext}
2020-11-09 00:08:14.433 INFO 16588 --- [io-8080-exec-10] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{10=MyContext}
2020-11-09 00:08:14.434 INFO 16588 --- [ Thread-13] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{10=MyContext}
2020-11-09 00:08:19.382 INFO 16588 --- [ Thread-4] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{1=MyContext}
2020-11-09 00:08:19.404 INFO 16588 --- [ Thread-5] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{2=MyContext}
2020-11-09 00:08:19.406 INFO 16588 --- [ Thread-6] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{3=MyContext}
2020-11-09 00:08:19.416 INFO 16588 --- [ Thread-7] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{4=MyContext}
2020-11-09 00:08:19.418 INFO 16588 --- [ Thread-8] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{5=MyContext}
2020-11-09 00:08:19.422 INFO 16588 --- [ Thread-9] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{6=MyContext}
2020-11-09 00:08:19.425 INFO 16588 --- [ Thread-10] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{7=MyContext}
2020-11-09 00:08:19.428 INFO 16588 --- [ Thread-11] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{8=MyContext}
2020-11-09 00:08:19.432 INFO 16588 --- [ Thread-12] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{9=MyContext}
2020-11-09 00:08:19.434 INFO 16588 --- [ Thread-13] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{10=MyContext}
如果不加异步Agent,则输出结果,如下,可以发现在子线程中ThreadLocal上下文全部都丢失
2020-11-09 00:01:40.133 INFO 16692 --- [nio-8080-exec-1] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{1=MyContext}
2020-11-09 00:01:40.135 INFO 16692 --- [ Thread-8] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.158 INFO 16692 --- [nio-8080-exec-2] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{2=MyContext}
2020-11-09 00:01:40.159 INFO 16692 --- [ Thread-9] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.162 INFO 16692 --- [nio-8080-exec-3] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{3=MyContext}
2020-11-09 00:01:40.163 INFO 16692 --- [ Thread-10] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.170 INFO 16692 --- [nio-8080-exec-5] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{4=MyContext}
2020-11-09 00:01:40.170 INFO 16692 --- [ Thread-11] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.173 INFO 16692 --- [nio-8080-exec-4] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{5=MyContext}
2020-11-09 00:01:40.174 INFO 16692 --- [ Thread-12] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.176 INFO 16692 --- [nio-8080-exec-6] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{6=MyContext}
2020-11-09 00:01:40.177 INFO 16692 --- [ Thread-13] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.179 INFO 16692 --- [nio-8080-exec-8] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{7=MyContext}
2020-11-09 00:01:40.180 INFO 16692 --- [ Thread-14] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.182 INFO 16692 --- [nio-8080-exec-7] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{8=MyContext}
2020-11-09 00:01:40.182 INFO 16692 --- [ Thread-15] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.185 INFO 16692 --- [nio-8080-exec-9] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{9=MyContext}
2020-11-09 00:01:40.186 INFO 16692 --- [ Thread-16] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:40.188 INFO 16692 --- [io-8080-exec-10] c.n.d.example.application.MyApplication : 【主】线程ThreadLocal:{10=MyContext}
2020-11-09 00:01:40.189 INFO 16692 --- [ Thread-17] c.n.d.example.application.MyApplication : 【子】线程ThreadLocal:{}
2020-11-09 00:01:45.136 INFO 16692 --- [ Thread-8] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.160 INFO 16692 --- [ Thread-9] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.163 INFO 16692 --- [ Thread-10] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.171 INFO 16692 --- [ Thread-11] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.174 INFO 16692 --- [ Thread-12] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.177 INFO 16692 --- [ Thread-13] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.181 INFO 16692 --- [ Thread-14] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.183 INFO 16692 --- [ Thread-15] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.187 INFO 16692 --- [ Thread-16] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
2020-11-09 00:01:45.190 INFO 16692 --- [ Thread-17] c.n.d.example.application.MyApplication : Sleep 5秒之后,【子】线程ThreadLocal:{}
完整示例,请参考https://github.com/Nepxion/DiscoveryAgent/tree/master/discovery-agent-example。上述自定义插件的方式,即可解决使用者在线程切换时丢失ThreadLocal上下文的问题
全链路策略路由Header和调用链Span在Hystrix线程池隔离模式(信号量模式不需要引入)下传递时,通过线程上下文切换会存在丢失Header的问题,通过下述步骤解决,同时适用于网关端和服务端
① Pom引入
<!-- 当服务用Hystrix做线程隔离的时候,才需要导入下面的包 -->
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-hystrix</artifactId>
<version>${discovery.version}</version>
</dependency>
② 配置开启
# 开启和关闭Hystrix线程隔离模式做服务隔离时,对线程切换上下文传递的功能。缺失则默认为false
# Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失
spring.application.strategy.hystrix.threadlocal.supported=true
该方案也可以通过异步场景下DiscoveryAgent解决方案解决
网关动态路由功能,主要包括
- 路由动态添加
- 路由动态修改
- 路由动态删除
- 路由动态批量更新
- 路由查询
- 路由动态变更后,通过事件总线方式发出事件通知
上述操作,可以通过
- 网关暴露Rest Endpoint接口实施
- 控制台暴露Rest Endpoint接口,对同一个网关下若干个实例批量实施
- 网关订阅配置中心(包括Nacos、Apollo、Consul、Etcd、Redis、Zookeeper)批量实施
Spring Cloud Gateway网关在自动路由模式下,动态路由不能工作
支持Spring Cloud Gateway网关官方断言器和过滤器,也支持用户自定义断言器和过滤器
① 精简配置
[
{
"id": "route0",
"uri": "lb://discovery-guide-service-a",
"predicates": [
"Path=/discovery-guide-service-a/**,/x/**,/y/**"
],
"filters": [
"StripPrefix=1"
]
}
]
② 完整配置
[
{
"id": "route0",
"uri": "lb://discovery-guide-service-a",
"predicates": [
"Path=/discovery-guide-service-a/**,/x/**,/y/**"
],
"filters": [
"StripPrefix=1"
],
"order": 0,
"metadata": {}
}
]
① 自定义方式描述网关内置断言器和过滤器
自定义方式描述网关内置断言器和过滤器的Key必须遵循如下规则
- 对于没有显式args定义的配置,类似Path、StripPrefix这种配置,args名称必须是
_genkey_序号
格式。例如,"_genkey_0": "/discovery-guide-service-a/**" - 对于显式args定义的配置,类似Header、Cookie、Query这种配置,args名称遵照Spring Cloud Gateway内置格式,请查看相关文档或者源码。例如,Header的KV格式为header -> regexp,Cookie的KV格式为name->regexp,Query的KV格式为param->regexp
[
{
"id": "route0",
"uri": "lb://discovery-guide-service-a",
"userPredicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/discovery-guide-service-a/**",
"_genkey_1": "/x/**",
"_genkey_2": "/y/**"
}
},
{
"name": "Header",
"args": {
"header": "a",
"regexp": "1"
}
},
{
"name": "Header",
"args": {
"header": "b",
"regexp": "2"
}
},
{
"name": "Cookie",
"args": {
"name": "c",
"regexp": "3"
}
},
{
"name": "Cookie",
"args": {
"name": "d",
"regexp": "4"
}
},
{
"name": "Query",
"args": {
"param": "e",
"regexp": "5"
}
},
{
"name": "Query",
"args": {
"param": "f",
"regexp": "6"
}
}
],
"userFilters": [
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
]
}
]
在DiscoveryPlatform界面上,格式为
Path={"_genkey_0":"/discovery-guide-service-a/**", "_genkey_1":"/x/**", "_genkey_2":"/y/**"}
StripPrefix={"_genkey_0":"1"}
Header={"header":"a","regexp":"1"}
Header={"header":"b","regexp":"2"}
Cookie={"name":"c","regexp":"3"}
Cookie={"name":"d","regexp":"4"}
Query={"param":"e","regexp":"5"}
Query={"param":"f","regexp":"6"}
② 自定义方式描述用户扩展的断言器和过滤器
自定义方式描述用户扩展的断言器和过滤器的Key必须遵循如下规则
- List结构,args名称必须是
list的变量名.序号
格式。例如,"whiteList.0": "* swagger-ui.html" - Map<String, String>结构,args名称必须是
map的变量名.map的key
格式。例如,"userMap.name": "jason"
[
{
"id": "route0",
"uri": "lb://discovery-guide-service-a",
"predicates": [
"Path=/discovery-guide-service-a/**,/x/**,/y/**"
],
"filters": [
"StripPrefix=1"
],
"userPredicates": [],
"userFilters": [
{
"name": "Authentication",
"args": {
"secretKey": "abc",
"whiteList.0": "* swagger-ui.html",
"whiteList.1": "* /swagger-resources/**",
"whiteList.2": "* /doc.html",
"userMap.name": "jason",
"userMap.age": "20",
"authInfoCarryStrategy": "AuthWriteToHeader"
}
}
]
}
]
在DiscoveryPlatform界面上,格式为
Authentication={"secretKey":"abc", "whiteList.0":"* swagger-ui.html", "whiteList.1":"* /swagger-resources/**", "whiteList.2":"* /doc.html", "userMap.name":"jason", "userMap.age":"20", "authInfoCarryStrategy":"AuthWriteToHeader"}
网关订阅配置中心的使用方式,如下
- Key为
- Nacos、Redis、Zookeeper配置中心,Group为{group},DataId为{网关serviceId}-dynamic-route
- Apollo、Consul、Etcd配置中心,Key的格式为{group}-{网关serviceId}-dynamic-route
- {group}为注册中心元数据group值
- Value参考Gateway网关动态路由配置
支持如下开关开启该动能,默认是关闭的
# 开启和关闭网关订阅配置中心的动态路由策略。缺失则默认为false
spring.application.strategy.gateway.dynamic.route.enabled=true
配置中心配置的网关动态路由推送到网关后,网关会自动根据已经存在的路由表进行判断后实施增删改操作,而不是全部清空后再全部插入,这样有助于提高性能和安全性。网关控制台上会打印出如下日志
--- Gateway Dynamic Routes Update Information ----
Total count=3
Added count=1
Modified count=1
Deleted count=1
--------------------------------------------------
@EventBus
public class MySubscriber {
@Subscribe
public void onGatewayStrategyRouteAdded(GatewayStrategyRouteAddedEvent gatewayStrategyRouteAddedEvent) {
System.out.println("增加网关路由=" + gatewayStrategyRouteAddedEvent.getGatewayStrategyRouteEntity());
}
@Subscribe
public void onGatewayStrategyRouteModified(GatewayStrategyRouteModifiedEvent gatewayStrategyRouteModifiedEvent) {
System.out.println("修改网关路由=" + gatewayStrategyRouteModifiedEvent.getGatewayStrategyRouteEntity());
}
@Subscribe
public void onGatewayStrategyRouteDeleted(GatewayStrategyRouteDeletedEvent gatewayStrategyRouteDeletedEvent) {
System.out.println("删除网关路由=" + gatewayStrategyRouteDeletedEvent.getRouteId());
}
@Subscribe
public void onGatewayStrategyRouteUpdatedAll(GatewayStrategyRouteUpdatedAllEvent gatewayStrategyRouteUpdatedAllEvent) {
System.out.println("更新全部网关路由=" + gatewayStrategyRouteUpdatedAllEvent.getGatewayStrategyRouteEntityList());
}
}
Zuul网关在自动路由模式下,动态路由可以工作
① 精简配置
[
{
"id": "route0",
"serviceId": "discovery-guide-service-a",
"path": "/discovery-guide-service-a/**"
},
{
"id": "route1",
"serviceId": "discovery-guide-service-a",
"path": "/x/**"
},
{
"id": "route2",
"serviceId": "discovery-guide-service-a",
"path": "/y/**"
}
]
如果希望一个服务只映射一个动态路由路径,则不需要id,可以简化为
[
{
"serviceId": "discovery-guide-service-a",
"path": "/x/**"
}
]
② 完整配置
[
{
"id": "route0",
"serviceId": "discovery-guide-service-a",
"path": "/discovery-guide-service-a/**",
"url": null,
"stripPrefix": true,
"retryable": null,
"sensitiveHeaders": [],
"customSensitiveHeaders": false
},
{
"id": "route1",
"serviceId": "discovery-guide-service-a",
"path": "/x/**",
"url": null,
"stripPrefix": true,
"retryable": null,
"sensitiveHeaders": [],
"customSensitiveHeaders": false
},
{
"id": "route2",
"serviceId": "discovery-guide-service-a",
"path": "/y/**",
"url": null,
"stripPrefix": true,
"retryable": null,
"sensitiveHeaders": [],
"customSensitiveHeaders": false
}
]
网关订阅配置中心的使用方式,如下
- Key为
- Nacos、Redis、Zookeeper配置中心,Group为{group},DataId为{网关serviceId}-dynamic-route
- Apollo、Consul、Etcd配置中心,Key的格式为{group}-{网关serviceId}-dynamic-route
- {group}为注册中心元数据group值
- Value参考Zuul网关动态路由配置
支持如下开关开启该动能,默认是关闭的
# 开启和关闭网关订阅配置中心的动态路由策略。缺失则默认为false
spring.application.strategy.zuul.dynamic.route.enabled=true
配置中心配置的网关动态路由推送到网关后,网关会自动根据已经存在的路由表进行判断后实施增删改操作,而不是全部清空后再全部插入,这样有助于提高性能和安全性。网关控制台上会打印出如下日志
----- Zuul Dynamic Routes Update Information -----
Total count=3
Added count=1
Modified count=1
Deleted count=1
--------------------------------------------------
@EventBus
public class MySubscriber {
@Subscribe
public void onZuulStrategyRouteAdded(ZuulStrategyRouteAddedEvent zuulStrategyRouteAddedEvent) {
System.out.println("增加网关路由=" + zuulStrategyRouteAddedEvent.getZuulStrategyRouteEntity());
}
@Subscribe
public void onZuulStrategyRouteModified(ZuulStrategyRouteModifiedEvent zuulStrategyRouteModifiedEvent) {
System.out.println("修改网关路由=" + zuulStrategyRouteModifiedEvent.getZuulStrategyRouteEntity());
}
@Subscribe
public void onZuulStrategyRouteDeleted(ZuulStrategyRouteDeletedEvent zuulStrategyRouteDeletedEvent) {
System.out.println("删除网关路由=" + zuulStrategyRouteDeletedEvent.getRouteId());
}
@Subscribe
public void onZuulStrategyRouteUpdatedAll(ZuulStrategyRouteUpdatedAllEvent zuulStrategyRouteUpdatedAllEvent) {
System.out.println("更新全部网关路由=" + zuulStrategyRouteUpdatedAllEvent.getZuulStrategyRouteEntityList());
}
}
集成Sentinel熔断隔离限流降级平台
通过集成Sentinel,在服务端实现该功能
由于本功能早于Spring Cloud Alibaba Sentinel而产生,下述功能也可以通过Spring Cloud Alibaba Sentinel功能来实现
Sentinel订阅配置中心的使用方式,如下
- Key为
- Nacos、Redis、Zookeeper配置中心,Group为{group},DataId为{serviceId}-{规则类型}
- Apollo、Consul、Etcd配置中心,Key的格式为{group}-{serviceId}-{规则类型}
- {group}为注册中心元数据group值
- Value为Json格式的规则
支持远程配置中心和本地规则文件的读取逻辑,即优先读取远程配置,如果不存在或者规则错误,则读取本地规则文件。动态实现远程配置中心对于规则的热刷新
支持如下开关开启该动能,默认是关闭的
# 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展。缺失则默认为false
spring.application.strategy.sentinel.datasource.enabled=true
参照下面代码,为接口方法增加@SentinelResource注解,value为sentinel-resource,blockHandler和fallback是防护其作用后需要执行的方法
@RestController
@ConditionalOnProperty(name = DiscoveryConstant.SPRING_APPLICATION_NAME, havingValue = "discovery-guide-service-b")
public class BFeignImpl extends AbstractFeignImpl implements BFeign {
private static final Logger LOG = LoggerFactory.getLogger(BFeignImpl.class);
@Override
@SentinelResource(value = "sentinel-resource", blockHandler = "handleBlock", fallback = "handleFallback")
public String invoke(@PathVariable(value = "value") String value) {
value = doInvoke(value);
LOG.info("调用路径:{}", value);
return value;
}
public String handleBlock(String value, BlockException e) {
return value + "-> B server sentinel block, cause=" + e.getClass().getName() + ", rule=" + e.getRule() + ", limitApp=" + e.getRuleLimitApp();
}
public String handleFallback(String value) {
return value + "-> B server sentinel fallback";
}
}
原生Sentinel规则的用法,请参照Sentinel官方文档
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-flow,规则内容如下
[
{
"resource": "sentinel-resource",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"refResource": null,
"controlBehavior": 0,
"warmUpPeriodSec": 10,
"maxQueueingTimeMs": 500,
"clusterMode": false,
"clusterConfig": null
}
]
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-degrade,规则内容如下
[
{
"resource": "sentinel-resource",
"limitApp": "default",
"count": 2,
"timeWindow": 10,
"grade": 0,
"passCount": 0
}
]
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下
[
{
"resource": "sentinel-resource",
"limitApp": "discovery-guide-service-a",
"strategy": 0
}
]
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-system,规则内容如下
[
{
"resource": null,
"limitApp": null,
"highestSystemLoad": -1.0,
"highestCpuUsage": -1.0,
"qps": 200.0,
"avgRt": -1,
"maxThread": -1
}
]
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-param-flow,规则内容如下
[
{
"resource": "sentinel-resource",
"limitApp": "default",
"grade": 1,
"paramIdx": 0,
"count": 1,
"controlBehavior": 0,
"maxQueueingTimeMs": 0,
"burstCount": 0,
"durationInSec": 1,
"paramFlowItemList": [],
"clusterMode": false
}
]
该功能对于上面5种规则都有效,这里以授权规则展开阐述
授权规则中,limitApp,如果有多个,可以通过“,”分隔。"strategy": 0 表示白名单,"strategy": 1 表示黑名单
支持如下开关开启该动能,默认是关闭的
# 启动和关闭Sentinel LimitApp限流等功能。缺失则默认为false
spring.application.strategy.sentinel.limit.app.enabled=true
修改配置项Sentinel Request Origin Key为服务名Header,修改授权规则中limitApp为对应的服务名,可实现基于服务名的防护
配置项,该配置项默认为n-d-service-id,可以不配置
spring.application.strategy.sentinel.request.origin.key=n-d-service-id
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示所有discovery-guide-service-a服务允许访问discovery-guide-service-b服务
[
{
"resource": "sentinel-resource",
"limitApp": "discovery-guide-service-a",
"strategy": 0
}
]
修改配置项Sentinel Request Origin Key为组Header,修改授权规则中limitApp为对应的组名,可实现基于组的防护
配置项
spring.application.strategy.sentinel.request.origin.key=n-d-service-group
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示隶属nepxion组的所有服务都允许访问服务discovery-guide-service-b
[
{
"resource": "sentinel-resource",
"limitApp": "nepxion",
"strategy": 0
}
]
修改配置项Sentinel Request Origin Key为版本Header,修改授权规则中limitApp为对应的版本,可实现基于版本的防护机制
配置项
spring.application.strategy.sentinel.request.origin.key=n-d-service-version
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示版本为1.0的所有服务都允许访问服务discovery-guide-service-b
[
{
"resource": "sentinel-resource",
"limitApp": "1.0",
"strategy": 0
}
]
修改配置项Sentinel Request Origin Key为区域Header,修改授权规则中limitApp为对应的区域,可实现基于区域的防护
配置项
spring.application.strategy.sentinel.request.origin.key=n-d-service-region
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示区域为dev的所有服务都允许访问服务discovery-guide-service-b
[
{
"resource": "sentinel-resource",
"limitApp": "dev",
"strategy": 0
}
]
修改配置项Sentinel Request Origin Key为环境Header,修改授权规则中limitApp为对应的环境,可实现基于环境的防护
配置项
spring.application.strategy.sentinel.request.origin.key=n-d-service-env
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示环境为env1的所有服务都允许访问服务discovery-guide-service-b
[
{
"resource": "sentinel-resource",
"limitApp": "env1",
"strategy": 0
}
]
修改配置项Sentinel Request Origin Key为可用区Header,修改授权规则中limitApp为对应的可用区,可实现基于可用区的防护
配置项
spring.application.strategy.sentinel.request.origin.key=n-d-service-zone
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示可用区为zone1的所有服务都允许访问服务discovery-guide-service-b
[
{
"resource": "sentinel-resource",
"limitApp": "zone1",
"strategy": 0
}
]
修改配置项Sentinel Request Origin Key为IP地址和端口Header,修改授权规则中limitApp为对应的区域值,可实现基于IP地址和端口的防护
配置项
spring.application.strategy.sentinel.request.origin.key=n-d-service-address
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示地址和端口为192.168.0.88:8081和192.168.0.88:8082的服务都允许访问服务discovery-guide-service-b
[
{
"resource": "sentinel-resource",
"limitApp": "192.168.0.88:8081,192.168.0.88:8082",
"strategy": 0
}
]
通过适配类实现自定义组合式的防护,支持自定义Header、Parameter、Cookie参数的防护,自定义业务参数的防护,以及自定义前两者组合式的防护
// 自定义版本号+地域名,实现组合式熔断
public class MySentinelStrategyRequestOriginAdapter extends DefaultSentinelStrategyRequestOriginAdapter {
@Override
public String parseOrigin(HttpServletRequest request) {
String version = request.getHeader(DiscoveryConstant.N_D_SERVICE_VERSION);
String location = request.getHeader("location");
return version + "&" + location;
}
}
在配置类里@Bean方式进行适配类创建
@Bean
public SentinelStrategyRequestOriginAdapter sentinelStrategyRequestOriginAdapter() {
return new MySentinelStrategyRequestOriginAdapter();
}
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示版本为1.0且传入Header的location=shanghai,同时满足这两个条件下的所有服务都允许访问服务discovery-guide-service-b
[
{
"resource": "sentinel-resource",
"limitApp": "1.0&shanghai",
"strategy": 0
}
]
运行效果
- 当传递的Header中location=shanghai,当全链路调用中,API网关负载均衡discovery-guide-service-a服务到1.0版本后再去调用discovery-guide-service-b服务,最终调用成功
- 当传递的Header中location=beijing,不满足条件,最终调用在discovery-guide-service-b服务端被拒绝掉
- 当传递的Header中location=shanghai,满足条件之一,当全链路调用中,API网关负载均衡discovery-guide-service-a服务到1.1版本后再去调用discovery-guide-service-b服务,不满足version=1.0的条件,最终调用在discovery-guide-service-b服务端被拒绝掉
① 内置蓝绿灰度埋点
内置蓝绿灰度埋点,包括如下
n-d-service-group - 服务所属组或者应用
n-d-service-type - 服务类型,分为网关端 | 服务端 | 控制台端 | 测试端,使用者只需要关注前两个即可
n-d-service-app-id - 应用ID,当接入Apollo配置中心才有该属性
n-d-service-id - 服务ID
n-d-service-address - 服务地址,包括Host和Port
n-d-service-version - 服务版本
n-d-service-region - 服务所属区域
n-d-service-env - 服务所属环境
n-d-service-zone - 服务所属可用区
n-d-version - 版本路由值
n-d-region - 区域路由值
n-d-env - 环境路由值
n-d-address - 地址路由值
n-d-version-weight - 版本权重路由值
n-d-region-weight - 区域权重路由值
n-d-version-prefer - 版本偏好路由值
n-d-version-failover - 版本故障转移路由值
n-d-region-transfer - 区域调试路由值
n-d-region-failover - 区域故障转移路由值
n-d-env-failover - 环境故障转移路由值
n-d-zone-failover - 可用区故障转移路由值
n-d-address-failover - IP地址和端口故障转移路由值
n-d-id-blacklist - 全局唯一ID屏蔽值
n-d-address-blacklist - IP地址和端口屏蔽值
- n-d-service开头的埋点代表是服务自身的属性
- n-d-开头的埋点是蓝绿灰度传递的策略路由值
② 外置自定义埋点
用户可以自定义外置埋点
- 自定义要传递的调用链参数,例如:traceId,spanId等
- 自定义要传递的业务参数,例如:mobile,user等
③ 跟调用链中间件集成
- 集成OpenTracing + Jaeger蓝绿灰度全链路监控
- 集成OpenTracing + SkyWalking蓝绿灰度全链路监控
Debug辅助监控只是通过普通的System.out.println方式输出,便于开发人员在IDE上调试,在生产环境下不建议开启
对于Debug辅助监控功能的开启和关闭,需要通过如下开关做控制
# 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false
spring.application.strategy.monitor.enabled=true
# 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.rest.intercept.debug.enabled=true
# 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.logger.debug.enabled=true
① 网关端和服务端自身蓝绿灰度埋点Debug辅助监控
----------------------- Logger Debug -----------------------
trace-id=dade3982ae65e9e1
span-id=997e31021e9fce20
n-d-service-group=discovery-guide-group
n-d-service-type=service
n-d-service-id=discovery-guide-service-a
n-d-service-address=172.27.208.1:3001
n-d-service-version=1.0
n-d-service-region=dev
n-d-service-env=env1
n-d-service-zone=zone1
n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
mobile=13812345678
user=
------------------------------------------------------------
② 服务端Feign、RestTemplate或者WebClient拦截输入的蓝绿灰度埋点Debug辅助监控
--------- Feign Intercept Input Header Information ---------
n-d-service-group=discovery-guide-group
n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
n-d-service-type=gateway
n-d-service-id=discovery-guide-zuul
n-d-service-env=default
mobile=13812345678
n-d-service-region=default
n-d-service-zone=default
n-d-service-address=172.27.208.1:5002
n-d-service-version=1.0
------------------------------------------------------------
③ 服务端Feign、RestTemplate或者WebClient调用拦截输出的蓝绿灰度埋点Debug辅助监控
-------- Feign Intercept Output Header Information ---------
mobile=[13812345678]
n-d-service-address=[172.27.208.1:3001]
n-d-service-env=[env1]
n-d-service-group=[discovery-guide-group]
n-d-service-id=[discovery-guide-service-a]
n-d-service-region=[dev]
n-d-service-type=[service]
n-d-service-version=[1.0]
n-d-service-zone=[zone1]
n-d-version=[{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}]
------------------------------------------------------------
蓝绿灰度埋点失效问题的解决办法
排除引用配置不正确的因素之外,蓝绿灰度埋点失效问题一般都是服务中带有异步调用的场景引起的
网关 -> A服务 -> B服务的调用链,假设A服务的日志中,Input Header Information
中n-d-version
带有Json值,而Output Header Information
不带有Json值,说明A服务收到了网关传递过来的Header,但是在转发中丢失了,可能的原因和解决方案总结如下:
① Spring Cloud H版(或者以下)引入了Hystrix插件,并且启用了线程池隔离模式,转发过程中,因为线程切换,导致Header转发丢失。通过异步场景下DiscoveryAgent解决方案或者异步场景下Hystrix线程池隔离解决方案来解决
② Spring Cloud 2020版(或者以上)的负载均衡器是基于WebFlux技术的,响应式方式也会触发线程切换,导致Header转发丢失。通过异步场景下DiscoveryAgent解决方案来解决
③ 业务代码里,Feign或者RestTemplate调用代码放在异步场景中,因为线程切换,导致Header转发丢失。通过异步场景下DiscoveryAgent解决方案来解决
- 集成OpenTracing + Jaeger + Sentinel限流熔断降级权限埋点全链路监控
- 集成OpenTracing + SkyWalking + Sentinel限流熔断降级权限埋点全链路监控
① 自定义调用链上下文参数的创建,继承DefaultStrategyTracerAdapter
// 自定义调用链上下文参数的创建
// 对于getTraceId和getSpanId方法,在OpenTracing等调用链中间件引入的情况下,由调用链中间件决定,在这里定义不会起作用;在OpenTracing等调用链中间件未引入的情况下,在这里定义才有效,下面代码中表示从Http Header中获取,并全链路传递
// 对于getCustomizationMap方法,表示输出到调用链中的定制化业务参数,可以同时输出到日志和OpenTracing等调用链中间件,下面代码中表示从Http Header中获取,并全链路传递
public class MyStrategyTracerAdapter extends DefaultStrategyTracerAdapter {
@Override
public String getTraceId() {
return StringUtils.isNotEmpty(strategyContextHolder.getHeader(DiscoveryConstant.TRACE_ID)) ? strategyContextHolder.getHeader(DiscoveryConstant.TRACE_ID) : StringUtils.EMPTY;
}
@Override
public String getSpanId() {
return StringUtils.isNotEmpty(strategyContextHolder.getHeader(DiscoveryConstant.SPAN_ID)) ? strategyContextHolder.getHeader(DiscoveryConstant.SPAN_ID) : StringUtils.EMPTY;
}
@Override
public Map<String, String> getCustomizationMap() {
Map<String, String> customizationMap = new LinkedHashMap<String, String>();
customizationMap.put("mobile", StringUtils.isNotEmpty(strategyContextHolder.getHeader("mobile")) ? strategyContextHolder.getHeader("mobile") : StringUtils.EMPTY);
customizationMap.put("user", StringUtils.isNotEmpty(strategyContextHolder.getHeader("user")) ? strategyContextHolder.getHeader("user") : StringUtils.EMPTY);
return customizationMap;
}
}
在配置类里@Bean方式进行调用链类创建,覆盖框架内置的调用链适配器
@Bean
public StrategyTracerAdapter strategyTracerAdapter() {
return new MyStrategyTracerAdapter();
}
② 自定义类方法上入参和出参输出到调用链,继承ServiceStrategyMonitorAdapter
// 自定义类方法上入参和出参输出到调用链
// parameterMap格式:
// key为入参名
// value为入参值
public class MyServiceStrategyMonitorAdapter implements ServiceStrategyMonitorAdapter {
@Override
public Map<String, String> getCustomizationMap(ServiceStrategyMonitorInterceptor interceptor, MethodInvocation invocation, Map<String, Object> parameterMap, Object returnValue) {
Map<String, String> customizationMap = new LinkedHashMap<String, String>();
customizationMap.put(DiscoveryConstant.PARAMETER, parameterMap.toString());
customizationMap.put(DiscoveryConstant.RETURN, returnValue != null ? returnValue.toString() : null);
return customizationMap;
}
}
在配置类里@Bean方式进行监控适配类创建
@Bean
public ServiceStrategyMonitorAdapter serviceStrategyMonitorAdapter() {
return new MyServiceStrategyMonitorAdapter();
}
③ 业务方法上获取TraceId和SpanId
public class MyClass {
@Autowired
private StrategyMonitorContext strategyMonitorContext;
public void doXXX() {
String traceId = strategyMonitorContext.getTraceId();
String spanId = strategyMonitorContext.getSpanId();
...
}
}
对于全链路监控功能的开启和关闭,需要通过如下开关做控制
# 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false
spring.application.strategy.monitor.enabled=true
# 启动和关闭告警,一旦关闭,蓝绿灰度上下文输出都将关闭。缺失则默认为false
spring.application.strategy.alarm.enabled=true
# 启动和关闭日志输出。缺失则默认为false
spring.application.strategy.logger.enabled=true
# 日志输出中,是否显示MDC前面的Key。缺失则默认为true
spring.application.strategy.logger.mdc.key.shown=true
# 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.logger.debug.enabled=true
# 启动和关闭调用链输出。缺失则默认为false
spring.application.strategy.tracer.enabled=true
# 启动和关闭调用链的蓝绿灰度信息以独立的Span节点输出,如果关闭,则蓝绿灰度信息输出到原生的Span节点中(SkyWalking不支持原生模式)。缺失则默认为true
spring.application.strategy.tracer.separate.span.enabled=true
# 启动和关闭调用链的蓝绿灰度规则策略信息输出。缺失则默认为true
spring.application.strategy.tracer.rule.output.enabled=true
# 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false
spring.application.strategy.tracer.exception.detail.output.enabled=true
# 启动和关闭类方法上入参和出参输出到调用链。缺失则默认为false
spring.application.strategy.tracer.method.context.output.enabled=true
# 显示在调用链界面上蓝绿灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION
spring.application.strategy.tracer.span.value=NEPXION
# 显示在调用链界面上蓝绿灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery
spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery
# 启动和关闭Sentinel调用链上规则在Span上的输出。缺失则默认为true
spring.application.strategy.tracer.sentinel.rule.output.enabled=true
# 启动和关闭Sentinel调用链上方法入参在Span上的输出。缺失则默认为false
spring.application.strategy.tracer.sentinel.args.output.enabled=true
OpenTracing对Finchley版的Spring Cloud Gateway的reactor-core包存在版本兼容性问题,如果使用者希望Finchley版的Spring Cloud Gateway上使用OpenTracing,需要做如下改造
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-strategy-starter-gateway</artifactId>
<version>${discovery.version}</version>
<exclusions>
<exclusion>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
上述方式也适用于其它引入了低版本reactor-core包版本兼容性的场景
蓝绿灰度埋点日志输出,需要使用者配置logback.xml或者log4j.xml日志格式,参考如下
<!-- Logback configuration. See http://logback.qos.ch/manual/index.html -->
<configuration scan="true" scanPeriod="10 seconds">
<!-- Simple file output -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>discovery %date %level [%thread] [%X{trace-id}] [%X{span-id}] [%X{n-d-service-group}] [%X{n-d-service-type}] [%X{n-d-service-app-id}] [%X{n-d-service-id}] [%X{n-d-service-address}] [%X{n-d-service-version}] [%X{n-d-service-region}] [%X{n-d-service-env}] [%X{n-d-service-zone}] [%X{mobile}] [%X{user}] %logger{10} [%file:%line] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>log/discovery-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<!-- Safely log to the same file from multiple JVMs. Degrades performance! -->
<prudent>true</prudent>
</appender>
<appender name="FILE_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="FILE" />
</appender>
<!-- Console output -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoder defaults to ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>discovery %date %level [%thread] [%X{trace-id}] [%X{span-id}] [%X{n-d-service-group}] [%X{n-d-service-type}] [%X{n-d-service-app-id}] [%X{n-d-service-id}] [%X{n-d-service-address}] [%X{n-d-service-version}] [%X{n-d-service-region}] [%X{n-d-service-env}] [%X{n-d-service-zone}] [%X{mobile}] [%X{user}] %logger{10} [%file:%line] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- Only log level WARN and above -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<!-- For loggers in the these namespaces, log at all levels. -->
<logger name="pedestal" level="ALL" />
<logger name="hammock-cafe" level="ALL" />
<logger name="user" level="ALL" />
<root level="INFO">
<!-- <appender-ref ref="FILE_ASYNC" /> -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
全链路调用过程中,实施端到端Sentinel熔断时,在不同场景下会触发如下四个事件
- Pass
- Block
- Success
- Exception
通过Prometheus Micrometer对上述事件进行计数统计,可输出Grafana看板上
使用者可以通过如下开关打开或者关闭输出功能项
# 启动和关闭Sentinel Metric通过次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.pass.qps.output.enabled=true
# 启动和关闭Sentinel Metric阻塞次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.block.qps.output.enabled=true
# 启动和关闭Sentinel Metric成功次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.success.qps.output.enabled=true
# 启动和关闭Sentinel Metric异常次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.exception.qps.output.enabled=true
全链路蓝绿灰度实施过程中,使用者需要快速判断蓝绿灰度是否已经生效,可以通过
- Debug开关开启,通过控制台输出去判断相关蓝绿灰度Header是否传递,是否相同
- 依托监控调用链中间件,通过埋点输出去判断相关蓝绿灰度Header是否传递,是否相同
上述方式需要人工观察和干预,并不友好,使用者也可以通过集成如下蓝绿灰度告警监控模块来实现
① 网关和服务加上下面的类
@EventBus
public class MySubscriber {
@Subscribe
public void onAlarm(StrategyAlarmEvent strategyAlarmEvent) {
// 在本告警中告警类型为StrategyConstant.STRATEGY_CONTEXT_ALARM的静态变量值,表示蓝绿灰度上下文告警
String alarmType = strategyAlarmEvent.getAlarmType();
// 通过事件总线把告警数据alarmMap存储到ElasticSearch、MessageQueue、数据库等
Map<String, String> alarmMap = strategyAlarmEvent.getAlarmMap();
}
}
在配置类里@Bean方式进行订阅类创建
@Bean
public MySubscriber mySubscriber() {
return new MySubscriber();
}
并开启如下开关
# 启动和关闭告警,一旦关闭,蓝绿灰度上下文输出都将关闭。缺失则默认为false
spring.application.strategy.alarm.enabled=true
② 通过事件总线把告警数据存储到ElasticSearch、MessageQueue、数据库等
③ 根据端到端的traceId对应的蓝绿灰度Header是否传递,是否相同,判断蓝绿灰度是否成功
④ 如果不相同,结合DevOps系统发送告警邮件或者通知
⑤ 告警数据具体信息列表参考源码:
com.nepxion.discovery.plugin.strategy.monitor.DefaultStrategyAlarm
示例如下:
{n-d-service-group=discovery-guide-group, n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}, n-d-service-type=service, n-d-service-id=discovery-guide-service-b, n-d-service-env=env1, mobile=, n-d-service-region=qa, span-id=c37b54d7fec6bd07, n-d-service-zone=zone1, n-d-service-address=192.168.0.107:4001, trace-id=64c79e1ef68eecf3, n-d-service-version=1.0}
① 获取图形化桌面端
桌面端获取方式有两种方式
- 通过https://github.com/Nepxion/DiscoveryUI/releases下载最新版本的discovery-desktop-release
- 编译https://github.com/Nepxion/DiscoveryUI下的desktop,在target目录下产生discovery-desktop-release
② 启动控制台
- 通过https://github.com/Nepxion/DiscoveryPlatform下载最新版本的控制台
- 导入IDE或者编译成Spring Boot程序运行
- 运行之前,先修改src/main/resources/bootstrap.properties的相关配置,包括注册中心和配置中心的地址等
③ 启动图形化桌面端
- 修改config/console.properties中的url,指向控制台的地址
- 在Windows操作系统下,运行startup.bat,在Mac或者Linux操作系统下,运行startup.sh
④ 登录图形化桌面端
登录认证,用户名和密码为admin/admin或者nepxion/nepxion。控制台支持简单的认证,用户名和密码配置在上述控制台的bootstrap.properties中,使用者可以自己扩展AuthenticationResource并注入,实现更专业的认证功能
全链路编排建模工具,只提供最经典和最常用的蓝绿灰度发布场景功能,并不覆盖框架所有的功能
① 导航栏上选择〔全链路服务蓝绿发布〕
② 〔全链路服务蓝绿发布〕界面的工具栏上,点击【新建】按钮,弹出【新建配置】对话框。确认下面选项后,点击【确定】按钮后,进行全链路蓝绿发布编排建模
- 〔订阅参数〕项。选择〔局部订阅〕或者〔全局订阅〕,通过下拉菜单〔订阅组名〕和〔订阅服务名〕,〔订阅服务名〕可以选择网关(以网关为发布入口)或者服务(以服务为发布入口)。如果是〔全局订阅〕,则不需要选择〔订阅服务名〕
- 〔部署参数〕项。选择〔域网关模式〕(发布界面上提供只属于〔订阅组〕下的服务列表)或者〔非域网模式〕(发布界面上提供所有服务列表)
- 〔发布策略〕项。选择〔版本策略〕或者〔区域策略〕
- 〔路由类型〕项。选择〔蓝 | 绿 | 兜底〕或者〔蓝 | 兜底〕
根据全链路版本条件匹配蓝绿发布示例中的场景
③ 在〔蓝绿条件〕中,分别输入〔蓝条件〕和〔绿条件〕
- 〔蓝条件〕输入a==1
- 〔绿条件〕输入a==1&&b==2
使用者可以通过〔条件校验〕来判断条件是否正确。例如,在〔绿条件〕区的校验文本框里,输入a=1,执行校验,将提示〔校验结果:false〕,输入a=1;b=2,将提示〔校验结果:true〕
④ 在〔蓝绿编排〕中,分别选择如下服务以及其版本,并点击【添加】按钮,把路由链路添加到拓扑图上
- 服务discovery-guide-service-a,〔蓝版本〕=1.1,〔绿版本〕=1.0,〔兜底版本〕=1.0
- 服务discovery-guide-service-b,〔蓝版本〕=1.1,〔绿版本〕=1.0,〔兜底版本〕=1.0
⑤ 如果希望内置Header参数,可以〔蓝绿参数〕的文本框中输入
⑥ 全链路编排建模完毕,点击工具栏上【保存】按钮进行保存,也可以先点击【预览】按钮,在弹出的【预览配置】对话框中,确认规则策略无误后再保存。使用者可以访问Nacos界面查看相关的规则策略是否已经存在
⑦ 对于已经存在的策略配置,可以通过点击工具栏上【打开】按钮,在弹出的【打开配置】对话框中,根据上述逻辑相似,确定〔订阅参数〕项后,选择〔打开远程配置〕(载入Nacos上对应的规则策略)或者〔打开本地配置〕(载入本地硬盘上规则策略文件rule.xml)
⑧ 对于已经存在的策略配置,如果想重置清除掉,点击工具栏上【重置】按钮进行重置清除
① 导航栏上选择〔全链路服务灰度发布〕
根据全链路版本条件权重灰度发布示例中的场景
② 在〔灰度条件〕中,〔灰度条件〕(灰度流量占比)选择95%,〔稳定条件〕(稳定流量占比)会自动切换成5%
其它步骤跟全链路蓝绿发布编排建模相似,但比其简单
① 导航栏上选择〔全链路服务流量侦测〕
② 在〔侦测入口〕中,操作如下
- 〔类型〕项。选择〔网关〕或者〔服务〕,本示例的规则策略是配置在网关上,所以选择〔网关〕
- 〔协议〕项。选择〔http://〕或者〔https://〕,视网关或者服务暴露出来的协议类型而定,本示例暴露出来的是http协议,所以选择〔http://〕
- 〔服务〕项。选择一个网关名或者服务名,下拉菜单列表随着〔类型〕项的改变而改变,蓝绿发布规则策略是配置在discovery-guide-gateway上,所以选择它
- 〔实例〕项。选择一个网关实例或者服务实例的IP地址和端口,下拉菜单列表随着〔服务〕的改变而改变
③ 在〔侦测参数〕中,操作如下
添加〔Header〕项和〔Parameter〕项,也可以〔Cookie〕项,使用者可以任意选择2个
- 〔Header〕项。输入a=1
- 〔Parameter〕项。输入b=2
④ 在〔侦测链路〕中,操作如下
- 增加服务discovery-guide-service-a
- 增加服务discovery-guide-service-b
⑤ 在〔侦测执行〕中,操作如下
- 〔维护〕项。选择〔版本〕、〔区域〕、〔环境〕、〔可用区〕、〔地址〕或者〔组〕,维护表示在拓扑图上聚合调用场景的维度,本示例的规则策略是是基于版本维度进行发布,所以选择〔版本〕
- 〔次数〕项。选择执行侦测的次数,基于网关和服务的性能压力,使用者需要酌情考虑调用次数
- 〔次数〕项。选择执行侦测的同一时刻线程并发数,并发数是对于图形化桌面端而言的
- 〔成功〕项。用来显示侦测成功的百分比
- 〔失败〕项。用来显示侦测失败的百分比
- 〔耗时〕项。用来显示侦测执行的消耗时间
⑥ 点击工具栏上【开始】按钮开始侦测,在侦测执行过程中,可以点击工具栏上【停止】按钮停止侦测
从上述截图中,可以看到
- 在条件a==1&&b==2的〔绿条件〕下,执行〔网关〕->〔a服务1.0版本〕->〔b服务1.0版本〕的〔绿路由〕
⑦ 点击工具栏上【查看】按钮查看拓扑图上所有节点配置的规则策略,包括局部配置和全局配置
⑧ 支持直接n-d-version策略路由Header驱动的蓝绿发布流量侦测
① 导航栏上选择〔全链路服务流量侦测〕
② 在〔侦测入口〕中,操作如下
- 〔服务〕项。灰度发布规则策略是配置在discovery-guide-zuul上,所以选择它
③ 在〔侦测参数〕中,不需要输入任何值
④ 在〔侦测执行〕中,〔次数〕项的值越大,灰度权重百分比越准确
其它步骤跟全链路蓝绿发布流量侦测相似,但比其简单
从上述截图中,可以看到
- 执行〔网关〕->〔a服务1.1版本〕->〔b服务1.1版本〕的〔灰度路由〕权重百分比95%左右
- 执行〔网关〕->〔a服务1.0版本〕->〔b服务1.0版本〕的〔稳定路由〕权重百分比5%左右
① 全链路蓝绿发布 + 灰度发布混合模式下流量侦测
在网关上配置了蓝绿发布规则策略,在a服务上配置了灰度发布规则策略
② 全链路灰度发布 + 蓝绿发布混合模式下流量侦测
在网关上配置了灰度发布规则策略,在a服务上配置了蓝绿发布规则策略
通过订阅相关参数的变化,实现参数化蓝绿发布,可用于如下场景
① 基于多DataSource的数据库蓝绿发布
② 基于多Queue的消息队列蓝绿发布
增加参数化蓝绿发布规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现功能
① 服务a在版本为1.0的时候,数据库的数据源指向db1;服务a在版本为1.1的时候,数据库的数据源指向db2
② 服务b在区域为dev的时候,消息队列指向queue1;服务b在区域为qa的时候,消息队列指向queue2
③ 服务c在环境为env1的时候,数据库的数据源指向db1;服务c在环境为env2的时候,数据库的数据源指向db2
④ 服务d在可用区为zone1的时候,消息队列指向queue1;服务d在可用区为zone2的时候,消息队列指向queue2
⑤ 服务c在IP地址和端口为192.168.43.101:1201的时候,数据库的数据源指向db1;服务c在IP地址和端口为192.168.43.102:1201的时候,数据库的数据源指向db2
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<parameter>
<service service-name="discovery-guide-service-a" tag-key="version" tag-value="1.0" key="ShardingSphere" value="db1"/>
<service service-name="discovery-guide-service-a" tag-key="version" tag-value="1.1" key="ShardingSphere" value="db2"/>
<service service-name="discovery-guide-service-b" tag-key="region" tag-value="dev" key="RocketMQ" value="queue1"/>
<service service-name="discovery-guide-service-b" tag-key="region" tag-value="qa" key="RocketMQ" value="queue2"/>
<service service-name="discovery-guide-service-c" tag-key="env" tag-value="env1" key="ShardingSphere" value="db1"/>
<service service-name="discovery-guide-service-c" tag-key="env" tag-value="env2" key="ShardingSphere" value="db2"/>
<service service-name="discovery-guide-service-d" tag-key="zone" tag-value="zone1" key="RocketMQ" value="queue1"/>
<service service-name="discovery-guide-service-d" tag-key="zone" tag-value="zone2" key="RocketMQ" value="queue2"/>
<service service-name="discovery-guide-service-e" tag-key="address" tag-value="192.168.43.101:1201" key="ShardingSphere" value="db1"/>
<service service-name="discovery-guide-service-e" tag-key="address" tag-value="192.168.43.102:1201" key="ShardingSphere" value="db2"/>
</parameter>
</rule>
通过事件总线方式,对参数改变动态实现监听,并在此类里自行对接相关的数据库和消息队列中间件的切换和驱动
@EventBus
public class MySubscriber {
@Autowired
private PluginAdapter pluginAdapter;
@Subscribe
public void onParameterChanged(ParameterChangedEvent parameterChangedEvent) {
ParameterEntity parameterEntity = parameterChangedEvent.getParameterEntity();
String serviceId = pluginAdapter.getServiceId();
List<ParameterServiceEntity> parameterServiceEntityList = null;
if (parameterEntity != null) {
Map<String, List<ParameterServiceEntity>> parameterServiceMap = parameterEntity.getParameterServiceMap();
parameterServiceEntityList = parameterServiceMap.get(serviceId);
}
// parameterServiceEntityList为动态参数列表
}
}
使用者可以通过如下开关,决定在服务启动过程中,读到参数配置的时候,是否要发送一个事件触发数据库和消息队列中间件的切换
# 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true
spring.application.parameter.event.onstart.enabled=true
参考https://github.com/Nepxion/DiscoveryContrib里的实现方式
服务侧对于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能,默认映射到RestController类(含有@RestController注解),并配合如下的扫描路径才能工作
# 路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能
spring.application.strategy.scan.packages=com.nepxion.discovery.guide.service.feign
当使用者不希望只局限于RestController类(含有@RestController注解)方式,而要求在任何类中实现上述功能,那么框架提供@ServiceStrategy注解,使用者把它加在类头部即可,可以达到和@RestController注解同样的效果
服务侧对于RPC方式的调用,可以加入API权限控制,通过在接口或者类名上加@Permission注解,或者在接口或者类的方法名上加@Permission注解,实现API权限控制。如果两者都加,以前者为优先
- 实现权限自动扫描入库
- 实现提供显式基于注解的权限验证,参数通过注解传递;实现提供基于Rest请求的权限验证,参数通过Header传递
- 实现提供入库方法和权限判断方法的扩展,这两者需要自行实现
请参考权限代码
通过集成插件git-commit-id-plugin,通过产生git信息文件的方式,获取git.commit.id(最后一次代码的提交ID)或者git.build.version(对应到Maven工程的版本)来自动创建版本号,这样就可以避免使用者手工维护版本号
当两者都启用的时候,手工配置的版本号优先级要高于Git插件方式的版本号
- 增加Git编译插件
需要在4个工程下的pom.xml里增加git-commit-id-plugin
默认配置
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- 必须配置,并指定为true -->
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<!-- 指定日期格式 -->
<dateFormat>yyyyMMdd</dateFormat>
<!-- <dateFormat>yyyy-MM-dd-HH:mm:ss</dateFormat> -->
</configuration>
</plugin>
特色配置
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>revision</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- 指定git信息文件是否生成。缺失则默认为false -->
<generateGitPropertiesFile>true</generateGitPropertiesFile>
<!-- 指定.git文件夹路径。缺失则默认为${project.basedir}/.git -->
<dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
<!-- 指定git信息文件的输出路径 -->
<generateGitPropertiesFilename>${project.build.outputDirectory}/git.json</generateGitPropertiesFilename>
<!-- <generateGitPropertiesFilename>${project.basedir}/git.json</generateGitPropertiesFilename> -->
<!-- 指定git信息文件的输出格式。缺失则默认为properties -->
<format>json</format>
<!-- 指定当.git文件夹未找到时,构建是否失败。若设置true,则构建失败,若设置false,则跳过执行该构建步骤。缺失则默认为true -->
<failOnNoGitDirectory>true</failOnNoGitDirectory>
<!-- 指定当项目打包类型为pom时,是否取消构建。缺失则默认为true -->
<skipPoms>false</skipPoms>
<!-- 指定构建过程中,是否打印详细信息。缺失则默认为false -->
<verbose>false</verbose>
<dateFormat>yyyyMMdd</dateFormat>
<!-- <dateFormat>yyyy-MM-dd-HH:mm:ss</dateFormat> -->
</configuration>
</plugin>
- 增加配置项
# 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false
spring.application.git.generator.enabled=true
# 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties
spring.application.git.generator.path=classpath:git.properties
# spring.application.git.generator.path=classpath:git.json
# 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count}
spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time}
# spring.application.git.version.key={git.build.version}-{git.commit.time}
下面是可供选择的Git字段,比较实际意义的字段为git.commit.id,git.commit.id.abbrev,git.build.version,git.total.commit.count
git.branch=master
git.build.host=Nepxion
git.build.time=2019-10-21-10\:07\:41
[email protected]
git.build.user.name=Nepxion
git.build.version=1.0.0
git.closest.tag.commit.count=
git.closest.tag.name=
git.commit.id=04d7e45b11b975db37bdcdbc5a97c02e9d80e5fa
git.commit.id.abbrev=04d7e45
git.commit.id.describe=04d7e45-dirty
git.commit.id.describe-short=04d7e45-dirty
git.commit.message.full=\u4FEE\u6539\u914D\u7F6E
git.commit.message.short=\u4FEE\u6539\u914D\u7F6E
git.commit.time=2019-10-21T09\:09\:25+0800
[email protected]
git.commit.user.name=Nepxion
git.dirty=true
git.local.branch.ahead=0
git.local.branch.behind=0
git.remote.origin.url=https\://github.com/Nepxion/DiscoveryGuide.git
git.tags=
git.total.commit.count=765
一般情况下,上述两个地方的配置都同时保持默认即可。对于一些特殊的用法,两个地方的配置项用法必须保持一致,例如
# 输出到工程根目录下
<generateGitPropertiesFilename>${project.basedir}/git.json</generateGitPropertiesFilename>
# 输出成json格式
<format>json</format>
下面配置项必须上面两个配置项的操作逻辑相同
# 输出到工程根目录下的json格式文件
spring.application.git.generator.path=file:git.json
内置基于Swagger的Rest接口,可以供外部查询当前服务的Git信息
操作 | 路径 | 参数 | 方式 |
---|---|---|---|
获取Git信息的Map格式 | http:// [IP:PORT]/git/map |
无 | GET |
获取Git信息的文本格式 | http:// [IP:PORT]/git/text |
无 | GET |
通过指定长度截断或者标志截断服务名的前缀来自动创建组名,这样就可以避免使用者手工维护组名
当两者都启用的时候,手工配置的组名优先级要高于截断方式的组名
- 增加配置项
# 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false
spring.application.group.generator.enabled=true
# 服务名前缀的截断长度,必须大于0
spring.application.group.generator.length=15
# 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式
spring.application.group.generator.character=-
在启动微服务的时候,可以通过参数方式初始化元数据,框架会自动把它注册到远程注册中心。有如下两种方式
- 通过VM arguments来传递,它的用法是前面加-D。支持上述所有的注册组件,它的限制是变量前面必须要加metadata.,推荐使用该方式。例如:-Dmetadata.version=1.0
- 通过Program arguments来传递,它的用法是前面加--。支持Eureka、Zookeeper和Nacos的增量覆盖,Consul由于使用了全量覆盖的tag方式,不适用改变单个元数据的方式。例如:--spring.cloud.nacos.discovery.metadata.version=1.0
- 两种方式尽量避免同时用
参考流量染色配置
自动扫描目录功能为省掉手工配置扫描目录而设定的,当使用者手工配置了扫描目录,则采用使用者配置的目录,如果没配置,则采用自动扫描目录的方式
如下配置是手工配置扫描目录的样例
# 路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能
spring.application.strategy.scan.packages=com.nepxion.discovery.guide.service
① 自动扫描目录的配置
# 启动和关闭自动扫描目录,当扫描目录未人工配置的时候,可以通过自动扫描方式决定扫描目录。缺失则默认为true
spring.application.strategy.auto.scan.packages.enabled=true
# 启动和关闭嵌套扫描,嵌套扫描指扫描非本工程下外部包的目录,可以支持多层嵌套。缺失则默认为false
spring.application.strategy.auto.scan.recursion.enabled=false
② 自动扫描目录的逻辑
在假设的场景中,SpringBoot入口设定扫描目录为com.a,com.a目录下有个Spring对象通过ComponentScan方式设定扫描目录为com.b,com.b目录下有个Spring对象通过ComponentScan方式设定扫描目录为com.c,那么最终计算出来的目录为
嵌套扫描下,得到的扫描目录是
SpringBoot入口所在的目录;com.a;com.b;com.c
非嵌套扫描下,得到的扫描目录是
SpringBoot入口所在的目录;com.a
③ 扩展获取自动扫描目录
使用者可以通过如下代码得到自动扫描目录
public class MyService {
@Autowired
private StrategyPackagesExtractor strategyPackagesExtractor;
public void getPackages() {
// 获取@SpringBootApplication所在类入口的扫描目录(一般只有一个),返回List<String>类型
strategyPackagesExtractor.getBasePackagesList();
// 获取所有嵌套的扫描目录(包括当前工程的所有类中@SpringBootApplication和@ComponentScan注解设定的扫描目录),返回List<String>类型
strategyPackagesExtractor.getScanningPackagesList();
// 上面两种目录的相加,返回List<String>类型
strategyPackagesExtractor.getAllPackagesList();
}
}
统一配置订阅执行器,基于Nacos、Apollo、Consul、Etcd、Redis、Zookeeper六种配置中心,通过封装适配成同样的写法,通过切换继承类,可切换配置中心,无须修改其它代码
Spring Cloud配置动态刷新机制固化在一个比较单一的场景(例如,通过@Value方式)里,无法满足更灵活更高级的订阅场景,例如,Spring Cloud Gateway和Zuul网关通过改变配置中心的路由信息无法动态刷新路由路径
本框架提供更简单灵活的实现方式,以Nacos为例子,使用者先确定订阅的Group和DataId,在Nacos界面填入这两个参数对应的配置内容,然后通过回调方法处理业务逻辑。具体使用方式,如下
// 把继承类(extends)换成如下任何一个,即可切换配置中心,代码无需任何变动
// 1. NacosProcessor
// 2. ApolloProcessor
// 3. ConsulProcessor
// 4. EtcdProcessor
// 5. ZookeeperProcessor
// 6. RedisProcessor
// Group和DataId自行决定,需要注意
// 1. 对于Nacos、Redis、Zookeeper配置中心,Group和DataId需要和界面相对应
// 2. 对于Apollo、Consul、Etcd配置中心,Key的格式为Group-DataId
// 可以同时支持多个配置中心的订阅,需要同时创建多个不同的Processor,同时@Bean方式进入到Spring容器
public class MyConfigProcessor extends NacosProcessor {
@Override
public void beforeInitialization() {
System.out.println("订阅器初始化之前,可以做一些工作");
}
@Override
public void afterInitialization() {
System.out.println("订阅器初始化之后,可以做一些工作");
}
@Override
public String getGroup() {
return "b";
}
@Override
public String getDataId() {
return "a";
}
@Override
public String getDescription() {
// description为日志打印显示而设置,作用是帮助使用者在日志上定位订阅器是否在执行
return "My subscription";
}
@Override
public void callbackConfig(String config) {
// config为配置中心对应键值的内容变更,使用者可以根据此变更对业务模块做回调处理
System.out.println("监听配置改变:config=" + config);
}
}
统一配置订阅执行器可以单独运行在Spring Boot应用上,它是一个通用的解决方案
- 如果使用者希望脱离Nepxion Discovery以及Spring Cloud框架,使用者只需要引入如下依赖之一即可
- 如果使用者正在使用Nepxion Discovery框架,则跟随它的内置引入即可,不需要额外引入如下依赖之一
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>discovery-common-nacos</artifactId>
<version>${discovery.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>discovery-common-apollo</artifactId>
<version>${discovery.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>discovery-common-redis</artifactId>
<version>${discovery.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>discovery-common-zookeeper</artifactId>
<version>${discovery.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>discovery-common-consul</artifactId>
<version>${discovery.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>discovery-common-etcd</artifactId>
<version>${discovery.version}</version>
</dependency>
具体用法和配置,请参考6.x.x指南示例配置版,分支为6.x.x-config
服务名大小写规则
- 在配置文件(application.properties、application.yaml等)里,定义服务名(spring.application.name)不区分大小写
- 在规则文件(Xml、Json)里,引用的服务名必须小写
- 在Nacos、Apollo、Redis等远程配置中心的Key,包含的服务名必须小写
规则策略的格式是Xml或者Json,存储于本地文件或者远程配置中心,可以通过远程配置中心修改的方式达到规则策略动态化。其核心代码参考discovery-plugin-framework以及它的扩展、discovery-plugin-config-center以及它的扩展和discovery-plugin-admin-center等
Xml最全的示例如下,Json示例见源码discovery-springcloud-example-service工程下的rule.json
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<!-- 如果不想开启相关功能,只需要把相关节点删除即可,例如不想要黑名单功能,把blacklist节点删除 -->
<register>
<!-- 服务注册的黑/白名单注册过滤,只在服务启动的时候生效。白名单表示只允许指定IP地址前缀注册,黑名单表示不允许指定IP地址前缀注册。每个服务只能同时开启要么白名单,要么黑名单 -->
<!-- filter-type,可选值blacklist/whitelist,表示白名单或者黑名单 -->
<!-- service-name,表示服务名 -->
<!-- filter-value,表示黑/白名单的IP地址列表。IP地址一般用前缀来表示,如果多个用“;”分隔,不允许出现空格 -->
<!-- 表示下面所有服务,不允许10.10和11.11为前缀的IP地址注册(全局过滤) -->
<blacklist filter-value="10.10;11.11">
<!-- 表示下面服务,不允许172.16和10.10和11.11为前缀的IP地址注册 -->
<service service-name="discovery-springcloud-example-a" filter-value="172.16"/>
</blacklist>
<!-- <whitelist filter-value="">
<service service-name="" filter-value=""/>
</whitelist> -->
<!-- 服务注册的数目限制注册过滤,只在服务启动的时候生效。当某个服务的实例注册达到指定数目时候,更多的实例将无法注册 -->
<!-- service-name,表示服务名 -->
<!-- filter-value,表示最大实例注册数 -->
<!-- 表示下面所有服务,最大实例注册数为10000(全局配置) -->
<count filter-value="10000">
<!-- 表示下面服务,最大实例注册数为5000,全局配置值10000将不起作用,以局部配置值为准 -->
<service service-name="discovery-springcloud-example-a" filter-value="5000"/>
</count>
</register>
<discovery>
<!-- 服务发现的黑/白名单发现过滤,使用方式跟“服务注册的黑/白名单过滤”一致 -->
<!-- 表示下面所有服务,不允许10.10和11.11为前缀的IP地址被发现(全局过滤) -->
<blacklist filter-value="10.10;11.11">
<!-- 表示下面服务,不允许172.16和10.10和11.11为前缀的IP地址被发现 -->
<service service-name="discovery-springcloud-example-b" filter-value="172.16"/>
</blacklist>
<!-- 服务发现的多版本灰度匹配控制 -->
<!-- service-name,表示服务名 -->
<!-- version-value,表示可供访问的版本,如果多个用“;”分隔,不允许出现空格 -->
<!-- 版本策略介绍 -->
<!-- 1. 标准配置,举例如下 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.0" provider-version-value="1.0;1.1"/> 表示消费端1.0版本,允许访问提供端1.0和1.1版本 -->
<!-- 2. 版本值不配置,举例如下 -->
<!-- <service consumer-service-name="a" provider-service-name="b" provider-version-value="1.0;1.1"/> 表示消费端任何版本,允许访问提供端1.0和1.1版本 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.0"/> 表示消费端1.0版本,允许访问提供端任何版本 -->
<!-- <service consumer-service-name="a" provider-service-name="b"/> 表示消费端任何版本,允许访问提供端任何版本 -->
<!-- 3. 版本值空字符串,举例如下 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-version-value="" provider-version-value="1.0;1.1"/> 表示消费端任何版本,允许访问提供端1.0和1.1版本 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-version-value="1.0" provider-version-value=""/> 表示消费端1.0版本,允许访问提供端任何版本 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-version-value="" provider-version-value=""/> 表示消费端任何版本,允许访问提供端任何版本 -->
<!-- 4. 版本对应关系未定义,默认消费端任何版本,允许访问提供端任何版本 -->
<!-- 特殊情况处理,在使用上需要极力避免该情况发生 -->
<!-- 1. 消费端的application.properties未定义版本号,则该消费端可以访问提供端任何版本 -->
<!-- 2. 提供端的application.properties未定义版本号,当消费端在xml里不做任何版本配置,才可以访问该提供端 -->
<version>
<!-- 表示网关g的1.0,允许访问提供端服务a的1.0版本 -->
<service consumer-service-name="discovery-springcloud-example-gateway" provider-service-name="discovery-springcloud-example-a" consumer-version-value="1.0" provider-version-value="1.0"/>
<!-- 表示网关g的1.1,允许访问提供端服务a的1.1版本 -->
<service consumer-service-name="discovery-springcloud-example-gateway" provider-service-name="discovery-springcloud-example-a" consumer-version-value="1.1" provider-version-value="1.1"/>
<!-- 表示网关z的1.0,允许访问提供端服务a的1.0版本 -->
<service consumer-service-name="discovery-springcloud-example-zuul" provider-service-name="discovery-springcloud-example-a" consumer-version-value="1.0" provider-version-value="1.0"/>
<!-- 表示网关z的1.1,允许访问提供端服务a的1.1版本 -->
<service consumer-service-name="discovery-springcloud-example-zuul" provider-service-name="discovery-springcloud-example-a" consumer-version-value="1.1" provider-version-value="1.1"/>
<!-- 表示消费端服务a的1.0,允许访问提供端服务b的1.0版本 -->
<service consumer-service-name="discovery-springcloud-example-a" provider-service-name="discovery-springcloud-example-b" consumer-version-value="1.0" provider-version-value="1.0"/>
<!-- 表示消费端服务a的1.1,允许访问提供端服务b的1.1版本 -->
<service consumer-service-name="discovery-springcloud-example-a" provider-service-name="discovery-springcloud-example-b" consumer-version-value="1.1" provider-version-value="1.1"/>
<!-- 表示消费端服务b的1.0,允许访问提供端服务c的1.0和1.1版本 -->
<service consumer-service-name="discovery-springcloud-example-b" provider-service-name="discovery-springcloud-example-c" consumer-version-value="1.0" provider-version-value="1.0;1.1"/>
<!-- 表示消费端服务b的1.1,允许访问提供端服务c的1.2版本 -->
<service consumer-service-name="discovery-springcloud-example-b" provider-service-name="discovery-springcloud-example-c" consumer-version-value="1.1" provider-version-value="1.2"/>
</version>
<!-- 服务发现的多区域灰度匹配控制 -->
<!-- service-name,表示服务名 -->
<!-- region-value,表示可供访问的区域,如果多个用“;”分隔,不允许出现空格 -->
<!-- 区域策略介绍 -->
<!-- 1. 标准配置,举例如下 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-region-value="dev" provider-region-value="dev"/> 表示dev区域的消费端,允许访问dev区域的提供端 -->
<!-- 2. 区域值不配置,举例如下 -->
<!-- <service consumer-service-name="a" provider-service-name="b" provider-region-value="dev;qa"/> 表示任何区域的消费端,允许访问dev区域和qa区域的提供端 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-region-value="dev"/> 表示dev区域的消费端,允许访问任何区域的提供端 -->
<!-- <service consumer-service-name="a" provider-service-name="b"/> 表示任何区域的消费端,允许访问任何区域的提供端 -->
<!-- 3. 区域值空字符串,举例如下 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-region-value="" provider-region-value="dev;qa"/> 表示任何区域的消费端,允许访问dev区域和qa区域的提供端 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-region-value="dev" provider-region-value=""/> 表示dev区域的消费端,允许访问任何区域的提供端 -->
<!-- <service consumer-service-name="a" provider-service-name="b" consumer-region-value="" provider-region-value=""/> 表示任何区域的消费端,允许访问任何区域的提供端 -->
<!-- 4. 区域对应关系未定义,默认表示任何区域的消费端,允许访问任何区域的提供端 -->
<!-- 特殊情况处理,在使用上需要极力避免该情况发生 -->
<!-- 1. 消费端的application.properties未定义区域值,则该消费端可以访问任何区域的提供端 -->
<!-- 2. 提供端的application.properties未定义区域值,当消费端在xml里不做任何区域配置,才可以访问该提供端 -->
<region>
<!-- 表示dev区域的消费端服务a,允许访问dev区域的提供端服务b -->
<service consumer-service-name="discovery-springcloud-example-a" provider-service-name="discovery-springcloud-example-b" consumer-region-value="dev" provider-region-value="dev"/>
<!-- 表示qa区域的消费端服务a,允许访问qa区域的提供端服务b -->
<service consumer-service-name="discovery-springcloud-example-a" provider-service-name="discovery-springcloud-example-b" consumer-region-value="qa" provider-region-value="qa"/>
<!-- 表示dev区域的消费端服务b,允许访问dev区域的提供端服务c -->
<service consumer-service-name="discovery-springcloud-example-b" provider-service-name="discovery-springcloud-example-c" consumer-region-value="dev" provider-region-value="dev"/>
<!-- 表示qa区域的消费端服务b,允许访问qa区域的提供端服务c -->
<service consumer-service-name="discovery-springcloud-example-b" provider-service-name="discovery-springcloud-example-c" consumer-region-value="qa" provider-region-value="qa"/>
</region>
<!-- 服务发现的多版本或者多区域的灰度权重控制 -->
<!-- service-name,表示服务名 -->
<!-- weight-value,表示版本对应的权重值,格式为"版本/区域值=权重值",如果多个用“;”分隔,不允许出现空格 -->
<!-- 版本权重策略介绍 -->
<!-- 1. 标准配置,举例如下 -->
<!-- <service consumer-service-name="a" provider-service-name="b" provider-weight-value="1.0=90;1.1=10"/> 表示消费端访问提供端的时候,提供端的1.0版本提供90%的权重流量,1.1版本提供10%的权重流量 -->
<!-- <service provider-service-name="b" provider-weight-value="1.0=90;1.1=10"/> 表示所有消费端访问提供端的时候,提供端的1.0版本提供90%的权重流量,1.1版本提供10%的权重流量 -->
<!-- 2. 局部配置,即指定consumer-service-name,专门为该消费端配置权重。全局配置,即不指定consumer-service-name,为所有消费端配置相同情形的权重。当局部配置和全局配置同时存在的时候,以局部配置优先 -->
<!-- 3. 尽量为线上所有版本都赋予权重值 -->
<!-- 全局版本权重策略介绍 -->
<!-- 1. 标准配置,举例如下 -->
<!-- <version provider-weight-value="1.0=85;1.1=15"/> 表示版本为1.0的服务提供85%的权重流量,版本为1.1的服务提供15%的权重流量 -->
<!-- 2. 全局版本权重可以切换整条调用链的权重配比 -->
<!-- 3. 尽量为线上所有版本都赋予权重值 -->
<!-- 区域权重策略介绍 -->
<!-- 1. 标准配置,举例如下 -->
<!-- <service consumer-service-name="a" provider-service-name="b" provider-weight-value="dev=85;qa=15"/> 表示消费端访问提供端的时候,区域为dev的服务提供85%的权重流量,区域为qa的服务提供15%的权重流量 -->
<!-- <service provider-service-name="b" provider-weight-value="dev=85;qa=15"/> 表示所有消费端访问提供端的时候,区域为dev的服务提供85%的权重流量,区域为qa的服务提供15%的权重流量 -->
<!-- 2. 局部配置,即指定consumer-service-name,专门为该消费端配置权重。全局配置,即不指定consumer-service-name,为所有消费端配置相同情形的权重。当局部配置和全局配置同时存在的时候,以局部配置优先 -->
<!-- 3. 尽量为线上所有版本都赋予权重值 -->
<!-- 全局区域权重策略介绍 -->
<!-- 1. 标准配置,举例如下 -->
<!-- <region provider-weight-value="dev=85;qa=15"/> 表示区域为dev的服务提供85%的权重流量,区域为qa的服务提供15%的权重流量 -->
<!-- 2. 全局区域权重可以切换整条调用链的权重配比 -->
<!-- 3. 尽量为线上所有区域都赋予权重值 -->
<weight>
<!-- 权重流量配置有如下六种方式,优先级分别是由高到底,即先从第一种方式取权重流量值,取不到则到第二种方式取值,以此类推,最后仍取不到则忽略。使用者按照实际情况,选择一种即可 -->
<!-- 表示消费端服务b访问提供端服务c的时候,提供端服务c的1.0版本提供90%的权重流量,1.1版本提供10%的权重流量 -->
<service consumer-service-name="discovery-springcloud-example-b" provider-service-name="discovery-springcloud-example-c" provider-weight-value="1.0=90;1.1=10" type="version"/>
<!-- 表示所有消费端服务访问提供端服务c的时候,提供端服务c的1.0版本提供90%的权重流量,1.1版本提供10%的权重流量 -->
<service provider-service-name="discovery-springcloud-example-c" provider-weight-value="1.0=90;1.1=10" type="version"/>
<!-- 表示所有版本为1.0的服务提供90%的权重流量,版本为1.1的服务提供10%的权重流量 -->
<version provider-weight-value="1.0=90;1.1=10"/>
<!-- 表示消费端服务b访问提供端服务c的时候,提供端服务c的dev区域提供85%的权重流量,qa区域提供15%的权重流量 -->
<service consumer-service-name="discovery-springcloud-example-b" provider-service-name="discovery-springcloud-example-c" provider-weight-value="dev=85;qa=15" type="region"/>
<!-- 表示所有消费端服务访问提供端服务c的时候,提供端服务c的dev区域提供85%的权重流量,qa区域提供15%的权重流量 -->
<service provider-service-name="discovery-springcloud-example-c" provider-weight-value="dev=85;qa=15" type="region"/>
<!-- 表示所有区域为dev的服务提供85%的权重流量,区域为qa的服务提供15%的权重流量 -->
<region provider-weight-value="dev=85;qa=15"/>
</weight>
</discovery>
<!-- 基于Http Header传递的策略路由,全局缺省路由(第三优先级) -->
<strategy>
<!-- 版本路由 -->
<version>{"discovery-springcloud-example-a":"1.0", "discovery-springcloud-example-b":"1.0", "discovery-springcloud-example-c":"1.0;1.2"}</version>
<!-- <version>1.0</version> -->
<!-- 区域路由 -->
<region>{"discovery-springcloud-example-a":"qa;dev", "discovery-springcloud-example-b":"dev", "discovery-springcloud-example-c":"qa"}</region>
<!-- <region>dev</region> -->
<!-- IP地址和端口路由 -->
<address>{"discovery-springcloud-example-a":"192.168.43.101:1100", "discovery-springcloud-example-b":"192.168.43.101:1201", "discovery-springcloud-example-c":"192.168.43.101:1300"}</address>
<!-- 权重流量配置有如下四种方式,优先级分别是由高到底,即先从第一种方式取权重流量值,取不到则到第二种方式取值,以此类推,最后仍取不到则忽略。使用者按照实际情况,选择一种即可 -->
<!-- 版本权重路由 -->
<version-weight>{"discovery-springcloud-example-a":"1.0=90;1.1=10", "discovery-springcloud-example-b":"1.0=90;1.1=10", "discovery-springcloud-example-c":"1.0=90;1.1=10"}</version-weight>
<!-- <version-weight>1.0=90;1.1=10</version-weight> -->
<!-- 区域权重路由 -->
<region-weight>{"discovery-springcloud-example-a":"dev=85;qa=15", "discovery-springcloud-example-b":"dev=85;qa=15", "discovery-springcloud-example-c":"dev=85;qa=15"}</region-weight>
<!-- <region-weight>dev=85;qa=15</region-weight> -->
</strategy>
<!-- 基于Http Header传递的策略路由,支持蓝绿发布和灰度发布两种模式。如果都不命中,则执行上面的全局缺省路由 -->
<strategy-release>
<!-- Spel表达式在XML中的转义符:-->
<!-- 和符号 & 转义为 & 必须转义 -->
<!-- 小于号 < 转义为 < 必须转义 -->
<!-- 双引号 " 转义为 " 必须转义 -->
<!-- 大于号 > 转义为 > -->
<!-- 单引号 ' 转义为 ' -->
<!-- 全链路蓝绿发布:条件命中的匹配方式(第一优先级),支持版本匹配、区域匹配、IP地址和端口匹配、版本权重匹配、区域权重匹配 -->
<!-- Expression节点允许缺失,当含Expression和未含Expression的配置并存时,以含Expression的配置为优先 -->
<conditions type="blue-green">
<condition id="1" expression="#H['a'] == '1' and #H['b'] == '2'" version-id="a-1" region-id="b-1" address-id="c-1" version-weight-id="d-1" region-weight-id="e-1"/>
<condition id="2" expression="#H['c'] == '3'" version-id="a-2" region-id="b-2" address-id="c-2" version-weight-id="d-2" region-weight-id="e-2"/>
<condition id="3" version-id="a-2" region-id="b-2" address-id="c-2" version-weight-id="d-2" region-weight-id="e-2"/>
</conditions>
<!-- 全链路灰度发布:条件命中的随机权重(第二优先级),支持版本匹配、区域匹配、IP地址和端口匹配 -->
<!-- Expression节点允许缺失,当含Expression和未含Expression的配置并存时,以含Expression的配置为优先 -->
<conditions type="gray">
<condition id="1" expression="#H['a'] == '1' and #H['b'] == '2'" version-id="a-1=10;a-2=90" region-id="b-1=20;b-2=80" address-id="c-1=30;c-2=70"/>
<condition id="2" expression="#H['c'] == '3'" version-id="a-1=90;a-2=10" region-id="b-1=80;b-2=20" address-id="c-1=70;c-2=30"/>
<condition id="3" version-id="a-1=5;a-2=95" region-id="b-1=5;b-2=95" address-id="c-1=5;c-2=95"/>
</conditions>
<routes>
<route id="a-1" type="version">{"discovery-springcloud-example-a":"1.0", "discovery-springcloud-example-b":"1.0", "discovery-springcloud-example-c":"1.0;1.2"}</route>
<route id="a-2" type="version">{"discovery-springcloud-example-a":"1.1", "discovery-springcloud-example-b":"1.1", "discovery-springcloud-example-c":"1.2"}</route>
<route id="b-1" type="region">{"discovery-springcloud-example-a":"qa;dev", "discovery-springcloud-example-b":"dev", "discovery-springcloud-example-c":"qa"}</route>
<route id="b-2" type="region">{"discovery-springcloud-example-a":"qa", "discovery-springcloud-example-b":"qa", "discovery-springcloud-example-c":"qa"}</route>
<route id="c-1" type="address">{"discovery-springcloud-example-a":"192.168.43.101:1100", "discovery-springcloud-example-b":"192.168.43.101:1201", "discovery-springcloud-example-c":"192.168.43.101:1300"}</route>
<route id="c-2" type="address">{"discovery-springcloud-example-a":"192.168.43.101:1101", "discovery-springcloud-example-b":"192.168.43.101:1201", "discovery-springcloud-example-c":"192.168.43.101:1301"}</route>
<route id="d-1" type="version-weight">{"discovery-springcloud-example-a":"1.0=90;1.1=10", "discovery-springcloud-example-b":"1.0=90;1.1=10", "discovery-springcloud-example-c":"1.0=90;1.1=10"}</route>
<route id="d-2" type="version-weight">{"discovery-springcloud-example-a":"1.0=10;1.1=90", "discovery-springcloud-example-b":"1.0=10;1.1=90", "discovery-springcloud-example-c":"1.0=10;1.1=90"}</route>
<route id="e-1" type="region-weight">{"discovery-springcloud-example-a":"dev=85;qa=15", "discovery-springcloud-example-b":"dev=85;qa=15", "discovery-springcloud-example-c":"dev=85;qa=15"}</route>
<route id="e-2" type="region-weight">{"discovery-springcloud-example-a":"dev=15;qa=85", "discovery-springcloud-example-b":"dev=15;qa=85", "discovery-springcloud-example-c":"dev=15;qa=85"}</route>
</routes>
<!-- 策略中配置条件表达式中的Header来决策蓝绿和灰度,可以代替外部传入Header -->
<header>{"a":"1", "b":"2", "c":"3"}</header>
</strategy-release>
<!-- 策略路由上服务容灾名单 -->
<strategy-failover>
<!-- 版本偏好,非蓝绿灰度发布场景下,路由到指定版本的实例 -->
<version-prefer>{"discovery-springcloud-example-a":"1.0", "discovery-springcloud-example-b":"1.0", "discovery-springcloud-example-c":"1.0"}</version-prefer>
<!-- 版本故障转移,无法找到相应版本的服务实例,路由到指定版本的实例 -->
<version-failover>{"discovery-springcloud-example-a":"1.1", "discovery-springcloud-example-b":"1.1", "discovery-springcloud-example-c":"1.1"}</version-failover>
<!-- 区域调试转移,跨区调试路由到指定区域的实例 -->
<region-transfer>qa</region-transfer>
<!-- 区域故障转移,无法找到相应区域的服务实例,路由到指定区域的实例 -->
<region-failover>dev</region-failover>
<!-- 环境故障转移,无法找到相应环境的服务实例,路由到指定环境的实例 -->
<env-failover>env1;default</env-failover>
<!-- 可用区故障转移,无法找到相应可用区的服务实例,路由到指定可用区的实例 -->
<zone-failover>zone1;default</zone-failover>
<!-- IP地址和端口故障转移,无法找到相应IP地址和端口的服务实例,路由到指定IP地址和端口的实例 -->
<address-failover>*1</address-failover>
</strategy-failover>
<!-- 策略路由上服务屏蔽黑名单。一般适用于服务下线场景,流量实现实时性的绝对无损:下线之前,把服务实例添加到下面屏蔽名单中,负载均衡不会去寻址该服务实例。下线之后,清除该名单 -->
<strategy-blacklist>
<!-- 通过全局唯一ID进行屏蔽,ID对应于元数据spring.application.uuid字段 -->
<id>{"discovery-springcloud-example-a":"20210601-222214-909-1146-372-698", "discovery-springcloud-example-b":"20210601-222623-277-4978-633-279", "discovery-springcloud-example-c":"20210601-222728-133-2597-222-609"}</id>
<!-- 通过IP地址或者端口或者IP地址+端口进行屏蔽 -->
<address>{"discovery-springcloud-example-a":"192.168.43.101:1100", "discovery-springcloud-example-b":"192.168.43.101:1201", "discovery-springcloud-example-c":"192.168.43.101:1300"}</address>
</strategy-blacklist>
<!-- 参数控制,由远程推送参数的改变,实现一些特色化的蓝绿发布,例如,基于数据库和消息队列的蓝绿发布 -->
<parameter>
<!-- 服务a在版本为1.0的时候,数据库的数据源指向db1;服务a在版本为1.1的时候,数据库的数据源指向db2 -->
<!-- 服务b在区域为dev的时候,消息队列指向queue1;服务b在区域为dev的时候,消息队列指向queue2 -->
<!-- 服务c在环境为env1的时候,数据库的数据源指向db1;服务c在环境为env2的时候,数据库的数据源指向db2 -->
<!-- 服务d在可用区为zone1的时候,消息队列指向queue1;服务d在可用区为zone2的时候,消息队列指向queue2 -->
<!-- 服务c在IP地址和端口为192.168.43.101:1201的时候,数据库的数据源指向db1;服务c在IP地址和端口为192.168.43.102:1201的时候,数据库的数据源指向db2 -->
<service service-name="discovery-springcloud-example-a" tag-key="version" tag-value="1.0" key="ShardingSphere" value="db1"/>
<service service-name="discovery-springcloud-example-a" tag-key="version" tag-value="1.1" key="ShardingSphere" value="db2"/>
<service service-name="discovery-springcloud-example-b" tag-key="region" tag-value="dev" key="RocketMQ" value="queue1"/>
<service service-name="discovery-springcloud-example-b" tag-key="region" tag-value="qa" key="RocketMQ" value="queue2"/>
<service service-name="discovery-springcloud-example-c" tag-key="env" tag-value="env1" key="ShardingSphere" value="db1"/>
<service service-name="discovery-springcloud-example-c" tag-key="env" tag-value="env2" key="ShardingSphere" value="db2"/>
<service service-name="discovery-springcloud-example-d" tag-key="zone" tag-value="zone1" key="RocketMQ" value="queue1"/>
<service service-name="discovery-springcloud-example-d" tag-key="zone" tag-value="zone2" key="RocketMQ" value="queue2"/>
<service service-name="discovery-springcloud-example-e" tag-key="address" tag-value="192.168.43.101:1201" key="ShardingSphere" value="db1"/>
<service service-name="discovery-springcloud-example-e" tag-key="address" tag-value="192.168.43.102:1201" key="ShardingSphere" value="db2"/>
</parameter>
</rule>
Apollo订阅推送界面
① 参考Apollo官方文档https://github.com/ctripcorp/apollo相关文档,搭建Apollo环境,以及熟悉相关的基本操作
② 根据上图,做如下步骤操作
- 设置页面中AppId和配置文件里面app.id一致
- 设置页面中Namespace和配置文件里面apollo.namespace一致,如果配置文件里不设置,那么页面默认采用内置的application
- 在页面中添加配置
- 局部配置方式:一个服务集群(eureka.instance.metadataMap.group和spring.application.name都相同的服务)对应一个配置文件,通过group+serviceId方式添加,Key为group-serviceId,Value为Xml或者Json格式的规则策略内容。group取值于配置文件里的eureka.instance.metadataMap.group配置项,serviceId取值于spring.application.name配置项目
- 全局配置方式:一组服务集群(eureka.instance.metadataMap.group相同,但spring.application.name可以不相同的服务)对应一个配置文件,通过group方式添加,Key为group-group,Value为Xml或者Json格式的规则内容。group取值于配置文件里的eureka.instance.metadataMap.group配置项
- 其他更多参数,例如evn, cluster等,请自行参考Apollo官方文档,保持一致
③ 需要注意
- 局部配置方式建议使用Apollo的私有(private)配置方式,全局配置方式必须采用Apollo的共享(public)配置方式
- 如果业务配置和蓝绿灰度配置在同一个namespace里且namespace只有一个,蓝绿灰度配置可以通过apollo.bootstrap.namespaces或者apollo.namespace来指定(如果namespace为application则都不需要配置)
- 如果业务配置和蓝绿灰度配置不在同一个namespace里或者业务配置横跨几个namespace,蓝绿灰度配置必须通过apollo.namespace来指定唯一的namespace
Nacos订阅推送界面
-
参考Nacos官方文档https://github.com/alibaba/nacos相关文档,搭建Nacos环境,以及熟悉相关的基本操作
-
添加配置步骤跟Apollo配置界面中的【在页面中添加配置】操作项相似
Redis订阅推送界面
Consul订阅推送界面
Etcd订阅推送界面
Zookeeper订阅推送界面
略
服务侧单个推送界面
控制平台批量推送界面
除了提供基本的Swagger功能之外,内置模块还对使用者提供扩展
- 自定义Swagger接口利用内置的SwaggerConfiguration来初始化,这样使用者可以不需要定义自己的SwaggerConfiguration。通过如下配置实现
swagger.service.scan.group=your-scan-group
swagger.service.scan.packages=your-scan-packages
- 自定义内置的基准Docket组名。通过如下配置实现
swagger.service.base.group=your-base-group
- 自定义覆盖内置的Swagger配置。通过如下配置实现
# 启动和关闭Swagger。缺失则默认为true
swagger.service.enabled=true
# Swagger基准Docket组名
swagger.service.base.group=Nepxion Discovery
# Swagger自定义Docket组名
swagger.service.scan.group=Admin Center Restful APIs
# Swagger自定义扫描目录
swagger.service.scan.packages=your-scan-packages
# Swagger描述
swagger.service.description=your-description
# Swagger版本
swagger.service.version=6.11.0
# Swagger License名称
swagger.service.license.name=Apache License 2.0
# Swagger License链接
swagger.service.license.url=http://www.apache.org/licenses/LICENSE-2.0
# Swagger联系人名称
swagger.service.contact.name=Nepxion
# Swagger联系人网址
swagger.service.contact.url=https://github.com/Nepxion/Discovery
# Swagger联系人邮件
[email protected]
# Swagger服务条件网址
swagger.service.termsOfService.url=http://www.nepxion.com
- 自定义基于Access Token Header的Swagger授权,包括全局授权和接口级授权。使用者通过如下方式进行扩展支持,可以选择其中一种,也可以两种并存。当两种并存的时候,全局授权优先于接口级授权
@Configuration
@ConditionalOnProperty(value = DiscoverySwaggerConstant.SWAGGER_SERVICE_ENABLED, matchIfMissing = true)
public class SwaggerAutoConfiguration {
// Access Token Header全局授权
@Bean
public List<SecurityScheme> swaggerSecuritySchemes() {
return Collections.singletonList(new ApiKey(DiscoveryConstant.N_D_ACCESS_TOKEN, DiscoveryConstant.N_D_ACCESS_TOKEN, "header"));
}
@Bean
public List<SecurityContext> swaggerSecurityContexts() {
return Collections.singletonList(
SecurityContext
.builder()
.securityReferences(Collections.singletonList(new SecurityReference(DiscoveryConstant.N_D_ACCESS_TOKEN, scopes())))
.forPaths(PathSelectors.any())
.build());
}
private AuthorizationScope[] scopes() {
return new AuthorizationScope[] { new AuthorizationScope("global", "accessAnything") };
}
// Access Token Header接口级授权
@Bean
public List<Parameter> swaggerHeaderParameters() {
return Collections.singletonList(
new ParameterBuilder()
.name(DiscoveryConstant.N_D_ACCESS_TOKEN)
.description("Access Token。格式:" + DiscoveryConstant.BEARER + "空格${access-token}。当全局授权(Authorize)后,此处不必填写")
.modelRef(new ModelRef("string"))
.parameterType("header")
.defaultValue(DiscoveryConstant.BEARER + " ${access-token}")
.required(false)
.build());
}
}
把SwaggerAutoConfiguration加入到src/main/resources/META-INF/spring.factories进行自动装配
统一注册中心配置方式,适用于所有注册中心
spring.cloud.discovery.metadata.group=xxx-service-group
spring.cloud.discovery.metadata.version=1.0
spring.cloud.discovery.metadata.region=dev
spring.cloud.discovery.metadata.env=env1
spring.cloud.discovery.metadata.zone=zone1
spring.cloud.discovery.metadata.active=true
不同注册中心原生配置方式
# Eureka config for discovery
eureka.instance.metadataMap.group=xxx-service-group
eureka.instance.metadataMap.version=1.0
eureka.instance.metadataMap.region=dev
eureka.instance.metadataMap.env=env1
eureka.instance.metadataMap.zone=zone1
eureka.instance.metadataMap.active=true
# Consul config for discovery
# 参考https://springcloud.cc/spring-cloud-consul.html - 元数据和Consul标签
spring.cloud.consul.discovery.tags=group=xxx-service-group,version=1.0,region=dev,env=env1,zone=zone1,active=true
# Zookeeper config for discovery
spring.cloud.zookeeper.discovery.metadata.group=xxx-service-group
spring.cloud.zookeeper.discovery.metadata.version=1.0
spring.cloud.zookeeper.discovery.metadata.region=dev
spring.cloud.zookeeper.discovery.metadata.env=env1
spring.cloud.zookeeper.discovery.metadata.zone=zone1
spring.cloud.zookeeper.discovery.metadata.active=true
# Nacos config for discovery
spring.cloud.nacos.discovery.metadata.group=xxx-service-group
spring.cloud.nacos.discovery.metadata.version=1.0
spring.cloud.nacos.discovery.metadata.region=dev
spring.cloud.nacos.discovery.metadata.env=env1
spring.cloud.nacos.discovery.metadata.zone=zone1
spring.cloud.nacos.discovery.metadata.active=true
对于Spring Cloud 202x版,由于它重构了Consul元数据的方式,需要通过如下方式配置
# Consul config for discovery
spring.cloud.consul.discovery.metadata.group=xxx-service-group
spring.cloud.consul.discovery.metadata.version=1.0
spring.cloud.consul.discovery.metadata.region=dev
spring.cloud.consul.discovery.metadata.env=env1
spring.cloud.consul.discovery.metadata.zone=zone1
spring.cloud.consul.discovery.metadata.active=true
对于用户自定义的Consul元数据的Key,不能带有包含“.”,“@”等字符,否则服务无法启动,但允许包含“_”,“-”等字符,参考如下配置
# 合法格式
spring.cloud.consul.discovery.metadata.my_data=abc
spring.cloud.consul.discovery.metadata.my-data=abc
# 非法格式
spring.cloud.consul.discovery.metadata.my.data=abc
spring.cloud.consul.discovery.metadata.my@data=abc
① 注册中心配置
- Nacos注册中心配置
# Nacos config for discovery
spring.cloud.nacos.discovery.server-addr=localhost:8848
# spring.cloud.nacos.discovery.namespace=discovery
- Eureka注册中心配置
# Eureka config for discovery
eureka.client.serviceUrl.defaultZone=http://localhost:9528/eureka/
eureka.instance.preferIpAddress=true
- Consul注册中心配置
# Consul config for discovery
spring.cloud.consul.host=localhost
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.preferIpAddress=true
- Zookeeper注册中心配置
# Zookeeper config for discovery
spring.cloud.zookeeper.connectString=localhost:2181
spring.cloud.zookeeper.discovery.instancePort=${server.port}
spring.cloud.zookeeper.discovery.root=/spring-cloud
spring.cloud.zookeeper.discovery.preferIpAddress=true
② 配置中心配置
蓝绿灰度发布专用配置
- Apollo配置中心配置
# Apollo config for rule
app.id=discovery
apollo.meta=http://localhost:8080
# apollo.namespace=application
- Nacos配置中心配置
# Nacos config for rule
spring.cloud.nacos.config.server-addr=localhost:8848
# spring.cloud.nacos.config.namespace=application
或者
# Nacos config for rule
nacos.server-addr=localhost:8848
# nacos.namespace=application
- Redis配置中心配置
# Redis config for rule
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
- Zookeeper配置中心配置
# Zookeeper config for rule
zookeeper.connect-string=localhost:2181
zookeeper.retry-count=3
zookeeper.sleep-time=3000
- Consul配置中心配置
# Consul config for rule
consul.host=localhost
consul.port=8500
consul.timeout=1
consul.token=
- Etcd配置中心配置
# Etcd config for rule
etcd.server.addr=http://localhost:2379
etcd.username=
etcd.password=
③ 监控中心配置
- OpenTracing + Jaeger监控中心配置
# OpenTracing config for jaeger
opentracing.jaeger.enabled=true
opentracing.jaeger.http-sender.url=http://localhost:14268/api/traces
- SkyWalking监控中心配置
-javaagent:C:/opt/skywalking-agent/skywalking-agent.jar -Dskywalking.agent.service_name=discovery-guide-service-a
- Spring Boot Admin监控中心配置
# Spring boot admin config
spring.boot.admin.client.instance.prefer-ip=true
spring.boot.admin.client.url=http://localhost:9728
④ 异步跨线程Agent配置
-javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.nepxion.discovery.guide.service.feign
① 服务端配置
# Plugin core config
# 开启和关闭服务注册层面的控制。一旦关闭,服务注册的黑/白名单过滤功能将失效,最大注册数的限制过滤功能将失效。缺失则默认为true
spring.application.register.control.enabled=true
# 开启和关闭服务发现层面的控制。一旦关闭,服务多版本调用的控制功能将失效,动态屏蔽指定IP地址的服务实例被发现的功能将失效。缺失则默认为true
spring.application.discovery.control.enabled=true
# 开启和关闭通过Rest方式对规则配置的控制和推送。一旦关闭,只能通过远程配置中心来控制和推送。缺失则默认为true
spring.application.config.rest.control.enabled=true
# 随机权重算法。缺失则默认为MapWeightRandom
spring.application.weight.random.type=MapWeightRandom
# 规则文件的格式,支持xml和json。缺失则默认为xml
spring.application.config.format=xml
# spring.application.config.format=json
# 本地规则文件的路径,支持两种方式:classpath:rule.xml(rule.json) - 规则文件放在resources目录下,便于打包进jar;file:rule.xml(rule.json) - 规则文件放在工程根目录下,放置在外部便于修改。缺失则默认为不装载本地规则
spring.application.config.path=classpath:rule.xml
# spring.application.config.path=classpath:rule.json
# 为微服务归类的Key,一般通过group字段来归类,例如eureka.instance.metadataMap.group=xxx-group或者eureka.instance.metadataMap.application=xxx-application。缺失则默认为group
spring.application.group.key=group
# spring.application.group.key=application
# 业务系统希望大多数时候Spring、SpringBoot或者SpringCloud的基本配置、调优参数(非业务系统配置参数),不配置在业务端,集成到基础框架里。但特殊情况下,业务系统有时候也希望能把基础框架里配置的参数给覆盖掉,用他们自己的配置
# 对于此类型的配置需求,可以配置在下面的配置文件里。该文件一般放在resource目录下。缺失则默认为spring-application-default.properties
spring.application.default.properties.path=spring-application-default.properties
# 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试。缺失则默认为false
spring.application.no.servers.retry.enabled=false
# 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的次数。缺失则默认为5
spring.application.no.servers.retry.times=5
# 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的时间间隔。缺失则默认为2000
spring.application.no.servers.retry.await.time=2000
# 负载均衡下,消费端尝试获取对应提供端服务实例列表为空的时候,通过日志方式通知。缺失则默认为false
spring.application.no.servers.notify.enabled=false
# 由于Nacos注册中心会自动把服务名处理成GROUP@@SERVICE_ID的格式,导致根据服务名去获取元数据的时候会找不到。通过如下开关开启是否要过滤掉GROUP前缀。缺失则默认为true
spring.application.nacos.service.id.filter.enabled=true
# 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true
spring.application.parameter.event.onstart.enabled=true
# Plugin strategy config
# 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true
spring.application.strategy.zone.avoidance.rule.enabled=true
# 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true
spring.application.strategy.rest.intercept.enabled=true
# 启动和关闭路由策略的时候,对REST方式在异步调用场景下在服务端的Request请求的装饰,当主线程先于子线程执行完的时候,Request会被Destory,导致Header仍旧拿不到,开启装饰,就可以确保拿到。缺失则默认为true
spring.application.strategy.rest.request.decorator.enabled=true
# 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.rest.intercept.debug.enabled=true
# 路由策略过滤器的执行顺序(Order)。缺失则默认为0
spring.application.strategy.service.route.filter.order=0
# 当外界传值Header的时候,服务也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以服务设置为优先,否则以外界传值为优先。缺失则默认为true
spring.application.strategy.service.header.priority=true
# 启动和关闭Feign上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Heade
spring.application.strategy.feign.core.header.transmission.enabled=true
# 启动和关闭RestTemplate上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.rest.template.core.header.transmission.enabled=true
# 启动和关闭WebClient上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.web.client.core.header.transmission.enabled=true
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.context.request.headers=trace-id;span-id
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于业务系统自定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.business.request.headers=token
# 路由策略的时候,执行请求过滤,对指定包含的URI字段进行排除。缺失则默认为/actuator/,如果多个用“;”分隔,不允许出现空格
spring.application.strategy.uri.filter.exclusion=/actuator/
# 启动和关闭路由策略的时候,对RPC方式的调用拦截。缺失则默认为false
spring.application.strategy.rpc.intercept.enabled=true
# 路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能
spring.application.strategy.scan.packages=com.nepxion.discovery.plugin.example.service.feign
# 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false
spring.application.strategy.monitor.enabled=true
# 启动和关闭告警,一旦关闭,蓝绿灰度上下文输出都将关闭。缺失则默认为false
spring.application.strategy.alarm.enabled=true
# 启动和关闭日志输出。缺失则默认为false
spring.application.strategy.logger.enabled=true
# 日志输出中,是否显示MDC前面的Key。缺失则默认为true
spring.application.strategy.logger.mdc.key.shown=true
# 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.logger.debug.enabled=true
# 启动和关闭调用链输出。缺失则默认为false
spring.application.strategy.tracer.enabled=true
# 启动和关闭调用链的蓝绿灰度信息以独立的Span节点输出,如果关闭,则蓝绿灰度信息输出到原生的Span节点中(SkyWalking不支持原生模式)。缺失则默认为true
spring.application.strategy.tracer.separate.span.enabled=true
# 启动和关闭调用链的蓝绿灰度规则策略信息输出。缺失则默认为true
spring.application.strategy.tracer.rule.output.enabled=true
# 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false
spring.application.strategy.tracer.exception.detail.output.enabled=false
# 启动和关闭类方法上入参和出参输出到调用链。缺失则默认为false
spring.application.strategy.tracer.method.context.output.enabled=false
# 显示在调用链界面上蓝绿灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION
spring.application.strategy.tracer.span.value=NEPXION
# 显示在调用链界面上蓝绿灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery
spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery
# 启动和关闭Sentinel调用链上规则在Span上的输出。缺失则默认为true
spring.application.strategy.tracer.sentinel.rule.output.enabled=true
# 启动和关闭Sentinel调用链上方法入参在Span上的输出。缺失则默认为false
spring.application.strategy.tracer.sentinel.args.output.enabled=false
# 启动和关闭Sentinel Metric通过次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.pass.qps.output.enabled=true
# 启动和关闭Sentinel Metric阻塞次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.block.qps.output.enabled=true
# 启动和关闭Sentinel Metric成功次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.success.qps.output.enabled=true
# 启动和关闭Sentinel Metric异常次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.exception.qps.output.enabled=true
# 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展。缺失则默认为false
spring.application.strategy.sentinel.datasource.enabled=true
# 流控规则文件路径。缺失则默认为classpath:sentinel-flow.json
spring.application.strategy.sentinel.flow.path=classpath:sentinel-flow.json
# 降级规则文件路径。缺失则默认为classpath:sentinel-degrade.json
spring.application.strategy.sentinel.degrade.path=classpath:sentinel-degrade.json
# 授权规则文件路径。缺失则默认为classpath:sentinel-authority.json
spring.application.strategy.sentinel.authority.path=classpath:sentinel-authority.json
# 系统规则文件路径。缺失则默认为classpath:sentinel-system.json
spring.application.strategy.sentinel.system.path=classpath:sentinel-system.json
# 热点参数流控规则文件路径。缺失则默认为classpath:sentinel-param-flow.json
spring.application.strategy.sentinel.param.flow.path=classpath:sentinel-param-flow.json
# 启动和关闭Sentinel LimitApp高级限流熔断功能。缺失则默认为false
spring.application.strategy.sentinel.limit.app.enabled=false
# 执行Sentinel LimitApp高级限流熔断时候,以Http请求中的Header值作为关键Key。缺失则默认为n-d-service-id,即以服务名作为关键Key
spring.application.strategy.sentinel.request.origin.key=n-d-service-id
# 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止蓝绿灰度版本发布人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损
# 在开启版本故障转移的开关前提下,故障转移有三种策略:
# 1. 如果“version-failover”值已配置,指定版本的故障转移,即找不到实例的时候,直接路由到该版本实例
# 2. 如果“version-failover”值未配置
# 2.1 开启“version.failover.stable.enabled”开关,版本列表排序策略的(取最老的稳定版本的实例)故障转移,即找不到实例的时候,直接路由到最老的稳定版本的实例
# 2.2 关闭“version.failover.stable.enabled”开关,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.enabled=true
# 开启和关闭版本列表排序策略下取稳定版本的版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.stable.enabled=true
# 版本偏好,即非蓝绿灰度发布场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施蓝绿灰度版本发布产生混乱,对处于非蓝绿灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;蓝绿灰度状态的服务,还是根据传递的Header版本号进行匹配
# 在开启版本偏好的开关前提下,偏好有两种策略:
# 1. 如果“version-prefer”值已配置,指定版本的偏好,即不管存在多少版本,直接路由到该版本实例
# 2. 如果“version-prefer”值未配置,版本列表排序策略的(取最老的稳定版本的实例)偏好,即不管存在多少版本,直接路由到最老的稳定版本的实例
# 启动和关闭版本偏好。缺失则默认为false
spring.application.strategy.version.prefer.enabled=true
# 区域调试转移,即当未对服务指定访问区域的时候,转移到事先指定的区域
# 使用场景示例:
# 开发环境(个人电脑环境)在测试环境(线上环境)进行联调
# 访问路径为A服务 -> B服务 -> C服务,A服务和B服务在开发环境上,C服务在测试环境上
# 调用时候,在B服务上进行如下两个配置,并在最前端传入的Header(n-d-region)指定为B的开发环境区域(用来保证A服务和B服务只在开发环境调用),而B服务会自动转移调用到测试环境上的C服务实例,但不会转移到其它个人电脑的C服务实例
# 该功能的意义,个人电脑环境可以接入到测试环境联调,当多套个人环境接入时候,可以保护不同的个人环境间不会彼此调用
# 通过“region-transfer”值进行区域转移值配置,如果缺失,则报错
# 启动和关闭区域调试转移。缺失则默认为false
spring.application.strategy.region.transfer.enabled=true
# 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.consumer.isolation.enabled=true
# 启动和关闭提供端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.provider.isolation.enabled=true
# 在开启区域故障转移的开关前提下,故障转移有两种策略:
# 1. 如果“region-failover”值已配置,指定区域的故障转移,即找不到实例的时候,直接路由到该区域实例
# 2. 如果“region-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭区域故障转移。缺失则默认为false
spring.application.strategy.region.failover.enabled=true
# 启动和关闭环境故障转移。缺失则默认为false
# 如果“env-failover”值未配置,则默认为common
spring.application.strategy.environment.failover.enabled=true
# 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false
spring.application.strategy.zone.affinity.enabled=true
# 在开启可用区故障转移的开关前提下,故障转移有两种策略:
# 1. 如果“zone-failover”值已配置,指定可用区的故障转移,即找不到实例的时候,直接路由到该可用区实例
# 2. 如果“zone-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭可用区故障转移。缺失则默认为false
spring.application.strategy.zone.failover.enabled=true
# 在开启IP地址和端口故障转移的开关前提下,故障转移有两种策略:
# 1. 如果“address-failover”值已配置,指定IP地址或者端口的故障转移,即找不到实例的时候,直接路由到该IP地址或者端口实例
# 2. 如果“address-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭IP地址和端口故障转移。缺失则默认为false
spring.application.strategy.address.failover.enabled=true
# 启动和关闭自动扫描目录,当扫描目录未人工配置的时候,可以通过自动扫描方式决定扫描目录。缺失则默认为true
spring.application.strategy.auto.scan.packages.enabled=true
# 启动和关闭嵌套扫描,嵌套扫描指扫描非本工程下外部包的目录,可以支持多层嵌套。缺失则默认为false
spring.application.strategy.auto.scan.recursion.enabled=false
# 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false
spring.application.group.generator.enabled=true
# 服务名前缀的截断长度,必须大于0
spring.application.group.generator.length=15
# 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式
spring.application.group.generator.character=-
# 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false
spring.application.git.generator.enabled=true
# 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties
spring.application.git.generator.path=classpath:git.properties
# spring.application.git.generator.path=classpath:git.json
# 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count}
spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time}
# spring.application.git.version.key={git.build.version}-{git.commit.time}
# 开启和关闭Hystrix线程隔离模式做服务隔离时,对线程切换上下文传递的功能。缺失则默认为false
# Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失
spring.application.strategy.hystrix.threadlocal.supported=true
# 启动和关闭Swagger。缺失则默认为true
swagger.service.enabled=true
# Swagger基准Docket组名
swagger.service.base.group=Nepxion Discovery
# Swagger自定义Docket组名
swagger.service.scan.group=Admin Center Restful APIs
# Swagger自定义扫描目录
swagger.service.scan.packages=your-scan-packages
# Swagger描述
swagger.service.description=your-description
# Swagger版本
swagger.service.version=6.11.0
# Swagger License名称
swagger.service.license.name=Apache License 2.0
# Swagger License链接
swagger.service.license.url=http://www.apache.org/licenses/LICENSE-2.0
# Swagger联系人名称
swagger.service.contact.name=Nepxion
# Swagger联系人网址
swagger.service.contact.url=https://github.com/Nepxion/Discovery
# Swagger联系人邮件
[email protected]
# Swagger服务条件网址
swagger.service.termsOfService.url=http://www.nepxion.com
② Spring Cloud Gateway端配置
# Plugin core config
# 开启和关闭服务注册层面的控制。一旦关闭,服务注册的黑/白名单过滤功能将失效,最大注册数的限制过滤功能将失效。缺失则默认为true
spring.application.register.control.enabled=true
# 开启和关闭服务发现层面的控制。一旦关闭,服务多版本调用的控制功能将失效,动态屏蔽指定IP地址的服务实例被发现的功能将失效。缺失则默认为true
spring.application.discovery.control.enabled=true
# 开启和关闭通过Rest方式对规则配置的控制和推送。一旦关闭,只能通过远程配置中心来控制和推送。缺失则默认为true
spring.application.config.rest.control.enabled=true
# 随机权重算法。缺失则默认为MapWeightRandom
spring.application.weight.random.type=MapWeightRandom
# 规则文件的格式,支持xml和json。缺失则默认为xml
spring.application.config.format=xml
# spring.application.config.format=json
# 本地规则文件的路径,支持两种方式:classpath:rule.xml(rule.json) - 规则文件放在resources目录下,便于打包进jar;file:rule.xml(rule.json) - 规则文件放在工程根目录下,放置在外部便于修改。缺失则默认为不装载本地规则
spring.application.config.path=classpath:rule.xml
# spring.application.config.path=classpath:rule.json
# 为微服务归类的Key,一般通过group字段来归类,例如eureka.instance.metadataMap.group=xxx-group或者eureka.instance.metadataMap.application=xxx-application。缺失则默认为group
spring.application.group.key=group
# spring.application.group.key=application
# 业务系统希望大多数时候Spring、SpringBoot或者SpringCloud的基本配置、调优参数(非业务系统配置参数),不配置在业务端,集成到基础框架里。但特殊情况下,业务系统有时候也希望能把基础框架里配置的参数给覆盖掉,用他们自己的配置
# 对于此类型的配置需求,可以配置在下面的配置文件里。该文件一般放在resource目录下。缺失则默认为spring-application-default.properties
spring.application.default.properties.path=spring-application-default.properties
# 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试。缺失则默认为false
spring.application.no.servers.retry.enabled=false
# 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的次数。缺失则默认为5
spring.application.no.servers.retry.times=5
# 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的时间间隔。缺失则默认为2000
spring.application.no.servers.retry.await.time=2000
# 负载均衡下,消费端尝试获取对应提供端服务实例列表为空的时候,通过日志方式通知。缺失则默认为false
spring.application.no.servers.notify.enabled=false
# 由于Nacos注册中心会自动把服务名处理成GROUP@@SERVICE_ID的格式,导致根据服务名去获取元数据的时候会找不到。通过如下开关开启是否要过滤掉GROUP前缀。缺失则默认为true
spring.application.nacos.service.id.filter.enabled=true
# 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true
spring.application.parameter.event.onstart.enabled=true
# Plugin strategy config
# 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true
spring.application.strategy.zone.avoidance.rule.enabled=true
# 路由策略过滤器的执行顺序(Order)。缺失则默认为9000
spring.application.strategy.gateway.route.filter.order=9000
# 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true
spring.application.strategy.gateway.header.priority=false
# 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true
spring.application.strategy.gateway.original.header.ignored=true
# 开启和关闭网关订阅配置中心的动态路由策略。缺失则默认为false
spring.application.strategy.gateway.dynamic.route.enabled=true
# 启动和关闭网关上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.gateway.core.header.transmission.enabled=true
# 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false
spring.application.strategy.monitor.enabled=true
# 启动和关闭告警,一旦关闭,蓝绿灰度上下文输出都将关闭。缺失则默认为false
spring.application.strategy.alarm.enabled=true
# 启动和关闭日志输出。缺失则默认为false
spring.application.strategy.logger.enabled=true
# 日志输出中,是否显示MDC前面的Key。缺失则默认为true
spring.application.strategy.logger.mdc.key.shown=true
# 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.logger.debug.enabled=true
# 启动和关闭调用链输出。缺失则默认为false
spring.application.strategy.tracer.enabled=true
# 启动和关闭调用链的蓝绿灰度信息以独立的Span节点输出,如果关闭,则蓝绿灰度信息输出到原生的Span节点中(SkyWalking不支持原生模式)。缺失则默认为true
spring.application.strategy.tracer.separate.span.enabled=true
# 启动和关闭调用链的蓝绿灰度规则策略信息输出。缺失则默认为true
spring.application.strategy.tracer.rule.output.enabled=true
# 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false
spring.application.strategy.tracer.exception.detail.output.enabled=false
# 显示在调用链界面上蓝绿灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION
spring.application.strategy.tracer.span.value=NEPXION
# 显示在调用链界面上蓝绿灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery
spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery
# 启动和关闭Sentinel调用链上规则在Span上的输出。缺失则默认为true
spring.application.strategy.tracer.sentinel.rule.output.enabled=true
# 启动和关闭Sentinel调用链上方法入参在Span上的输出。缺失则默认为false
spring.application.strategy.tracer.sentinel.args.output.enabled=false
# 启动和关闭Sentinel Metric通过次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.pass.qps.output.enabled=true
# 启动和关闭Sentinel Metric阻塞次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.block.qps.output.enabled=true
# 启动和关闭Sentinel Metric成功次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.success.qps.output.enabled=true
# 启动和关闭Sentinel Metric异常次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.exception.qps.output.enabled=true
# 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展。缺失则默认为false
spring.application.strategy.sentinel.datasource.enabled=true
# 流控规则文件路径。缺失则默认为classpath:sentinel-flow.json
spring.application.strategy.sentinel.flow.path=classpath:sentinel-flow.json
# 降级规则文件路径。缺失则默认为classpath:sentinel-degrade.json
spring.application.strategy.sentinel.degrade.path=classpath:sentinel-degrade.json
# 授权规则文件路径。缺失则默认为classpath:sentinel-authority.json
spring.application.strategy.sentinel.authority.path=classpath:sentinel-authority.json
# 系统规则文件路径。缺失则默认为classpath:sentinel-system.json
spring.application.strategy.sentinel.system.path=classpath:sentinel-system.json
# 热点参数流控规则文件路径。缺失则默认为classpath:sentinel-param-flow.json
spring.application.strategy.sentinel.param.flow.path=classpath:sentinel-param-flow.json
# 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.consumer.isolation.enabled=true
# 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止蓝绿灰度版本发布人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损
# 在开启版本故障转移的开关前提下,故障转移有三种策略:
# 1. 如果“version-failover”值已配置,指定版本的故障转移,即找不到实例的时候,直接路由到该版本实例
# 2. 如果“version-failover”值未配置
# 2.1 开启“version.failover.stable.enabled”开关,版本列表排序策略的(取最老的稳定版本的实例)故障转移,即找不到实例的时候,直接路由到最老的稳定版本的实例
# 2.2 关闭“version.failover.stable.enabled”开关,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.enabled=true
# 开启和关闭版本列表排序策略下取稳定版本的版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.stable.enabled=true
# 版本偏好,即非蓝绿灰度发布场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施蓝绿灰度版本发布产生混乱,对处于非蓝绿灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;蓝绿灰度状态的服务,还是根据传递的Header版本号进行匹配
# 在开启版本偏好的开关前提下,偏好有两种策略:
# 1. 如果“version-prefer”值已配置,指定版本的偏好,即不管存在多少版本,直接路由到该版本实例
# 2. 如果“version-prefer”值未配置,版本列表排序策略的(取最老的稳定版本的实例)偏好,即不管存在多少版本,直接路由到最老的稳定版本的实例
# 启动和关闭版本偏好。缺失则默认为false
spring.application.strategy.version.prefer.enabled=true
# 区域调试转移,即当未对服务指定访问区域的时候,转移到事先指定的区域
# 使用场景示例:
# 开发环境(个人电脑环境)在测试环境(线上环境)进行联调
# 访问路径为A服务 -> B服务 -> C服务,A服务和B服务在开发环境上,C服务在测试环境上
# 调用时候,在B服务上进行如下两个配置,并在最前端传入的Header(n-d-region)指定为B的开发环境区域(用来保证A服务和B服务只在开发环境调用),而B服务会自动转移调用到测试环境上的C服务实例,但不会转移到其它个人电脑的C服务实例
# 该功能的意义,个人电脑环境可以接入到测试环境联调,当多套个人环境接入时候,可以保护不同的个人环境间不会彼此调用
# 通过“region-transfer”值进行区域转移值配置,如果缺失,则报错
# 启动和关闭区域调试转移。缺失则默认为false
spring.application.strategy.region.transfer.enabled=true
# 在开启区域故障转移的开关前提下,故障转移有两种策略:
# 1. 如果“region-failover”值已配置,指定区域的故障转移,即找不到实例的时候,直接路由到该区域实例
# 2. 如果“region-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭区域故障转移。缺失则默认为false
spring.application.strategy.region.failover.enabled=true
# 启动和关闭环境故障转移。缺失则默认为false
# 如果“env-failover”值未配置,则默认为common
spring.application.strategy.environment.failover.enabled=true
# 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false
spring.application.strategy.zone.affinity.enabled=true
# 在开启可用区故障转移的开关前提下,故障转移有两种策略:
# 1. 如果“zone-failover”值已配置,指定可用区的故障转移,即找不到实例的时候,直接路由到该可用区实例
# 2. 如果“zone-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭可用区故障转移。缺失则默认为false
spring.application.strategy.zone.failover.enabled=true
# 在开启IP地址和端口故障转移的开关前提下,故障转移有两种策略:
# 1. 如果“address-failover”值已配置,指定IP地址或者端口的故障转移,即找不到实例的时候,直接路由到该IP地址或者端口实例
# 2. 如果“address-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭IP地址和端口故障转移。缺失则默认为false
spring.application.strategy.address.failover.enabled=true
# 启动和关闭自动扫描目录,当扫描目录未人工配置的时候,可以通过自动扫描方式决定扫描目录。缺失则默认为true
spring.application.strategy.auto.scan.packages.enabled=true
# 启动和关闭嵌套扫描,嵌套扫描指扫描非本工程下外部包的目录,可以支持多层嵌套。缺失则默认为false
spring.application.strategy.auto.scan.recursion.enabled=false
# 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false
spring.application.group.generator.enabled=true
# 服务名前缀的截断长度,必须大于0
spring.application.group.generator.length=15
# 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式
spring.application.group.generator.character=-
# 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false
spring.application.git.generator.enabled=true
# 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties
spring.application.git.generator.path=classpath:git.properties
# spring.application.git.generator.path=classpath:git.json
# 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count}
spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time}
# spring.application.git.version.key={git.build.version}-{git.commit.time}
# 开启和关闭从SkyWalking apm-agent-core里反射获取TraceId并复制。由于SkyWalking对WebFlux上下文Threadlocal处理机制不恰当,导致产生的TraceId在全链路中并不一致,打开这个开关可以保证全链路TraceId都是一致的。缺失则默认为true
spring.application.strategy.gateway.skywalking.traceid.enabled=true
# 下面配置只适用于网关里直接进行Feign、RestTemplate或者WebClient调用场景
# 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true
spring.application.strategy.rest.intercept.enabled=true
# 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.rest.intercept.debug.enabled=true
# 启动和关闭Feign上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.feign.core.header.transmission.enabled=true
# 启动和关闭RestTemplate上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.rest.template.core.header.transmission.enabled=true
# 启动和关闭WebClient上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.web.client.core.header.transmission.enabled=true
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.context.request.headers=trace-id;span-id
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于业务系统自定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.business.request.headers=user;mobile;location
# 开启和关闭Hystrix线程隔离模式做服务隔离时,对线程切换上下文传递的功能。缺失则默认为false
# Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失
spring.application.strategy.hystrix.threadlocal.supported=true
③ Zuul端配置
# Plugin core config
# 开启和关闭服务注册层面的控制。一旦关闭,服务注册的黑/白名单过滤功能将失效,最大注册数的限制过滤功能将失效。缺失则默认为true
spring.application.register.control.enabled=true
# 开启和关闭服务发现层面的控制。一旦关闭,服务多版本调用的控制功能将失效,动态屏蔽指定IP地址的服务实例被发现的功能将失效。缺失则默认为true
spring.application.discovery.control.enabled=true
# 开启和关闭通过Rest方式对规则配置的控制和推送。一旦关闭,只能通过远程配置中心来控制和推送。缺失则默认为true
spring.application.config.rest.control.enabled=true
# 随机权重算法。缺失则默认为MapWeightRandom
spring.application.weight.random.type=MapWeightRandom
# 规则文件的格式,支持xml和json。缺失则默认为xml
spring.application.config.format=xml
# spring.application.config.format=json
# 本地规则文件的路径,支持两种方式:classpath:rule.xml(rule.json) - 规则文件放在resources目录下,便于打包进jar;file:rule.xml(rule.json) - 规则文件放在工程根目录下,放置在外部便于修改。缺失则默认为不装载本地规则
spring.application.config.path=classpath:rule.xml
# spring.application.config.path=classpath:rule.json
# 为微服务归类的Key,一般通过group字段来归类,例如eureka.instance.metadataMap.group=xxx-group或者eureka.instance.metadataMap.application=xxx-application。缺失则默认为group
spring.application.group.key=group
# spring.application.group.key=application
# 业务系统希望大多数时候Spring、SpringBoot或者SpringCloud的基本配置、调优参数(非业务系统配置参数),不配置在业务端,集成到基础框架里。但特殊情况下,业务系统有时候也希望能把基础框架里配置的参数给覆盖掉,用他们自己的配置
# 对于此类型的配置需求,可以配置在下面的配置文件里。该文件一般放在resource目录下。缺失则默认为spring-application-default.properties
spring.application.default.properties.path=spring-application-default.properties
# 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试。缺失则默认为false
spring.application.no.servers.retry.enabled=false
# 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的次数。缺失则默认为5
spring.application.no.servers.retry.times=5
# 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的时间间隔。缺失则默认为2000
spring.application.no.servers.retry.await.time=2000
# 负载均衡下,消费端尝试获取对应提供端服务实例列表为空的时候,通过日志方式通知。缺失则默认为false
spring.application.no.servers.notify.enabled=false
# 由于Nacos注册中心会自动把服务名处理成GROUP@@SERVICE_ID的格式,导致根据服务名去获取元数据的时候会找不到。通过如下开关开启是否要过滤掉GROUP前缀。缺失则默认为true
spring.application.nacos.service.id.filter.enabled=true
# 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true
spring.application.parameter.event.onstart.enabled=true
# Plugin strategy config
# 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true
spring.application.strategy.zone.avoidance.rule.enabled=true
# 路由策略过滤器的执行顺序(Order)。缺失则默认为0
spring.application.strategy.zuul.route.filter.order=0
# 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true
spring.application.strategy.zuul.header.priority=false
# 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true
spring.application.strategy.zuul.original.header.ignored=true
# 开启和关闭网关订阅配置中心的动态路由策略。缺失则默认为false
spring.application.strategy.zuul.dynamic.route.enabled=true
# 启动和关闭网关上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.zuul.core.header.transmission.enabled=true
# 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false
spring.application.strategy.monitor.enabled=true
# 启动和关闭告警,一旦关闭,蓝绿灰度上下文输出都将关闭。缺失则默认为false
spring.application.strategy.alarm.enabled=true
# 启动和关闭日志输出。缺失则默认为false
spring.application.strategy.logger.enabled=true
# 日志输出中,是否显示MDC前面的Key。缺失则默认为true
spring.application.strategy.logger.mdc.key.shown=true
# 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.logger.debug.enabled=true
# 启动和关闭调用链输出。缺失则默认为false
spring.application.strategy.tracer.enabled=true
# 启动和关闭调用链的蓝绿灰度信息以独立的Span节点输出,如果关闭,则蓝绿灰度信息输出到原生的Span节点中(SkyWalking不支持原生模式)。缺失则默认为true
spring.application.strategy.tracer.separate.span.enabled=true
# 启动和关闭调用链的蓝绿灰度规则策略信息输出。缺失则默认为true
spring.application.strategy.tracer.rule.output.enabled=true
# 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false
spring.application.strategy.tracer.exception.detail.output.enabled=false
# 显示在调用链界面上蓝绿灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION
spring.application.strategy.tracer.span.value=NEPXION
# 显示在调用链界面上蓝绿灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery
spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery
# 启动和关闭Sentinel调用链上规则在Span上的输出。缺失则默认为true
spring.application.strategy.tracer.sentinel.rule.output.enabled=true
# 启动和关闭Sentinel调用链上方法入参在Span上的输出。缺失则默认为false
spring.application.strategy.tracer.sentinel.args.output.enabled=false
# 启动和关闭Sentinel Metric通过次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.pass.qps.output.enabled=true
# 启动和关闭Sentinel Metric阻塞次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.block.qps.output.enabled=true
# 启动和关闭Sentinel Metric成功次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.success.qps.output.enabled=true
# 启动和关闭Sentinel Metric异常次数统计输出功能。缺失则默认为true
spring.application.strategy.metric.sentinel.exception.qps.output.enabled=true
# 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展。缺失则默认为false
spring.application.strategy.sentinel.datasource.enabled=true
# 流控规则文件路径。缺失则默认为classpath:sentinel-flow.json
spring.application.strategy.sentinel.flow.path=classpath:sentinel-flow.json
# 降级规则文件路径。缺失则默认为classpath:sentinel-degrade.json
spring.application.strategy.sentinel.degrade.path=classpath:sentinel-degrade.json
# 授权规则文件路径。缺失则默认为classpath:sentinel-authority.json
spring.application.strategy.sentinel.authority.path=classpath:sentinel-authority.json
# 系统规则文件路径。缺失则默认为classpath:sentinel-system.json
spring.application.strategy.sentinel.system.path=classpath:sentinel-system.json
# 热点参数流控规则文件路径。缺失则默认为classpath:sentinel-param-flow.json
spring.application.strategy.sentinel.param.flow.path=classpath:sentinel-param-flow.json
# 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.consumer.isolation.enabled=true
# 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止蓝绿灰度版本发布人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损
# 在开启版本故障转移的开关前提下,故障转移有三种策略:
# 1. 如果“version-failover”值已配置,指定版本的故障转移,即找不到实例的时候,直接路由到该版本实例
# 2. 如果“version-failover”值未配置
# 2.1 开启“version.failover.stable.enabled”开关,版本列表排序策略的(取最老的稳定版本的实例)故障转移,即找不到实例的时候,直接路由到最老的稳定版本的实例
# 2.2 关闭“version.failover.stable.enabled”开关,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.enabled=true
# 开启和关闭版本列表排序策略下取稳定版本的版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.stable.enabled=true
# 版本偏好,即非蓝绿灰度发布场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施蓝绿灰度版本发布产生混乱,对处于非蓝绿灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;蓝绿灰度状态的服务,还是根据传递的Header版本号进行匹配
# 在开启版本偏好的开关前提下,偏好有两种策略:
# 1. 如果“version-prefer”值已配置,指定版本的偏好,即不管存在多少版本,直接路由到该版本实例
# 2. 如果“version-prefer”值未配置,版本列表排序策略的(取最老的稳定版本的实例)偏好,即不管存在多少版本,直接路由到最老的稳定版本的实例
# 启动和关闭版本偏好。缺失则默认为false
spring.application.strategy.version.prefer.enabled=true
# 区域调试转移,即当未对服务指定访问区域的时候,转移到事先指定的区域
# 使用场景示例:
# 开发环境(个人电脑环境)在测试环境(线上环境)进行联调
# 访问路径为A服务 -> B服务 -> C服务,A服务和B服务在开发环境上,C服务在测试环境上
# 调用时候,在B服务上进行如下两个配置,并在最前端传入的Header(n-d-region)指定为B的开发环境区域(用来保证A服务和B服务只在开发环境调用),而B服务会自动转移调用到测试环境上的C服务实例,但不会转移到其它个人电脑的C服务实例
# 该功能的意义,个人电脑环境可以接入到测试环境联调,当多套个人环境接入时候,可以保护不同的个人环境间不会彼此调用
# 通过“region-transfer”值进行区域转移值配置,如果缺失,则报错
# 启动和关闭区域调试转移。缺失则默认为false
spring.application.strategy.region.transfer.enabled=true
# 在开启区域故障转移的开关前提下,故障转移有两种策略:
# 1. 如果“region-failover”值已配置,指定区域的故障转移,即找不到实例的时候,直接路由到该区域实例
# 2. 如果“region-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭区域故障转移。缺失则默认为false
spring.application.strategy.region.failover.enabled=true
# 启动和关闭环境故障转移。缺失则默认为false
# 如果“env-failover”值未配置,则默认为common
spring.application.strategy.environment.failover.enabled=true
# 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false
spring.application.strategy.zone.affinity.enabled=true
# 在开启可用区故障转移的开关前提下,故障转移有两种策略:
# 1. 如果“zone-failover”值已配置,指定可用区的故障转移,即找不到实例的时候,直接路由到该可用区实例
# 2. 如果“zone-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭可用区故障转移。缺失则默认为false
spring.application.strategy.zone.failover.enabled=true
# 在开启IP地址和端口故障转移的开关前提下,故障转移有两种策略:
# 1. 如果“address-failover”值已配置,指定IP地址或者端口的故障转移,即找不到实例的时候,直接路由到该IP地址或者端口实例
# 2. 如果“address-failover”值未配置,负载均衡策略的故障转移,即找不到实例的时候,执行负载均衡策略
# 启动和关闭IP地址和端口故障转移。缺失则默认为false
spring.application.strategy.address.failover.enabled=true
# 启动和关闭自动扫描目录,当扫描目录未人工配置的时候,可以通过自动扫描方式决定扫描目录。缺失则默认为true
spring.application.strategy.auto.scan.packages.enabled=true
# 启动和关闭嵌套扫描,嵌套扫描指扫描非本工程下外部包的目录,可以支持多层嵌套。缺失则默认为false
spring.application.strategy.auto.scan.recursion.enabled=false
# 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false
spring.application.group.generator.enabled=true
# 服务名前缀的截断长度,必须大于0
spring.application.group.generator.length=15
# 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式
spring.application.group.generator.character=-
# 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false
spring.application.git.generator.enabled=true
# 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties
spring.application.git.generator.path=classpath:git.properties
# spring.application.git.generator.path=classpath:git.json
# 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count}
# spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time}
# spring.application.git.version.key={git.build.version}-{git.commit.time}
# 下面配置只适用于网关里直接进行Feign、RestTemplate或者WebClient调用场景
# 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true
spring.application.strategy.rest.intercept.enabled=true
# 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.rest.intercept.debug.enabled=true
# 启动和关闭Feign上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.feign.core.header.transmission.enabled=true
# 启动和关闭RestTemplate上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.rest.template.core.header.transmission.enabled=true
# 启动和关闭WebClient上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能
# 核心策略Header指n-d-开头的Header(不包括n-d-env,因为环境路由隔离,必须传递该Header),不包括n-d-service开头的Header
spring.application.strategy.web.client.core.header.transmission.enabled=true
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.context.request.headers=trace-id;span-id
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于业务系统自定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.business.request.headers=user;mobile;location
# 开启和关闭Hystrix线程隔离模式做服务隔离时,对线程切换上下文传递的功能。缺失则默认为false
# Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失
spring.application.strategy.hystrix.threadlocal.supported=true
# 启动和关闭Swagger。缺失则默认为true
swagger.service.enabled=true
# Swagger基准Docket组名
swagger.service.base.group=Nepxion Discovery
# Swagger自定义Docket组名
swagger.service.scan.group=Admin Center Restful APIs
# Swagger自定义扫描目录
swagger.service.scan.packages=your-scan-packages
# Swagger描述
swagger.service.description=your-description
# Swagger版本
swagger.service.version=6.11.0
# Swagger License名称
swagger.service.license.name=Apache License 2.0
# Swagger License链接
swagger.service.license.url=http://www.apache.org/licenses/LICENSE-2.0
# Swagger联系人名称
swagger.service.contact.name=Nepxion
# Swagger联系人网址
swagger.service.contact.url=https://github.com/Nepxion/Discovery
# Swagger联系人邮件
[email protected]
# Swagger服务条件网址
swagger.service.termsOfService.url=http://www.nepxion.com
框架提供内置文件方式的配置spring-application-default.properties。如果使用者希望对框架做封装,并提供相应的默认配置,可以在src/main/resources目录下放置spring-application-default.properties
该文件在整个服务目录和包中只能出现一次
Spring 2.3.x支持Docker分层部署,步骤也更简单,请参考Polaris【北极星】企业级云原生微服务框架里的介绍
① 搭建Windows10操作系统或者Linux操作系统下的Docker环境
- Windows10环境下,具体步骤参考Docker安装步骤
- Linux环境请自行研究
② 需要在4个工程下的pom.xml里增加spring-boot-maven-plugin和docker-maven-plugin
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<mainClass>com.nepxion.discovery.guide.gateway.DiscoveryGuideGateway</mainClass>
<layout>JAR</layout>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<attach>false</attach>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<imageName>${ImageName}</imageName>
<baseImage>openjdk:8-jre-alpine</baseImage>
<entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
<exposes>
<expose>${ExposePort}</expose>
</exposes>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
③ 拷贝discovery-guide-docker目录下的所有脚本文件到根目录下
④ 所有脚本文件下的MIDDLEWARE_HOST=10.0.75.1改成使用者本地物理IP地址(Docker是不能去连接容器外地址为localhost的中间件服务器)
⑤ 全自动部署和运行Docker化的服务。在根目录下
- 一键运行install-docker-gateway.bat或者.sh,把Spring Cloud Gateway网关全自动部署且运行起来
- 一键运行install-docker-zuul.bat或者.sh,把Zuul网关全自动部署且运行起来
- 一键运行install-docker-service-xx.bat或者.sh,把微服务全自动部署且运行起来。需要注意,必须依次运行,即等上一个部署完毕后才能执行下一个
- 一键运行install-docker-console.bat或者.sh,把控制平台全自动部署且运行起来
- 一键运行install-docker-admin.bat或者.sh,把监控平台全自动部署且运行起来
上述步骤为演示步骤,和DevOps平台结合在一起,更为完美
⑥ Docker运行效果
- Docker Desktop
- Docker Windows
- Docker Linux
请自行研究
自动化测试,基于Spring Boot/Spring Cloud的自动化测试框架,包括普通调用测试、蓝绿灰度调用测试和扩展调用测试(例如:支持阿里巴巴的Sentinel,FF4j的功能开关等)。通过注解形式,跟Spring Boot内置的测试机制集成,使用简单方便。该自动化测试框架的现实意义,可以把服务注册发现中心、远程配置中心、负载均衡、蓝绿灰度发布、熔断降级限流、功能开关、Feign、RestTemplate或者WebClient调用等中间件或者组件,一条龙组合起来进行自动化测试
自动化测试代码参考指南示例自动化测试
通过Matrix Aop框架,实现TestAutoScanProxy和TestInterceptor拦截测试用例,实现配置内容的自动化推送
运行指南示例下的DiscoveryGuideConsole.java控制台服务,它是连接服务注册发现中心、远程配置中心和服务的纽带,自动化测试利用控制台实现配置的自动更新和清除
# 自动化测试框架内置配置
# 测试用例类的扫描路径
spring.application.test.scan.packages=com.nepxion.discovery.guide.test
# 测试用例的配置内容推送时,是否打印配置日志。缺失则默认为true
spring.application.test.config.print.enabled=true
# 测试用例的配置内容推送后,等待生效的时间。推送远程配置中心后,再通知各服务更新自身的配置缓存,需要一定的时间,缺失则默认为3000
spring.application.test.config.operation.await.time=5000
# 测试用例的配置内容推送的控制台地址。控制台是连接服务注册发现中心、远程配置中心和服务的纽带
spring.application.test.console.url=http://localhost:6001/
# 业务测试配置
# Spring Cloud Gateway网关配置
gateway.group=discovery-guide-group
gateway.service.id=discovery-guide-gateway
gateway.test.url=http://localhost:5001/discovery-guide-service-a/invoke/gateway
gateway.route0.test.url=http://localhost:5001/x/invoke/gateway
gateway.route1.test.url=http://localhost:5001/y/invoke/gateway
gateway.route2.test.url=http://localhost:5001/z/invoke/gateway
gateway.inspect.url=http://localhost:5001/discovery-guide-service-a/inspector/inspect
# Zuul网关配置
zuul.group=discovery-guide-group
zuul.service.id=discovery-guide-zuul
zuul.test.url=http://localhost:5002/discovery-guide-service-a/invoke/zuul
zuul.route0.test.url=http://localhost:5002/x/invoke/zuul
zuul.route1.test.url=http://localhost:5002/y/invoke/zuul
zuul.route2.test.url=http://localhost:5002/z/invoke/zuul
zuul.inspect.url=http://localhost:5002/discovery-guide-service-a/inspector/inspect
# 每个测试用例执行循环次数
testcase.loop.times=1
# 测试用例的灰度权重测试开关。由于权重测试需要大量采样调用,会造成整个自动化测试时间很长,可以通过下面开关开启和关闭。缺失则默认为true
gray.weight.testcases.enabled=true
# 测试用例的灰度权重采样总数。采样总数越大,灰度权重准确率越高,但耗费时间越长
gray.weight.testcase.sample.count=1500
# 测试用例的灰度权重准确率偏离值。采样总数越大,灰度权重准确率偏离值越小
gray.weight.testcase.result.offset=5
当使用Eureka注册中心的时候,因为Spring Cloud内嵌了Eureka可用区亲和性功能,会自动开启该策略,则导致某些自动化测试用例失败。需要把所有服务实例的元数据zone值改成相同或者也可以把该行元数据删除,然后进行自动化测试
<dependency>
<groupId>com.nepxion</groupId>
<artifactId>discovery-plugin-test-starter</artifactId>
<version>${discovery.version}</version>
</dependency>
对于带有注解@DTestConfig的测试用例,要用到Spring的Spel语法格式(即group = "#group", serviceId = "#serviceId"),需要引入Java8的带"-parameters"编译方式,见上面的参数设置
在IDE环境里需要设置"-parameters"的Compiler Argument
- Eclipse加"-parameters"参数:https://www.concretepage.com/java/jdk-8/java-8-reflection-access-to-parameter-names-of-method-and-constructor-with-maven-gradle-and-eclipse-using-parameters-compiler-argument
- Idea加"-parameters"参数:http://blog.csdn.net/royal_lr/article/details/52279993
如果通过Spring Boot打包来执行,则需要增加如下插件
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<encoding>${project.build.sourceEncoding}</encoding>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
结合Spring Boot Junit,TestApplication.class为测试框架内置应用启动程序,DiscoveryGuideTestConfiguration用于初始化所有测试用例类。在测试方法上面加入JUnit的@Test注解
@RunWith(SpringRunner.class)
@SpringBootTest(classes = { TestApplication.class, DiscoveryGuideTestConfiguration.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DiscoveryGuideTest {
@Autowired
private DiscoveryGuideTestCases discoveryGuideTestCases;
private static long startTime;
@BeforeClass
public static void beforeTest() {
startTime = System.currentTimeMillis();
}
@AfterClass
public static void afterTest() {
LOG.info("* Finished automation test in {} seconds", (System.currentTimeMillis() - startTime) / 1000);
}
@Test
public void testNoGray() throws Exception {
discoveryGuideTestCases.testNoGray(gatewayTestUrl);
discoveryGuideTestCases.testNoGray(zuulTestUrl);
}
@Test
public void testVersionStrategyGray() throws Exception {
discoveryGuideTestCases.testVersionStrategyGray1(gatewayGroup, gatewayServiceId, gatewayTestUrl);
discoveryGuideTestCases.testVersionStrategyGray1(zuulGroup, zuulServiceId, zuulTestUrl);
}
}
@Configuration
public class DiscoveryGuideTestConfiguration {
@Bean
public DiscoveryGuideTestCases discoveryGuideTestCases() {
return new DiscoveryGuideTestCases();
}
}
在测试方法上面增加注解@DTest,通过断言Assert来判断测试结果。注解@DTest内容如下
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DTest {
}
代码如下
public class DiscoveryGuideTestCases {
@Autowired
private TestRestTemplate testRestTemplate;
@DTest
public void testNoGray(String testUrl) {
int noRepeatCount = 0;
List<String> resultList = new ArrayList<String>();
for (int i = 0; i < 4; i++) {
String result = testRestTemplate.getForEntity(testUrl, String.class).getBody();
LOG.info("Result{} : {}", i + 1, result);
if (!resultList.contains(result)) {
noRepeatCount++;
}
resultList.add(result);
}
Assert.assertEquals(noRepeatCount, 4);
}
}
在测试方法上面增加注解@DTestConfig,通过断言Assert来判断测试结果。注解DTestConfig注解内容如下
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DTestConfig {
// 组名
String group();
// 服务名
String serviceId();
// 配置类型
FormatType formatType() default FormatType.TEXT_FORMAT;
// 组名-服务名组合键值的前缀
String prefix() default StringUtils.EMPTY;
// 组名-服务名组合键值的后缀
String suffix() default StringUtils.EMPTY;
// 执行配置的文件路径。测试用例运行前,会把该文件里的内容推送到远程配置中心或者服务
String executePath();
// 重置配置的文件路径。测试用例运行后,会把该文件里的内容推送到远程配置中心或者服务。该文件内容是最初的默认配置
// 如果该注解属性为空,则直接删除从配置中心删除组名-服务名组合键值
String resetPath() default StringUtils.EMPTY;
}
代码如下
public class DiscoveryGuideTestCases {
@Autowired
private TestRestTemplate testRestTemplate;
@DTestConfig(group = "#group", serviceId = "#serviceId", executePath = "gray-strategy-version-1.xml", resetPath = "gray-default.xml")
public void testVersionStrategyGray(String group, String serviceId, String testUrl) {
for (int i = 0; i < 4; i++) {
String result = testRestTemplate.getForEntity(testUrl, String.class).getBody();
LOG.info("Result{} : {}", i + 1, result);
int index = result.indexOf("[V=1.0]");
int lastIndex = result.lastIndexOf("[V=1.0]");
Assert.assertNotEquals(index, -1);
Assert.assertNotEquals(lastIndex, -1);
Assert.assertNotEquals(index, lastIndex);
}
}
}
蓝绿灰度配置文件gray-strategy-version-1.xml的内容如下
<?xml version="1.0" encoding="UTF-8"?>
<rule>
<strategy>
<version>1.0</version>
</strategy>
</rule>
蓝绿灰度配置文件gray-default.xml的内容如下
<?xml version="1.0" encoding="UTF-8"?>
<rule>
</rule>
除了支持蓝绿灰度自动化测试外,使用者可扩展出以远程配置中心内容做变更的自动化测试。以阿里巴巴的Sentinel的权限功能为例子,参考PolarisGuide,测试实现方式如下
① 远程配置中心约定
以Nacos和Apollo为例
- Nacos的Key格式
Group为DEFAULT_GROUP,Data ID为sentinel-authority-${spring.application.name}。每个服务都专享自己的Sentinel规则
- Apollo的Key格式
namespace为application,Key为sentinel-authority。每个服务都专享自己的Sentinel规则
② 执行测试用例前,把执行限流降级熔断等逻辑的内容(executePath = "sentinel-authority-2.json")推送到远程配置中心
③ 执行测试用例,通过断言Assert来判断测试结果
④ 执行测试用例后,把修改过的内容(resetPath = "sentinel-authority-1.json")复原,再推送一次到远程配置中心
public class PolarisTestCases {
@Autowired
private TestRestTemplate testRestTemplate;
@DTestConfig(group = "DEFAULT_GROUP", serviceId = "sentinel-authority-polaris-service-b", executePath = "sentinel-authority-2.json", resetPath = "sentinel-authority-1.json")
public void testSentinelAuthority1(String testUrl) {
int count = 0;
for (int i = 0; i < 4; i++) {
String result = testRestTemplate.postForEntity(testUrl, "gateway", String.class).getBody();
LOG.info("Result{} : {}", i + 1, result);
if (result.contains("AuthorityRule")) {
count++;
}
}
Assert.assertEquals(count, 4);
}
}
- 路由策略测试报告样例
---------- Run automation testcase :: testNoGray() ----------
Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
* Passed
---------- Run automation testcase :: testEnabledStrategyGray1() ----------
Header : [mobile:"138"]
Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
* Passed
---------- Run automation testcase :: testVersionStrategyGray1() ----------
Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
* Passed
---------- Run automation testcase :: testRegionStrategyGray1() ----------
Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
* Passed
---------- Run automation testcase :: testVersionWeightStrategyGray() ----------
Sample count=3000
Weight result offset desired=5%
A service desired : 1.0 version weight=90%, 1.1 version weight=10%
B service desired : 1.0 version weight=20%, 1.1 version weight=80%
Result : A service 1.0 version weight=89.6%
Result : A service 1.1 version weight=10.4%
Result : B service 1.0 version weight=20.1333%
Result : B service 1.1 version weight=79.8667%
* Passed
---------- Run automation testcase :: testRegionWeightStrategyGray() ----------
Sample count=3000
Weight result offset desired=5%
A service desired : dev region weight=85%, qa region weight=15%
B service desired : dev region weight=85%, qa region weight=15%
Result : A service dev region weight=83.7667%
Result : A service qa region weight=16.2333%
Result : B service dev region weight=86.2%
Result : B service qa region weight=13.8%
* Passed
---------- Run automation testcase :: testStrategyGray1() ----------
Header : [a:"1", b:"2"]
Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
- 路由规则测试报告样例
* Passed
---------- Run automation testcase :: testVersionRuleGray() ----------
Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
* Passed
---------- Run automation testcase :: testRegionRuleGray() ----------
Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group]
Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group]
* Passed
---------- Run automation testcase :: testVersionWeightRuleGray() ----------
Sample count=3000
Weight result offset desired=5%
A service desired : 1.0 version weight=75%, 1.1 version weight=25%
B service desired : 1.0 version weight=35%, 1.1 version weight=65%
Result : A service 1.0 version weight=75.2667%
Result : A service 1.1 version weight=24.7333%
Result : B service 1.0 version weight=35.1667%
Result : B service 1.1 version weight=64.8333%
* Passed
---------- Run automation testcase :: testRegionWeightRuleGray() ----------
Sample count=3000
Weight result offset desired=5%
A service desired : dev region weight=95%, qa region weight=5%
B service desired : dev region weight=95%, qa region weight=5%
Result : A service dev region weight=94.9333%
Result : A service qa region weight=5.0667%
Result : B service dev region weight=95.0667%
Result : B service qa region weight=4.9333%
* Passed
---------- Run automation testcase :: testVersionCompositeRuleGray() ----------
Sample count=3000
Weight result offset desired=5%
A service desired : 1.0 version weight=40%, 1.1 version weight=60%
Route desired : A Service 1.0 version -> B Service 1.0 version, A Service 1.1 version -> B Service 1.1 version
Result : A service 1.0 version weight=39.8333%
A service 1.1 version weight=60.1667%
* Passed
压力测试,基于WRK的异步压力测试框架,能用很少的线程压测出很大的并发量,使用简单方便
① 准备两台机器部署Spring Cloud应用
② 准备一台机器部署网关(Spring Cloud或者Zuul)
③ 准备一台机器部署压测工具
服务 | 配置 | 数目 |
---|---|---|
Spring Cloud Gateway | 16C 32G | 1 |
Zuul 1.x | 16C 32G | 1 |
Service | 4C 8G | 2 |
④ 优化方式
- Spring Cloud Gateway,不需要优化
- Zuul 1.x,优化如下
zuul.host.max-per-route-connections=1000
zuul.host.max-total-connections=1000
zuul.semaphore.max-semaphores=5000
- 使用WRK脚本进行性能测试,WRK脚本参考post.lua(位于discovery-guide-test-automation目录下),不带参数运行
- 使用WRK详细说明参考https://github.com/wg/wrk
- 登录到WRK的机器,进入WRK目录
- 运行命令 wrk -t64 -c2000 -d30s -H "id: 123" -H "token: abc" --timeout=2s --latency --script=post.lua http://localhost:5001/discovery-guide-service-a/invoke/gateway
使用方法: wrk <选项> <被测HTTP服务的URL>
Options:
-c, --connections 跟服务器建立并保持的TCP连接数量
-d, --duration 压测时间。例如:2s,2m,2h
-t, --threads 使用多少个线程进行压测
-s, --script 指定Lua脚本路径
-H, --header 为每一个HTTP请求添加HTTP头。例如:-H "id: 123" -H "token: abc",冒号后面要带空格
--latency 在压测结束后,打印延迟统计信息
--timeout 超时时间
- 等待结果,Requests/sec 表示每秒处理的请求数
基于WRK极限压测,报告如下
服务 | 性质 | 线程数 | 连接数 | 每秒最大请求数 | 资源耗费 |
---|---|---|---|---|---|
Spring Cloud Gateway为起始的调用链 | 原生框架 | 5000 | 20000 | 28100左右 | CPU占用率42% |
Spring Cloud Gateway为起始的调用链 | 本框架 | 5000 | 20000 | 27800左右 | CPU占用率42.3% |
Zuul 1.x为起始的调用链 | 原生框架 | 5000 | 20000 | 24050左右 | CPU占用率56% |
Zuul 1.x为起始的调用链 | 本框架 | 5000 | 20000 | 23500左右 | CPU占用率56.5% |
① Nacos
- Nacos服务器版本,推荐用最新版本,从https://github.com/alibaba/nacos/releases获取
- 功能界面主页,http://localhost:8848/nacos/index.html
② Consul
- Consul服务器版本不限制,推荐用最新版本,从https://releases.hashicorp.com/consul/获取
- 功能界面主页,http://localhost:8500
③ Eureka
- 跟Spring Cloud版本保持一致,自行搭建服务器
- 功能界面主页,http://localhost:9528
④ Zookeeper
- Spring Cloud F版或以上,必须采用Zookeeper服务器的3.5.x服务器版本(或者更高),从http://zookeeper.apache.org/releases.html#download获取
- Spring Cloud E版,Zookeeper服务器版本不限制
① Nacos
- Nacos服务器版本,推荐用最新版本,从https://github.com/alibaba/nacos/releases获取
- 功能界面主页,http://localhost:8848/nacos/index.html
② Apollo
- Apollo服务器版本,推荐用最新版本,从https://github.com/ctripcorp/apollo/releases获取
- 功能界面主页,http://localhost:8088
③ Redis
- Redis服务器版本,推荐用最新版本,从https://redis.io获取
④ Etcd
- Etcd服务器版本,推荐用最新版本,从https://github.com/etcd-io/etcd/releases获取
① Sentinel
- Sentinel服务器版本,推荐用最新版本,从https://github.com/alibaba/Sentinel/releases获取
- 功能界面主页,http://localhost:8075/#/dashboard
① Jaeger
- Jaeger服务器版本,推荐用最新版本,从https://github.com/jaegertracing/jaeger/releases获取
- 功能界面主页,http://localhost:16686
② SkyWalking
- SkyWalking服务器版本,推荐用最新版本,从http://skywalking.apache.org/downloads获取
- 功能界面主页,http://127.0.0.1:8080/
③ Zipkin
- Zipkin服务器版本,推荐用最新版本,从https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec获取
- 功能界面主页,http://localhost:9411/zipkin
① Prometheus
- Prometheus服务器版本,推荐用最新版本,从https://github.com/prometheus/prometheus/releases获取
- 功能界面主页,http://localhost:9090
② Grafana
- Grafana服务器版本,推荐用最新版本,从https://grafana.com/grafana/download?platform=windows获取
- 功能界面主页,http://localhost:3000
③ Spring Boot Admin
- 跟Spring Boot版本保持一致,自行搭建服务器。从https://github.com/codecentric/spring-boot-admin获取
- 功能界面主页,http://localhost:6002
① H2内存数据库
- H2内存数据库服务器版本,推荐用最新版本,从http://h2database.com/html/download.html获取
- 功能界面主页,http://localhost:8082