# Discovery
**Repository Path**: WqlPersionalRepository/Discovery
## Basic Information
- **Project Name**: Discovery
- **Description**: 🐳 Nepxion Discovery is an enhancement for Spring Cloud Discovery with gray release, router, weight, limitation, circuit breaker, degrade, isolation, monitor, tracing 灰度发布、路由、权重、限流、熔断、降级、隔离、监控、追踪
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: http://www.nepxion.com
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 292
- **Created**: 2020-09-06
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[^_^]: 

# Discovery【探索】微服务企业级解决方案
[](https://tokei.rs/b1/github/Nepxion/Discovery?category=lines) [](https://github.com/Nepxion/Discovery/blob/master/LICENSE) [](https://search.maven.org/artifact/com.nepxion/discovery) [](http://www.javadoc.io/doc/com.nepxion/discovery-plugin-framework-starter) [](https://travis-ci.org/Nepxion/Discovery) [](https://www.codacy.com/project/HaojunRen/Discovery/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Nepxion/Discovery&utm_campaign=Badge_Grade_Dashboard) [](https://github.com/Nepxion/Discovery/stargazers) [](https://gitee.com/nepxion/Discovery/stargazers)
[](https://search.maven.org/artifact/org.springframework.boot/spring-boot-dependencies) [](https://search.maven.org/artifact/org.springframework.cloud/spring-cloud-dependencies) [](https://search.maven.org/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies) [](https://search.maven.org/artifact/com.nepxion/discovery)
[](http://nepxion.gitee.io/docs/link-doc/discovery-ppt.html) [](http://nepxion.gitee.io/docs/link-doc/discovery-pdf.html) [](http://nepxion.gitee.io/docs/link-doc/discovery-html.html)
 如果您觉得本框架具有一定的参考价值和借鉴意义,请帮忙在页面右上角 [**Star**]
 首席作者简介
- Nepxion开源社区创始人
- 2020年阿里巴巴中国云原生峰会出品人
- Nacos Group Member
- Spring Cloud Alibaba、Nacos、Sentinel、OpenTracing Committer & Contributor
 Discovery【探索】微服务企业级解决方案
① Discovery【探索】微服务企业级解决方案文档
- [Discovery【探索】微服务企业级解决方案(PPT版)](http://nepxion.gitee.io/docs/link-doc/discovery-ppt.html)
- [Discovery【探索】微服务企业级解决方案(PDF版)](http://nepxion.gitee.io/docs/link-doc/discovery-pdf.html)
- [Discovery【探索】微服务企业级解决方案(HTML版)](http://nepxion.gitee.io/docs/link-doc/discovery-html.html)
② Discovery【探索】微服务企业级解决方案源码。请访问Gitee镜像获得最佳体验
- [源码Gitee同步镜像](https://gitee.com/Nepxion/Discovery)
- [源码Github原镜像](https://github.com/Nepxion/Discovery)
③ Discovery【探索】微服务企业级解决方案指南示例源码。请访问Gitee镜像获得最佳体验
- [指南Gitee同步镜像](https://gitee.com/Nepxion/DiscoveryGuide)
- [指南Github原镜像](https://github.com/Nepxion/DiscoveryGuide)
④ Discovery【探索】微服务框架指南示例说明
- 对于入门级玩家,参考[指南示例极简版](https://github.com/Nepxion/DiscoveryGuide/tree/simple),分支为simple。涉及到指南篇里的灰度路由和发布的基本功能, 参考[新手快速入门](https://gitee.com/nepxion/DiscoveryGuide/blob/simple/GUIDE.md)
- 对于熟练级玩家,参考[指南示例精进版](https://github.com/Nepxion/DiscoveryGuide/tree/master),分支为master。除上述《极简版》功能外,涉及到指南篇里的绝大多数高级功能
- 对于骨灰级玩家,参考[指南示例高级版](https://github.com/Nepxion/DiscoveryGuide/tree/premium),分支为premium。除上述《精进版》功能外,涉及到指南篇里的ActiveMQ、MongoDB、RabbitMQ、Redis、RocketMQ、MySQL等高级调用链和灰度调用链的整合
 Polaris【北极星】企业级云原生微服务框架
① Polaris【北极星】企业级云原生微服务框架文档
- [Polaris【北极星】企业级云原生微服务框架(PDF版)](http://nepxion.gitee.io/docs/link-doc/polaris-pdf.html)
- [Polaris【北极星】企业级云原生微服务框架(HTML版)](http://nepxion.gitee.io/docs/link-doc/polaris-html.html)
② Polaris【北极星】企业级云原生微服务框架源码。请访问Gitee镜像获得最佳体验
- [源码Gitee同步镜像](https://gitee.com/polaris-paas/polaris-sdk)
- [源码Github原镜像](https://github.com/polaris-paas/polaris-sdk)
③ Polaris【北极星】企业级云原生微服务框架指南示例源码。请访问Gitee镜像获得最佳体验
- [指南Gitee同步镜像](https://gitee.com/polaris-paas/polaris-guide)
- [指南Github原镜像](https://github.com/polaris-paas/polaris-guide)
 Discovery【探索】和Polaris【北极星】架构体系
① Discovery【探索】和Polaris【北极星】联合架构图

② Discovery【探索】和Polaris【北极星】联合拓扑图

③ Polaris【北极星】分层架构图

④ Discovery【探索】实施方案图

⑤ Discovery【探索】域网关实施图

⑥ Discovery【探索】非域网关实施图

⑦ Discovery【探索】全局订阅实施图

## 简介
Discovery【探索】微服务框架,基于Spring Cloud Discovery服务注册发现、Ribbon负载均衡、Feign和RestTemplate调用等组件全方位增强的企业级微服务开源解决方案,更贴近企业级需求,更具有企业级的插件引入、开箱即用特征
① 微服务框架支持的基本功能,如下
- 支持阿里巴巴Nacos、Eureka、Consul和Zookeeper四个服务注册发现中心
- 支持阿里巴巴Nacos、携程Apollo和Redis三个远程配置中心
- 支持阿里巴巴Sentinel和Hystrix两个熔断限流降级权限中间件
- 支持OpenTracing和OpenTelemetry规范下的调用链中间件,Uber Jaeger、Apache Skywalking和Zipkin等
- 支持Prometheus Micrometer和Spring Boot Admin两个指标中间件
- 支持Java Agent解决异步跨线程ThreadLocal上下文传递
- 支持Spring Cloud Gateway、Zuul网关和微服务三大模块的灰度发布和路由等一系列功能
- 支持和兼容Spring Cloud Edgware版、Finchley版、Greenwich版和Hoxton版
② 微服务框架支持的应用功能,如下
- 基于Header传递的全链路灰度路由。采用配置中心配置路由策略映射在网关或者服务上,支持根据用户自定义Header跟路由策略整合,最终转化为路由Header信息而实现,路由策略传递到全链路服务中。主要包括
- 匹配路由。包括版本匹配路由、区域匹配路由、IP地址和端口匹配路由
- 权重路由。包括版本权重路由、区域权重路由
- 前端触发路由
- 过滤器触发路由
- 负载均衡策略类触发路由
- 灰度路由下的版本故障转移
- 并行灰度路由下的版本偏好
- 异步场景下的触发路由
- 通过Spring Spel的条件表达式支持等于[=]、不等于[!=]、大于[>]、小于[<]、与[&&]、或[||]、匹配[matches],以及加减乘除取模等全部标准Spring Spel表达式用法
- 通过Spring Matcher的通配符表达式支持多个通配[*]、单个通配[?]等全部标准的Spring Matcher表达式用法
- 通过Header、Query Parameter、Cookie支持混合策略表达式
- 通过内置Header支持定时Job的服务调用灰度路由
- 基于Query Parameter的全链路灰度路由。跟基于Header传递的全链路灰度路由一样,区别是支持根据用户自定义Query Parameter跟路由策略整合。也支持过滤器和负载均衡策略类中自定义方式
- 基于Cookie的全链路灰度路由。跟基于Header传递的全链路灰度路由一样,区别是支持根据用户自定义Cookie跟路由策略整合。也支持过滤器和负载均衡策略类中自定义方式
- 基于域名的全链路灰度路由。通过自定义过滤器和负载均衡策略类解析域名映射成路由Header信息而实现,路由策略传递到全链路服务中
- 基于RPC Method的全链路灰度路由。通过自定义过滤器和负载均衡策略类解析RPC Method参数实现路由
- 基于动态变更元数据的全链路灰度路由。通过某些提供动态改变元数据的Open API接口方式而实现
- 基于全局订阅式的全链路灰度路由。通过所有网关和服务共同订阅同一策略配置的方式而实现,规避Header、Query Parameter、Cookie使用或者传递
- 基于服务下线实时性的流量绝对无损策略。支持全局订阅和Header全链路传递两种方式,主要包括
- 通过全局唯一ID进行屏蔽。适用于Docker和Kubernetes上IP地址不确定的场景
- 通过IP地址或者端口或者IP地址+端口进行屏蔽。适用于IP地址确定的场景
- 基于规则订阅的全链路灰度发布。采用配置中心配置灰度规则映射在全链路服务而实现,所有服务都订阅一个共享配置。主要包括
- 匹配发布。包括版本匹配发布、区域匹配发布
- 权重发布。包括版本权重发布、区域权重发布
- 基于灰度发布和灰度路由的多种组合式规则策略。主要包括
- 全链路灰度条件命中和灰度匹配组合式策略
- 全链路灰度条件权重和灰度匹配组合式策略
- 前端灰度和网关灰度路由组合式策略
- 全链路灰度权重和灰度匹配组合式规则
- 基于多方式的规则策略推送。主要包括
- 基于远程配置中心的规则策略订阅推送
- 基于Swagger和Rest的规则策略推送
- 基于图形化界面的规则策略推送
- 基于组Group和黑白名单的全链路服务隔离和准入。主要包括
- 服务注册发现准入。包括基于组Group黑白名单注册准入、基于IP地址黑白名单注册准入、基于最大注册数限制注册准入、基于IP地址黑白名单发现准入
- 消费端服务隔离。包括基于组Group负载均衡隔离
- 提供端服务隔离。包括基于组Group Header传值策略隔离
- 基于Env的全链路环境隔离和路由。主要包括
- 环境隔离。基于服务实例的元数据Metadata的env参数和全链路传递的环境Header值进行比对实现隔离
- 环境路由。基于调用端实例找不到符合条件的提供端实例,把流量路由到一个通用或者备份环境
- 基于Zone的全链路可用区亲和性隔离和路由。主要包括
- 可用区亲和性隔离。基于调用端实例和提供端实例的元数据Metadata的zone配置值相等实现隔离
- 可用区亲和性路由。基于调用端实例找不到同一可用区的提供端实例,把流量路由到其它可用区或者不归属任何可用区
- 基于Sentinel的全链路服务限流熔断降级权限。除了支持Sentinel原生五个相关规则外,扩展LimitApp的机制,整合灰度路由,通过Header传递方式实现组合式防护机制。主要包括
- 基于服务名的防护
- 基于灰度组的防护
- 基于灰度版本的防护
- 基于灰度区域的防护
- 基于IP地址和端口的防护
- 基于自定义业务参数的组合式防护机制
- 基于Hystrix的全链路服务限流熔断和灰度融合
- 全链路监控。主要包括
- 全链路调用链监控
- 全链路日志监控
- 全链路指标监控
- 全链路Header传递
- 全链路侦测
- 全链路服务侧注解
- 全链路服务侧API权限
- 异步跨线程Agent。主要包括
- 插件获取
- 插件使用
- 插件扩展
- 元数据Metadata自动化策略。主要包括
- 基于服务名前缀自动创建灰度组名
- 基于Git插件自动创建灰度版本号
- 元数据Metadata运维平台策略
- 同城双活多机房切换。基于区域匹配发布或者路由的同城双活多机房切换
- 数据库和消息队列灰度发布。基于多Datasource的数据库灰度发布,基于多Queue的消息队列灰度发布
- 灰度路由和发布的自动化测试。主要包括
- 基于Spring Boot/Spring Cloud自动化测试,包括普通调用测试、灰度调用测试和扩展调用测试
- 基于WRK的性能压力测试
- Docker容器化和Kubernetes平台的无缝支持部署
 基于RESTful层面的功能全景

③ 微服务框架易用性表现,如下
- 引入相关依赖到pom.xml
- 设置元数据MetaData。如下五个元数据可以按需设置
- 定义所属组名Group,也可以通过服务名前缀来自动产生服务组名
- 定义版本号Version,也可以通过Git插件方式自动产生版本号
- 定义所属区域名Region
- 定义所属环境Env
- 定义所属可用区Zone
- 执行采用“约定大于配置”的准则,使用者也可以开启和关闭相关功能项或者属性值,达到最佳配置
- 规则策略文件设置和推送,或者通过Header、Query Parameter、Cookie触发,并通过Header方式全链路传递路由策略
④ 微服务框架版本兼容列表,如下
 提醒:版本号右边, `↑` 表示>=该版本号, `↓` 表示<=该版本号
| 框架版本 | 框架分支 | 框架状态 | Spring Cloud版本 | Spring Boot版本 | Spring Cloud Alibaba版本 |
| --- | --- | --- | --- | --- | --- |
| 6.3.3 | master |  | Hoxton.SR5 `↑`
Hoxton
Greenwich
Finchley | 2.3.x.RELEASE
2.2.x.RELEASE
2.1.x.RELEASE
2.0.x.RELEASE | 2.2.x.RELEASE
2.2.x.RELEASE
2.1.x.RELEASE
2.0.x.RELEASE |
| ~~5.6.0~~ | ~~5.x.x~~ |  | Greenwich | 2.1.x.RELEASE | 2.1.x.RELEASE |
| ~~4.15.0~~ | ~~4.x.x~~ |  | Finchley | 2.0.x.RELEASE | 2.0.x.RELEASE |
| 3.20.3 | 3.x.x |  | Edgware | 1.5.x.RELEASE | 1.5.x.RELEASE |
| ~~2.0.x~~ | ~~2.x.x~~ |  | Dalston | 1.x.x.RELEASE | N/A |
| ~~1.0.x~~ | ~~1.x.x~~ |  | Camden | 1.x.x.RELEASE | N/A |
 表示维护中 |  表示不维护,但可用,强烈建议升级 |  表示不维护,不可用,已废弃
- 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)已废弃
## 鸣谢
 郑重致谢
- 感谢阿里巴巴中间件Nacos、Sentinel和Spring Cloud Alibaba团队,尤其是Nacos负责人@彦林、@于怀,Sentinel负责人@宿何、@子衿,Spring Cloud Alibaba负责人@小马哥、@洛夜、@亦盏的技术支持
- 感谢携程Apollo团队,尤其是@宋顺的技术支持
- 感谢所有Committers和Contributors
- 感谢所有帮忙分析和定位问题的同学
- 感谢所有提出宝贵建议和意见的同学
- 感谢阿里巴巴中间件Nacos和Spring Cloud Alibaba团队,纳入本框架为相关开源项目

- 感谢支持和使用本框架的公司和企业。不完全统计,目前社区开源项目(包括本框架以及关联框架或组件)已经被如下公司使用或者调研
 为提供更好的专业级服务,请更多已经使用本框架的公司和企业联系我,并希望在[Github Issues](https://github.com/Nepxion/Discovery/issues/56)上登记
 某大型互联网教育公司在生产环境全套接入Nepxion Discovery框架的服务实例数截至到2020年11月已达到2100个
## 目录
- [简介](#简介)
- [鸣谢](#鸣谢)
- [请联系我](#请联系我)
- [相关链接](#相关链接)
- [源码主页](#源码主页)
- [指南主页](#指南主页)
- [文档主页](#文档主页)
- [现有痛点](#现有痛点)
- [名词解释](#名词解释)
- [工程架构](#工程架构)
- [工程清单](#工程清单)
- [架构核心](#架构核心)
- [依赖引入](#依赖引入)
- [准备工作](#准备工作)
- [环境搭建](#环境搭建)
- [启动服务](#启动服务)
- [环境验证](#环境验证)
- [基于Header传递方式的灰度路由策略](#基于Header传递方式的灰度路由策略)
- [配置网关灰度路由策略](#配置网关灰度路由策略)
- [版本匹配灰度路由策略](#版本匹配灰度路由策略)
- [版本权重灰度路由策略](#版本权重灰度路由策略)
- [区域匹配灰度路由策略](#区域匹配灰度路由策略)
- [区域权重灰度路由策略](#区域权重灰度路由策略)
- [IP地址和端口匹配灰度路由策略](#IP地址和端口匹配灰度路由策略)
- [配置全链路灰度条件命中和灰度匹配组合式策略](#配置全链路灰度条件命中和灰度匹配组合式策略)
- [配置全链路灰度条件权重和灰度匹配组合式策略](#配置全链路灰度条件权重和灰度匹配组合式策略)
- [配置前端灰度和网关灰度路由组合式策略](#配置前端灰度和网关灰度路由组合式策略)
- [通过其它方式设置灰度路由策略](#通过其它方式设置灰度路由策略)
- [通过前端传入灰度路由策略](#通过前端传入灰度路由策略)
- [通过业务参数在过滤器中自定义灰度路由策略](#通过业务参数在过滤器中自定义灰度路由策略)
- [通过业务参数在策略类中自定义灰度路由策略](#通过业务参数在策略类中自定义灰度路由策略)
- [灰度路由下的版本故障转移](#灰度路由下的版本故障转移)
- [并行灰度路由下的版本偏好策略](#并行灰度路由下的版本偏好策略)
- [异步场景的全链路灰度路由策略](#异步场景的全链路灰度路由策略)
- [基于Query-Parameter的全链路灰度路由](#基于Query-Parameter的全链路灰度路由)
- [基于Cookie的全链路灰度路由](#基于Cookie的全链路灰度路由)
- [基于域名的全链路灰度路由](#基于域名的全链路灰度路由)
- [基于RPC-Method的全链路灰度路由](#基于RPC-Method的全链路灰度路由)
- [基于动态变更元数据的灰度路由策略](#基于动态变更元数据的灰度路由策略)
- [基于全局订阅式的灰度路由策略](#基于全局订阅式的灰度路由策略)
- [基于服务下线实时性的流量绝对无损策略](#基于服务下线实时性的流量绝对无损策略)
- [配置全局唯一ID屏蔽策略](#配置全局唯一ID屏蔽策略)
- [配置IP地址和端口屏蔽策略](#配置IP地址和端口屏蔽策略)
- [基于订阅方式的全链路灰度发布规则](#基于订阅方式的全链路灰度发布规则)
- [配置全链路灰度匹配规则](#配置全链路灰度匹配规则)
- [版本匹配灰度规则](#版本匹配灰度规则)
- [区域匹配灰度规则](#区域匹配灰度规则)
- [配置全链路灰度权重规则](#配置全链路灰度权重规则)
- [全局版本权重灰度规则](#全局版本权重灰度规则)
- [局部版本权重灰度规则](#局部版本权重灰度规则)
- [全局区域权重灰度规则](#全局区域权重灰度规则)
- [局部区域权重灰度规则](#局部区域权重灰度规则)
- [配置全链路灰度权重和灰度匹配组合式规则](#配置全链路灰度权重和灰度匹配组合式规则)
- [数据库和消息队列灰度发布规则](#数据库和消息队列灰度发布规则)
- [基于多格式的规则策略定义](#基于多格式的规则策略定义)
- [规则策略格式定义](#规则策略格式定义)
- [规则策略内容定义](#规则策略内容定义)
- [规则策略示例](#规则策略示例)
- [基于多方式的规则策略推送](#基于多方式的规则策略推送)
- [基于远程配置中心的规则策略订阅推送](#基于远程配置中心的规则策略订阅推送)
- [基于Swagger和Rest的规则策略推送](#基于Swagger和Rest的规则策略推送)
- [基于图形化界面的规则策略推送](#基于图形化界面的规则策略推送)
- [基于组和黑白名单的全链路服务隔离和准入](#基于组和黑白名单的全链路服务隔离和准入)
- [服务注册发现准入](#服务注册发现准入)
- [基于组黑白名单注册准入](#基于组黑白名单注册准入)
- [基于IP地址黑白名单注册准入](#基于IP地址黑白名单注册准入)
- [基于最大注册数限制注册准入](#基于最大注册数限制注册准入)
- [基于IP地址黑白名单发现准入](#基于IP地址黑白名单发现准入)
- [自定义注册发现准入](#自定义注册发现准入)
- [消费端服务隔离](#消费端服务隔离)
- [基于组负载均衡隔离](#基于组负载均衡隔离)
- [提供端服务隔离](#提供端服务隔离)
- [基于组Header传值策略隔离](#基于组Header传值策略隔离)
- [基于Env的全链路环境隔离和路由](#基于Env的全链路环境隔离和路由)
- [环境隔离](#环境隔离)
- [环境路由](#环境路由)
- [基于Zone的全链路可用区亲和性隔离和路由](#基于Zone的全链路可用区亲和性隔离和路由)
- [可用区亲和性隔离](#可用区亲和性隔离)
- [可用区亲和性路由](#可用区亲和性路由)
- [基于Sentinel的全链路服务限流熔断降级权限和灰度融合](#基于Sentinel的全链路服务限流熔断降级权限和灰度融合)
- [原生Sentinel注解](#原生Sentinel注解)
- [原生Sentinel规则](#原生Sentinel规则)
- [流控规则](#流控规则)
- [降级规则](#降级规则)
- [授权规则](#授权规则)
- [系统规则](#系统规则)
- [热点参数流控规则](#热点参数流控规则)
- [基于灰度路由和Sentinel-LimitApp扩展的防护机制](#基于灰度路由和Sentinel-LimitApp扩展的防护机制)
- [基于服务名的防护机制](#基于服务名的防护机制)
- [基于灰度组的防护机制](#基于灰度组的防护机制)
- [基于灰度版本的防护机制](#基于灰度版本的防护机制)
- [基于灰度区域的防护机制](#基于灰度区域的防护机制)
- [基于IP地址和端口的防护机制](#基于IP地址和端口的防护机制)
- [自定义业务参数的组合式防护机制](#自定义业务参数的组合式防护机制)
- [基于Hystrix的全链路服务限流熔断和灰度融合](#基于Hystrix的全链路服务限流熔断和灰度融合)
- [全链路监控](#全链路监控)
- [全链路调用链监控](#全链路调用链监控)
- [Header输出方式](#Header输出方式)
- [调用链输出方式](#调用链输出方式)
- [日志输出方式](#日志输出方式)
- [全链路指标监控](#全链路指标监控)
- [Prometheus监控方式](#Prometheus监控方式)
- [Grafana监控方式](#Grafana监控方式)
- [Spring-Boot-Admin监控方式](#Spring-Boot-Admin监控方式)
- [全链路Header传递](#全链路Header传递)
- [全链路侦测](#全链路侦测)
- [全链路服务侧注解](#全链路服务侧注解)
- [全链路服务侧API权限](#全链路服务侧API权限)
- [异步跨线程Agent](#异步跨线程Agent)
- [插件获取](#插件获取)
- [插件使用](#插件使用)
- [插件扩展](#插件扩展)
- [元数据Metadata自动化策略](#元数据Metadata自动化策略)
- [基于服务名前缀自动创建灰度组名](#基于服务名前缀自动创建灰度组名)
- [基于Git插件自动创建灰度版本号](#基于Git插件自动创建灰度版本号)
- [元数据Metadata运维平台策略](#元数据Metadata运维平台策略)
- [配置文件](#配置文件)
- [基础属性配置](#基础属性配置)
- [功能开关配置](#功能开关配置)
- [内置文件配置](#内置文件配置)
- [Docker容器化和Kubernetes平台支持](#Docker容器化和Kubernetes平台支持)
- [Docker容器化](#Docker容器化)
- [Kubernetes平台支持](#Kubernetes平台支持)
- [自动化测试](#自动化测试)
- [架构设计](#架构设计)
- [启动控制台](#启动控制台)
- [配置文件](#配置文件)
- [测试用例](#测试用例)
- [测试包引入](#测试包引入)
- [测试入口程序](#测试入口程序)
- [普通调用测试](#普通调用测试)
- [灰度调用测试](#灰度调用测试)
- [扩展调用测试](#扩展调用测试)
- [测试报告](#测试报告)
- [压力测试](#压力测试)
- [测试环境](#测试环境)
- [测试介绍](#测试介绍)
- [测试步骤](#测试步骤)
- [附录](#附录)
- [中间件服务器下载地址](#中间件服务器下载地址)
- [Star走势图](#Star走势图)
## 请联系我
微信、公众号和文档

## 相关链接
### 源码主页
[Discovery源码主页](https://github.com/Nepxion/Discovery)
[Polaris源码主页](https://github.com/Nepxion/Polaris)
### 指南主页
[Discovery指南主页](https://github.com/Nepxion/DiscoveryGuide)
[Polaris指南主页](https://github.com/Nepxion/PolarisGuide)
### 文档主页
[文档主页](https://gitee.com/Nepxion/Docs/tree/master/web-doc)
## 现有痛点
现有的Spring Cloud微服务架构的痛点
- 如果你是运维负责人,是否会经常发现,你掌管的测试环境中的服务注册中心,被一些不负责的开发人员把他本地开发环境注册上来,造成测试人员测试失败。你希望可以把本地开发环境注册给屏蔽掉,不让注册
- 如果你是运维负责人,生产环境的某个微服务集群下的某个实例,暂时出了问题,但又不希望它下线。你希望可以把该实例给屏蔽掉,暂时不让它被调用
- 如果你是业务负责人,鉴于业务服务的快速迭代性,微服务集群下的实例发布不同的版本。你希望根据版本管理策略进行路由,提供给下游微服务区别调用,例如访问控制快速基于版本的不同而切换,例如在不同的版本之间进行流量调拨
- 如果你是业务负责人,希望灰度发布功能可以基于业务场景特色定制,例如根据用户手机号进行不同服务器的路由
- 如果你是DBA负责人,希望灰度发布功能可以基于数据库切换上
- 如果你是测试负责人,希望对微服务做A/B测试
## 名词解释
- E版、F版、G版、H版,即Spring Cloud的Edgware、Finchley、Greenwich、Hoxton的首字母,以此类推
- 灰度发布和灰度路由。灰度发布即基于订阅方式的灰度功能,灰度路由即通过Header传递路由信息的灰度功能,但它也能支持订阅方式
- 匹配灰度和权重灰度。匹配灰度即在灰度的时候,没有过渡过程,用版本匹配的方式流量,直接从旧版本切换到新版本,权重灰度即在灰度的时候,有个过渡过程,可以根据实际情况,先给新版本分配低额流量,给旧版本分配高额流量,对新版本进行监测,如果没有问题,就继续把旧版的流量切换到新版本上
- 规则定义和策略定义。规则定义即通过XML或者Json定义既有格式的规则,策略定义即通过Header策略方式传递路由信息
- 本地版本,即初始化读取本地配置文件获取的版本,也可以是第一次读取远程配置中心获取的版本。本地版本和初始版本是同一个概念
- 动态版本,即灰度发布时的版本。动态版本和灰度版本是同一个概念。这里的动态版本跟通过注册中心动态元数据不是同一个概念
- 本地规则,即初始化读取本地配置文件获取的规则,也可以是第一次读取远程配置中心获取的规则。本地规则和初始规则是同一个概念
- 动态规则,即灰度发布时的规则。动态规则和灰度规则是同一个概念
- 事件总线,即基于Google Guava的EventBus构建的组件。通过事件总线可以推送动态规则策略和动态版本的更新和删除
- 远程配置中心,即可以存储规则策略配置XML格式的配置中心,可以包括不限于Nacos、Apollo、Redis等
- 配置Config和规则Rule。在本系统中属于同一个概念,例如,更新配置即更新规则;例如,远程配置中心存储的配置即规则XML
 灰度发布(规则)和灰度路由(策略)
① 灰度发布(规则)和灰度路由(策略)对比
| | 灰度发布 | 灰度路由 |
| --- | --- | --- |
| 驱动域 | 规则 | 策略 |
| 驱动方式 | 通过XML或者Json配置直接驱动 | 通过REST或者RPC调用传递Header和参数
+ XML或者Json配置辅助驱动 |
| 驱动频率 | 配置更新 | 每次调用时候传递 + 配置更新 |
| 扩展性 | 扩展继承三个AbstractXXXListener | 扩展实现DiscoveryEnabledStrategy |
| 依赖性 | 依赖配置中心或者本地配置文件 | 依赖每次调用 + 配置中心或者本地配置文件 |
| 优点 | 封闭式灰度,无Header驱动,业务
无感知 | 开放式灰度,支持Header驱动,条件型灵活
灰度,灰度生效实时无延迟性 |
| 缺点 | 不支持条件型灵活灰度,灰度生效
有延迟性 | 需要业务介入,需要处理好异步调用场景下
Header丢失的问题 |
② 灰度发布(规则)和灰度路由(策略)关系
- 灰度发布(规则)和灰度路由(策略),可以并行在一起工作,也关闭一项,让另一项单独工作
- 灰度发布(规则)和灰度路由(策略),一起工作的时候,先执行规则过滤逻辑,再执行策略过滤逻辑
- 灰度发布(规则)和灰度路由(策略)关闭方式
灰度发布(规则)关闭
```
spring.application.register.control.enabled=false
spring.application.discovery.control.enabled=false
```
灰度路由(策略)关闭
```
spring.application.strategy.control.enabled=false
```
 动态改变规则策略和动态改变版本
① 动态改变规则策略
微服务启动的时候,由于规则策略(例如:rule.xml)已经配置在本地,使用者希望改变一下规则策略,而不重启微服务,达到规则策略的改变
- 规则策略分为本地规则策略和动态规则策略
- 本地规则策略是通过在本地规则策略(例如:rule.xml)文件定义的,也可以从远程配置中心获取,在微服务启动的时候读取
- 动态规则策略是通过POST方式动态设置,或者由远程配置中心推送设置
- 规则策略初始化的时候,如果接入了远程配置中心,先读取远程规则策略,如果不存在,再读取本地规则策略文件
- 规则策略可以持久化到远程配置中心,一旦微服务死掉后,再次启动,仍旧可以拿到灰度规则策略,所以动态改变规则策略策略属于永久灰度手段
- 规则策略推送到远程配置中心可以分为局部推送和全局推送
- 局部推送是基于Group+ServiceId来推送的,就是同一个Group下同一个ServiceId的服务集群独立拥有一个规则策略配置,如果采用这种方式,需要在每个微服务集群下做一次灰度。优点是独立封闭,本服务集群灰度失败不会影响到其它服务集群,缺点是相对繁琐
- 全局推送是基于Group来推送的(接口参数中的ServiceId由Group来代替),就是同一个Group下所有服务集群共同拥有一个规则策略配置,如果采用这种方式,只需要做一次灰度,所有服务集群都生效。优点是非常简便,缺点是具有一定风险,因为这个规则策略配置掌握着所有服务集群的命运。全局推送用于全链路灰度
- 如果既执行了全局推送,又执行了局部推送,在服务运行期间或者服务重新启动,优先读取局部规则策略中的相应配置项,当局部规则策略的相应配置项不存在,则读取全局规则策略的相应配置项
② 动态改变版本
 注意:动态改变版本,只允许发生在调用链的起点,例如网关,如果没有网关,则取第一个服务,其它层级的服务不能使用该功能
微服务启动的时候,由于版本已经写死在application.properties里,使用者希望改变一下版本,而不重启微服务,达到访问版本的路径改变
- 版本分为本地版本和动态版本
- 本地版本是通过在application.properties里配置的,在微服务启动的时候读取
- 动态版本是通过POST方式动态设置
- 获取版本值的时候,先获取动态版本,如果不存在,再获取本地版本
- 版本不会持久化到远程配置中心,一旦微服务重新启动,拿到的还是本地版本,所以动态改变版本策略属于临时灰度手段
## 工程架构
### 工程清单
① Discovery工程清单
| 工程名 | 描述 |
| --- | --- |
|
discovery-commons | 通用模块目录 |
|
discovery-common | 通用模块 |
|
discovery-common-apollo | 封装Apollo通用操作逻辑 |
|
discovery-common-nacos | 封装Nacos通用操作逻辑 |
|
discovery-common-redis | 封装Redis通用操作逻辑 |
|
discovery-plugin-framework | 基本框架目录 |
|
discovery-plugin-framework-starter| 基本框架的Starter |
|
discovery-plugin-register-center | 注册中心目录 |
|
discovery-plugin-register-center-starter | 注册中心的Starter |
|
discovery-plugin-register-center-starter-eureka | 注册中心的Eureka Starter |
|
discovery-plugin-register-center-starter-consul | 注册中心的Consul Starter |
|
discovery-plugin-register-center-starter-zookeeper | 注册中心的Zookeeper Starter |
|
discovery-plugin-register-center-starter-nacos | 注册中心的Nacos Starter |
|
discovery-plugin-config-center | 配置中心目录 |
|
discovery-plugin-config-center-starter | 配置中心的Starter |
|
discovery-plugin-config-center-starter-apollo | 配置中心的Apollo Starter |
|
discovery-plugin-config-center-starter-nacos | 配置中心的Nacos Starter |
|
discovery-plugin-config-center-starter-redis | 配置中心的Redis Starter |
|
discovery-plugin-admin-center | 管理中心目录 |
|
discovery-plugin-admin-center-starter | 管理中心的Starter |
|
discovery-plugin-strategy | 路由策略目录 |
|
discovery-plugin-strategy-starter | 路由策略的Starter |
|
discovery-plugin-strategy-starter-service | 路由策略在微服务端的Starter |
|
discovery-plugin-strategy-starter-service-sentinel | 路由策略在微服务端的Sentinel Starter |
|
discovery-plugin-strategy-starter-zuul | 路由策略在Zuul网关端的Starter |
|
discovery-plugin-strategy-starter-gateway | 路由策略在Spring Cloud Gateway网关端的Starter |
|
discovery-plugin-strategy-starter-hystrix | 路由策略下,Hystrix做线程模式的服务隔离必须引入插件的Starter |
|
discovery-plugin-strategy-starter-opentelemetry | 路由策略的OpenTelemetry调用链的Starter |
|
discovery-plugin-strategy-starter-opentracing | 路由策略的OpenTracing调用链的Starter |
|
discovery-plugin-strategy-starter-skywalking | 路由策略的Skywalking调用链的Starter |
|
discovery-plugin-strategy-starter-sentinel | 路由策略的Sentinel Starter |
|
discovery-plugin-strategy-starter-sentinel-local | 路由策略的Sentinel Local配置订阅的Starter |
|
discovery-plugin-strategy-starter-sentinel-apollo | 路由策略的Sentinel Apollo配置订阅的Starter |
|
discovery-plugin-strategy-starter-sentinel-nacos | 路由策略的Sentinel Nacos配置订阅的Starter |
|
discovery-plugin-strategy-starter-sentinel-monitor | 路由策略的Sentinel监控抽象的Starter |
|
discovery-plugin-strategy-starter-sentinel-opentelemetry | 路由策略的Sentinel OpenTelemetry调用链的Starter |
|
discovery-plugin-strategy-starter-sentinel-opentracing | 路由策略的Sentinel OpenTracing调用链的Starter |
|
discovery-plugin-strategy-starter-sentinel-skywalking | 路由策略的Sentinel Skywalking调用链的Starter |
|
discovery-plugin-test | 测试模块目录 |
|
discovery-plugin-test-starter | 自动化测试的Starter |
|
discovery-console | 控制平台目录 |
|
discovery-console-starter | 控制平台的starter |
|
discovery-console-starter-apollo | 控制平台的Apollo Starter |
|
discovery-console-starter-nacos | 控制平台的Nacos Starter |
|
discovery-console-starter-redis | 控制平台的Redis Starter |
|
discovery-console-desktop | 图形化灰度发布等桌面程序 |
|
discovery-springcloud-examples | 示例目录 |
|
discovery-springcloud-example-admin | Spring Boot Admin服务台示例 |
|
discovery-springcloud-example-console | 控制平台示例 |
|
discovery-springcloud-example-eureka | Eureka服务器示例 |
|
discovery-springcloud-example-service | 微服务示例 |
|
discovery-springcloud-example-zuul | Zuul网关示例 |
|
discovery-springcloud-example-gateway | Spring Cloud Gateway网关示例 |
② DiscoveryAgent工程清单
| 工程名 | 描述 |
| --- | --- |
|
discovery-agent-starter | 异步跨线程Agent Starter |
|
discovery-agent-starter-plugin-strategy | 路由策略的异步跨线程Agent Plugin Starter |
### 架构核心
- 灰度方式区别图

① 基于网关为触点的Header传递的全链路灰度路由,适用于网关前置部署方式的企业。域网关部署模式下,最适用于该方式;非域网关部署模式下,开启并行灰度路由下的版本偏好策略
② 基于全局订阅方式的全链路灰度发布,适用于网关部署方式比较弱化的企业
③ 基于全局订阅和Header传递组合式全链路灰度路由,上述两种方式的结合体,是比较理想和节省成本的落地方式
- 服务治理架构图

- 模块结构图

### 依赖引入
① 服务注册发现依赖引入
服务注册发现中间件的四个插件,必须引入其中一个。该依赖提供灰度发布功能、注册发现中心、管理中心等功能
```xml
com.nepxion
discovery-plugin-register-center-starter-nacos
discovery-plugin-register-center-starter-eureka
discovery-plugin-register-center-starter-consul
discovery-plugin-register-center-starter-zookeeper
${discovery.version}
```
② 配置中心依赖引入
配置中心中间件的三个插件,选择引入其中一个(也可以引入使用者自己的扩展)。该依赖提供配置中心、规则策略解析等功能
```xml
com.nepxion
discovery-plugin-config-center-starter-apollo
discovery-plugin-config-center-starter-nacos
discovery-plugin-config-center-starter-redis
${discovery.version}
```
③ 管理中心依赖引入
该依赖提供基于Swagger和Rest的配置接口、版本接口、策略接口、路由接口、全链路侦测接口、哨兵接口、Git信息接口等功能
```xml
${project.groupId}
discovery-plugin-admin-center-starter
${discovery.version}
```
④ 路由策略依赖引入
微服务端、网关Zuul端和网关Spring Cloud Gateway端三个路由策略插件,选择引入其中一个。该依赖提供灰度路由功能、Header、Query Parameter、Cookie触发和传递等功能
```xml
com.nepxion
discovery-plugin-strategy-starter-service
discovery-plugin-strategy-starter-zuul
discovery-plugin-strategy-starter-gateway
${discovery.version}
```
⑤ 防护插件依赖引入
- Sentinel防护插件。只适用于微服务端
```xml
com.nepxion
discovery-plugin-strategy-starter-service-sentinel
${discovery.version}
```
- Sentinel防护的数据源插件,选择引入其中一个(也可以引入使用者自己的扩展)
```xml
com.nepxion
discovery-plugin-strategy-sentinel-starter-nacos
discovery-plugin-strategy-sentinel-starter-apollo
discovery-plugin-strategy-sentinel-starter-local
${discovery.version}
```
- Hystrix防护插件。Hystrix线程池隔离模式(信号量隔离模式不需要引入)下必须引入该插件,灰度路由Header和调用链Span在Hystrix线程池隔离模式下传递时,通过线程上下文切换会存在丢失Header的问题。通过该插件解决,支持微服务端、网关Zuul端和网关Spring Cloud Gateway端
```xml
com.nepxion
discovery-plugin-strategy-starter-hystrix
${discovery.version}
```
⑥ 控制台依赖引入
控制台对于配置中心中间件的三个插件,选择引入其中一个(也可以引入使用者自己的扩展)。该依赖提供配置中心、规则策略推送等功能
```xml
com.nepxion
discovery-console-starter-apollo
discovery-console-starter-nacos
discovery-console-starter-redis
${discovery.version}
```
⑦ 调用链插件依赖引入
调用链功能引入,支持微服务端、网关Zuul端和网关Spring Cloud Gateway端
 注意:该模块支持F版或更高版本,且不能同时引入
```xml
微服务端引入
com.nepxion
discovery-plugin-strategy-sentinel-starter-opentelemetry
discovery-plugin-strategy-sentinel-starter-opentracing
discovery-plugin-strategy-sentinel-starter-skywalking
${discovery.version}
```
⑧ 自动化测试插件依赖引入
```xml
com.nepxion
discovery-plugin-test-starter
${discovery.version}
```
⑨ 异步跨线程Agent引入
异步跨线程Agent的引入,通过Java Agent方式启动。灰度路由Header和调用链Span在Hystrix线程池隔离模式下或者线程、线程池、@Async注解等异步调用Feign或者RestTemplate时,通过线程上下文切换会存在丢失Header的问题。通过该插件解决,支持微服务端、网关Zuul端和网关Spring Cloud Gateway端
```
-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](https://github.com/Nepxion/DiscoveryGuide.git)
② 代码导入IDE
③ 下载Nacos服务器
- 从[https://github.com/alibaba/nacos/releases](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 | 无 | 无 | 无 |
| DiscoveryGuideConsole.java | Console | 6001 | 1.0 | 无 | 无 | 无 |
| DiscoveryGuideAdmin.java | Admin | 6002 | 1.0 | 无 | 无 | 无 |
- 部署拓扑图

全链路路径, 如下
```
API网关 -> 服务A(两个实例) -> 服务B(两个实例)
```
### 环境验证
① 通过Postman工具验证
- 导入Postman的测试脚本postman.json(位于根目录下)
- 在Postman中执行目录结构下 ”Nepxion“ -> ”Discovery指南网关接口“ -> ”Gateway网关调用示例“,调用地址为[http://localhost:5001/discovery-guide-service-a/invoke/gateway](http://localhost:5001/discovery-guide-service-a/invoke/gateway),相关的Header值已经预设,供开发者修改。执行通过Spring Cloud Gateway网关发起的调用,结果为如下格式
```
gateway
-> [ID=discovery-guide-service-a][P=Nacos][H=192.168.0.107:3001][V=1.0][R=dev][E=env1][Z=zone1][G=discovery-guide-group][TID=48682.7508.15870951148324081][SID=49570.77.15870951148480000]
-> [ID=discovery-guide-service-b][P=Nacos][H=192.168.0.107:4001][V=1.0][R=qa][E=env1][Z=zone2][G=discovery-guide-group][TID=48682.7508.15870951148324081][SID=49571.85.15870951189970000]
```
- 在Postman中执行目录结构下 ”Nepxion“ -> ”Discovery指南网关接口“ -> ”Zuul网关调用示例“,调用地址为[http://localhost:5002/discovery-guide-service-a/invoke/zuul](http://localhost:5002/discovery-guide-service-a/invoke/zuul),相关的Header值已经预设,供开发者修改。执行通过Zuul网关发起的调用,结果为如下格式
```
zuul
-> [ID=discovery-guide-service-a][P=Nacos][H=192.168.0.107:3001][V=1.0][R=dev][E=env1][Z=zone1][G=discovery-guide-group][TID=48682.7508.15870951148324081][SID=49570.77.15870951148480000]
-> [ID=discovery-guide-service-b][P=Nacos][H=192.168.0.107:4001][V=1.0][R=qa][E=env1][Z=zone2][G=discovery-guide-group][TID=48682.7508.15870951148324081][SID=49571.85.15870951189970000]
```
- 在Postman中多种同步和异步的调用方式,异步方式需要增加DiscoveryAgent,才能保证灰度路由成功
| URL | 调用方式 |
| --- | --- |
| /invoke/ | 同步调用 |
| /invoke-async/ | @Async注解方式的异步调用 |
| /invoke-thread/ | 单线程方式的异步调用 |
| /invoke-threadpool/ | 线程池方式的异步调用 |
- 上述步骤在下面每次更改规则策略的时候执行,并验证结果和规则策略的期望值是否相同
② 通过图形化界面验证
 该方式有点古老,并不再维护,请斟酌使用
- 下载[源码主页](https://github.com/Nepxion/Discovery)的工程,并导入IDE
- 启动源码工程下的discovery-springcloud-example-console/ConsoleApplication
- 启动源码工程下的discovery-console-desktop/ConsoleLauncher
- 通过admin/admin或者nepxion/nepxion登录,点击【显示服务拓扑】按钮,将呈现如下界面

- 在加入上述规则策略前,选中网关节点,右键点击【执行灰度路由】,在弹出路由界面中,依次加入“discovery-guide-service-a”和“discovery-guide-service-b”,点击【执行路由】按钮,将呈现如下界面

- 在加入上述规则策略后,在路由界面中,再次点击【执行路由】按钮,将呈现如下界面

## 基于Header传递方式的灰度路由策略
 本章节通过网关为触发点来介绍灰度路由策略功能,使用者也可以不通过网关,直接以微服务为触发点进行实施
### 配置网关灰度路由策略
在Nacos配置中心,增加网关灰度路由策略
#### 版本匹配灰度路由策略
增加Spring Cloud Gateway的基于版本匹配路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用都走版本为1.0的服务
```xml
1.0
```

每个服务调用的版本都可以自行指定,见下面第二条。当所有服务都选同一版本的时候,可以简化成下面第一条
```
1. 1.0
2. {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
```
如果上述表达式还未满足需求,也可以采用通配符(具体详细用法,参考Spring AntPathMatcher)
```
* - 表示调用范围为所有服务的所有版本
1.* - 表示调用范围为所有服务的1开头的所有版本
```
或者
```
"discovery-guide-service-b":"1.*;1.2.?"
```
表示discovery-guide-service-b服务的版本调用范围是1开头的所有版本,或者是1.2开头的所有版本(末尾必须是1个字符),多个用分号隔开
也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略)
版本灰度路由架构图

#### 版本权重灰度路由策略
增加Spring Cloud Gateway的基于版本权重路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用1.0版本流量调用为90%,1.1流量调用为10%
```xml
1.0=90;1.1=10
```

每个服务调用的版本权重都可以自行指定,见下面第二条。当所有服务都选相同版本权重的时候,可以简化成下面第一条
```
1. 1.0=90;1.1=10
2. {"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=90;1.1=10"}
```
也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略)
#### 区域匹配灰度路由策略
增加Zuul的基于区域匹配路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用都走区域为dev的服务
```xml
dev
```

每个服务调用的区域都可以自行指定,见下面第二条。当所有服务都选同一区域的时候,可以简化成下面第一条
```
1. dev
2. {"discovery-guide-service-a":"dev", "discovery-guide-service-b":"dev"}
```
如果上述表达式还未满足需求,也可以采用通配符(具体详细用法,参考Spring AntPathMatcher)
```
* - 表示调用范围为所有服务的所有区域
d* - 表示调用范围为所有服务的d开头的所有区域
```
或者
```
"discovery-guide-service-b":"d*;q?"
```
表示discovery-guide-service-b服务的区域调用范围是d开头的所有区域,或者是q开头的所有区域(末尾必须是1个字符),多个用分号隔开
也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略)
区域灰度路由架构图

#### 区域权重灰度路由策略
增加Zuul的基于区域权重路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用dev区域流量调用为85%,qa区域流量调用为15%
```xml
dev=85;qa=15
```

每个服务调用的区域权重都可以自行指定,见下面第二条。当所有服务都选相同区域权重的时候,可以简化成下面第一条
```
1. dev=85;qa=15
2. {"discovery-guide-service-a":"dev=85;qa=15", "discovery-guide-service-b":"dev=85;qa=15"}
```
也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略)
#### IP地址和端口匹配灰度路由策略
增加Zuul的基于IP地址和端口匹配的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用走指定IP地址和端口,或者指定IP地址,或者指定端口(下面策略以端口为例)的服务
```xml
3001
```

每个服务调用的端口都可以自行指定,见下面第二条。当所有服务都选同一端口的时候,可以简化成下面第一条(单机版不适用于该策略)
```
1. 3001
2. {"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"}
```
如果上述表达式还未满足需求,也可以采用通配符(具体详细用法,参考Spring AntPathMatcher)
```
* - 表示调用范围为所有服务的所有端口
3* - 表示调用范围为所有服务的3开头的所有端口
```
或者
```
"discovery-guide-service-b":"3*;400?"
```
表示discovery-guide-service-b服务的端口调用范围是3开头的所有端口,或者是4开头的所有端口(末尾必须是1个字符),多个用分号隔开
也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略)
IP地址和端口灰度路由架构图

### 配置全链路灰度条件命中和灰度匹配组合式策略
属于全链路蓝绿部署的范畴。既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务,一般来说,网关已经加了,服务上就不需要加,当不存在的网关的时候,服务就可以考虑加上
支持Spel表达式进行自定义策略,支持所有标准的Spel表达式,包括==,!=,>,>=,<,<=,&&,||等,由于策略保存在XML文件里,对于特殊符号需要转义,见下面表格
| 符号 | 转义符 | 含义 | 备注 |
| --- | --- | --- | --- |
| `&` | `&` | 和符号 | 必须转义 |
| `<` | `<` | 小于号 | 必须转义 |
| `"` | `"` | 双引号 | 必须转义 |
| `>` | `>` | 大于号 | |
| `'` | `'` | 单引号 | |
从Header获取到值进行逻辑判断,例如Header的Key为a,它的格式表示为#H['a'],H为Header的首字母。假如路由触发的条件为a等于1,b小于等于2,c不等于3,那么表达式可以写为
`#`H['a'] == '1' && `#`H['b'] <= '2' && `#`H['c'] != '3'
特殊符号必须转义,所以表达式必须改成如下
`#`H['a'] == '1' `&&` `#`H['b'] `<`= '2' `&&` `#`H['c'] != '3'
增加组合式的灰度策略,支持版本匹配、区域匹配、IP地址和端口匹配、版本权重匹配、区域权重匹配。以版本匹配为例,Group为discovery-guide-group,Data Id为discovery-guide-gateway,或者,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现功能
 Spel表达式的内容,需要注意
- 支持Header、Query Parameter、Cookie三种参数。例如,上面表达式,a、b、c的值可以来自Header、Query Parameter、Cookie中的任何一种。为兼容老的用法,统一以header节点来描述
- 支持Header、Query Parameter、Cookie混合策略表达式,例如,上面表达式,a的值可以来自于Header,b的值可以来自于Query Parameter,c的值可以来自于Cookie。如果同一个值同时存在于Header、Query Parameter、Cookie,优先级Header > Query Parameter > Cookie
 Spel表达式的逻辑,需要注意
- 任何值都大于null。当某个Header未传值,但又指定了该Header大于的表达式,那么正则结果是true。例如,表达式为#H['a'] > '2',但a作为Header未传递进来,即为null,判断结果为false
- null满足不等于。当某个Header未传值,但又指定了该Header不等于的表达式,那么正则结果是true。例如,表达式为#H['a'] != '2',但a作为Header未传递进来,即为null,判断结果为true
具体使用逻辑如下
① 当外部调用带有的Header中的值a=1同时b=2
``节点中 **header="#H['a'] == '1' && #H['b'] == '2'"** 对应的 **version-id="version-route1"** ,找到下面``节点中 **id="version-route1" type="version"** 的那项,那么路由即为
```
{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}
```
② 当外部调用带有的Header中的值a=1
``节点中 **header="#H['a'] == '1'"** 对应的 **version-id="version-route2"** ,找到下面``节点中 **id="version-route2" type="version"** 的那项,那么路由即为
```
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"}
```
③ 当外部调用带有的Header中的值都不命中
- 执行``节点中的全局缺省路由,那么路由即为
```
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
```
- 如果全局缺省路由未配置,则执行Spring Cloud Ribbon轮询策略
④ 假如不愿意从网关外部传入Header,那么支持策略下内置Header来决策蓝绿和灰度,可以代替外部传入Header,参考如下配置
```xml
```
内置Header一般使用场景为定时Job的服务调用灰度路由。当服务侧配置了内置Header,而网关也传递给对应Header给该服务,那么以网关传递的Header为优先
 注意:Spring Cloud Gateway在Finchley版不支持该方式
⑤ 策略总共支持5种,可以单独一项使用,也可以多项叠加使用
- version 版本路由
- region 区域路由
- address IP地址和端口路由
- version-weight 版本权重路由
- region-weight 区域权重路由
⑥ 策略支持Spring Spel的条件表达式方式
⑦ 策略支持Spring Matcher的通配符方式
⑧ 支持并行实施。通过namespace(可以自定义)的Header进行发布隔离
具体示例内容如下
```xml
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"}
```

内置基于Swagger的Rest接口,可以校验策略的条件表达式、校验策略的全链路路由
### 配置全链路灰度条件权重和灰度匹配组合式策略
属于全链路灰度发布的范畴。既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务,一般来说,网关已经加了,服务上就不需要加,当不存在的网关的时候,服务就可以考虑加上
增加组合式的灰度策略,支持版本匹配、区域匹配、IP地址和端口匹配。以版本匹配为例,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现功能
- a服务1.0版本向网关提供90%的流量,1.1版本向网关提供10%的流量
- a服务1.0版本只能访问b服务1.0版本,1.1版本只能访问b服务1.1版本
该功能的意义是,网关随机权重调用服务,而服务链路按照版本匹配方式调用
① version-route1链路配比10%的流量,version-route2链路配比90%的流量
② 策略总共支持3种,可以单独一项使用,也可以多项叠加使用
- version 版本路由
- region 区域路由
- address IP地址和端口路由
③ 其它用法和[配置全链路灰度条件命中和灰度匹配组合式策略](#配置全链路灰度条件命中和灰度匹配组合式策略)一致
具体示例内容如下
```xml
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}
```

### 配置前端灰度和网关灰度路由组合式策略
当前端(例如:APP)和后端微服务同时存在多个版本时,可以执行[前端灰度&网关灰度路由组合式策略](#前端灰度&网关灰度路由组合式策略)
例如:前端存在1.0和2.0版本,微服务存在1.0和2.0版本,由于存在版本不兼容的情况(前端1.0版本只能调用微服务的1.0版本,前端2.0版本只能调用微服务的2.0版本),那么前端调用网关时候,可以通过Header传递它的版本号给网关,网关根据前端版本号,去路由对应版本的微服务
该场景可以用[通过业务参数在过滤器中自定义灰度路由策略](#通过业务参数在过滤器中自定义灰度路由策略)的方案来解决,如下
```xml
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
{"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"}
```
上述配置,模拟出全链路中,两条独立不受干扰的调用路径
```
1. APP v1.0 -> 网关 -> A服务 v1.0 -> B服务 v1.0
2. APP v1.1 -> 网关 -> A服务 v1.1 -> B服务 v1.1
```
### 通过其它方式设置灰度路由策略
除了上面通过配置中心发布灰度规路由则外,还有如下三种方式,这三种方式既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务
#### 通过前端传入灰度路由策略
通过前端(Postman)方式传入灰度路由策略,来代替配置中心方式,传递全链路路由策略
 注意:当配置中心和界面都配置后,以界面传入优先
- 版本匹配策略,Header格式如下任选一个
```
1. n-d-version=1.0
2. n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}
```
- 版本权重策略,Header格式如下任选一个
```
1. n-d-version-weight=1.0=90;1.1=10
2. 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格式如下任选一个
```
1. n-d-region=qa
2. n-d-region={"discovery-guide-service-a":"qa", "discovery-guide-service-b":"qa"}
```
- 区域权重策略,Header格式如下任选一个
```
1. n-d-region-weight=dev=99;qa=1
2. n-d-region-weight={"discovery-guide-service-a":"dev=99;qa=1", "discovery-guide-service-b":"dev=99;qa=1"}
```
- IP地址和端口匹配策略,Header格式如下任选一个
```
1. n-d-address={"discovery-guide-service-a":"127.0.0.1:3001", "discovery-guide-service-b":"127.0.0.1:4002"}
2. n-d-address={"discovery-guide-service-a":"127.0.0.1", "discovery-guide-service-b":"127.0.0.1"}
3. n-d-address={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4002"}
```
- 环境隔离下动态环境匹配策略
```
1. n-d-env=env1
```
- 服务下线实时性的流量绝对无损,全局唯一ID屏蔽策略
```
1. n-d-id-blacklist=e92edde5-0153-4ec8-9cbb-b4d3f415aa33;af043384-c8a5-451e-88f4-457914e8e3bc
```
- 服务下线实时性的流量绝对无损,IP地址和端口屏蔽策略
```
1. n-d-address-blacklist=192.168.43.101:1201;192.168.*.102;1301
```


当外界传值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的方式传递全链路灰度路由策略
下面代码既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务,一般来说,网关已经加了,服务就不需要加,当不存在的网关的时候,服务上就可以考虑。继承GatewayStrategyRouteFilter、ZuulStrategyRouteFilter或者ServiceStrategyRouteFilter,覆盖掉如下方法中的一个或者多个,通过@Bean方式覆盖框架内置的过滤类
```java
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示例
在代码里根据不同的Header选择不同的路由路径,示例提供全链路匹配和全链路权重两种方式
```java
// 适用于A/B Testing或者更根据某业务参数决定灰度路由路径。可以结合配置中心分别配置A/B两条路径,可以动态改变并通知
// 当Header中传来的用户为张三,执行一条路由路径;为李四,执行另一条路由路径
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.getRouteEnvironment();
}
@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> weightList = new ArrayList>();
weightList.add(new ImmutablePair(aRouteVersion, 30D));
weightList.add(new ImmutablePair(bRouteVersion, 70D));
MapWeightRandom weightRandom = new MapWeightRandom(weightList);
return weightRandom.random();
}
}
```
在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类
```java
@Bean
public GatewayStrategyRouteFilter gatewayStrategyRouteFilter() {
return new MyGatewayStrategyRouteFilter();
}
```
ZuulStrategyRouteFilter示例
在代码里根据不同的Header选择不同的路由路径,示例提供全链路匹配和全链路权重两种方式
```java
// 适用于A/B Testing或者更根据某业务参数决定灰度路由路径。可以结合配置中心分别配置A/B两条路径,可以动态改变并通知
// 当Header中传来的用户为张三,执行一条路由路径;为李四,执行另一条路由路径
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> weightList = new ArrayList>();
weightList.add(new ImmutablePair(aRouteVersion, 30D));
weightList.add(new ImmutablePair(bRouteVersion, 70D));
MapWeightRandom weightRandom = new MapWeightRandom(weightList);
return weightRandom.random();
}
}
```
在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类
```java
@Bean
public ZuulStrategyRouteFilter zuulStrategyRouteFilter() {
return new MyZuulStrategyRouteFilter();
}
```
ServiceStrategyRouteFilter示例
在代码里根据不同的Header选择不同的路由路径,示例提供全链路匹配和全链路权重两种方式
 注意:当网关有对应策略传入时,以网关策略优先,服务侧的过滤器无效
```java
// 适用于A/B Testing或者更根据某业务参数决定灰度路由路径。可以结合配置中心分别配置A/B两条路径,可以动态改变并通知
// 当Header中传来的用户为张三,执行一条路由路径;为李四,执行另一条路由路径
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> weightList = new ArrayList>();
weightList.add(new ImmutablePair(aRouteVersion, 30D));
weightList.add(new ImmutablePair(bRouteVersion, 70D));
MapWeightRandom weightRandom = new MapWeightRandom(weightList);
return weightRandom.random();
}
}
```
在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类
```java
@Bean
public ServiceStrategyRouteFilter serviceStrategyRouteFilter() {
return new MyServiceStrategyRouteFilter();
}
```
#### 通过业务参数在策略类中自定义灰度路由策略
通过策略方式自定义灰度路由策略。下面代码既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务,同时全链路中网关和服务都必须加该方式,DefaultDiscoveryEnabledStrategy可以有多个,框架会组合判断
```java
// 实现了组合策略,版本路由策略+区域路由策略+IP地址和端口路由策略+自定义策略
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.getHostPort();
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;
}
}
```
在配置类里@Bean方式进行策略类创建
```java
@Bean
public DiscoveryEnabledStrategy discoveryEnabledStrategy() {
return new MyDiscoveryEnabledStrategy();
}
```
在网关和服务中支持基于Rest Header传递的自定义灰度路由策略,除此之外,服务还支持基于Rpc方法参数传递的自定义灰度路由策略,它包括接口名、方法名、参数名或参数值等多种形式。下面的示例表示在服务中同时开启基于Rest Header传递和Rpc方法参数传递的自定义组合式灰度路由策略,DefaultDiscoveryEnabledStrategy可以有多个,框架会组合判断
```java
// 实现了组合策略,版本路由策略+区域路由策略+IP地址和端口路由策略+自定义策略
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.getHostPort();
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 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.getHostPort();
LOG.info("负载均衡用户定制触发:attributes={}, serviceId={}, version={}, region={}, env={}, address={}", attributes, serviceId, version, region, environment, address);
if (attributes.containsKey(DiscoveryConstant.PARAMETER_MAP)) {
Map parameterMap = (Map) 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
```
### 灰度路由下的版本故障转移
版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止灰度版本路由人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损
故障转移方式,对版本号进行排序,取第一个版本号,所以此方案的前置条件是必须版本号是规律的有次序,例如,以时间戳的方式。如果所有服务实例的版本号未设置,那么将转移到未设置版本号的实例上
需要通过如下开关开启该功能
```
# 启动和关闭版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.enabled=true
```
### 并行灰度路由下的版本偏好策略
版本偏好,即非灰度路由场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施灰度版本路由产生混乱,对处于非灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;灰度状态的服务,还是根据传递的Header版本号进行匹配
偏好方式,对版本号进行排序,取第一个版本号,所以此方案的前置条件是必须版本号是规律的有次序,例如,以时间戳的方式。如果所有服务实例的版本号未设置,那么将转移到未设置版本号的实例上
需要通过如下开关开启该功能
```
# 启动和关闭版本偏好。缺失则默认为false
spring.application.strategy.version.prefer.enabled=true
```
### 异步场景的全链路灰度路由策略
当若干个服务之间调用,存在异步场景,如下
- 调用时候,启用了Hystrix线程池隔离机制
- 线程池里的线程触发调用
- 新创建单个线程触发调用
参考[基于Hystrix的全链路服务限流熔断和灰度融合](#基于Hystrix的全链路服务限流熔断和灰度融合)
参考[异步跨线程Agent](#异步跨线程Agent)
## 基于Query-Parameter的全链路灰度路由
通过取值Query Parameter方式,即可实现既定功能
[http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=1](http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=1)
[http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=2](http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=2)
策略表达式的用法和Header类似
自定义方式参考[通过业务参数在过滤器中自定义灰度路由策略](#通过业务参数在过滤器中自定义灰度路由策略)
## 基于Cookie的全链路灰度路由
通过取值Cookie方式,即可实现既定功能
策略表达式的用法和Header类似
自定义方式参考[通过业务参数在过滤器中自定义灰度路由策略](#通过业务参数在过滤器中自定义灰度路由策略)
## 基于域名的全链路灰度路由
通过取值域名前缀等方式,即可实现既定功能
策略表达式的用法和Header类似
自定义方式参考[通过业务参数在过滤器中自定义灰度路由策略](#通过业务参数在过滤器中自定义灰度路由策略)
本地测试,为验证结果,请事先在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调用中的方法入参方式,即可实现既定功能
只适用于服务侧
自定义方式参考[通过业务参数在策略类中自定义灰度路由策略](#通过业务参数在策略类中自定义灰度路由策略)
## 基于动态变更元数据的灰度路由策略
利用注册中心的Open API接口动态变更服务实例的元数据,达到稳定版本和灰度版本流量灰度控制的目的。以Nacos的版本匹配为例
老的稳定版本的服务实例配置版本元数据,如下
```
spring.cloud.nacos.discovery.metadata.version=stable
```
新的稳定版本的服务实例配置版本元数据,如下
```
spring.cloud.nacos.discovery.metadata.version=gray
```
灰度路由策略,如下
表示所有的服务流量走灰度版本
```xml
gray
```
表示a服务流量走灰度版本,b服务流量走稳定版本
```xml
{"discovery-guide-service-a":"gray", "discovery-guide-service-b":"stable"}
```
也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略)
```
n-d-version=gray
n-d-version={"discovery-guide-service-a":"gray", "discovery-guide-service-b":"stable"}
```
新上线的服务实例版本为gray,即默认是灰度版本。等灰度成功后,通过注册中心的Open API接口变更服务版本为stable,或者在注册中心界面手工修改
- Nacos Open API变更元数据
```
curl -X PUT 'http://ip:port/nacos/v1/ns/instance?serviceName={appId}&ip={ip}&port={port}&metadata={"version", "stable"}'
```
Nacos Open API使用手册,参考[https://nacos.io/zh-cn/docs/open-api.html](https://nacos.io/zh-cn/docs/open-api.html)
- Eureka Open API变更元数据
```
curl -X PUT 'http://ip:port/eureka/apps/{appId}/{instanceId}/metadata?version=stable'
```
- Consul Open API变更元数据
自行研究
- Zookeeper Open API变更元数据
自行研究
 需要注意
① 并非所有的注册中心都支持动态元数据变更方式,需要使用者自行研究
② 动态元数据变更方式利用第三方注册中心的Open API达到最终目的,其可能具有一定的延迟性,不如本框架那样具有灰度路由实时生效的特征,但比本框架动态变更灰度路由策略简单了一些
③ 动态元数据变更方式只是让新的元数据驻留在内存里,并不持久化。当服务重启后,服务的元数据仍旧会以初始值为准
## 基于全局订阅式的灰度路由策略
通过全链路传递Header实现灰度路由,会存在一定的困难,框架提供另外一种很简单的方式来规避Header传递,但能达到Header传递一样的效果。以版本匹配为例
增加版本匹配的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),策略内容如下,实现a服务走1.0版本,b服务走1.1版本
```xml
{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"}
```

如果采用上述方式,可以考虑开启下面的开关
```
# 启动和关闭核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下
# 1. n-d-version
# 2. n-d-region
# 3. n-d-address
# 4. n-d-version-weight
# 5. n-d-region-weight
# 6. n-d-id-blacklist
# 7. n-d-address-blacklist
# 8. n-d-env (不属于灰度蓝绿范畴的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
```
## 基于服务下线实时性的流量绝对无损策略
服务下线场景中,由于Ribbon负载均衡组件存在着缓存机制,当被调用的服务实例已经下线,而调用的服务实例还暂时缓存着它,直到下个心跳周期才会把已下线的服务实例剔除,在此期间,会造成流量有损
框架提供流量的实时性的绝对无损。采用下线之前,把服务实例添加到屏蔽名单中,负载均衡不会去寻址该服务实例。下线之后,清除该名单。实现该方式,需要通过DevOps调用配置中心的Open API推送或者在配置中心界面手工修改,通过全局订阅方式实现,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名)
### 配置全局唯一ID屏蔽策略
全局唯一ID对应于元数据spring.application.uuid字段,框架会自动把该ID注册到注册中心。此用法适用于Docker和Kubernetes上IP地址不确定的场景,策略内容如下,采用如下两种方式之一均可
```xml
```

也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略)
### 配置IP地址和端口屏蔽策略
通过IP地址或者端口或者IP地址+端口进行屏蔽,支持通配符方式。此用法适用于IP地址确定的场景,策略内容如下,采用如下两种方式之一均可
```xml
```

也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略)
## 基于订阅方式的全链路灰度发布规则
在Nacos配置中心,增加全链路灰度发布规则
 注意:该功能和网关灰度路由和灰度权重功能会叠加,为了不影响演示效果,请先清除网关灰度路由的策略
### 配置全链路灰度匹配规则
#### 版本匹配灰度规则
增加版本匹配的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现a服务1.0版本只能访问b服务1.0版本,a服务1.1版本只能访问b服务1.1版本
```xml
```

#### 区域匹配灰度规则
增加区域匹配的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现dev区域的a服务只能访问dev区域的b服务,qa区域的a服务只能访问qa区域的b服务
```xml
```

### 配置全链路灰度权重规则
#### 全局版本权重灰度规则
增加全局版本权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现版本为1.0的服务提供90%的流量,版本为1.1的服务提供10%的流量
```xml
```

#### 局部版本权重灰度规则
增加局部版本权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现a服务1.0版本提供90%的流量,1.1版本提供10%的流量;b服务1.0版本提供20%的流量,1.1版本提供80%的流量
```xml
```

#### 全局区域权重灰度规则
增加全局区域权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现区域为dev的服务提供90%的流量,区域为qa的服务提供10%的流量
```xml
```

#### 局部区域权重灰度规则
增加局部区域权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现a服务dev区域提供90%的流量,qa区域提供10%的流量;b服务dev区域提供20%的流量,qa区域提供80%的流量
```xml
```

 注意:局部权重优先级高于全局权重,版本权重优先级高于区域权重
请执行Postman操作,请仔细观察服务被随机权重调用到的概率
### 配置全链路灰度权重和灰度匹配组合式规则
增加组合式的灰度规则,支持版本匹配和区域匹配。以版本匹配为例,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现功能
- a服务1.0版本向网关提供90%的流量,1.1版本向网关提供10%的流量
- a服务1.0版本只能访问b服务1.0版本,1.1版本只能访问b服务1.1版本
该功能的意义是,网关随机权重调用服务,而服务链路按照版本匹配方式调用
```xml
```

### 数据库和消息队列灰度发布规则
通过订阅业务参数的变化,实现参数化灰度发布,例如,基于多Datasource的数据库灰度发布,基于多Queue的消息队列灰度发布
增加参数化灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现功能
- 服务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
```xml
```
通过事件总线方式,对参数改变动态实现监听,并在此类里自行对接相关的数据库和消息队列中间件的切换和驱动
```java
@EventBus
public class MySubscriber {
@Autowired
private PluginAdapter pluginAdapter;
@Subscribe
public void onParameterChanged(ParameterChangedEvent parameterChangedEvent) {
ParameterEntity parameterEntity = parameterChangedEvent.getParameterEntity();
String serviceId = pluginAdapter.getServiceId();
List parameterServiceEntityList = null;
if (parameterEntity != null) {
Map> parameterServiceMap = parameterEntity.getParameterServiceMap();
parameterServiceEntityList = parameterServiceMap.get(serviceId);
}
// parameterServiceEntityList为动态参数列表
}
}
```
使用者可以通过如下开关,决定在服务启动过程中,读到参数配置的时候,是否要发送一个事件触发数据库和消息队列中间件的切换
```
# 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true
spring.application.parameter.event.onstart.enabled=true
```
具体参考[https://github.com/Nepxion/DiscoveryContrib](https://github.com/Nepxion/DiscoveryContrib)里的实现方式
## 基于多格式的规则策略定义
### 规则策略格式定义
 注意:服务名大小写规则
- 在配置文件(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
{"discovery-springcloud-example-a":"1.0", "discovery-springcloud-example-b":"1.0", "discovery-springcloud-example-c":"1.0;1.2"}
{"discovery-springcloud-example-a":"qa;dev", "discovery-springcloud-example-b":"dev", "discovery-springcloud-example-c":"qa"}
{"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"}
{"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"}
{"discovery-springcloud-example-a":"dev=85;qa=15", "discovery-springcloud-example-b":"dev=85;qa=15", "discovery-springcloud-example-c":"dev=85;qa=15"}
{"discovery-springcloud-example-a":"1.0", "discovery-springcloud-example-b":"1.0", "discovery-springcloud-example-c":"1.0;1.2"}
{"discovery-springcloud-example-a":"1.1", "discovery-springcloud-example-b":"1.1", "discovery-springcloud-example-c":"1.2"}
{"discovery-springcloud-example-a":"qa;dev", "discovery-springcloud-example-b":"dev", "discovery-springcloud-example-c":"qa"}
{"discovery-springcloud-example-a":"qa", "discovery-springcloud-example-b":"qa", "discovery-springcloud-example-c":"qa"}
{"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"}
{"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"}
{"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"}
{"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"}
{"discovery-springcloud-example-a":"dev=85;qa=15", "discovery-springcloud-example-b":"dev=85;qa=15", "discovery-springcloud-example-c":"dev=85;qa=15"}
{"discovery-springcloud-example-a":"dev=15;qa=85", "discovery-springcloud-example-b":"dev=15;qa=85", "discovery-springcloud-example-c":"dev=15;qa=85"}
```
## 基于多方式的规则策略推送
### 基于远程配置中心的规则策略订阅推送
Apollo订阅推送界面

① 参考Apollo官方文档[https://github.com/ctripcorp/apollo](https://github.com/ctripcorp/apollo)相关文档,搭建Apollo环境,以及熟悉相关的基本操作
② 根据上图,做如下步骤操作
- 设置页面中AppId和配置文件里面app.id一致
- 设置页面中Namespace和配置文件里面apollo.plugin.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.plugin.namespace来指定(如果namespace为application则都不需要配置)
- 如果业务配置和灰度配置不在同一个namespace里或者业务配置横跨几个namespace,灰度配置必须通过apollo.plugin.namespace来指定唯一的namespace
Nacos订阅推送界面

- 参考Nacos官方文档[https://github.com/alibaba/nacos](https://github.com/alibaba/nacos)相关文档,搭建Nacos环境,以及熟悉相关的基本操作
- 下面配置中,nacos.server-addr必须要配置,其它配置可选
```
nacos.server-addr=localhost:8848
# nacos.access-key=
# nacos.secret-key=
# nacos.plugin.namespace=application
# nacos.plugin.cluster-name=
# nacos.plugin.context-path=
# nacos.plugin.config-long-poll-timeout=
# nacos.plugin.config-retry-time=
# nacos.plugin.max-retry=
# nacos.plugin.endpoint=
# nacos.plugin.endpoint-port=
# nacos.plugin.is-use-endpoint-parsing-rule=
# nacos.plugin.is-use-cloud-namespace-parsing=
# nacos.plugin.encode=
# nacos.plugin.naming-load-cache-at-start=
# nacos.plugin.naming-client-beat-thread-count=
# nacos.plugin.naming-polling-thread-count=
# nacos.plugin.ram-role-name=
# nacos.plugin.timout=
```
- 添加配置步骤跟Apollo配置界面中的【在页面中添加配置】操作项相似
Redis订阅推送界面

### 基于Swagger和Rest的规则策略推送
服务侧单个推送界面

控制平台批量推送界面

### 基于图形化界面的规则策略推送
 下面两种方式有点古老,并不再维护,请斟酌使用
基于图形化桌面程序的灰度发布路由


① 桌面程序对Windows和Mac操作系统都支持,但在Mac操作系统中界面显示有点瑕疵,但不影响功能使用
② 下载代码,Git clone [https://github.com/Nepxion/Discovery.git](https://github.com/Nepxion/Discovery.git)
③ 通过IDE启动
- 运行discovery-console-desktop\ConsoleLauncher.java启动
④ 通过脚本启动
- 在discovery-console-desktop目录下执行mvn clean install,target目录下将产生discovery-console-desktop-[版本号]-release的目录
- 进入discovery-console-desktop-[版本号]-release,请修改config/console.properties中的url,该地址指向控制平台的地址
- 运行“Discovery灰度发布控制台.bat”,启动桌面程序
- 如果您是操作系统,请参考“Discovery灰度发布控制台.bat”,自行编写“Discovery灰度发布控制台.sh”脚本,启动桌面程序
⑤ 操作界面
- 登录认证,用户名和密码为admin/admin或者nepxion/nepxion。顺便说一下,控制台支持简单的认证,用户名和密码配置在discovery-springcloud-example-console\bootstrap.properties中,您可以自己扩展AuthenticationResource并注入,实现更专业的认证功能

- 点击【显示服务拓扑】按钮,弹出【服务集群组过滤】对话框,列表是以服务所在的集群组列表(例如:eureka.instance.metadataMap.group=example-service-group),选择若干个并点击【确定】按钮,如果使用者想获取全部的服务集群(可能会耗性能),则直接点击【取消】按钮

- 从服务注册发现中心获取服务拓扑

- 执行灰度路由,选择一个服务,右键菜单【执行灰度路由】

- 通过【服务列表】切换,或者点击增加和删除服务按钮,确定灰度路由路径,点击【执行路由】


- 推送模式设置,【异步推送】和【同步推送】,前者是推送完后立刻返回,后者是推送完后等待推送结果(包括规则XML解析的异常等都能在界面上反映出来);【规则推送到远程配置中心】和【规则推送到服务或者服务集群】,前者是推送到配置中心(持久化),后者是推送到一个或者多个服务机器的内存(非持久化,重启后丢失)

- 执行灰度发布,选择一个服务或者服务组,右键菜单【执行灰度发布】,前者是通过单个服务实例执行灰度发布,后者是通过一组服务实例执行灰度发布

- 灰度发布,包括【更改版本】和【更改规则】,前者通过更改版本号去适配灰度规则中的版本匹配关系,后者直接修改规则。【更改版本】是推送到一个或者多个服务机器的内存(非持久化,重启后丢失),【更改规则】是根据不同的推送模式,两种方式都支持

- 全链路灰度发布,所有在同一个集群组(例如:eureka.instance.metadataMap.group=example-service-group)里的服务统一做灰度发布,即一个规则配置搞定所有服务的灰度发布。点击【全链路灰度发布】按钮,弹出【全链路灰度发布】对话框


- 刷新灰度状态,选择一个服务或者服务组,右键菜单【刷新灰度状态】,查看某个服务或者服务组是否正在做灰度发布

⑥ 动画效果
参考[图形化桌面程序的灰度发布路由动画效果](http://nepxion.gitee.io/videos/discovery-video/DiscoveryConsole.gif)
⑥ 操作视频
- 灰度发布的版本匹配功能,参考[Discovery灰度发布版本匹配演示视频](http://nepxion.gitee.io/videos/discovery-video/DiscoveryGrayRlease.wmv)
- 灰度路由的版本匹配功能,参考[Discovery灰度路由版本匹配链演示视频](http://nepxion.gitee.io/videos/discovery-video/DiscoveryGrayRoute.wmv)
基于图形化Web程序的灰度发布路由
- 参考[图形化Web](https://github.com/Nepxion/DiscoveryUI)
- 操作过程跟[基于图形化界面的规则策略推送](#基于图形化界面的规则策略推送)类似

## 基于组和黑白名单的全链路服务隔离和准入
### 服务注册发现准入
#### 基于组黑白名单注册准入
微服务启动的时候,它所属的Group不在Group的黑名单或者在白名单里,才允许被注册。只需要在网关或者服务端,开启如下配置即可
```
# 启动和关闭注册的服务隔离(基于Group黑/白名单的策略)。缺失则默认为false
spring.application.strategy.register.isolation.enabled=true
```
黑/白名单通过如下方式配置
```
spring.application.strategy.register.isolation.group.blacklist=
spring.application.strategy.register.isolation.group.whitelist=
```
#### 基于IP地址黑白名单注册准入
微服务启动的时候,禁止指定的IP地址注册到注册中心。支持黑/白名单,白名单表示只允许指定IP地址前缀注册,黑名单表示不允许指定IP地址前缀注册
- 全局过滤,指注册到服务注册发现中心的所有微服务,只有IP地址包含在全局过滤字段的前缀中,都允许注册(对于白名单而言),或者不允许注册(对于黑名单而言)
- 局部过滤,指专门针对某个微服务而言,那么真正的过滤条件是全局过滤 + 局部过滤结合在一起
#### 基于最大注册数限制注册准入
微服务启动的时候,一旦微服务集群下注册的实例数目已经达到上限(可配置),将禁止后续的微服务进行注册
- 全局配置值,只下面配置所有的微服务集群,最多能注册多少个
- 局部配置值,指专门针对某个微服务而言,如果该值如存在,全局配置值失效
#### 基于IP地址黑白名单发现准入
微服务启动的时候,禁止指定的IP地址被服务发现。它使用的方式和[基于IP地址黑白名单注册准入](#基于IP地址黑白名单注册准入)一致
#### 自定义注册发现准入
- 集成AbstractRegisterListener,实现自定义禁止注册
- 集成AbstractDiscoveryListener,实现自定义禁止被发现。注意,在Consul下,同时会触发service和management两个实例的事件,需要区别判断
- 集成AbstractLoadBalanceListener,实现自定义禁止被负载均衡
### 消费端服务隔离
#### 基于组负载均衡隔离
元数据中的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消费端进行了隔离
### 提供端服务隔离
#### 基于组Header传值策略隔离
元数据中的Group在一定意义上代表着系统ID或者系统逻辑分组,基于Group策略意味着只有同一个系统中的服务才能调用
基于Group是否相同的策略,即服务端被消费端调用,两者的Group必须相同,否则拒绝调用,异构系统可以通过Header方式传递n-d-service-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](http://localhost:4001/invoke/abc),去调用discovery-guide-service-b服务,将出现如下异常。意味着在discovery-guide-service-b提供端进行了隔离
```
Reject to invoke because of isolation with different service group
```

如果加上n-d-service-group=discovery-guide-group的Header,那么两者保持Group相同,则调用通过。这是解决异构系统调用微服务被隔离的一种手段

## 基于Env的全链路环境隔离和路由
基于服务实例的元数据Metadata的env参数和全链路传递的环境Header值进行比对实现隔离,当从网关传递来的环境Header(n-d-env)值和提供端实例的元数据Metadata环境配置值相等才能调用。环境隔离下,调用端实例找不到符合条件的提供端实例,把流量路由到一个通用或者备份环境

### 环境隔离
在网关或者服务端,配置环境元数据,在同一套环境下,env值必须是一样的,这样才能达到在同一个注册中心下,环境隔离的目的
```
spring.cloud.nacos.discovery.metadata.env=env1
```
### 环境路由
在环境隔离执行的时候,如果无法找到对应的环境,则会路由到一个通用或者备份环境,默认为env为common的环境,可以通过如下参数进行更改
```
# 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common
spring.application.environment.route=common
```
 注意事项
- 如果存在环境,优先寻址环境的服务实例
- 如果不存在环境,则寻址Common环境的服务实例(未设置元数据Metadata的env参数的服务实例也归为Common环境)
- 如果Common环境也不存在,则调用失败
- 如果没有传递环境Header(n-d-env)值,则执行Spring Cloud Ribbon轮询策略
- 环境隔离和路由适用于测试环境,性能压测等场景
## 基于Zone的全链路可用区亲和性隔离和路由

### 可用区亲和性隔离
基于调用端实例和提供端实例的元数据Metadata的zone配置值相等实现隔离
```
spring.cloud.nacos.discovery.metadata.zone=zone
```
通过如下开关进行开启和关闭
```
# 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false
spring.application.zone.affinity.enabled=false
```
### 可用区亲和性路由
在可用区亲和性隔离执行的时候,调用端实例找不到同一可用区的提供端实例,把流量路由到其它可用区或者不归属任何可用区
通过如下开关进行开启和关闭
```
# 启动和关闭可用区亲和性失败后的路由,即调用端实例没有找到同一个可用区的提供端实例的时候,当开关打开,可路由到其它可用区或者不归属任何可用区,当开关关闭,则直接调用失败。缺失则默认为true
spring.application.zone.route.enabled=true
```
 注意事项
- 不归属任何可用区,含义是服务实例未设置任何zone元数据值。可用区亲和性路由功能,是为了尽量保证流量不损失
- 如果采用Eureka注册中心,Ribbon本身就具有可用区亲和性功能,跟本框架类似。如果使用者采用了Eureka注册中心下的Ribbon可用区亲和性功能,请关闭本框架提供的相似功能,以免冲突
- 本框架提供的可用区亲和性功能适用于一切注册中心
## 基于Sentinel的全链路服务限流熔断降级权限和灰度融合
集成Sentinel熔断隔离限流降级平台


通过集成Sentinel,在服务端实现该功能
 由于该块功能早于Spring Cloud Alibaba Sentinel而产生,下述功能可以通过Spring Cloud Alibaba Sentinel功能来实现
封装NacosDataSource和ApolloDataSource,支持Nacos和Apollo两个远程配置中心,零代码实现Sentinel功能。更多的远程配置中心,请参照Sentinel官方的DataSource并自行集成
- Nacos的Key格式
```
Group为元数据中配置的[组名],Data Id为[服务名]-[规则类型]
```
- Apollo的Key格式
```
[组名]-[服务名]-[规则类型]
```
支持远程配置中心和本地规则文件的读取逻辑,即优先读取远程配置,如果不存在或者规则错误,则读取本地规则文件。动态实现远程配置中心对于规则的热刷新
支持如下开关开启该动能,默认是关闭的
```
# 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展。缺失则默认为false
spring.application.strategy.sentinel.enabled=true
```
### 原生Sentinel注解
参照下面代码,为接口方法增加@SentinelResource注解,value为sentinel-resource,blockHandler和fallback是防护其作用后需要执行的方法
```java
@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规则的用法,请参照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
}
]
```

### 基于灰度路由和Sentinel-LimitApp扩展的防护机制
该方式对于上面5种规则都有效,这里以授权规则展开阐述
授权规则中,limitApp,如果有多个,可以通过“,”分隔。"strategy": 0 表示白名单,"strategy": 1 表示黑名单
#### 基于服务名的防护机制
修改配置项Sentinel Request Origin Key为服务名的Header名称,修改授权规则中limitApp为对应的服务名,可实现基于服务名的防护机制
配置项,该配置项默认为n-d-service-id,可以不配置
```
spring.application.strategy.service.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.service.sentinel.request.origin.key=n-d-service-group
```
增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示隶属my-group组的所有服务都允许访问服务discovery-guide-service-b
```
[
{
"resource": "sentinel-resource",
"limitApp": "my-group",
"strategy": 0
}
]
```
#### 基于灰度版本的防护机制
修改配置项Sentinel Request Origin Key为灰度版本的Header名称,修改授权规则中limitApp为对应的版本,可实现基于版本的防护机制
配置项
```
spring.application.strategy.service.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.service.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
}
]
```
#### 基于IP地址和端口的防护机制
修改配置项Sentinel Request Origin Key为灰度区域的Header名称,修改授权规则中limitApp为对应的区域值,可实现基于IP地址和端口的防护机制
配置项
```
spring.application.strategy.service.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
}
]
```
支持如下开关开启该动能,默认是关闭的
```
# 启动和关闭Sentinel LimitApp限流等功能。缺失则默认为false
spring.application.strategy.service.sentinel.limit.app.enabled=true
```
#### 自定义业务参数的组合式防护机制
通过适配类实现自定义业务参数的组合式防护机制
```java
// 自定义版本号+地域名,实现组合式熔断
public class MyServiceSentinelRequestOriginAdapter extends DefaultServiceSentinelRequestOriginAdapter {
@Override
public String parseOrigin(HttpServletRequest request) {
String version = request.getHeader(DiscoveryConstant.N_D_SERVICE_VERSION);
String location = request.getHeader("location");
return version + "&" + location;
}
}
```
在配置类里@Bean方式进行适配类创建
```java
@Bean
public ServiceSentinelRequestOriginAdapter ServiceSentinelRequestOriginAdapter() {
return new MyServiceSentinelRequestOriginAdapter();
}
```
增加服务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服务端被拒绝掉

## 基于Hystrix的全链路服务限流熔断和灰度融合
通过引入Hystrix组件实现服务限流熔断的功能。灰度路由Header和调用链Span在Hystrix线程池隔离模式(信号量模式不需要引入)下传递时,通过线程上下文切换会存在丢失Header的问题,通过下述步骤解决,同时适用于网关端和服务端
- Pom引入
```xml
com.nepxion
discovery-plugin-strategy-starter-hystrix
${discovery.version}
```
- 配置开启
```
# 开启服务端实现Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失。缺失则默认为false
spring.application.strategy.hystrix.threadlocal.supported=true
```
该方案也可以被异步跨线程Agent代替
## 全链路监控
### 全链路调用链监控
调用链监控,在本文主要指灰度调用链监控。快速入门操作,参考[Discovery灰度发布路由调用链演示视频](http://nepxion.gitee.io/videos/discovery-video/DiscoveryJaeger.wmv)
灰度调用链主要包括如下15个参数,以n-d-service开头的是必须的,其它是可选的或者按照场景而定。使用者可以自行定义要传递的调用链参数,例如:traceId, spanId等;也可以自行定义要传递的业务调用链参数,例如:mobile, user等
```
1. n-d-service-group - 服务所属组或者应用
2. n-d-service-type - 服务类型,分为“网关”和“服务”
3. n-d-service-id - 服务ID
4. n-d-service-address - 服务地址,包括Host和Port
5. n-d-service-version - 服务版本
6. n-d-service-region - 服务所属区域
7. n-d-service-env - 服务所属环境
8. n-d-version - 版本路由值
9. n-d-region - 区域路由值
10. n-d-env - 环境值
11. n-d-address - 地址路由值
12. n-d-version-weight - 版本权重路由值
13. n-d-region-weight - 区域权重路由值
14. n-d-id-blacklist - 全局唯一ID屏蔽值
15. n-d-address-blacklist - IP地址和端口屏蔽值
```
灰度调用链输出分为Header方式、调用链方式、日志MDC方式,三种方式可以并存使用。调用链方式支持WebMvc和WebFlux
#### Header输出方式
- Spring Cloud Gateway网关端自行会传输Header值(参考Discovery源码中的AbstractGatewayStrategyRouteFilter.java)
- Zuul网关端自行会传输Header值(参考Discovery源码中的AbstractZuulStrategyRouteFilter.java)
- 服务端通过Feign和RestTemplate拦截器传输Header值(参考Discovery源码中的FeignStrategyInterceptor.java和RestTemplateStrategyInterceptor.java)
#### 调用链输出方式
调用链输出方式以OpenUber Jaeger为例来说明
- Jaeger服务器版本,推荐用最新版本,从[https://github.com/jaegertracing/jaeger/releases](https://github.com/jaegertracing/jaeger/releases)获取
- 执行Postman调用后,访问[http://localhost:16686](http://localhost:16686)查看灰度调用链
- 灰度调用链支持WebMvc和WebFlux两种方式,以NEPXION字样(可自定义)的标记来标识
- 支持对Sentinel自动埋点
 注意:因为OpenTracing规范不仅仅只被Jaeger所遵守,所以框架并没有直接引入Jaeger包,需要使用者自行引入,可以参照指南示例中的pom.xml引用
```xml
io.opentracing.contrib
opentracing-spring-cloud-starter
io.opentracing.contrib
opentracing-spring-jaeger-starter
```
集成Opentracing + Uber Jaeger灰度全链路监控





集成Opentracing + Uber Jaeger + Sentinel限流熔断降级权限埋点全链路监控

集成主流中间件 + 灰度全链路监控
代码请从[指南示例高级版](https://github.com/Nepxion/DiscoveryGuide)获取,分支为premium。运行出下图强大效果的前提,需要事先搭建Nacos、Jaeger、ActiveMQ、MongoDB、RabbitMQ、Redis、RocketMQ以及MySQL数据库等环境
使用者如果不想搭建环境,想直接观看效果,可以直接把离线数据tracing.json(位于根目录下)导入到Jaeger界面(JSON File栏,拖进去即可),观看到下图效果



集成Opentracing + Apache Skywalking灰度全链路监控


集成Opentracing + Apache Skywalking + Sentinel限流熔断降级权限埋点全链路监控


 请注意如下配置,将决定终端界面的显示
- 如果开启,灰度信息输出到独立的Span节点中,意味着在界面显示中,灰度信息通过独立的NEPXION Span节点来显示
- 如果关闭,灰度信息输出到原生的Span节点中,意味着在界面显示中,灰度信息会和原生Span节点的调用信息、协议信息等合在一起
```
# 启动和关闭调用链的灰度信息以独立的Span节点输出,如果关闭,则灰度信息输出到原生的Span节点中(Skywalking不支持原生模式)。缺失则默认为true
spring.application.strategy.tracer.separate.span.enabled=true
```
自定义调用链上下文参数的创建(该类不是必须的),继承DefaultStrategyTracerAdapter
```java
// 自定义调用链上下文参数的创建
// 对于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 getCustomizationMap() {
Map customizationMap = new HashMap();
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方式进行调用链类创建,覆盖框架内置的调用链类
```java
@Bean
public StrategyTracerAdapter strategyTracerAdapter() {
return new MyStrategyTracerAdapter();
}
```
自定义类方法上入参和出参输出到调用链(该类不是必须的),继承ServiceStrategyMonitorAdapter
```java
// 自定义类方法上入参和出参输出到调用链
// parameterMap格式:
// key为入参名
// value为入参值
public class MyServiceStrategyMonitorAdapter implements ServiceStrategyMonitorAdapter {
@Override
public Map getCustomizationMap(ServiceStrategyMonitorInterceptor interceptor, MethodInvocation invocation, Map parameterMap, Object returnValue) {
Map customizationMap = new HashMap();
customizationMap.put(DiscoveryConstant.PARAMETER, parameterMap.toString());
customizationMap.put(DiscoveryConstant.RETURN, returnValue != null ? returnValue.toString() : null);
return customizationMap;
}
}
```
在配置类里@Bean方式进行创建
```java
@Bean
public ServiceStrategyMonitorAdapter serviceStrategyMonitorAdapter() {
return new MyServiceStrategyMonitorAdapter();
}
```
业务方法上获取TraceId和SpanId
```java
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.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输出在调用链界面上的显示,提供如下配置
```
# 显示在调用链界面上灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION
spring.application.strategy.tracer.span.value=NEPXION
# 显示在调用链界面上灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery
spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery
```
对Sentinel自动埋点,有如下两个参数默认处于关闭状态,但因为原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去
```
# 启动和关闭Sentinel调用链上规则在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为true
spring.application.strategy.tracer.sentinel.rule.output.enabled=true
# 启动和关闭Sentinel调用链上方法入参在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为false
spring.application.strategy.tracer.sentinel.args.output.enabled=true
```
 注意:OpenTracing对Finchley版的Spring Cloud Gateway的reactor-core包存在版本兼容性问题,如果使用者希望Finchley版的Spring Cloud Gateway上使用OpenTracing,需要做如下改造
```xml
com.nepxion
discovery-plugin-strategy-starter-gateway
${discovery.version}
io.projectreactor
reactor-core
io.projectreactor
reactor-core
3.2.3.RELEASE
```
上述方式也适用于其它引入了低版本reactor-core包版本兼容性的场景
#### 日志输出方式
可以单独输出,也可以结合调用链一起组合输出,使用方式跟调用链方式类似
参考在IDE控制台打印的结果

### 全链路指标监控
#### Prometheus监控方式

#### Grafana监控方式

#### Spring-Boot-Admin监控方式


## 全链路Header传递
框架会默认把相关的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调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.context.request.headers=trace-id;span-id
# 灰度路由策略的时候,对REST方式调用拦截的时候(支持Feign或者RestTemplate调用),希望把来自外部自定义的Header参数(用于业务系统子定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.business.request.headers=user;mobile;location
```
## 全链路侦测
通过内置基于LoadBalanced RestTemplate方式的/inspector/inspect接口方法,实现全链路侦测,可以查看全链路中调用的各个服务的版本、区域、环境、可用区、IP地址和端口等是否符合预期,是否满足灰度条件,该接口可以集成到使用者的界面中,就可以规避通过Postman工具或者调用链系统去判断,有利于节省人工成本。使用方式,如下
- 执行Post请求
- 请求的路径
```
http://[网关URL]/[服务名1]/inspector/inspect
```
- 请求的内容
```
{"serviceIdList":["服务名2", "服务名3", ....]}
```
服务名不分前后次序
## 全链路服务侧注解
服务侧对于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能,默认映射到RestController类(含有@RestController注解),并配合如下的扫描路径才能工作
```
# 灰度路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能
spring.application.strategy.scan.packages=com.nepxion.discovery.guide.service.feign
```
当使用者不希望只局限于RestController类(含有@RestController注解)方式,而要求在任何类中实现上述功能,那么框架提供@ServiceStrategy注解,使用者把它加在类头部即可,可以达到和@RestController注解同样的效果
## 全链路服务侧API权限
服务侧对于RPC方式的调用,可以加入API权限控制,通过在接口或者类名上加@Permission注解,或者在接口或者类的方法名上加@Permission注解,实现API权限控制。如果两者都加,以前者为优先
- 实现权限自动扫描入库
- 实现提供显式基于注解的权限验证,参数通过注解传递;实现提供基于Rest请求的权限验证,参数通过Header传递
- 实现提供入库方法和权限判断方法的扩展,这两者需要自行实现
请参考[权限代码](https://github.com/Nepxion/DiscoveryGuide/blob/master/discovery-guide-service/src/main/java/com/nepxion/discovery/guide/service/permission)
## 异步跨线程Agent
灰度路由Header和调用链Span在Hystrix线程池隔离模式下或者线程、线程池、@Async注解等异步调用Feign或者RestTemplate时,通过线程上下文切换会存在丢失Header的问题,通过下述步骤解决,同时适用于网关端和服务端。该方案可以替代Hystrix线程池隔离模式下的解决方案,也适用于其它有相同使用场景的基础框架和业务场景,例如:Dubbo
ThreadLocal的作用是提供线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立。在异步场景下,由于出现线程切换的问题,例如主线程切换到子线程,会导致线程ThreadLocal上下文丢失。DiscoveryAgent通过Java Agent方式解决这些痛点
涵盖所有Java框架的异步场景,解决如下7个异步场景下丢失线程ThreadLocal上下文的问题
- `@`Async
- Hystrix Thread Pool Isolation
- Runnable
- Callable
- Single Thread
- Thread Pool
- SLF4J MDC
### 插件获取
插件获取方式有两种方式
- 通过[https://github.com/Nepxion/DiscoveryAgent/releases](https://github.com/Nepxion/DiscoveryAgent/releases)下载最新版本的Discovery Agent
- 编译[https://github.com/Nepxion/DiscoveryAgent](https://github.com/Nepxion/DiscoveryAgent)产生discovery-agent目录
### 插件使用
- discovery-agent-starter-`$`{discovery.version}.jar为Agent引导启动程序,JVM启动时进行加载;discovery-agent/plugin目录包含discovery-agent-starter-plugin-strategy-`$`{discovery.version}.jar为Nepxion Discovery自带的实现方案,业务系统可以自定义plugin,解决业务自己定义的上下文跨线程传递
- 通过如下-javaagent启动,基本格式,如下
```
-javaagent:/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.abc;com.xyz
```
例如
```
-javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=org.springframework.aop.interceptor;com.netflix.hystrix;com.nepxion.discovery.guide.service.feign
```
参数说明
- /discovery-agent:Agent所在的目录,需要对应到实际的目录上
- `-D`thread.scan.packages:Runnable,Callable对象所在的扫描目录,该目录下的Runnable,Callable对象都会被装饰。该目录最好精细和准确,这样可以减少被装饰的对象数,提高性能,目录如果有多个,用“;”分隔。该参数只作用于服务侧,网关侧不需要加。参数定义为
- `@`Async场景下的扫描目录对应为org.springframework.aop.interceptor
- Hystrix线程池隔离场景下的扫描目录对应为com.netflix.hystrix
- 线程,线程池的扫描目录对应为自定义Runnable,Callable对象所在类的目录
- 上述扫描路径如果含有“;”,可能会在某些操作系统中无法被识别,请用`""`进行引入,例如,-Dthread.scan.packages="com.abc;com.xyz"
- `-D`thread.request.decorator.enabled:异步调用场景下在服务端的Request请求的装饰,当主线程先于子线程执行完的时候,Request会被Destory,导致Header仍旧拿不到,开启装饰,就可以确保拿到。默认为开启,根据实践经验,大多数场景下,需要开启该开关
- `-D`thread.mdc.enabled:SLF4J MDC日志输出到异步子线程。默认关闭,如果需要,则开启该开关
### 插件扩展
- 根据规范开发一个插件,插件提供了钩子函数,在某个类被加载的时候,可以注册一个事件到线程上下文切换事件当中,实现业务自定义ThreadLocal的跨线程传递
- plugin目录为放置需要在线程切换时进行ThreadLocal传递的自定义插件。业务自定义插件开发完后,放入到plugin目录下即可
具体步骤介绍,如下
① SDK侧工作
- 新建ThreadLocal上下文类
```java
public class MyContext {
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal() {
@Override
protected MyContext initialValue() {
return new MyContext();
}
};
public static MyContext getCurrentContext() {
return THREAD_LOCAL.get();
}
public static void clearCurrentContext() {
THREAD_LOCAL.remove();
}
private Map attributes = new HashMap<>();
public Map getAttributes() {
return attributes;
}
public void setAttributes(Map attributes) {
this.attributes = attributes;
}
}
```
② Agent侧工作
- 新建一个模块,引入如下依赖
```xml
com.nepxion
discovery-agent-starter
${discovery.agent.version}
provided
```
- 新建一个ThreadLocalHook类继承AbstractThreadLocalHook
```java
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) object);
}
}
@Override
public void after() {
// 线程结束,销毁上下文对象
MyContext.clearCurrentContext();
}
}
```
- 新建一个Plugin类继承AbstractPlugin
```java
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数据,并打印出来
```java
@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 attributes = new HashMap();
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](https://github.com/Nepxion/DiscoveryAgent/tree/master/discovery-agent-example)。上述自定义插件的方式,即可解决使用者在线程切换时丢失ThreadLocal上下文的问题
## 元数据Metadata自动化策略
### 基于服务名前缀自动创建灰度组名
通过指定长度截断或者标志截断服务名的前缀来自动创建灰度组名,这样就可以避免使用者手工维护灰度组名。当两者都启用的时候,截断方式的组名优先级要高于手工配置的组名
- 增加配置项
```
# 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false
spring.application.group.generator.enabled=true
# 服务名前缀的截断长度,必须大于0
spring.application.group.generator.length=15
# 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式
spring.application.group.generator.character=-
```
### 基于Git插件自动创建灰度版本号
通过集成插件git-commit-id-plugin,通过产生git信息文件的方式,获取git.commit.id(最后一次代码的提交ID)或者git.build.version(对应到Maven工程的版本)来自动创建灰度版本号,这样就可以避免使用者手工维护灰度版本号。当两者都启用的时候,Git插件方式的版本号优先级要高于手工配置的版本号
- 增加Git编译插件
需要在4个工程下的pom.xml里增加git-commit-id-plugin
默认配置
```xml
pl.project13.maven
git-commit-id-plugin
revision
true
yyyyMMdd
```
特色配置
```xml
pl.project13.maven
git-commit-id-plugin
revision
true
${project.basedir}/.git
${project.build.outputDirectory}/git.json
json
true
false
false
yyyyMMdd
```
更多的配置方式,参考[https://github.com/git-commit-id/maven-git-commit-id-plugin/blob/master/maven/docs/using-the-plugin.md](https://github.com/git-commit-id/maven-git-commit-id-plugin/blob/master/maven/docs/using-the-plugin.md)
- 增加配置项
```
# 开启和关闭使用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
git.build.user.email=1394997@qq.com
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
git.commit.user.email=1394997@qq.com
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
```
 注意:一般情况下,上述两个地方的配置都同时保持默认即可。对于一些特色化的用法,两个地方的配置项用法必须保持一致,例如
```
# 输出到工程根目录下
${project.basedir}/git.json
# 输出成json格式
json
```
下面配置项必须上面两个配置项的操作逻辑相同
```
# 输出到工程根目录下的json格式文件
spring.application.git.generator.path=file:git.json
```
内置基于Swagger的Rest接口,可以供外部查询当前服务的Git信息
## 元数据Metadata运维平台策略
外部系统(例如:运维发布平台)在远程启动微服务的时候,可以通过参数传递来动态改变元数据或者增加运维特色的参数,最后注册到远程配置中心。有如下两种方式
- 通过Program arguments来传递,它的用法是前面加“--”。支持Eureka、Zookeeper和Nacos的增量覆盖,Consul由于使用了全量覆盖的tag方式,不适用改变单个元数据的方式。例如:--spring.cloud.nacos.discovery.metadata.version=1.0
- 通过VM arguments来传递,它的用法是前面加“-D”。支持上述所有的注册组件,它的限制是变量前面必须要加“metadata.”,推荐使用该方式。例如:-Dmetadata.version=1.0
- 两种方式尽量避免同时用
## 配置文件
### 基础属性配置
不同的服务注册发现组件对应的不同的配置值,请仔细阅读
```
# 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
# 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
# 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
# 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
```
### 功能开关配置
服务端配置
```
# 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
# 规则文件的格式,支持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
# Plugin strategy config
# 开启和关闭路由策略的控制。一旦关闭,路由策略功能将失效。缺失则默认为true
spring.application.strategy.control.enabled=true
# 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true
spring.application.strategy.zone.avoidance.rule.enabled=true
# 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true
spring.application.strategy.rest.intercept.enabled=true
# 启动和关闭Feign上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下
# 1. n-d-version
# 2. n-d-region
# 3. n-d-address
# 4. n-d-version-weight
# 5. n-d-region-weight
# 6. n-d-id-blacklist
# 7. n-d-address-blacklist
# 8. n-d-env (不属于灰度蓝绿范畴的Header,只要外部传入就会全程传递)
spring.application.strategy.feign.core.header.transmission.enabled=true
# 启动和关闭RestTemplate上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下
# 1. n-d-version
# 2. n-d-region
# 3. n-d-address
# 4. n-d-version-weight
# 5. n-d-region-weight
# 6. n-d-id-blacklist
# 7. n-d-address-blacklist
# 8. n-d-env (不属于灰度蓝绿范畴的Header,只要外部传入就会全程传递)
spring.application.strategy.rest.template.core.header.transmission.enabled=true
# 启动和关闭路由策略的时候,对REST方式在异步调用场景下在服务端的Request请求的装饰,当主线程先于子线程执行完的时候,Request会被Destory,导致Header仍旧拿不到,开启装饰,就可以确保拿到。缺失则默认为false
spring.application.strategy.rest.request.decorator.enabled=true
# 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false
spring.application.strategy.rest.intercept.debug.enabled=true
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign或者RestTemplate调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.context.request.headers=trace-id;span-id
# 路由策略的时候,对REST方式调用拦截的时候(支持Feign或者RestTemplate调用),希望把来自外部自定义的Header参数(用于业务系统子定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格
spring.application.strategy.business.request.headers=token
# 启动和关闭路由策略的时候,对RPC方式的调用拦截。缺失则默认为false
spring.application.strategy.rpc.intercept.enabled=true
# 路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能
spring.application.strategy.scan.packages=com.nepxion.discovery.plugin.example.service.feign
# 启动和关闭注册的服务隔离(基于Group黑/白名单的策略)。缺失则默认为false
spring.application.strategy.register.isolation.enabled=true
# 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.consumer.isolation.enabled=true
# 启动和关闭提供端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.provider.isolation.enabled=true
# 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false
spring.application.strategy.monitor.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上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为true
spring.application.strategy.tracer.sentinel.rule.output.enabled=true
# 启动和关闭Sentinel调用链上方法入参在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为false
spring.application.strategy.tracer.sentinel.args.output.enabled=true
# 开启服务端实现Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失。缺失则默认为false
spring.application.strategy.hystrix.threadlocal.supported=true
# 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展。缺失则默认为false
spring.application.strategy.sentinel.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
# 服务端执行规则时候,以Http请求中的Header值作为关键Key。缺失则默认为n-d-service-id,即以服务名作为关键Key
spring.application.strategy.service.sentinel.request.origin.key=n-d-service-id
# 启动和关闭Sentinel LimitApp限流等功能。缺失则默认为false
spring.application.strategy.service.sentinel.limit.app.enabled=true
# 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止灰度版本路由人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损
# 启动和关闭版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.enabled=true
# 版本偏好,即非灰度路由场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施灰度版本路由产生混乱,对处于非灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;灰度状态的服务,还是根据传递的Header版本号进行匹配
# 启动和关闭版本偏好。缺失则默认为false
spring.application.strategy.version.prefer.enabled=true
# 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common
spring.application.environment.route=common
# 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false
spring.application.zone.affinity.enabled=true
# 启动和关闭可用区亲和性失败后的路由,即调用端实例没有找到同一个可用区的提供端实例的时候,当开关打开,可路由到其它可用区或者不归属任何可用区,当开关关闭,则直接调用失败。缺失则默认为true
spring.application.zone.route.enabled=true
# 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true
spring.application.parameter.event.onstart.enabled=true
# 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为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}
```
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
# 规则文件的格式,支持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
# Plugin strategy config
# 开启和关闭路由策略的控制。一旦关闭,路由策略功能将失效。缺失则默认为true
spring.application.strategy.control.enabled=true
# 开启和关闭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
# 启动和关闭网关上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下
# 1. n-d-version
# 2. n-d-region
# 3. n-d-address
# 4. n-d-version-weight
# 5. n-d-region-weight
# 6. n-d-id-blacklist
# 7. n-d-address-blacklist
# 8. n-d-env (不属于灰度蓝绿范畴的Header,只要外部传入就会全程传递)
spring.application.strategy.gateway.core.header.transmission.enabled=true
# 启动和关闭注册的服务隔离(基于Group黑/白名单的策略)。缺失则默认为false
spring.application.strategy.register.isolation.enabled=true
# 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.consumer.isolation.enabled=true
# 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false
spring.application.strategy.monitor.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
# 显示在调用链界面上灰度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上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为true
spring.application.strategy.tracer.sentinel.rule.output.enabled=true
# 启动和关闭Sentinel调用链上方法入参在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为false
spring.application.strategy.tracer.sentinel.args.output.enabled=true
# 开启Spring Cloud Gateway网关上实现Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失。缺失则默认为false
spring.application.strategy.hystrix.threadlocal.supported=true
# 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止灰度版本路由人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损
# 启动和关闭版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.enabled=true
# 版本偏好,即非灰度路由场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施灰度版本路由产生混乱,对处于非灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;灰度状态的服务,还是根据传递的Header版本号进行匹配
# 启动和关闭版本偏好。缺失则默认为false
spring.application.strategy.version.prefer.enabled=true
# 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common
spring.application.environment.route=common
# 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false
spring.application.zone.affinity.enabled=true
# 启动和关闭可用区亲和性失败后的路由,即调用端实例没有找到同一个可用区的提供端实例的时候,当开关打开,可路由到其它可用区或者不归属任何可用区,当开关关闭,则直接调用失败。缺失则默认为true
spring.application.zone.route.enabled=true
# 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true
spring.application.parameter.event.onstart.enabled=true
# 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为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}
```
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
# 规则文件的格式,支持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
# Plugin strategy config
# 开启和关闭路由策略的控制。一旦关闭,路由策略功能将失效。缺失则默认为true
spring.application.strategy.control.enabled=true
# 开启和关闭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
# 启动和关闭网关上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下
# 1. n-d-version
# 2. n-d-region
# 3. n-d-address
# 4. n-d-version-weight
# 5. n-d-region-weight
# 6. n-d-id-blacklist
# 7. n-d-address-blacklist
# 8. n-d-env (不属于灰度蓝绿范畴的Header,只要外部传入就会全程传递)
spring.application.strategy.zuul.core.header.transmission.enabled=true
# 启动和关闭注册的服务隔离(基于Group黑/白名单的策略)。缺失则默认为false
spring.application.strategy.register.isolation.enabled=true
# 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false
spring.application.strategy.consumer.isolation.enabled=true
# 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false
spring.application.strategy.monitor.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
# 显示在调用链界面上灰度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上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为true
spring.application.strategy.tracer.sentinel.rule.output.enabled=true
# 启动和关闭Sentinel调用链上方法入参在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为false
spring.application.strategy.tracer.sentinel.args.output.enabled=true
# 开启Zuul网关上实现Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失。缺失则默认为false
spring.application.strategy.hystrix.threadlocal.supported=true
# 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止灰度版本路由人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损
# 启动和关闭版本故障转移。缺失则默认为false
spring.application.strategy.version.failover.enabled=true
# 版本偏好,即非灰度路由场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施灰度版本路由产生混乱,对处于非灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;灰度状态的服务,还是根据传递的Header版本号进行匹配
# 启动和关闭版本偏好。缺失则默认为false
spring.application.strategy.version.prefer.enabled=true
# 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common
spring.application.environment.route=common
# 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false
spring.application.zone.affinity.enabled=true
# 启动和关闭可用区亲和性失败后的路由,即调用端实例没有找到同一个可用区的提供端实例的时候,当开关打开,可路由到其它可用区或者不归属任何可用区,当开关关闭,则直接调用失败。缺失则默认为true
spring.application.zone.route.enabled=true
# 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true
spring.application.parameter.event.onstart.enabled=true
# 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为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}
```
### 内置文件配置
框架提供内置文件方式的配置spring-application-default.properties。如果使用者希望对框架做封装,并提供相应的默认配置,可以在src/main/resources目录下放置spring-application-default.properties
 注意:该文件在整个服务目录和包中只能出现一次
## Docker容器化和Kubernetes平台支持
### Docker容器化
 Spring 2.3.x支持Docker分层部署,步骤也更简单,请参考Polaris【北极星】企业级云原生微服务框架里的介绍
① 搭建Windows10操作系统或者Linux操作系统下的Docker环境
- Windows10环境下,具体步骤参考[Docker安装步骤](https://github.com/Nepxion/Thunder/blob/master/thunder-spring-boot-docker-example/README.md)
- Linux环境请自行研究
② 需要在4个工程下的pom.xml里增加spring-boot-maven-plugin和docker-maven-plugin
```xml
org.springframework.boot
spring-boot-maven-plugin
true
com.nepxion.discovery.guide.gateway.DiscoveryGuideGateway
JAR
repackage
false
com.spotify
docker-maven-plugin
${ImageName}
openjdk:8-jre-alpine
["java", "-jar", "/${project.build.finalName}.jar"]
${ExposePort}
/
${project.build.directory}
${project.build.finalName}.jar
```
③ 拷贝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

### Kubernetes平台支持
请自行研究
## 自动化测试
自动化测试,基于Spring Boot/Spring Cloud的自动化测试框架,包括普通调用测试、灰度调用测试和扩展调用测试(例如:支持阿里巴巴的Sentinel,FF4j的功能开关等)。通过注解形式,跟Spring Boot内置的测试机制集成,使用简单方便。该自动化测试框架的现实意义,可以把服务注册发现中心、远程配置中心、负载均衡、灰度发布、熔断降级限流、功能开关、Feign或者RestTemplate调用等中间件或者组件,一条龙组合起来进行自动化测试
自动化测试代码参考[指南示例自动化测试](https://github.com/Nepxion/DiscoveryGuide/tree/master/discovery-guide-test-automation)
### 架构设计
通过Matrix Aop框架,实现TestAutoScanProxy和TestInterceptor拦截测试用例,实现配置内容的自动化推送
### 启动控制台
运行[指南示例](https://github.com/Nepxion/DiscoveryGuide)下的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
# Zuul网关配置
zuul.group=discovery-guide-group
zuul.service.id=discovery-guide-zuul
zuul.test.url=http://localhost:5002/discovery-guide-service-a/invoke/zuul
# 每个测试用例执行循环次数
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值改成相同或者也可以把该行元数据删除,然后进行自动化测试
#### 测试包引入
```xml
com.nepxion
discovery-plugin-test-starter
${discovery.version}
org.apache.maven.plugins
maven-compiler-plugin
-parameters
${project.build.sourceEncoding}
${java.version}
${java.version}
```
 注意:对于带有注解@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 Junit,TestApplication.class为测试框架内置应用启动程序,DiscoveryGuideTestConfiguration用于初始化所有测试用例类。在测试方法上面加入JUnit的@Test注解
```java
@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);
}
}
```
```java
@Configuration
public class DiscoveryGuideTestConfiguration {
@Bean
public DiscoveryGuideTestCases discoveryGuideTestCases() {
return new DiscoveryGuideTestCases();
}
}
```
#### 普通调用测试
在测试方法上面增加注解@DTest,通过断言Assert来判断测试结果。注解@DTest内容如下
```java
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DTest {
}
```
代码如下
```java
public class DiscoveryGuideTestCases {
@Autowired
private TestRestTemplate testRestTemplate;
@DTest
public void testNoGray(String testUrl) {
int noRepeatCount = 0;
List resultList = new ArrayList();
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注解内容如下
```java
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DTestConfig {
// 组名
String group();
// 服务名
String serviceId();
// 组名-服务名组合键值的前缀
String prefix() default StringUtils.EMPTY;
// 组名-服务名组合键值的后缀
String suffix() default StringUtils.EMPTY;
// 执行配置的文件路径。测试用例运行前,会把该文件里的内容推送到远程配置中心或者服务
String executePath();
// 重置配置的文件路径。测试用例运行后,会把该文件里的内容推送到远程配置中心或者服务。该文件内容是最初的默认配置
// 如果该注解属性为空,则直接删除从配置中心删除组名-服务名组合键值
String resetPath() default StringUtils.EMPTY;
}
```
代码如下
```java
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
1.0
```
灰度配置文件gray-default.xml的内容如下
```xml
```
#### 扩展调用测试
除了支持灰度自动化测试外,使用者可扩展出以远程配置中心内容做变更的自动化测试。以阿里巴巴的Sentinel的权限功能为例子,参考PolarisGuide,测试实现方式如下
① 远程配置中心约定
- 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")复原,再推送一次到远程配置中心
```java
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 :: testVersionStrategyGray() ----------
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 :: testRegionStrategyGray() ----------
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 :: testStrategyCustomizationGray() ----------
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](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](https://github.com/alibaba/nacos/releases)获取
- 功能界面主页,[http://localhost:8848/nacos/index.html](http://localhost:8848/nacos/index.html)
② Consul
- Consul服务器版本不限制,推荐用最新版本,从[https://releases.hashicorp.com/consul/](https://releases.hashicorp.com/consul/)获取
- 功能界面主页,[http://localhost:8500](http://localhost:8500)
③ Eureka
- 跟Spring Cloud版本保持一致,自行搭建服务器
- 功能界面主页,[http://localhost:9528](http://localhost:9528)
④ Zookeeper
- Spring Cloud F版或以上,必须采用Zookeeper服务器的3.5.x服务器版本(或者更高),从[http://zookeeper.apache.org/releases.html#download](http://zookeeper.apache.org/releases.html#download)获取
- Spring Cloud E版,Zookeeper服务器版本不限制
 配置中心
① Nacos
- Nacos服务器版本,推荐用最新版本,从[https://github.com/alibaba/nacos/releases](https://github.com/alibaba/nacos/releases)获取
- 功能界面主页,[http://localhost:8848/nacos/index.html](http://localhost:8848/nacos/index.html)
② Apollo
- Apollo服务器版本,推荐用最新版本,从[https://github.com/ctripcorp/apollo/releases](https://github.com/ctripcorp/apollo/releases)获取
- 功能界面主页,[http://localhost:8088](http://localhost:8088)
③ Redis
- Redis服务器版本,推荐用最新版本,从[https://redis.io/](https://redis.io/)获取
 限流熔断
① Sentinel
- Sentinel服务器版本,推荐用最新版本,从[https://github.com/alibaba/Sentinel/releases](https://github.com/alibaba/Sentinel/releases)获取
- 功能界面主页,[http://localhost:8075/#/dashboard](http://localhost:8075/#/dashboard)
 调用链监控
① Jaeger
- Jaeger服务器版本,推荐用最新版本,从[https://github.com/jaegertracing/jaeger/releases](https://github.com/jaegertracing/jaeger/releases)获取
- 功能界面主页,[http://localhost:16686](http://localhost:16686)
② Skywalking
- Skywalking服务器版本,推荐用最新版本,从[http://skywalking.apache.org/downloads/](http://skywalking.apache.org/downloads/)获取
- 功能界面主页,[http://127.0.0.1:8080/](http://127.0.0.1:8080/)
③ Zipkin
- Zipkin服务器版本,推荐用最新版本,从[https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec](https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec)获取
- 功能界面主页,[http://localhost:9411/zipkin](http://localhost:9411/zipkin)
 指标监控
① Prometheus
- Prometheus服务器版本,推荐用最新版本,从[https://github.com/prometheus/prometheus/releases](https://github.com/prometheus/prometheus/releases)获取
- 功能界面主页,[http://localhost:9090](http://localhost:9090)
② Grafana
- Grafana服务器版本,推荐用最新版本,从[https://grafana.com/grafana/download?platform=windows](https://grafana.com/grafana/download?platform=windows)获取
- 功能界面主页,[http://localhost:3000](http://localhost:3000)
③ Spring Boot Admin
- 跟Spring Boot版本保持一致,自行搭建服务器。从[https://github.com/codecentric/spring-boot-admin](https://github.com/codecentric/spring-boot-admin)获取
- 功能界面主页,[http://localhost:6002](http://localhost:6002)
## Star走势图
[](https://starchart.cc/Nepxion/Discovery)