# 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 [^_^]: ![](http://nepxion.gitee.io/docs/discovery-doc/Cover.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Banner.png) # Discovery【探索】微服务企业级解决方案 [![Total lines](https://tokei.rs/b1/github/Nepxion/Discovery?category=lines)](https://tokei.rs/b1/github/Nepxion/Discovery?category=lines) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?label=license)](https://github.com/Nepxion/Discovery/blob/master/LICENSE) [![Maven Central](https://img.shields.io/maven-central/v/com.nepxion/discovery.svg?label=maven%20central)](https://search.maven.org/artifact/com.nepxion/discovery) [![Javadocs](http://www.javadoc.io/badge/com.nepxion/discovery-plugin-framework-starter.svg)](http://www.javadoc.io/doc/com.nepxion/discovery-plugin-framework-starter) [![Build Status](https://travis-ci.org/Nepxion/Discovery.svg?branch=master)](https://travis-ci.org/Nepxion/Discovery) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/8e39a24e1be740c58b83fb81763ba317)](https://www.codacy.com/project/HaojunRen/Discovery/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Nepxion/Discovery&utm_campaign=Badge_Grade_Dashboard) [![Stars](https://img.shields.io/github/stars/Nepxion/Discovery.svg?label=Stars&tyle=flat&logo=GitHub)](https://github.com/Nepxion/Discovery/stargazers) [![Stars](https://gitee.com/Nepxion/Discovery/badge/star.svg)](https://gitee.com/nepxion/Discovery/stargazers) [![Spring Boot](https://img.shields.io/maven-central/v/org.springframework.boot/spring-boot-dependencies.svg?label=Spring%20Boot&logo=Spring)](https://search.maven.org/artifact/org.springframework.boot/spring-boot-dependencies) [![Spring Cloud](https://img.shields.io/maven-central/v/org.springframework.cloud/spring-cloud-dependencies.svg?label=Spring%20Cloud&logo=Spring)](https://search.maven.org/artifact/org.springframework.cloud/spring-cloud-dependencies) [![Spring Cloud Alibaba](https://img.shields.io/maven-central/v/com.alibaba.cloud/spring-cloud-alibaba-dependencies.svg?label=Spring%20Cloud%20Alibaba&logo=Spring)](https://search.maven.org/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies) [![Nepxion Discovery](https://img.shields.io/maven-central/v/com.nepxion/discovery.svg?label=Nepxion%20Discovery&logo=Anaconda)](https://search.maven.org/artifact/com.nepxion/discovery) [![Discovery DOC PPT](https://img.shields.io/badge/Discovery%20Doc-PPT-brightgreen?logo=Microsoft%20PowerPoint)](http://nepxion.gitee.io/docs/link-doc/discovery-ppt.html) [![Discovery DOC PDF](https://img.shields.io/badge/Discovery%20Doc-PDF-brightgreen?logo=Adobe%20Acrobat%20Reader)](http://nepxion.gitee.io/docs/link-doc/discovery-pdf.html) [![Discovery DOC HTML](https://img.shields.io/badge/Discovery%20Doc-HTML-brightgreen?logo=Microsoft%20Edge)](http://nepxion.gitee.io/docs/link-doc/discovery-html.html) ![](http://nepxion.gitee.io/docs/icon-doc/star3.png) 如果您觉得本框架具有一定的参考价值和借鉴意义,请帮忙在页面右上角 [**Star**] ![](http://nepxion.gitee.io/docs/icon-doc/star1.png) 首席作者简介 - Nepxion开源社区创始人 - 2020年阿里巴巴中国云原生峰会出品人 - Nacos Group Member - Spring Cloud Alibaba、Nacos、Sentinel、OpenTracing Committer & Contributor ![](http://nepxion.gitee.io/docs/discovery-doc/Logo64.png) 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。涉及到指南篇里的灰度路由和发布的基本功能,![](http://nepxion.gitee.io/docs/icon-doc/information_message.png) 参考[新手快速入门](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等高级调用链和灰度调用链的整合 ![](http://nepxion.gitee.io/docs/polaris-doc/Logo64.png) 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) ![](http://nepxion.gitee.io/docs/icon-doc/Logo64.png) Discovery【探索】和Polaris【北极星】架构体系 ① Discovery【探索】和Polaris【北极星】联合架构图 ![](http://nepxion.gitee.io/docs/polaris-doc/Architecture.jpg) ② Discovery【探索】和Polaris【北极星】联合拓扑图 ![](http://nepxion.gitee.io/docs/polaris-doc/Topology.jpg) ③ Polaris【北极星】分层架构图 ![](http://nepxion.gitee.io/docs/polaris-doc/Layer.jpg) ④ Discovery【探索】实施方案图 ![](http://nepxion.gitee.io/docs/polaris-doc/All.jpg) ⑤ Discovery【探索】域网关实施图 ![](http://nepxion.gitee.io/docs/polaris-doc/DomainEnable.jpg) ⑥ Discovery【探索】非域网关实施图 ![](http://nepxion.gitee.io/docs/polaris-doc/DomainDisable.jpg) ⑦ Discovery【探索】全局订阅实施图 ![](http://nepxion.gitee.io/docs/polaris-doc/GlobalSub.jpg) ## 简介 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平台的无缝支持部署 ![](http://nepxion.gitee.io/docs/icon-doc/tip.png) 基于RESTful层面的功能全景 ![](http://nepxion.gitee.io/docs/discovery-doc/Introduction.jpg) ③ 微服务框架易用性表现,如下 - 引入相关依赖到pom.xml - 设置元数据MetaData。如下五个元数据可以按需设置 - 定义所属组名Group,也可以通过服务名前缀来自动产生服务组名 - 定义版本号Version,也可以通过Git插件方式自动产生版本号 - 定义所属区域名Region - 定义所属环境Env - 定义所属可用区Zone - 执行采用“约定大于配置”的准则,使用者也可以开启和关闭相关功能项或者属性值,达到最佳配置 - 规则策略文件设置和推送,或者通过Header、Query Parameter、Cookie触发,并通过Header方式全链路传递路由策略 ④ 微服务框架版本兼容列表,如下 ![](http://nepxion.gitee.io/docs/icon-doc/tip.png) 提醒:版本号右边, `↑` 表示>=该版本号, `↓` 表示<=该版本号 | 框架版本 | 框架分支 | 框架状态 | Spring Cloud版本 | Spring Boot版本 | Spring Cloud Alibaba版本 | | --- | --- | --- | --- | --- | --- | | 6.3.3 | master | ![](http://nepxion.gitee.io/docs/icon-doc/confirm_24.png) | 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~~ | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) | Greenwich | 2.1.x.RELEASE | 2.1.x.RELEASE | | ~~4.15.0~~ | ~~4.x.x~~ | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) | Finchley | 2.0.x.RELEASE | 2.0.x.RELEASE | | 3.20.3 | 3.x.x | ![](http://nepxion.gitee.io/docs/icon-doc/arrow_up_24.png) | Edgware | 1.5.x.RELEASE | 1.5.x.RELEASE | | ~~2.0.x~~ | ~~2.x.x~~ | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) | Dalston | 1.x.x.RELEASE | N/A | | ~~1.0.x~~ | ~~1.x.x~~ | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) | Camden | 1.x.x.RELEASE | N/A | ![](http://nepxion.gitee.io/docs/icon-doc/confirm_24.png) 表示维护中 | ![](http://nepxion.gitee.io/docs/icon-doc/arrow_up_24.png) 表示不维护,但可用,强烈建议升级 | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) 表示不维护,不可用,已废弃 - 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)已废弃 ## 鸣谢 ![](http://nepxion.gitee.io/docs/icon-doc/star4.png) 郑重致谢 - 感谢阿里巴巴中间件Nacos、Sentinel和Spring Cloud Alibaba团队,尤其是Nacos负责人@彦林、@于怀,Sentinel负责人@宿何、@子衿,Spring Cloud Alibaba负责人@小马哥、@洛夜、@亦盏的技术支持 - 感谢携程Apollo团队,尤其是@宋顺的技术支持 - 感谢所有Committers和Contributors - 感谢所有帮忙分析和定位问题的同学 - 感谢所有提出宝贵建议和意见的同学 - 感谢阿里巴巴中间件Nacos和Spring Cloud Alibaba团队,纳入本框架为相关开源项目 NacosSpring Cloud Alibaba - 感谢支持和使用本框架的公司和企业。不完全统计,目前社区开源项目(包括本框架以及关联框架或组件)已经被如下公司使用或者调研
![](http://nepxion.gitee.io/docs/icon-doc/edit_32.png) 为提供更好的专业级服务,请更多已经使用本框架的公司和企业联系我,并希望在[Github Issues](https://github.com/Nepxion/Discovery/issues/56)上登记 ![](http://nepxion.gitee.io/docs/icon-doc/chart_bar_32.png) 某大型互联网教育公司在生产环境全套接入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走势图) ## 请联系我 微信、公众号和文档 ![](http://nepxion.gitee.io/docs/zxing-doc/微信-1.jpg)![](http://nepxion.gitee.io/docs/zxing-doc/公众号-1.jpg)![](http://nepxion.gitee.io/docs/zxing-doc/文档-1.jpg) ## 相关链接 ### 源码主页 [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 ![](http://nepxion.gitee.io/docs/icon-doc/information.png) 灰度发布(规则)和灰度路由(策略) ① 灰度发布(规则)和灰度路由(策略)对比 |   | 灰度发布 | 灰度路由 | | --- | --- | --- | | 驱动域 | 规则 | 策略 | | 驱动方式 | 通过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 ``` ![](http://nepxion.gitee.io/docs/icon-doc/information.png) 动态改变规则策略和动态改变版本 ① 动态改变规则策略 微服务启动的时候,由于规则策略(例如:rule.xml)已经配置在本地,使用者希望改变一下规则策略,而不重启微服务,达到规则策略的改变 - 规则策略分为本地规则策略和动态规则策略 - 本地规则策略是通过在本地规则策略(例如:rule.xml)文件定义的,也可以从远程配置中心获取,在微服务启动的时候读取 - 动态规则策略是通过POST方式动态设置,或者由远程配置中心推送设置 - 规则策略初始化的时候,如果接入了远程配置中心,先读取远程规则策略,如果不存在,再读取本地规则策略文件 - 规则策略可以持久化到远程配置中心,一旦微服务死掉后,再次启动,仍旧可以拿到灰度规则策略,所以动态改变规则策略策略属于永久灰度手段 - 规则策略推送到远程配置中心可以分为局部推送和全局推送 - 局部推送是基于Group+ServiceId来推送的,就是同一个Group下同一个ServiceId的服务集群独立拥有一个规则策略配置,如果采用这种方式,需要在每个微服务集群下做一次灰度。优点是独立封闭,本服务集群灰度失败不会影响到其它服务集群,缺点是相对繁琐 - 全局推送是基于Group来推送的(接口参数中的ServiceId由Group来代替),就是同一个Group下所有服务集群共同拥有一个规则策略配置,如果采用这种方式,只需要做一次灰度,所有服务集群都生效。优点是非常简便,缺点是具有一定风险,因为这个规则策略配置掌握着所有服务集群的命运。全局推送用于全链路灰度 - 如果既执行了全局推送,又执行了局部推送,在服务运行期间或者服务重新启动,优先读取局部规则策略中的相应配置项,当局部规则策略的相应配置项不存在,则读取全局规则策略的相应配置项 ② 动态改变版本 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:动态改变版本,只允许发生在调用链的起点,例如网关,如果没有网关,则取第一个服务,其它层级的服务不能使用该功能 微服务启动的时候,由于版本已经写死在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 | ### 架构核心 - 灰度方式区别图 ![](http://nepxion.gitee.io/docs/discovery-doc/Difference.jpg) ① 基于网关为触点的Header传递的全链路灰度路由,适用于网关前置部署方式的企业。域网关部署模式下,最适用于该方式;非域网关部署模式下,开启并行灰度路由下的版本偏好策略 ② 基于全局订阅方式的全链路灰度发布,适用于网关部署方式比较弱化的企业 ③ 基于全局订阅和Header传递组合式全链路灰度路由,上述两种方式的结合体,是比较理想和节省成本的落地方式 - 服务治理架构图 ![](http://nepxion.gitee.io/docs/discovery-doc/Govern.jpg) - 模块结构图 ![](http://nepxion.gitee.io/docs/discovery-doc/Module.jpg) ### 依赖引入 ① 服务注册发现依赖引入 服务注册发现中间件的四个插件,必须引入其中一个。该依赖提供灰度发布功能、注册发现中心、管理中心等功能 ```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端 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:该模块支持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 | 无 | 无 | 无 | - 部署拓扑图 ![](http://nepxion.gitee.io/docs/discovery-doc/BasicTopology.jpg) 全链路路径, 如下 ``` 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/ | 线程池方式的异步调用 | - 上述步骤在下面每次更改规则策略的时候执行,并验证结果和规则策略的期望值是否相同 ② 通过图形化界面验证 ![](http://nepxion.gitee.io/docs/icon-doc/information.png) 该方式有点古老,并不再维护,请斟酌使用 - 下载[源码主页](https://github.com/Nepxion/Discovery)的工程,并导入IDE - 启动源码工程下的discovery-springcloud-example-console/ConsoleApplication - 启动源码工程下的discovery-console-desktop/ConsoleLauncher - 通过admin/admin或者nepxion/nepxion登录,点击【显示服务拓扑】按钮,将呈现如下界面 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide5-2.jpg) - 在加入上述规则策略前,选中网关节点,右键点击【执行灰度路由】,在弹出路由界面中,依次加入“discovery-guide-service-a”和“discovery-guide-service-b”,点击【执行路由】按钮,将呈现如下界面 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide5-3.jpg) - 在加入上述规则策略后,在路由界面中,再次点击【执行路由】按钮,将呈现如下界面 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide5-4.jpg) ## 基于Header传递方式的灰度路由策略 ![](http://nepxion.gitee.io/docs/icon-doc/tip.png) 本章节通过网关为触发点来介绍灰度路由策略功能,使用者也可以不通过网关,直接以微服务为触发点进行实施 ### 配置网关灰度路由策略 在Nacos配置中心,增加网关灰度路由策略 #### 版本匹配灰度路由策略 增加Spring Cloud Gateway的基于版本匹配路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用都走版本为1.0的服务 ```xml 1.0 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-1.jpg) 每个服务调用的版本都可以自行指定,见下面第二条。当所有服务都选同一版本的时候,可以简化成下面第一条 ``` 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方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略) 版本灰度路由架构图 ![](http://nepxion.gitee.io/docs/discovery-doc/RouteVersion.jpg) #### 版本权重灰度路由策略 增加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 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-2.jpg) 每个服务调用的版本权重都可以自行指定,见下面第二条。当所有服务都选相同版本权重的时候,可以简化成下面第一条 ``` 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 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-3.jpg) 每个服务调用的区域都可以自行指定,见下面第二条。当所有服务都选同一区域的时候,可以简化成下面第一条 ``` 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方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略) 区域灰度路由架构图 ![](http://nepxion.gitee.io/docs/discovery-doc/RouteRegion.jpg) #### 区域权重灰度路由策略 增加Zuul的基于区域权重路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用dev区域流量调用为85%,qa区域流量调用为15% ```xml dev=85;qa=15 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-4.jpg) 每个服务调用的区域权重都可以自行指定,见下面第二条。当所有服务都选相同区域权重的时候,可以简化成下面第一条 ``` 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
``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-5.jpg) 每个服务调用的端口都可以自行指定,见下面第二条。当所有服务都选同一端口的时候,可以简化成下面第一条(单机版不适用于该策略) ``` 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地址和端口灰度路由架构图 ![](http://nepxion.gitee.io/docs/discovery-doc/RouteAddress.jpg) ### 配置全链路灰度条件命中和灰度匹配组合式策略 属于全链路蓝绿部署的范畴。既适用于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,策略内容如下,实现功能 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 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 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 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为优先 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意: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"} ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-8.jpg) 内置基于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"} ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-9.jpg) ### 配置前端灰度和网关灰度路由组合式策略 当前端(例如: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)方式传入灰度路由策略,来代替配置中心方式,传递全链路路由策略 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:当配置中心和界面都配置后,以界面传入优先 - 版本匹配策略,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 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-6.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-7.jpg) 当外界传值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选择不同的路由路径,示例提供全链路匹配和全链路权重两种方式 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:当网关有对应策略传入时,以网关策略优先,服务侧的过滤器无效 ```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环境 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-15.jpg) - 根据common.nepxion.com域名路由到common环境 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-16.jpg) ## 基于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变更元数据 自行研究 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 需要注意 ① 并非所有的注册中心都支持动态元数据变更方式,需要使用者自行研究 ② 动态元数据变更方式利用第三方注册中心的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"} ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-10.jpg) 如果采用上述方式,可以考虑开启下面的开关 ``` # 启动和关闭核心策略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 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-11.jpg) 也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略) ### 配置IP地址和端口屏蔽策略 通过IP地址或者端口或者IP地址+端口进行屏蔽,支持通配符方式。此用法适用于IP地址确定的场景,策略内容如下,采用如下两种方式之一均可 ```xml
``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-12.jpg) 也可以通过全链路传递Header方式实现,参考[通过前端传入灰度路由策略](#通过前端传入灰度路由策略) ## 基于订阅方式的全链路灰度发布规则 在Nacos配置中心,增加全链路灰度发布规则 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:该功能和网关灰度路由和灰度权重功能会叠加,为了不影响演示效果,请先清除网关灰度路由的策略 ### 配置全链路灰度匹配规则 #### 版本匹配灰度规则 增加版本匹配的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现a服务1.0版本只能访问b服务1.0版本,a服务1.1版本只能访问b服务1.1版本 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide3-1.jpg) #### 区域匹配灰度规则 增加区域匹配的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现dev区域的a服务只能访问dev区域的b服务,qa区域的a服务只能访问qa区域的b服务 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide3-2.jpg) ### 配置全链路灰度权重规则 #### 全局版本权重灰度规则 增加全局版本权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现版本为1.0的服务提供90%的流量,版本为1.1的服务提供10%的流量 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide4-1.jpg) #### 局部版本权重灰度规则 增加局部版本权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现a服务1.0版本提供90%的流量,1.1版本提供10%的流量;b服务1.0版本提供20%的流量,1.1版本提供80%的流量 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide4-2.jpg) #### 全局区域权重灰度规则 增加全局区域权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现区域为dev的服务提供90%的流量,区域为qa的服务提供10%的流量 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide4-3.jpg) #### 局部区域权重灰度规则 增加局部区域权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现a服务dev区域提供90%的流量,qa区域提供10%的流量;b服务dev区域提供20%的流量,qa区域提供80%的流量 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide4-4.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:局部权重优先级高于全局权重,版本权重优先级高于区域权重 请执行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 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide5-1.jpg) ### 数据库和消息队列灰度发布规则 通过订阅业务参数的变化,实现参数化灰度发布,例如,基于多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)里的实现方式 ## 基于多格式的规则策略定义 ### 规则策略格式定义 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:服务名大小写规则 - 在配置文件(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订阅推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Apollo1.jpg) ① 参考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订阅推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Nacos2.jpg) - 参考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订阅推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Redis.jpg) ### 基于Swagger和Rest的规则策略推送 服务侧单个推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Swagger1.jpg) 控制平台批量推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Swagger2.jpg) ### 基于图形化界面的规则策略推送 ![](http://nepxion.gitee.io/docs/icon-doc/information.png) 下面两种方式有点古老,并不再维护,请斟酌使用 基于图形化桌面程序的灰度发布路由 ![](http://nepxion.gitee.io/docs/discovery-doc/Console1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Console2.jpg) ① 桌面程序对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并注入,实现更专业的认证功能 ![](http://nepxion.gitee.io/docs/discovery-doc/Console0.jpg) - 点击【显示服务拓扑】按钮,弹出【服务集群组过滤】对话框,列表是以服务所在的集群组列表(例如:eureka.instance.metadataMap.group=example-service-group),选择若干个并点击【确定】按钮,如果使用者想获取全部的服务集群(可能会耗性能),则直接点击【取消】按钮 ![](http://nepxion.gitee.io/docs/discovery-doc/Console4.jpg) - 从服务注册发现中心获取服务拓扑 ![](http://nepxion.gitee.io/docs/discovery-doc/Console5.jpg) - 执行灰度路由,选择一个服务,右键菜单【执行灰度路由】 ![](http://nepxion.gitee.io/docs/discovery-doc/Console6.jpg) - 通过【服务列表】切换,或者点击增加和删除服务按钮,确定灰度路由路径,点击【执行路由】 ![](http://nepxion.gitee.io/docs/discovery-doc/Console7.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Console2.jpg) - 推送模式设置,【异步推送】和【同步推送】,前者是推送完后立刻返回,后者是推送完后等待推送结果(包括规则XML解析的异常等都能在界面上反映出来);【规则推送到远程配置中心】和【规则推送到服务或者服务集群】,前者是推送到配置中心(持久化),后者是推送到一个或者多个服务机器的内存(非持久化,重启后丢失) ![](http://nepxion.gitee.io/docs/discovery-doc/Console8.jpg) - 执行灰度发布,选择一个服务或者服务组,右键菜单【执行灰度发布】,前者是通过单个服务实例执行灰度发布,后者是通过一组服务实例执行灰度发布 ![](http://nepxion.gitee.io/docs/discovery-doc/Console9.jpg) - 灰度发布,包括【更改版本】和【更改规则】,前者通过更改版本号去适配灰度规则中的版本匹配关系,后者直接修改规则。【更改版本】是推送到一个或者多个服务机器的内存(非持久化,重启后丢失),【更改规则】是根据不同的推送模式,两种方式都支持 ![](http://nepxion.gitee.io/docs/discovery-doc/Console10.jpg) - 全链路灰度发布,所有在同一个集群组(例如:eureka.instance.metadataMap.group=example-service-group)里的服务统一做灰度发布,即一个规则配置搞定所有服务的灰度发布。点击【全链路灰度发布】按钮,弹出【全链路灰度发布】对话框 ![](http://nepxion.gitee.io/docs/discovery-doc/Console11.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Console12.jpg) - 刷新灰度状态,选择一个服务或者服务组,右键菜单【刷新灰度状态】,查看某个服务或者服务组是否正在做灰度发布 ![](http://nepxion.gitee.io/docs/discovery-doc/Console13.jpg) ⑥ 动画效果 参考[图形化桌面程序的灰度发布路由动画效果](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) - 操作过程跟[基于图形化界面的规则策略推送](#基于图形化界面的规则策略推送)类似 ![](http://nepxion.gitee.io/docs/discovery-doc/Console14.jpg) ## 基于组和黑白名单的全链路服务隔离和准入 ### 服务注册发现准入 #### 基于组黑白名单注册准入 微服务启动的时候,它所属的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 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide6-1.jpg) 如果加上n-d-service-group=discovery-guide-group的Header,那么两者保持Group相同,则调用通过。这是解决异构系统调用微服务被隔离的一种手段 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide6-2.jpg) ## 基于Env的全链路环境隔离和路由 基于服务实例的元数据Metadata的env参数和全链路传递的环境Header值进行比对实现隔离,当从网关传递来的环境Header(n-d-env)值和提供端实例的元数据Metadata环境配置值相等才能调用。环境隔离下,调用端实例找不到符合条件的提供端实例,把流量路由到一个通用或者备份环境 ![](http://nepxion.gitee.io/docs/discovery-doc/IsolationEnvironment.jpg) ### 环境隔离 在网关或者服务端,配置环境元数据,在同一套环境下,env值必须是一样的,这样才能达到在同一个注册中心下,环境隔离的目的 ``` spring.cloud.nacos.discovery.metadata.env=env1 ``` ### 环境路由 在环境隔离执行的时候,如果无法找到对应的环境,则会路由到一个通用或者备份环境,默认为env为common的环境,可以通过如下参数进行更改 ``` # 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common spring.application.environment.route=common ``` ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意事项 - 如果存在环境,优先寻址环境的服务实例 - 如果不存在环境,则寻址Common环境的服务实例(未设置元数据Metadata的env参数的服务实例也归为Common环境) - 如果Common环境也不存在,则调用失败 - 如果没有传递环境Header(n-d-env)值,则执行Spring Cloud Ribbon轮询策略 - 环境隔离和路由适用于测试环境,性能压测等场景 ## 基于Zone的全链路可用区亲和性隔离和路由 ![](http://nepxion.gitee.io/docs/discovery-doc/IsolationZone.jpg) ### 可用区亲和性隔离 基于调用端实例和提供端实例的元数据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 ``` ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意事项 - 不归属任何可用区,含义是服务实例未设置任何zone元数据值。可用区亲和性路由功能,是为了尽量保证流量不损失 - 如果采用Eureka注册中心,Ribbon本身就具有可用区亲和性功能,跟本框架类似。如果使用者采用了Eureka注册中心下的Ribbon可用区亲和性功能,请关闭本框架提供的相似功能,以免冲突 - 本框架提供的可用区亲和性功能适用于一切注册中心 ## 基于Sentinel的全链路服务限流熔断降级权限和灰度融合 集成Sentinel熔断隔离限流降级平台 ![](http://nepxion.gitee.io/docs/discovery-doc/Sentinel3.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Sentinel4.jpg) 通过集成Sentinel,在服务端实现该功能 ![](http://nepxion.gitee.io/docs/icon-doc/information.png) 由于该块功能早于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 } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-1.jpg) #### 降级规则 增加服务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 } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-2.jpg) #### 授权规则 增加服务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 } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-3.jpg) #### 系统规则 增加服务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 } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-4.jpg) #### 热点参数流控规则 增加服务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 } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-5.jpg) ### 基于灰度路由和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服务,最终调用成功 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-6.jpg) - 当传递的Header中location=beijing,不满足条件,最终调用在discovery-guide-service-b服务端被拒绝掉 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-7.jpg) - 当传递的Header中location=shanghai,满足条件之一,当全链路调用中,API网关负载均衡discovery-guide-service-a服务到1.1版本后再去调用discovery-guide-service-b服务,不满足version=1.0的条件,最终调用在discovery-guide-service-b服务端被拒绝掉 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-8.jpg) ## 基于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自动埋点 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:因为OpenTracing规范不仅仅只被Jaeger所遵守,所以框架并没有直接引入Jaeger包,需要使用者自行引入,可以参照指南示例中的pom.xml引用 ```xml io.opentracing.contrib opentracing-spring-cloud-starter io.opentracing.contrib opentracing-spring-jaeger-starter ``` 集成Opentracing + Uber Jaeger灰度全链路监控 ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger2.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger3.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger4.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger5.jpg) 集成Opentracing + Uber Jaeger + Sentinel限流熔断降级权限埋点全链路监控 ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger6.jpg) 集成主流中间件 + 灰度全链路监控 代码请从[指南示例高级版](https://github.com/Nepxion/DiscoveryGuide)获取,分支为premium。运行出下图强大效果的前提,需要事先搭建Nacos、Jaeger、ActiveMQ、MongoDB、RabbitMQ、Redis、RocketMQ以及MySQL数据库等环境 使用者如果不想搭建环境,想直接观看效果,可以直接把离线数据tracing.json(位于根目录下)导入到Jaeger界面(JSON File栏,拖进去即可),观看到下图效果 ![](http://nepxion.gitee.io/docs/discovery-doc/JaegerPremium1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/JaegerPremium2.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/JaegerPremium3.jpg) 集成Opentracing + Apache Skywalking灰度全链路监控 ![](http://nepxion.gitee.io/docs/discovery-doc/Skywalking1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Skywalking2.jpg) 集成Opentracing + Apache Skywalking + Sentinel限流熔断降级权限埋点全链路监控 ![](http://nepxion.gitee.io/docs/discovery-doc/Skywalking3.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Skywalking4.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 请注意如下配置,将决定终端界面的显示 - 如果开启,灰度信息输出到独立的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 ``` ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意: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控制台打印的结果 ![](http://nepxion.gitee.io/docs/discovery-doc/Tracer.jpg) ### 全链路指标监控 #### Prometheus监控方式 ![](http://nepxion.gitee.io/docs/discovery-doc/Prometheus.jpg) #### Grafana监控方式 ![](http://nepxion.gitee.io/docs/discovery-doc/Grafana.jpg) #### Spring-Boot-Admin监控方式 ![](http://nepxion.gitee.io/docs/discovery-doc/Admin1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Admin7.jpg) ## 全链路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 ``` ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:一般情况下,上述两个地方的配置都同时保持默认即可。对于一些特色化的用法,两个地方的配置项用法必须保持一致,例如 ``` # 输出到工程根目录下 ${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 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:该文件在整个服务目录和包中只能出现一次 ## Docker容器化和Kubernetes平台支持 ### Docker容器化 ![](http://nepxion.gitee.io/docs/icon-doc/information.png) 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 ![](http://nepxion.gitee.io/docs/discovery-doc/Docker.jpg) - Docker Windows ![](http://nepxion.gitee.io/docs/polaris-doc/DockerWindows.jpg) - Docker Linux ![](http://nepxion.gitee.io/docs/polaris-doc/DockerLinux.jpg) ### 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 ``` ### 测试用例 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:当使用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} ``` ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:对于带有注解@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% | ## 附录 ### 中间件服务器下载地址 ![](http://nepxion.gitee.io/docs/icon-doc/information_message.png) 注册中心 ① 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服务器版本不限制 ![](http://nepxion.gitee.io/docs/icon-doc/information_message.png) 配置中心 ① 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/)获取 ![](http://nepxion.gitee.io/docs/icon-doc/information_message.png) 限流熔断 ① Sentinel - Sentinel服务器版本,推荐用最新版本,从[https://github.com/alibaba/Sentinel/releases](https://github.com/alibaba/Sentinel/releases)获取 - 功能界面主页,[http://localhost:8075/#/dashboard](http://localhost:8075/#/dashboard) ![](http://nepxion.gitee.io/docs/icon-doc/information_message.png) 调用链监控 ① 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) ![](http://nepxion.gitee.io/docs/icon-doc/information_message.png) 指标监控 ① 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走势图 [![Stargazers over time](https://starchart.cc/Nepxion/Discovery.svg)](https://starchart.cc/Nepxion/Discovery)