# Discovery **Repository Path**: private-codeing/Discovery ## Basic Information - **Project Name**: Discovery - **Description**: ☀️ Nepxion Discovery is a solution for Spring Cloud with blue green, gray, weight, limitation, circuit breaker, degrade, isolation, tracing, dye, failover 蓝绿、灰度、权重、限流、熔断、降级、隔离、追踪、流量染色、故障转移 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: 6.x.x - **Homepage**: http://www.nepxion.com - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 292 - **Created**: 2021-07-16 - **Last Updated**: 2021-07-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Banner.png) # Discovery【探索】云原生微服务解决方案 ![Total visits](https://visitor-badge.laobi.icu/badge?page_id=Nepxion&title=total%20visits) [![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/6.x.x/LICENSE) [![Maven Central](https://img.shields.io/maven-central/v/com.nepxion/discovery.svg?label=maven)](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=6.x.x)](https://travis-ci.org/Nepxion/Discovery) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/5c42eb719ef64def9cad773abd877e8b)](https://www.codacy.com/gh/Nepxion/Discovery/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Nepxion/Discovery&utm_campaign=Badge_Grade) [![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?theme=gvp)](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 PPT](https://img.shields.io/badge/Discovery%20-PPT-brightgreen?logo=Microsoft%20PowerPoint)](http://nepxion.gitee.io/discovery/docs/link-doc/discovery-ppt.html) [![Discovery WIKI](https://img.shields.io/badge/Discovery%20-WIKI-brightgreen?logo=Microsoft%20Edge)](http://nepxion.gitee.io/discovery/) [![Discovery Platform WIKI](https://img.shields.io/badge/Discovery%20Platform%20-WIKI-brightgreen?logo=Microsoft%20Edge)](http://nepxion.gitee.io/discoveryplatform) [![Polaris WIKI](https://img.shields.io/badge/Polaris%20-WIKI-brightgreen?logo=Microsoft%20Edge)](http://polaris-paas.gitee.io/polaris-sdk)             如果您觉得本框架具有一定的参考价值和借鉴意义,请帮忙在页面右上角 [**Star**] ![](http://nepxion.gitee.io/discovery/docs/icon-doc/star1.png) 首席作者简介 - Nepxion开源社区创始人 - 2020年阿里巴巴中国云原生峰会出品人 - 2020年被Nacos和Spring Cloud Alibaba纳入相关开源项目 - 2021年阿里巴巴技术峰会上海站演讲嘉宾 - 2021年荣获陆奇博士主持的奇绩资本,进行风险投资的关注和调研 - 2021年入选Gitee最有价值开源项目 - Nacos Group Member、Spring Cloud Alibaba Member - Spring Cloud Alibaba、Nacos、Sentinel、OpenTracing Committer & Contributor ![](http://nepxion.gitee.io/discovery/docs/icon-doc/star4.png) 商业化合作 ① Discovery系列 | 框架名称 | 框架版本 | 支持Spring Cloud版本 | 使用许可 | | --- | --- | --- | --- | | Discovery | 1.x.x ~ 6.x.x | Camden ~ Hoxton | 开源,永久免费 | | DiscoveryX | 7.x.x | 202x | 闭源,商业许可 | ② Polaris系列 Polaris为Discovery高级定制版,特色功能 - 基于Nepxion Discovery集成定制 - 多云、多活、多机房流量调配 - 跨云动态域名、跨环境适配 - DCN、DSU、SET单元化部署 - 组件灵活装配、配置对外屏蔽 - 极简低代码PaaS平台 | 框架名称 | 框架版本 | 支持Discovery版本 | 支持Spring Cloud版本 | 使用许可 | | --- | --- | --- | --- | --- | | Polaris | 1.x.x | 6.x.x | Finchley ~ Hoxton | 闭源,商业许可 | | Polaris | 2.x.x | 7.x.x | 202x | 闭源,商业许可 | 有商业版需求的企业和用户,请添加微信1394997,联系作者,洽谈合作事宜 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Logo64.png) Discovery【探索】企业级云原生微服务开源解决方案 ① 解决方案 - [解决方案WIKI版](http://nepxion.com/discovery) - [解决方案PPT版](http://nepxion.gitee.io/discovery/docs/link-doc/discovery-ppt.html) ② 平台界面 - [平台界面WIKI版](http://nepxion.com/discovery-platform) ③ 快速入门 - [快速入门Github版](https://github.com/Nepxion/Discovery/wiki) - [快速入门Gitee版](https://gitee.com/Nepxion/Discovery/wikis/pages) ④ 框架源码 - [框架源码Github版](https://github.com/Nepxion/Discovery) - [框架源码Gitee版](https://gitee.com/Nepxion/Discovery) ⑤ 指南示例源码 - [指南示例源码Github版](https://github.com/Nepxion/DiscoveryGuide) - [指南示例源码Gitee版](https://gitee.com/Nepxion/DiscoveryGuide) ⑥ 指南示例说明 - 对于入门级玩家,参考[6.x.x指南示例极简版](https://github.com/Nepxion/DiscoveryGuide/tree/6.x.x-simple),分支为6.x.x-simple - 对于熟练级玩家,参考[6.x.x指南示例精进版](https://github.com/Nepxion/DiscoveryGuide/tree/6.x.x),分支为6.x.x。除上述《极简版》功能外,涉及到指南篇里的绝大多数高级功能 - 对于骨灰级玩家,参考[6.x.x指南示例高级版](https://github.com/Nepxion/DiscoveryGuide/tree/6.x.x-complex),分支为6.x.x-complex。除上述《精进版》功能外,涉及到指南篇里的ActiveMQ、MongoDB、RabbitMQ、Redis、RocketMQ、MySQL等高级调用链和蓝绿灰度调用链的整合 - 上述指南实例分支是针对Spring Cloud旧版本。对于Spring Cloud 202x版本,参考[7.x.x指南示例精进版](https://github.com/Nepxion/DiscoveryGuide/tree/master),分支为master ![](http://nepxion.gitee.io/discovery/docs/polaris-doc/Logo64.png) Polaris【北极星】企业级云原生微服务商业解决方案 ① 解决方案 - [解决方案WIKI版](http://nepxion.com/polaris) ② 框架源码 - [框架源码Github版](https://github.com/polaris-paas/polaris-sdk) - [框架源码Gitee版](https://gitee.com/polaris-paas/polaris-sdk) ③ 指南示例源码 - [指南示例源码Github版](https://github.com/polaris-paas/polaris-guide) - [指南示例源码Gitee版](https://gitee.com/polaris-paas/polaris-guide) ④ 指南示例说明 - Spring Cloud旧版本,参考[1.x.x指南示例](https://github.com/polaris-paas/polaris-guide/tree/1.x.x),分支为1.x.x - Spring Cloud新版本,参考[2.x.x指南示例](https://github.com/polaris-paas/polaris-guide/tree/master),分支为master ![](http://nepxion.gitee.io/discovery/docs/icon-doc/Logo64.png) Discovery【探索】和Polaris【北极星】架构体系 ① Discovery【探索】和Polaris【北极星】联合架构图 ![](http://nepxion.gitee.io/discovery/docs/polaris-doc/Architecture.jpg) ② Discovery【探索】和Polaris【北极星】联合拓扑图 ![](http://nepxion.gitee.io/discovery/docs/polaris-doc/Topology.jpg) ③ Polaris【北极星】分层架构图 ![](http://nepxion.gitee.io/discovery/docs/polaris-doc/Layer.jpg) ④ Discovery【探索】实施方案图 ![](http://nepxion.gitee.io/discovery/docs/polaris-doc/All.jpg) ⑤ Discovery【探索】域网关实施图 ![](http://nepxion.gitee.io/discovery/docs/polaris-doc/DomainEnable.jpg) ⑥ Discovery【探索】非域网关实施图 ![](http://nepxion.gitee.io/discovery/docs/polaris-doc/DomainDisable.jpg) ⑦ Discovery【探索】全局订阅实施图 ![](http://nepxion.gitee.io/discovery/docs/polaris-doc/GlobalSub.jpg) ⑧ Discovery【探索】配置中心发布订阅图 ![](http://nepxion.gitee.io/discovery/docs/polaris-doc/Config.jpg) ## 简介 ### 功能概述 Discovery【探索】微服务框架,基于Spring Cloud & Spring Cloud Alibaba,Discovery服务注册发现、Ribbon & Spring Cloud LoadBalancer负载均衡、Feign & RestTemplate & WebClient调用、Spring Cloud Gateway & Zuul过滤等组件全方位增强的企业级微服务开源解决方案,更贴近企业级需求,更具有企业级的插件引入、开箱即用特征 ① 微服务框架支持的基本功能,如下 - 支持阿里巴巴Spring Cloud Alibaba中间件生态圈 - 支持阿里巴巴Nacos、Eureka、Consul和Zookeeper四个服务注册发现中心 - 支持阿里巴巴Nacos、携程Apollo、Redis、Zookeeper、Consul和Etcd六个远程配置中心 - 支持阿里巴巴Sentinel、Hystrix和Resilience4J三个熔断限流降级权限中间件 - 支持OpenTracing和OpenTelemetry规范下的调用链中间件,Jaeger、SkyWalking和Zipkin等 - 支持Prometheus Micrometer和Spring Boot Admin两个指标中间件 - 支持Java Agent解决异步跨线程ThreadLocal上下文传递 - 支持Spring Spel解决蓝绿灰度参数的驱动逻辑 - 支持Spring Matcher解决元数据匹配的通配逻辑 - 支持Spring Cloud Gateway、Zuul网关和微服务三大模块的蓝绿灰度发布等一系列功能 - 支持和兼容Spring Cloud Edgware版、Finchley版、Greenwich版、Hoxton版和202x版以及更高的Spring Cloud版本 - 支持和兼容Java8~Java16以及更高的SDK版本 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Diagram.jpg) ② 微服务框架支持的应用功能,如下 - 全链路蓝绿灰度发布 - 全链路版本、区域、 IP地址和端口匹配蓝绿发布 - 全链路版本、区域、 IP地址和端口权重灰度发布 - 全链路蓝 | 绿 | 兜底、蓝 | 兜底的蓝绿路由类型 - 全链路稳定、灰度的灰度路由类型 - 全链路网关、服务端到端混合蓝绿灰度发布 - 全链路域网关、非域网关部署 - 全链路条件驱动、非条件驱动 - 全链路前端触发后端蓝绿灰度发布 - 全局订阅式蓝绿灰度发布 - 全链路自定义网关、服务的过滤器、负载均衡策略类触发蓝绿灰度发布 - 全链路动态变更元数据的蓝绿灰度发布 - 全链路Header、Parameter、Cookie、域名、RPC Method等参数化规则策略驱动 - 全链路本地和远程、局部和全局无参数化规则策略驱动 - 全链路条件表达式、通配表达式支持 - 全链路内置Header,支持定时Job的服务调用蓝绿灰度发布 - 全链路蓝绿灰度发布编排建模和流量侦测 - 全链路蓝绿发布编排建模 - 全链路灰度发布编排建模 - 全链路蓝绿发布流量侦测 - 全链路灰度发布流量侦测 - 全链路蓝绿灰度发布混合流量侦测 - 全链路蓝绿灰度发布容灾 - 发布失败下的版本故障转移 - 并行发布下的版本偏好 - 服务下线场景下全链路蓝绿灰度发布,实时性的流量绝对无损 - 全局唯一ID屏蔽 - IP地址和端口屏蔽 - 异步场景下全链路蓝绿灰度发布 - 异步跨线程Agent插件 - Hystrix线程池隔离插件 - 全链路数据库和消息队列蓝绿发布 - 基于多DataSource的数据库蓝绿发布 - 基于多Queue的消息队列蓝绿发布 - 网关动态路由 - 路由动态添加 - 路由动态修改 - 路由动态删除 - 路由动态批量更新 - 路由查询 - 路由动态变更后的事件通知 - 统一配置订阅执行器 - 全链路规则策略推送 - 基于远程配置中心的规则策略订阅推送 - 基于Swagger和Rest的规则策略推送 - 基于图形化桌面端和Web端的规则策略推送 - 全链路环境隔离和路由 - 全链路环境隔离 - 全链路环境路由 - 全链路可用区亲和性隔离和路由 - 全链路可用区亲和性隔离 - 全链路可用区亲和性路由 - 全链路服务隔离和准入 - 消费端服务隔离 - 提供端服务隔离 - 注册发现隔离和准入 - 全链路服务限流熔断降级权限 - Sentinel基于服务名的防护 - Sentinel基于组的防护 - Sentinel基于版本的防护 - Sentinel基于区域的防护 - Sentinel基于环境的防护 - Sentinel基于可用区的防护 - Sentinel基于IP地址和端口的防护 - Sentinel自定义Header、Parameter、Cookie的防护 - Sentinel自定义业务参数的防护 - Sentinel自定义组合式的防护 - 全链路监控 - 蓝绿灰度埋点和熔断埋点的调用链监控 - 蓝绿灰度埋点和熔断埋点的日志监控 - 全链路服务侧注解 - 全链路服务侧API权限 - 元数据流量染色 - Git插件自动化的元数据流量染色 - 服务名前缀的元数据流量染色 - 运维平台参数化的元数据流量染色 - 注册中心动态化的元数据流量染色 - 用户自定义的元数据流量染色 - 多活、多云、多机房流量切换 - Docker容器化和Kubernetes平台无缝支持部署 - 自动化测试、压力测试 ③ 微服务框架易用性表现,如下 - 引入相关依赖到pom.xml - 元数据Metadata流量染色。5大元数据根据不同的使用场景按需设置 - 定义所属组名 - metadata.group,也可以通过服务名前缀来自动产生服务组名 - 定义版本号 - metadata.version,也可以通过Git插件方式自动产生版本号 - 定义所属区域名 - metadata.region - 定义所属环境 - metadata.env - 定义所属可用区 - metadata.zone - 执行采用【约定大于配置】的准则,使用者根据不同的使用场景开启和关闭相关功能项或者属性值,达到最佳配置 - 规则策略文件设置和推送,或者通过业务Header、Parameter、Cookie触发,并通过Json格式的Header路由策略全链路传递 ### 版本列表 ① 微服务框架版本兼容列表,如下 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:版本号右边, `↑` 表示>=该版本号, `↓` 表示<=该版本号 | 框架版本 | 框架分支 | 框架状态 | Spring Cloud版本 | Spring Boot版本 | Spring Cloud Alibaba版本 | | --- | --- | --- | --- | --- | --- | | 7.0.0
商业版 | master | ![](http://nepxion.gitee.io/discovery/docs/icon-doc/confirm_24.png) | 202x.x.x | 2.5.x
2.4.1 `↑` | 202x.x | | 6.11.0 | 6.x.x | ![](http://nepxion.gitee.io/discovery/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/discovery/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/discovery/docs/icon-doc/delete_24.png) | Finchley | 2.0.x.RELEASE | 2.0.x.RELEASE | | 3.27.0 | 3.x.x | ![](http://nepxion.gitee.io/discovery/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/discovery/docs/icon-doc/delete_24.png) | Dalston | 1.x.x.RELEASE | 1.5.x.RELEASE | | ~~1.0.x~~ | ~~1.x.x~~ | ![](http://nepxion.gitee.io/discovery/docs/icon-doc/delete_24.png) | Camden | 1.x.x.RELEASE | 1.5.x.RELEASE | ![](http://nepxion.gitee.io/discovery/docs/icon-doc/confirm_24.png) 表示维护中 | ![](http://nepxion.gitee.io/discovery/docs/icon-doc/arrow_up_24.png) 表示不维护,但可用,强烈建议升级 | ![](http://nepxion.gitee.io/discovery/docs/icon-doc/delete_24.png) 表示不维护,不可用,已废弃 - 7.x.x版本(适用于202x.x.x)将继续维护 - 6.x.x版本(同时适用于Finchley、Greenwich和Hoxton)将继续维护 - 5.x.x版本(适用于Greenwich)已废弃 - 4.x.x版本(适用于Finchley)已废弃 - 3.x.x版本(适用于Edgware)不维护,但可用,强烈建议升级 - 2.x.x版本(适用于Dalston)已废弃 - 1.x.x版本(适用于Camden)已废弃 ② 相关中间件版本列表,如下 | 组件类型 | 组件版本 | | --- | --- | | 基础组件 | [![Guava](https://img.shields.io/maven-central/v/com.google.guava/guava.svg?label=Guava)](https://search.maven.org/artifact/com.google.guava/guava)
[![Caffeine](https://img.shields.io/maven-central/v/com.github.ben-manes.caffeine/caffeine.svg?label=Caffeine)](https://search.maven.org/artifact/com.github.ben-manes.caffeine/caffeine)
[![Dom4J](https://img.shields.io/maven-central/v/org.dom4j/dom4j.svg?label=Dom4J)](https://search.maven.org/artifact/org.dom4j/dom4j)
[![Swagger](https://img.shields.io/maven-central/v/io.swagger/swagger-models?label=Swagger)](https://search.maven.org/artifact/io.swagger/swagger-models)
[![Swagger](https://img.shields.io/maven-central/v/io.springfox/springfox-swagger2?label=SpringFox%20Swagger)](https://search.maven.org/artifact/io.springfox/springfox-swagger2) | | 注册配置组件 | [![Apollo](https://img.shields.io/maven-central/v/com.ctrip.framework.apollo/apollo-client.svg?label=Apollo)](https://search.maven.org/artifact/com.ctrip.framework.apollo/apollo-client)
[![Zookeeper Curator](https://img.shields.io/maven-central/v/org.apache.curator/curator-framework.svg?label=Zookeeper%20Curator)](https://search.maven.org/artifact/org.apache.curator/curator-framework)
[![Consul](https://img.shields.io/maven-central/v/com.ecwid.consul/consul-api.svg?label=Consul)](https://search.maven.org/artifact/com.ecwid.consul/consul-api)
[![JEtcd](https://img.shields.io/maven-central/v/io.etcd/jetcd-core.svg?label=JEtcd)](https://search.maven.org/artifact/io.etcd/jetcd-core)
[![Nacos](https://img.shields.io/maven-central/v/com.alibaba.nacos/nacos-client.svg?label=Nacos)](https://search.maven.org/artifact/com.alibaba.nacos/nacos-client)
[![Eureka](https://img.shields.io/maven-central/v/com.netflix.eureka/eureka-client.svg?label=Eureka)](https://search.maven.org/artifact/com.netflix.eureka/eureka-client)
[![Redis](https://img.shields.io/maven-central/v/org.springframework.data/spring-data-redis.svg?label=Redis)](https://search.maven.org/artifact/org.springframework.data/spring-data-redis) | | 防护组件 | [![Sentinel](https://img.shields.io/maven-central/v/com.alibaba.csp/sentinel-core.svg?label=Sentinel)](https://search.maven.org/artifact/com.alibaba.csp/sentinel-core)
[![Hystrix](https://img.shields.io/maven-central/v/com.netflix.hystrix/hystrix-core.svg?label=Hystrix)](https://search.maven.org/artifact/com.netflix.hystrix/hystrix-core) | | 监控组件 | [![OpenTelemetry](https://img.shields.io/maven-central/v/io.opentelemetry/opentelemetry-api.svg?label=OpenTelemetry)](https://search.maven.org/artifact/io.opentelemetry/opentelemetry-api)
[![OpenTracing](https://img.shields.io/maven-central/v/io.opentracing/opentracing-api.svg?label=OpenTracing)](https://search.maven.org/artifact/io.opentracing/opentracing-api)
[![OpenTracing%20Sping%20Cloud](https://img.shields.io/maven-central/v/io.opentracing.contrib/opentracing-spring-cloud-starter.svg?label=OpenTracing%20Sping%20Cloud)](https://search.maven.org/artifact/io.opentracing.contrib/opentracing-spring-cloud-starter)
[![OpenTracing%20Jaeger](https://img.shields.io/maven-central/v/io.opentracing.contrib/opentracing-spring-jaeger-starter.svg?label=OpenTracing%20Jaeger)](https://search.maven.org/artifact/io.opentracing.contrib/opentracing-spring-jaeger-starter)
[![OpenTracing%20Concurrent](https://img.shields.io/maven-central/v/io.opentracing.contrib/opentracing-concurrent.svg?label=OpenTracing%20Concurrent)](https://search.maven.org/artifact/io.opentracing.contrib/opentracing-concurrent)
[![SkyWalking](https://img.shields.io/maven-central/v/org.apache.skywalking/apm-toolkit-opentracing.svg?label=SkyWalking)](https://search.maven.org/artifact/org.apache.skywalking/apm-toolkit-opentracing)
[![Spring Boot](https://img.shields.io/maven-central/v/de.codecentric/spring-boot-admin-dependencies.svg?label=Spring%20Boot%20Admin)](https://search.maven.org/artifact/de.codecentric/spring-boot-admin-dependencies) | | Spring组件 | [![Alibaba Spring](https://img.shields.io/maven-central/v/com.alibaba.spring/spring-context-support.svg?label=Alibaba%20Spring)](https://search.maven.org/artifact/com.alibaba.spring/spring-context-support)
[![Spring Cloud](https://img.shields.io/maven-central/v/org.springframework.cloud/spring-cloud-dependencies.svg?label=Spring%20Cloud)](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)](https://search.maven.org/artifact/com.alibaba.cloud/spring-cloud-alibaba-dependencies)
[![Spring Boot](https://img.shields.io/maven-central/v/org.springframework.boot/spring-boot-dependencies.svg?label=Spring%20Boot)](https://search.maven.org/artifact/org.springframework.boot/spring-boot-dependencies) | ### 郑重致谢 - 感谢阿里巴巴中间件Nacos、Sentinel和Spring Cloud Alibaba团队,尤其是Nacos负责人@彦林、@于怀,Sentinel负责人@宿何、@子衿,Spring Cloud Alibaba负责人@良名、@小马哥、@洛夜、@亦盏的技术支持 - 感谢携程Apollo团队,尤其是@宋顺的技术支持 - 感谢所有Committers和Contributors - 感谢所有帮忙分析和定位问题的同学 - 感谢所有提出宝贵建议和意见的同学 - 感谢支持和使用本框架的公司和企业 ### 企业用户 不完全统计,目前社区开源项目(包括本框架以及关联框架或组件)已经被如下公司使用或者调研
![](http://nepxion.gitee.io/discovery/docs/icon-doc/edit_32.png) 为提供更好的专业级服务,请更多已经使用本框架的公司和企业联系我,并希望在[Github Issues](https://github.com/Nepxion/Discovery/issues/56)上登记 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/chart_bar_32.png) 某大型银行信用卡新核心系统在生产环境接入Nepxion Discovery框架的服务实例数(包括异地双活,同城双活,多机房全部汇总)将近10000个 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/chart_bar_32.png) 某大型互联网教育公司在生产环境接入Nepxion Discovery框架的服务实例数截至到2021年2月已达到2600多个,基本接入完毕 ### 请联系我 微信、钉钉、公众号和文档 ![](http://nepxion.gitee.io/discovery/docs/contact-doc/wechat-1.jpg)![](http://nepxion.gitee.io/discovery/docs/contact-doc/dingding-1.jpg)![](http://nepxion.gitee.io/discovery/docs/contact-doc/gongzhonghao-1.jpg)![](http://nepxion.gitee.io/discovery/docs/contact-doc/document-1.jpg) ## 目录 - [简介](#简介) - [功能概述](#功能概述) - [版本列表](#版本列表) - [郑重致谢](#郑重致谢) - [企业用户](#企业用户) - [请联系我](#请联系我) - [主页链接](#主页链接) - [源码主页](#源码主页) - [发布主页](#发布主页) - [指南主页](#指南主页) - [入门主页](#入门主页) - [博客主页](#博客主页) - [工程架构](#工程架构) - [工程清单](#工程清单) - [代码清单](#代码清单) - [架构核心](#架构核心) - [依赖引入](#依赖引入) - [准备工作](#准备工作) - [环境搭建](#环境搭建) - [启动服务](#启动服务) - [环境验证](#环境验证) - [蓝绿灰度发布概念](#蓝绿灰度发布概念) - [蓝绿发布](#蓝绿发布) - [灰度发布](#灰度发布) - [滚动发布](#滚动发布) - [全链路蓝绿灰度发布](#全链路蓝绿灰度发布) - [全链路蓝绿发布](#全链路蓝绿发布) - [全链路版本匹配蓝绿发布](#全链路版本匹配蓝绿发布) - [全链路区域匹配蓝绿发布](#全链路区域匹配蓝绿发布) - [全链路IP地址和端口匹配蓝绿发布](#全链路IP地址和端口匹配蓝绿发布) - [全链路条件蓝绿发布](#全链路条件蓝绿发布) - [全链路版本条件匹配蓝绿发布](#全链路版本条件匹配蓝绿发布) - [全链路区域条件匹配蓝绿发布](#全链路区域条件匹配蓝绿发布) - [全链路IP地址和端口条件匹配蓝绿发布](#全链路IP地址和端口条件匹配蓝绿发布) - [全链路灰度发布](#全链路灰度发布) - [全链路版本权重灰度发布](#全链路版本权重灰度发布) - [全链路区域权重灰度发布](#全链路区域权重灰度发布) - [全链路条件灰度发布](#全链路条件灰度发布) - [全链路版本条件权重灰度发布](#全链路版本条件权重灰度发布) - [全链路区域条件权重灰度发布](#全链路区域条件权重灰度发布) - [全链路IP地址和端口权重条件灰度发布](#全链路IP地址和端口条件权重灰度发布) - [全链路端到端混合实施蓝绿灰度发布](#全链路端到端混合实施蓝绿灰度发布) - [全链路端到端实施蓝绿灰度发布](#全链路端到端实施蓝绿灰度发布) - [全链路混合实施蓝绿灰度发布](#全链路混合实施蓝绿灰度发布) - [单节点混合实施蓝绿灰度发布](#单节点混合实施蓝绿灰度发布) - [全链路域网关和非域网关部署](#全链路域网关和非域网关部署) - [全链路域网关部署](#全链路域网关部署) - [全链路非域网关部署](#全链路非域网关部署) - [全链路前端触发后端蓝绿灰度发布](#全链路前端触发后端蓝绿灰度发布) - [全链路驱动方式](#全链路驱动方式) - [全链路参数策略](#全链路参数策略) - [全局订阅式蓝绿灰度发布](#全局订阅式蓝绿灰度发布) - [全链路自定义蓝绿灰度发布](#全链路自定义蓝绿灰度发布) - [全链路自定义过滤器触发蓝绿灰度发布](#全链路自定义过滤器触发蓝绿灰度发布) - [全链路自定义负载均衡策略类触发蓝绿灰度发布](#全链路自定义负载均衡策略类触发蓝绿灰度发布) - [全链路动态变更元数据的蓝绿灰度发布](#全链路动态变更元数据的蓝绿灰度发布) - [全链路蓝绿灰度发布编排建模和流量侦测](#全链路蓝绿灰度发布编排建模和流量侦测) - [全链路编排建模](#全链路编排建模) - [全链路蓝绿发布编排建模](#全链路蓝绿发布编排建模) - [全链路灰度发布编排建模](#全链路灰度发布编排建模) - [全链路流量侦测](#全链路流量侦测) - [全链路蓝绿发布流量侦测](#全链路蓝绿发布流量侦测) - [全链路灰度发布流量侦测](#全链路灰度发布流量侦测) - [全链路蓝绿灰度发布混合流量侦测](#全链路蓝绿灰度发布混合流量侦测) - [全链路蓝绿灰度发布容灾](#全链路蓝绿灰度发布容灾) - [发布失败下的版本故障转移](#发布失败下的版本故障转移) - [并行发布下的版本偏好](#并行发布下的版本偏好) - [服务下线场景下全链路蓝绿灰度发布](#服务下线场景下全链路蓝绿灰度发布) - [全局唯一ID屏蔽](#全局唯一ID屏蔽) - [IP地址和端口屏蔽](#IP地址和端口屏蔽) - [异步场景下全链路蓝绿灰度发布](#异步场景下全链路蓝绿灰度发布) - [异步场景下DiscoveryAgent解决方案](#异步场景下DiscoveryAgent解决方案) - [异步跨线程DiscoveryAgent获取](#异步跨线程DiscoveryAgent获取) - [异步跨线程DiscoveryAgent使用](#异步跨线程DiscoveryAgent使用) - [异步跨线程DiscoveryAgent扩展](#异步跨线程DiscoveryAgent扩展) - [异步场景下Hystrix线程池隔离解决方案](#异步场景下Hystrix线程池隔离解决方案) - [全链路数据库和消息队列蓝绿发布](#全链路数据库和消息队列蓝绿发布) - [网关动态路由](#网关动态路由) - [Spring-Cloud-Gateway网关动态路由](#Spring-Cloud-Gateway网关动态路由) - [Spring-Cloud-Gateway网关动态路由配置](#Spring-Cloud-Gateway网关动态路由配置) - [Spring-Cloud-Gateway网关自定义动态路由配置](#Spring-Cloud-Gateway网关自定义动态路由配置) - [Spring-Cloud-Gateway网关Rest-Endpoint](#Spring-Cloud-Gateway网关Rest-Endpoint) - [Spring-Cloud-Gateway网关订阅配置中心](#Spring-Cloud-Gateway网关订阅配置中心) - [Spring-Cloud-Gateway网关事件总线通知的订阅](#Spring-Cloud-Gateway网关事件总线通知的订阅) - [Zuul网关动态路由](#Zuul网关动态路由) - [Zuul网关动态路由配置](#Zuul网关动态路由配置) - [Zuul网关Rest-Endpoint](#Zuul网关Rest-Endpoint) - [Zuul网关订阅配置中心](#Zuul网关订阅配置中心) - [Zuul网关事件总线通知的订阅](#Zuul网关事件总线通知的订阅) - [统一配置订阅执行器](#统一配置订阅执行器) - [规则策略定义](#规则策略定义) - [规则策略格式定义](#规则策略格式定义) - [规则策略内容定义](#规则策略内容定义) - [规则策略示例](#规则策略示例) - [规则策略推送](#规则策略推送) - [基于远程配置中心的规则策略订阅推送](#基于远程配置中心的规则策略订阅推送) - [基于Swagger和Rest的规则策略推送](#基于Swagger和Rest的规则策略推送) - [基于图形化桌面端和Web端的规则策略推送](#基于图形化桌面端和Web端的规则策略推送) - [全链路环境隔离和路由](#全链路环境隔离和路由) - [全链路环境隔离](#全链路环境隔离) - [全链路环境路由](#全链路环境路由) - [全链路可用区亲和性隔离和路由](#全链路可用区亲和性隔离和路由) - [全链路可用区亲和性隔离](#全链路可用区亲和性隔离) - [全链路可用区亲和性路由](#全链路可用区亲和性路由) - [全链路服务隔离和准入](#全链路服务隔离和准入) - [消费端服务隔离](#消费端服务隔离) - [基于组负载均衡隔离](#基于组负载均衡隔离) - [提供端服务隔离](#提供端服务隔离) - [基于组Header传值策略隔离](#基于组Header传值策略隔离) - [注册发现隔离和准入](#注册发现隔离和准入) - [基于IP地址黑白名单注册准入](#基于IP地址黑白名单注册准入) - [基于最大注册数限制注册准入](#基于最大注册数限制注册准入) - [基于IP地址黑白名单发现准入](#基于IP地址黑白名单发现准入) - [自定义注册发现准入](#自定义注册发现准入) - [全链路服务限流熔断降级权限](#全链路服务限流熔断降级权限) - [原生Sentinel注解](#原生Sentinel注解) - [原生Sentinel规则](#原生Sentinel规则) - [流控规则](#流控规则) - [降级规则](#降级规则) - [授权规则](#授权规则) - [系统规则](#系统规则) - [热点参数流控规则](#热点参数流控规则) - [基于Sentinel-LimitApp扩展的防护](#基于Sentinel-LimitApp扩展的防护) - [基于服务名的防护](#基于服务名的防护) - [基于组的防护](#基于组的防护) - [基于版本的防护](#基于版本的防护) - [基于区域的防护](#基于区域的防护) - [基于环境的防护](#基于环境的防护) - [基于可用区的防护](#基于可用区的防护) - [基于IP地址和端口的防护](#基于IP地址和端口的防护) - [自定义组合式的防护](#自定义组合式的防护) - [Sentinel-Rest-Endpoint](#Sentinel-Rest-Endpoint) - [全链路监控](#全链路监控) - [全链路调用链监控](#全链路调用链监控) - [蓝绿灰度埋点调用链监控](#蓝绿灰度埋点调用链监控) - [蓝绿灰度埋点Debug辅助监控](#蓝绿灰度埋点Debug辅助监控) - [Sentinel熔断埋点调用链监控](#Sentinel熔断埋点调用链监控) - [自定义埋点调用链监控](#自定义埋点调用链监控) - [全链路日志监控](#全链路日志监控) - [蓝绿灰度埋点日志监控](#蓝绿灰度埋点日志监控) - [全链路告警监控](#全链路告警监控) - [蓝绿灰度告警监控](#蓝绿灰度告警监控) - [全链路服务侧注解](#全链路服务侧注解) - [全链路服务侧API权限](#全链路服务侧API权限) - [元数据流量染色](#元数据流量染色) - [基于Git插件自动创建版本号](#基于Git插件自动创建版本号) - [基于服务名前缀自动创建组名](#基于服务名前缀自动创建组名) - [基于运维平台运行参数自动创建版本号](#基于运维平台运行参数自动创建版本号) - [基于用户自定义创建版本号](#基于用户自定义创建版本号) - [自动扫描目录](#自动扫描目录) - [配置文件](#配置文件) - [流量染色配置](#流量染色配置) - [中间件属性配置](#中间件属性配置) - [功能开关配置](#功能开关配置) - [内置文件配置](#内置文件配置) - [Docker容器化和Kubernetes平台支持](#Docker容器化和Kubernetes平台支持) - [Docker容器化](#Docker容器化) - [Kubernetes平台支持](#Kubernetes平台支持) - [自动化测试](#自动化测试) - [架构设计](#架构设计) - [启动控制台](#启动控制台) - [配置文件](#配置文件) - [测试用例](#测试用例) - [测试包引入](#测试包引入) - [测试入口程序](#测试入口程序) - [普通调用测试](#普通调用测试) - [蓝绿灰度调用测试](#蓝绿灰度调用测试) - [扩展调用测试](#扩展调用测试) - [测试报告](#测试报告) - [压力测试](#压力测试) - [测试环境](#测试环境) - [测试介绍](#测试介绍) - [测试步骤](#测试步骤) - [Star走势图](#Star走势图) ## 主页链接 ### 源码主页 [Discovery源码主页](https://github.com/Nepxion/Discovery) [Polaris源码主页](https://github.com/polaris-paas/polaris-sdk) ### 发布主页 [DiscoveryAgent](https://github.com/Nepxion/DiscoveryAgent/releases) [DiscoveryDesktop](https://github.com/Nepxion/DiscoveryUI/releases) ### 指南主页 [Discovery指南主页](https://github.com/Nepxion/DiscoveryGuide) [Polaris指南主页](https://github.com/polaris-paas/polaris-guide) ### 入门主页 [Gitee Wiki](https://gitee.com/Nepxion/Discovery/wikis/pages) [Github Wiki](https://github.com/Nepxion/Discovery/wiki) ### 博客主页 [博客主页](https://blog.csdn.net/u012410733/category_10633202.html?spm=1001.2014.3001.5482) ## 工程架构 ### 工程清单 ① Discovery工程清单 | 工程名 | 描述 | | --- | --- | | discovery-commons | 通用模块目录 | |    discovery-common | 通用模块 | |    discovery-common-apollo | 封装Apollo通用配置操作逻辑 | |    discovery-common-nacos | 封装Nacos通用配置操作逻辑 | |    discovery-common-redis | 封装Redis通用配置操作逻辑 | |    discovery-common-zookeeper | 封装Zookeeper通用配置操作逻辑 | |    discovery-common-consul | 封装Consul通用配置操作逻辑 | |    discovery-common-etcd | 封装Etcd通用配置操作逻辑 | | discovery-plugin-framework | 基本框架目录 | |    discovery-plugin-framework-starter| 基本框架的Starter | |    discovery-plugin-framework-starter-parser| 基本框架解析模块的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-config-center-starter-zookeeper | 配置中心的Zookeeper Starter | |    discovery-plugin-config-center-starter-consul | 配置中心的Consul Starter | |    discovery-plugin-config-center-starter-etcd | 配置中心的Etcd 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-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-datasource | 策略的Sentinel配置中心的Starter | |    discovery-plugin-strategy-starter-sentinel-limiter | 策略的Sentinel Limiter高级限流熔断的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-automation| 自动化测试的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-starter-zookeeper | 控制平台的Zookeeper Starter | |    discovery-console-starter-consul | 控制平台的Consul Starter | |    discovery-console-starter-etcd | 控制平台的Etcd Starter | | 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网关示例 | ② DiscoveryPlatform工程清单 | 工程名 | 描述 | | --- | --- | | discovery-platform-server | 平台服务端模块目录 | |    discovery-platform-starter-server | 平台服务端的Starter | |    discovery-platform-starter-server-mysql | 平台服务端数据库MySQL插件的Starter | |    discovery-platform-starter-server-h2 | 平台服务端H2插件的Starter| |    discovery-platform-starter-server-ldap | 平台服务端Ldap插件的Starter| |    discovery-platform-starter-server-ui | 平台服务端界面 | | discovery-platform-client | 平台服务端模块目录 | |    discovery-platform-starter-client | 平台客户端的Starter | | discovery-platform-common | 平台通用模块目录 | |    discovery-platform-starter-common-dingding | 封装钉钉通用操作逻辑的Starter | |    discovery-platform-starter-common-mail | 封装邮件通用操作逻辑的Starter | | discovery-platform-application | 平台服务端可执行应用 | ③ DiscoveryAgent工程清单 | 工程名 | 描述 | | --- | --- | | discovery-agent-starter | 异步跨线程Agent Starter | | discovery-agent-starter-plugin-strategy | 路由策略的异步跨线程Agent Plugin Starter | | discovery-agent-starter-plugin-mdc | MDC日志的异步跨线程Agent Plugin Starter | | discovery-agent-example | 异步跨线程示例 | ④ DiscoveryUI工程清单 | 工程名 | 描述 | | --- | --- | | desktop | Nepxion Discovery 服务治理平台前端桌面版 | | web | Nepxion Discovery 服务治理平台前端Web版 | ⑤ DiscoveryContrib工程清单 | 工程名 | 描述 | | --- | --- | | discovery-contrib-plugin-starter | 第三方非微服务范畴中间件的蓝绿灰度发布Contrib Plugin Starter | | discovery-contrib-plugin-starter-rocketmq | RocketMQ的蓝绿灰度发布Contrib Plugin Starter | | discovery-contrib-plugin-starter-shardingsphere | ShardingSphere日志的蓝绿灰度发布Contrib Plugin Starter | | discovery-contrib-example | 第三方非微服务范畴中间件的蓝绿灰度发布示例 | ### 代码清单 | 仓库主分支 | 代码行数 | | --- | --- | | Discovery | [![Total lines](https://tokei.rs/b1/github/Nepxion/Discovery?category=lines)](https://tokei.rs/b1/github/Nepxion/Discovery?category=lines) | | DiscoveryPlatform | [![Total lines](https://tokei.rs/b1/github/Nepxion/DiscoveryPlatform?category=lines)](https://tokei.rs/b1/github/Nepxion/DiscoveryPlatform?category=lines) | | DiscoveryGuide | [![Total lines](https://tokei.rs/b1/github/Nepxion/DiscoveryGuide?category=lines)](https://tokei.rs/b1/github/Nepxion/DiscoveryGuide?category=lines) | | DiscoveryAgent | [![Total lines](https://tokei.rs/b1/github/Nepxion/DiscoveryAgent?category=lines)](https://tokei.rs/b1/github/Nepxion/DiscoveryAgent?category=lines) | | DiscoveryUI | [![Total lines](https://tokei.rs/b1/github/Nepxion/DiscoveryUI?category=lines)](https://tokei.rs/b1/github/Nepxion/DiscoveryUI?category=lines) | | DiscoveryContrib | [![Total lines](https://tokei.rs/b1/github/Nepxion/DiscoveryContrib?category=lines)](https://tokei.rs/b1/github/Nepxion/DiscoveryContrib?category=lines) | ### 架构核心 - 服务治理架构图 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Govern.jpg) - 模块结构图 ![](http://nepxion.gitee.io/discovery/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-plugin-config-center-starter-zookeeper discovery-plugin-config-center-starter-consul discovery-plugin-config-center-starter-etcd ${discovery.version} ``` ③ 管理中心依赖引入 选择引入 ```xml ${project.groupId} discovery-plugin-admin-center-starter ${discovery.version} ``` ④ 路由策略依赖引入 微服务端、网关Zuul端和网关Spring Cloud Gateway端三个路由策略插件,选择引入其中一个 ```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-sentinel-datasource ${discovery.version} ``` - Sentinel防护的Sentinel Limiter高级限流熔断插件。只适用于Servlet模式 ```xml com.nepxion discovery-plugin-strategy-starter-sentinel-limiter ${discovery.version} ``` - Hystrix防护插件。Hystrix线程池隔离模式下必须引入该插件 ```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-console-starter-zookeeper ${discovery.version} ``` ⑦ 调用链插件依赖引入 支持微服务端、网关Zuul端和网关Spring Cloud Gateway端,选择引入其中一个 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/warning.png) 需要注意,该模块支持F版或更高版本 ```xml com.nepxion discovery-plugin-strategy-starter-sentinel-opentelemetry discovery-plugin-strategy-starter-sentinel-opentracing discovery-plugin-strategy-starter-sentinel-skywalking ${discovery.version} ``` ⑧ 自动化测试插件依赖引入 ```xml com.nepxion discovery-plugin-test-starter ${discovery.version} ``` ⑨ 异步跨线程Agent引入 ``` -javaagent:/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.abc;com.xyz ``` ## 准备工作 为了更好的阐述框架的各项功能,本文围绕指南示例展开,请使用者先进行下面的准备工作。指南示例以Nacos为服务注册中心和配置中心展开介绍,使用者可自行换成其它服务注册中心和配置中心 ### 环境搭建 ① 下载代码,Git clone [https://github.com/Nepxion/DiscoveryGuide.git](https://github.com/Nepxion/DiscoveryGuide.git),分支为6.x.x-simple ② 代码导入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 | 无 | 无 | 无 | - 部署拓扑图 ![](http://nepxion.gitee.io/discovery/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][T=service][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][T=service][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][T=service][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][T=service][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,才能保证蓝绿发布路由调用的成功 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 〔Spring Cloud 202x版〕特别提醒 > 对于Spring Cloud 202x版,由于它采用的负载均衡Spring Cloud LoadBalancer是基于异步的WebFlux,所以必须加上DiscoveryAgent,如下方式 > -javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar | URL | 调用方式 | | --- | --- | | /invoke/ | 同步调用 | | /invoke-async/ | @Async注解方式的异步调用 | | /invoke-thread/ | 单线程方式的异步调用 | | /invoke-threadpool/ | 线程池方式的异步调用 | - 上述步骤在下面每次更改规则策略的时候执行,并验证结果和规则策略的期望值是否相同 ## 蓝绿灰度发布概念 ### 蓝绿发布 蓝绿发布 Blue-Green Deployment ① 概念 不停机旧版本,部署新版本,通过用户标记将流量在新版本和老版本切换,属无损发布 ② 优点 新版本升级和老版本回滚迅速。用户可以灵活控制流量走向 ③ 缺点 成本较高,需要部署两套环境(蓝/绿)。新版本出现问题,切换不及时,会造成大面积故障 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/BlueGreenConcept.jpg) ### 灰度发布 灰度发布 Gray Release(又名金丝雀发布 Canary Release) ① 概念 不停机旧版本,部署新版本,低比例流量(例如:5%)切换到新版本,高比例流量(例如:95%)走旧版本,通过监控观察无问题,逐步扩大范围,最终把所有流量都迁移到新版本上,下线旧版本。属无损发布 ② 优点 灵活简单,不需要用户标记驱动。安全性高,新版本如果出现问题,只会发生在低比例的流量上 ③ 缺点 流量配比递增的配置修改,带来额外的操作成本。用户覆盖狭窄,低比例流量未必能发现所有问题 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/GrayConcept.jpg) ### 滚动发布 滚动发布 Rolling Release ① 概念 每次只升级一个或多个服务,升级完成监控观察,不断执行这个过程,直到集群中的全部旧版本升级到新版本。停止旧版本的过程中,无法精确计算旧版本是否已经完成它正在执行的工作,需要靠业务自身去判断。属有损发布 ② 优点 出现问题影响范围很小,只会发生在若干台正在滚动发布的服务上 ③ 缺点 发布和回滚需要较长的时间周期。按批次停止旧版本,启动新版本,由于旧版本不保留,一旦全部升级完毕后才发现问题,则无法快速回滚,必须重新降级部署 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/RollingConcept.jpg) ## 全链路蓝绿灰度发布 ### 全链路蓝绿发布 > 经典场景:当调用请求从网关或者服务发起的时候,通过Header | Parameter | Cookie一种或者几种参数进行驱动,在路由过滤中,根据这些参数,选择在配置中心配置的蓝路由 | 绿路由 | 兜底路由的规则策略(Json格式),并把命中的规则策略转化为策略路由Header(n-d-开头),实现全链路传递。每个端到端服务接收到策略路由Header后,执行负载均衡时,该Header跟注册中心的对应元数据进行相关比较,不符合条件的实例进行过滤,从而实现全链路蓝绿发布 > 实施概要:只涉及当前正在发布的服务,例如,对于 〔网关〕->〔A服务〕->〔B服务〕->〔C服务〕->〔D服务〕调用链来说,如果当前只是B服务和C服务正在实施发布,那么,只需要把B服务和C服务配置到规则策略中,其它则不需要配置。发布结束后,即B服务和C服务的所有实例都完全一致,例如,版本号都只有唯一一个,那么清除掉在配置中心配置的规则策略即可,从而进行下一轮全链路蓝绿发布 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/BlueGreen.jpg) ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 小贴士 n-d-的含义:n为Nepxion首字母,d为Discovery首字母 #### 全链路版本匹配蓝绿发布 增加Spring Cloud Gateway的版本匹配蓝绿发布策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用全链路都走版本为1.0的服务 ```xml 1.0 ``` ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide2-1.jpg) 如果希望每个服务的版本分别指定,那么策略内容如下,实现从Spring Cloud Gateway发起的调用走1.0版本的a服务,走1.1版本的b服务 ```xml {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"} ``` 当所有服务都选同一版本的时候,下面第1条和第2条是等效的 ``` 1. 1.0 2. {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` 如果希望可调用的版本是多个,也可以表示成如下方式,即1.0版本和1.1版本的a服务和b服务都可以被调用到,下面第1条和第2条是等效的 ``` 1. 1.0;1.1 2. {"discovery-guide-service-a":"1.0;1.1", "discovery-guide-service-b":"1.0;1.1"} ``` 如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法 ``` * - 表示调用范围为所有版本 1.* - 表示调用范围为1开头的所有版本 ``` 例如 ``` "discovery-guide-service-b":"1.*;1.2.?" ``` 表示discovery-guide-service-b服务的调用范围是1开头的所有版本,或者调用范围是1.2开头的所有版本(末尾必须是1个字符),多个用分号隔开 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:非条件驱动下的全链路蓝绿发布跟Header驱动下的全链路蓝绿发布等效,例如 ``` n-d-version=1.0 n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` 版本匹配蓝绿发布架构图 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/RouteVersion.jpg) #### 全链路区域匹配蓝绿发布 增加Zuul的区域匹配蓝绿发布策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用全链路都走区域为dev的服务 ```xml dev ``` ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide2-3.jpg) 如果希望每个服务的版本分别指定,那么策略内容如下,实现从Zuul发起的调用走dev区域的a服务,走qa区域的b服务 ```xml {"discovery-guide-service-a":"dev", "discovery-guide-service-b":"qa"} ``` 当所有服务都选同一区域的时候,下面第1条和第2条是等效的 ``` 1. dev 2. {"discovery-guide-service-a":"dev", "discovery-guide-service-b":"dev"} ``` 如果希望可调用区域是多个,也可以表示成如下方式,即dev区域和qa区域的a服务和b服务都可以被调用到,下面第1条和第2条是等效的 ``` 1. dev;qa 2. {"discovery-guide-service-a":"dev;qa", "discovery-guide-service-b":"dev;aq"} ``` 如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法 ``` * - 表示调用范围为所有区域 d* - 表示调用范围为d开头的所有区域 ``` 例如 ``` "discovery-guide-service-b":"d*;q?" ``` 表示discovery-guide-service-b服务的调用范围是d开头的所有区域,或者调用范围是q开头的所有区域(末尾必须是1个字符),多个用分号隔开 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:非条件驱动下的全链路蓝绿发布跟Header驱动下的全链路蓝绿发布等效,例如 ``` n-d-region=dev n-d-region={"discovery-guide-service-a":"dev", "discovery-guide-service-b":"dev"} ``` 区域匹配蓝绿发布架构图 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/RouteRegion.jpg) #### 全链路IP地址和端口匹配蓝绿发布 增加Zuul的IP地址和端口匹配蓝绿发布策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用走指定IP地址和端口,或者指定IP地址,或者指定端口(下面策略以端口为例)的服务 ```xml
3001
``` ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide2-5.jpg) 如果希望每个服务的IP地址或者端口分别指定,那么策略内容如下,实现从Zuul发起的调用走3001端口的a服务,走4001端口的b服务 ```xml
{"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4001"}
``` 当所有服务都选同一端口的时候,下面第1条和第2条是等效的 ``` 1.
3001
2.
{"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"}
``` 如果希望可调用端口是多个,也可以表示成如下方式,即3001端口和4001端口的a服务和b服务都可以被调用到,下面第1条和第2条是等效的 ``` 1.
3001;4001
2.
{"discovery-guide-service-a":"3001;4001", "discovery-guide-service-b":"3001;4001"}
``` 如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法 ``` * - 表示调用范围为所有端口 3* - 表示调用范围为3开头的所有端口 ``` 例如 ``` "discovery-guide-service-b":"3*;400?" ``` 表示discovery-guide-service-b服务的调用范围是3开头的所有端口,或者调用范围是400开头的所有端口(末尾必须是1个字符),多个用分号隔开 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:非条件驱动下的全链路蓝绿发布跟Header驱动下的全链路蓝绿发布等效,例如 ``` n-d-address=3001 n-d-address={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"} ``` IP地址和端口匹配蓝绿发布架构图 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/RouteAddress.jpg) ### 全链路条件蓝绿发布 #### 全链路版本条件匹配蓝绿发布 通过Header、Parameter、Cookie驱动参数和条件表达式结合,把业务定义的这三个驱动参数转化成全链路传递的策略路由Header,执行基于版本匹配的蓝、绿、兜底三条路由驱动,实现全链路版本条件匹配蓝绿发布 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 驱动参数 ① Header、Parameter、Cookie参数传递。对于要驱动发布的参数,例如,业务参数user,可以选择Header、Parameter、Cookie其中任意一个传递,都是等效的 ② Header、Parameter、Cookie参数优先级。对于要驱动发布的参数,例如,业务参数user,如果在这三者中都存在,且值不相同,那么取值优先级Parameter > Cookie > Header > 内置Header ③ Header、Parameter、Cookie参数混合。对于要驱动发布的参数,如果不止一个,例如,业务参数user、age、address,可以全部是Header或者Parameter或者Cookie,也可以是这三者混合传递:user通过Header传递,age通过Parameter传递,address通过Cookie传递 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 条件表达式 ① Spring Spel的条件表达式,支持等于=、不等于!=、大于>、小于<、与&&、或||、匹配matches,以及加减乘除取模等全部标准表达式用法 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 小贴士 通过Spring Spel的matches条件表达式 - 可通过如下表达式,判断入参是否为`邮件格式` ``` [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4} ``` - 可通过如下表达式,判断入参是否为`三个字母,结尾等于2` ``` [a-z]{3}2 ``` ② Spring Spel的条件表达式,整合驱动参数 例如,驱动参数分别为a、b、c,驱动条件为a等于1,b小于等于2,c不等于3,那么表达式可以写为 `#`H['a'] == '1' && `#`H['b'] <= '2' && `#`H['c'] != '3' 或者 `#`H['a'] == '1' and `#`H['b'] <= '2' and `#`H['c'] != '3' 其中,`#`H['a'],Spring Spel表达式用来表述驱动参数a的专有格式 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 小贴士 H的含义:H为Http首字母,即取值Http类型的参数,包括Header、Parameter、Cookie ③ Spring Spel的逻辑表达,需要注意 - 任何值都大于null。当某个参数未传值,但又指定了该参数大于的表达逻辑,那么表达式结果为false。例如,#H['a'] > '2',但a未传递进来,a即null,则null > 2,表达式结果为false - null满足不等于。当某个参数未传值,但又指定了该该参数不等于的表达逻辑,那么表达式结果为true。例如,#H['a'] != '2',但a未传递进来,a即null,则null != 2,表达式结果为true ④ Spring Spel的符号 符号和等价符号作用相同,任选一个,等价符号不区分大小写 | 符号 | 等价符号 | 含义 | 备注 | | --- | --- | --- | --- | | + | | 加 | | | - | | 减 | | | * | | 乘 | | | / | div | 除 | | | % | mod | 求余 | | | == | eq | 等于 | equal缩写 | | != | ne | 不等于 | not equal缩写 | | > | gt | 大于 | greater than缩写 | | >= | ge | 大于等于 | greater than equal缩写 | | < | lt | 小于 | less than缩写 | | <= | le | 小于等于 | less than equal缩写 | | && | and | 且 | | | || | or | 或 | | | ! | not | 非 | | | matches | | 正则表达式 | #H['a'] matches '[a-z]{3}2' | | contains | | 包含 | #H['a'].contains('123') | | between | | 区间 | #H['a'] between {1, 2} | | instanceof | | 实例表达式 | #H['a'] instanceof 'T(String)' | ⑤ Spring Spel的符号转义,对XML格式的规则策略文件,保存在配置中心的时候,需要对表达式中的特殊符号进行转义 | 符号 | 转义符 | 含义 | 备注 | | --- | --- | --- | --- | | & | `&` | 和符号 | 必须转义 | | < | `<` | 小于号 | 必须转义 | | " | `"` | 双引号 | 必须转义 | | > | `>` | 大于号 | | | ' | `'` | 单引号 | | 表达式如果包含跟XML格式冲突的字符,就必须转义,例如 `#`H['a'] == '1' `&&` `#`H['b'] `<`= '2' `&&` `#`H['c'] != '3' ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 规则策略配置 增加Spring Cloud Gateway的版本条件匹配蓝绿发布策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下 ```xml {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide2-8.jpg) ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 规则策略解释 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 特别提醒 > 为准确体现相关变量(例如上文中的 **a** )支持Header、Parameter、Cookie中的任意一个,表达式格式为 **expression="#H['a'] == '1'"** ① 当外部调用带有的Header/Parameter/Cookies中的值a=1同时b=2,执行绿路由 ``节点(id="blue-condition")中 **expression="#H['a'] == '1' and #H['b'] == '2'"** 对应的 **version-id="green-route"** ,找到下面``节点中 **id="green-route" type="version"** 的那项,那么路由即为 ``` {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` ② 当外部调用带有的Header/Parameter/Cookies中的值a=1,执行蓝路由 ``节点(id="green-condition")中 **expression="#H['a'] == '1'"** 对应的 **version-id="blue-route"** ,找到下面``节点中 **id="blue-route" type="version"** 的那项,那么路由即为 ``` {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} ``` ③ 当外部调用带有的Header/Parameter/Cookies中的值都不命中,或者未传值,执行兜底路由 - 执行``节点(id="basic-condition")中的兜底路由,那么路由即为 ``` {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` ④ 如果兜底路由未配置 - 执行``节点中的全局缺省路由,那么路由即为 ``` {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 特别提醒 > 兜底路由和全局缺省路由配置一个即可 - 如果上述配置都不存在,则执行Spring Cloud Ribbon轮询策略 ⑤ 假如不愿意从网关外部传入Header/Parameter/Cookies,那么支持策略下内置Header来决策蓝绿发布,可以代替外部传入Header/Parameter/Cookies,参考如下配置 ```xml
{"a":"1", "b":"2", "c":"3"}
``` 内置Header一般使用场景为定时Job的服务定时去调用其它服务,希望实施蓝绿灰度发布。当服务侧配置了内置Header,而网关也传递给对应Header给该服务,通过开关来决定,网关传递的Header为优先还是服务侧内置的Header优先 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/warning.png) 需要注意,Spring Cloud Gateway在Finchley版不支持该方式 ⑥ 路由类型支持如下 - 蓝 | 绿 | 兜底,即上述提到的路由场景 - 蓝 | 兜底,即绿路由缺省,那么兜底路由则为绿路由,逻辑更加简单的路由场景 - 如果蓝路由和路由都缺省,那就只有兜底路由(全局缺省路由),即为[全链路版本匹配蓝绿发布](#全链路版本匹配蓝绿发布)的路由场景 ⑦ 策略总共支持5种,可以单独一项使用,也可以多项叠加使用 - version 版本 - region 区域 - address IP地址和端口 - version-weight 版本权重 - region-weight 区域权重 ⑧ 策略支持Spring Spel的条件表达式方式 ⑨ 策略支持Spring Matcher的通配方式 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 上述方式,可以通过[全链路蓝绿发布编排建模](#全链路蓝绿发布编排建模)方式执行,并通过[全链路蓝绿发布流量侦测](#全链路蓝绿发布流量侦测)进行验证 #### 全链路区域条件匹配蓝绿发布 参考[全链路版本条件匹配蓝绿发布](#全链路版本条件匹配蓝绿发布) 用法相似,只需要把规则策略中 - 属性`version-id`替换成`region-id` - 属性`type="version"`替换成`type="region"` - 节点`route`对应的Json中版本替换成区域 #### 全链路IP地址和端口条件匹配蓝绿发布 参考[全链路版本条件匹配蓝绿发布](#全链路版本条件匹配蓝绿发布) 用法相似,只需要把规则策略中 - 属性`version-id`替换成`address-id` - 属性`type="version"`替换成`type="address"` - 节点`route`对应的Json中版本替换成IP地址和端口 ### 全链路灰度发布 > 经典场景:当调用请求从网关或者服务发起的时候,在路由过滤中,根据在配置中心配置的随机权重值,执行权重算法,选择灰度路由 | 稳定路由的规则策略(Json格式),并把命中的规则策略转化为策略路由Header(n-d-开头),实现全链路传递。每个端到端服务接收到策略路由Header后,执行负载均衡时,该Header跟注册中心的对应元数据进行相关比较,不符合条件的实例进行过滤,从而实现全链路灰度发布 > 实施概要:只涉及当前正在发布的服务,例如,对于 〔网关〕->〔A服务〕->〔B服务〕->〔C服务〕->〔D服务〕调用链来说,如果当前只是B服务和C服务正在实施发布,那么,只需要把B服务和C服务配置到规则策略中,其它则不需要配置。发布结束后,即B服务和C服务的所有实例都完全一致,例如,版本号都只有唯一一个,那么清除掉在配置中心配置的规则策略即可,从而进行下一轮全链路灰度发布 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Gray.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/discovery/docs/discovery-doc/DiscoveryGuide2-2.jpg) 如果希望每个服务的版本权重分别指定,那么策略内容如下,实现从Spring Cloud Gateway发起的调用a服务1.0版本流量权重为90%,1.1版本流量权重为10%,b服务1.0版本流量权重为80%,1.1版本流量权重为20% ```xml {"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=80;1.1=20"} ``` 当所有服务都选相同版本流量权重分配的时候,下面第1条和第2条是等效的 ``` 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"} ``` ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:非条件驱动下的全链路灰度发布跟Header驱动下的全链路灰度发布等效,例如 ``` n-d-version-weight=1.0=90;1.1=10 n-d-version-weight={"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=90;1.1=10"} ``` #### 全链路区域权重灰度发布 增加Zuul的区域权重灰度发布策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用全链路dev区域流量权重为85%,qa区域流量权重为15% ```xml dev=85;qa=15 ``` ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide2-4.jpg) 如果希望每个服务的区域权重分别指定,那么策略内容如下,实现从Zuul发起的调用a服务dev区域流量权重为85%,qa区域流量权重为15%,b服务dev区域流量权重为75%,qa区域流量权重为25% ```xml {"discovery-guide-service-a":"dev=85;qa=15", "discovery-guide-service-b":"dev=75;qa=25"} ``` 当所有服务都选相同区域流量权重分配的时候,下面第1条和第2条是等效的 ``` 1. dev=85;qa=15 2. {"discovery-guide-service-a":"dev=85;qa=15", "discovery-guide-service-b":"dev=85;qa=15"} ``` ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:非条件驱动下的全链路灰度发布跟Header驱动下的全链路灰度发布等效,例如 ``` n-d-region-weight=dev=85;qa=15 n-d-region-weight={"discovery-guide-service-a":"dev=85;qa=15", "discovery-guide-service-b":"dev=85;qa=15"} ``` ### 全链路条件灰度发布 #### 全链路版本条件权重灰度发布 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 规则策略配置 增加Zuul的版本条件权重灰度发布策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下 ```xml {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide2-9.jpg) ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 规则策略解释 网关随机权重调用服务,服务链路按照版本匹配方式调用 ① 稳定版本路由和灰度版本路由流量权重分配 - 稳定版本路由:a服务1.0版本向网关提供90%的流量,a服务1.0版本只能访问b服务1.0版本 - 灰度版本路由:a服务1.1版本向网关提供10%的流量,a服务1.1版本只能访问b服务1.1版本 ② gray-route链路配比10%的流量,stable-route链路配比90%的流量 ③ 策略总共支持3种,可以单独一项使用,也可以多项叠加使用 - version 版本 - region 区域 - address IP地址和端口 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 上述方式,可以通过[全链路灰度发布编排建模](#全链路灰度发布编排建模)方式执行,并通过[全链路灰度发布流量侦测](#全链路灰度发布流量侦测)进行验证 #### 全链路区域条件权重灰度发布 参考[全链路版本条件权重灰度发布](#全链路版本条件权重灰度发布) 用法相似,只需要把规则策略中 - 属性`version-id`替换成`region-id` - 属性`type="version"`替换成`type="region"` - 节点`route`对应的Json中版本替换成区域 #### 全链路IP地址和端口条件权重灰度发布 参考[全链路版本条件权重灰度发布](#全链路版本条件权重灰度发布) 用法相似,只需要把规则策略中 - 属性`version-id`替换成`address-id` - 属性`type="version"`替换成`type="address"` - 节点`route`对应的Json中版本替换成IP地址和端口 ### 全链路端到端混合实施蓝绿灰度发布 #### 全链路端到端实施蓝绿灰度发布 前端 -> 网关 -> 服务全链路调用中,可以实施端到端蓝绿灰度发布 ① 前端 -> 网关并行实施蓝绿灰度发布 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。需要通过如下开关做控制 ``` # 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true spring.application.strategy.gateway.header.priority=false # 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true spring.application.strategy.gateway.original.header.ignored=true # 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true spring.application.strategy.zuul.header.priority=false # 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true spring.application.strategy.zuul.original.header.ignored=true ``` ② 网关 -> 服务并行实施蓝绿灰度发布 当网关传值Header的时候,服务也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。需要通过如下开关做控制 ``` # 当外界传值Header的时候,服务也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以服务设置为优先,否则以外界传值为优先。缺失则默认为true # spring.application.strategy.service.header.priority=true ``` #### 全链路混合实施蓝绿灰度发布 网关 -> 服务全链路调用中,可以混合实施蓝绿灰度发布 ① 网关上实施蓝绿发布,服务上实施灰度发布 ② 网关上实施灰度发布,服务上实施蓝绿发布 上述两个发布场景,可以独立实施,互不影响,前提条件,需要控制服务上`header.priority`的开关 #### 单节点混合实施蓝绿灰度发布 网关或者服务上的规则同时含有蓝绿灰度发布策略 本着蓝绿发布优先于灰度发布的原则,当前端传入参数a(Header、Parameter、Cookie其中一种) - 当a等于1,执行蓝绿发布,即a服务调用1.1版本,b服务调用1.1版本 - 当a的值不命中,或者未传值,执行灰度发布,即服务a和b 1.1版本的链路流量分配为5%,服务a和b 1.0版本的链路流量分配为95% 分为如下两种方式,都可以达到相同的预期效果 ① 通过蓝绿灰度混合方式 ```xml {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} ``` ② 通过纯灰度方式 把其中一个链路的流量分配为0%,达到蓝绿发布的效果 ```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"} ``` 混合蓝绿灰度发布的逻辑 ```xml {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` 规则解读 原则:蓝绿规则优先于灰度规则 ```java if (a == 0) { 执行蓝绿发布blue-green的route-0下的路由 } else if (a == 1) { 执行蓝绿发布blue-green的route-1下的路由 } else { 执行蓝绿发布blue-green的basic-route下的兜底路由 } ``` ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:当蓝绿发布存在兜底策略(`basic-condition`),灰度发布永远不会被执行 如果删除掉蓝绿发布的兜底策略,那么执行逻辑则变为 ```java if (a == 0) { 执行蓝绿发布route-0下的路由 } else if (a == 1) { 执行蓝绿发布route-1下的路由 } else if (a == 2) { 执行灰度发布route-0=10;route-1=90下的流量百分比分配路由 } else if (a == 3) { 执行灰度发布route-0=85;route-1=15下的流量百分比分配路由 } else { 执行灰度发布route-0=0;route-1=100下的兜底路由 由于赋予了route-0=0,那么流量会全部打到route-1上,相当于变种的蓝绿发布 } ``` ### 全链路域网关和非域网关部署 #### 全链路域网关部署 A部门服务访问B部门服务必须通过B部门网关 该部署模式下,本部门服务的蓝绿灰度发布只由本部门的网关来实施,其它部门无权对本部门服务实施蓝绿灰度发布,前提条件,需要控制网关上`header.priority`的开关 #### 全链路非域网关部署 A部门服务直接访问B部门服务 该部署模式下,会发生本部门服务的蓝绿灰度发布会由其它部门的网关或者服务来触发,当本部门服务和其它部门服务在同一时刻实施蓝绿灰度发布的时候,会产生混乱。解决方案,参考[并行发布下的版本偏好](#并行发布下的版本偏好) ### 全链路前端触发后端蓝绿灰度发布 前端可以直接触发后端蓝绿灰度发布,前提条件,需要控制网关和服务上`header.priority`的开关 #### 全链路驱动方式 - 版本匹配策略,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=3001;4002 2. n-d-address={"discovery-guide-service-a":"127.0.0.1:3001", "discovery-guide-service-b":"127.0.0.1:4002"} 3. n-d-address={"discovery-guide-service-a":"127.0.0.1", "discovery-guide-service-b":"127.0.0.1"} 4. n-d-address={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4002"} ``` - 环境隔离下动态环境匹配策略 ``` 1. n-d-env=env1 ``` - 服务下线实时性的流量绝对无损,全局唯一ID屏蔽策略,Header格式如下任选一个 ``` 1. n-d-id-blacklist=20210601-222214-909-1146-372-698;20210601-222623-277-4978-633-279 2. n-d-id-blacklist={"discovery-guide-service-a":"20210601-222214-909-1146-372-698", "discovery-guide-service-b":"20210601-222623-277-4978-633-279"} ``` - 服务下线实时性的流量绝对无损,IP地址和端口屏蔽策略,Header格式如下任选一个 ``` 1. n-d-address-blacklist=3001;4002 2. n-d-address-blacklist={"discovery-guide-service-a":"127.0.0.1:3001", "discovery-guide-service-b":"127.0.0.1:4002"} 3. n-d-address-blacklist={"discovery-guide-service-a":"127.0.0.1", "discovery-guide-service-b":"127.0.0.1"} 4. n-d-address-blacklist={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4002"} ``` ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 全链路前端触发后端蓝绿灰度发布全景功能 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Introduction.jpg) #### 全链路参数策略 ① Header参数策略 基于标准Http传值方式 框架会默认把相关的Header,进行全链路传递,可以通过如下配置进行。除此之外,凡是以n-d-开头的任何Header,框架都会默认全链路传递 ``` # 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true spring.application.strategy.rest.intercept.enabled=true # 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.rest.intercept.debug.enabled=true # 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.context.request.headers=trace-id;span-id # 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于业务系统自定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.business.request.headers=user;mobile;location ``` ② Parameter参数策略 基于标准Http传值方式 [http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=1](http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=1) [http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=2](http://localhost:5001/discovery-guide-service-a/invoke/gateway?a=2) ③ Cookie参数策略 基于标准Http传值方式 ④ 域名参数策略 基于取值域名前缀等方式,即可实现既定功能 本地测试,为验证结果,请事先在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/discovery/docs/discovery-doc/DiscoveryGuide2-15.jpg) - 根据common.nepxion.com域名路由到common环境 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide2-16.jpg) 参考[全链路自定义过滤器触发蓝绿灰度发布](#全链路自定义过滤器触发蓝绿灰度发布)示例,以根据域名全链路环境隔离为例,根据域名前缀中的环境名路由到相应的全链路环境中 ⑤ RPC-Method参数策略 基于取值RPC调用中的方法入参等方式,即可实现既定功能,该方式只适用于服务侧 参考[全链路自定义负载均衡策略类触发蓝绿灰度发布](#全链路自定义负载均衡策略类触发蓝绿灰度发布)示例 ### 全局订阅式蓝绿灰度发布 如果使用者不希望通过全链路传递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/discovery/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 spring.application.strategy.web.client.core.header.transmission.enabled=true ``` ### 全链路自定义蓝绿灰度发布 #### 全链路自定义过滤器触发蓝绿灰度发布 下面代码既适用于Zuul和Spring Cloud Gateway网关,也适用于微服务。继承DefaultGatewayStrategyRouteFilter、DefaultZuulStrategyRouteFilter和DefaultServiceStrategyRouteFilter,覆盖掉如下方法中的一个或者多个,通过@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示例 ```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.getRouteAddress(); } @Autowired private GatewayStrategyContextHolder gatewayStrategyContextHolder; // 自定义根据域名全链路环境隔离 @Override public String getRouteEnvironment() { String host = gatewayStrategyContextHolder.getURI().getHost(); if (host.contains("nepxion.com")) { LOG.info("自定义根据域名全链路环境隔离, URL={}", host); String environment = host.substring(0, host.indexOf(".")); LOG.info("执行全链路环境隔离={}", environment); return environment; } return super.getRouteEnvironment(); } // 自定义全链路版本权重路由 /*@Override public String getRouteVersion() { LOG.info("自定义全链路版本权重路由"); List> 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示例 ```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示例 ```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(); } ``` #### 全链路自定义负载均衡策略类触发蓝绿灰度发布 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 〔Spring Cloud 202x版〕特别提醒 > 对于Spring Cloud 202x版,由于它已经移除了Ribbon,所以apply(Server server)方法上的入参,com.netflix.loadbalancer.Server需要改成org.springframework.cloud.client.ServiceInstance 下面代码既适用于Zuul和Spring Cloud Gateway网关,也适用于微服务。继承DefaultDiscoveryEnabledStrategy,可以有多个,通过@Bean方式注入 ```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.getHost() + ":" + server.getPort(); LOG.info("负载均衡用户定制触发:mobile={}, serviceId={}, version={}, region={}, env={}, address={}", mobile, serviceId, version, region, environment, address); if (StringUtils.isNotEmpty(mobile)) { // 手机号以移动138开头,路由到1.0版本的服务上 if (mobile.startsWith("138") && StringUtils.equals(version, "1.0")) { return true; // 手机号以联通133开头,路由到2.0版本的服务上 } else if (mobile.startsWith("133") && StringUtils.equals(version, "1.1")) { return true; } else { // 其它情况,直接拒绝请求 return false; } } return true; } } ``` 在配置类里@Bean方式进行策略类创建 ```java @Bean public DiscoveryEnabledStrategy discoveryEnabledStrategy() { return new MyDiscoveryEnabledStrategy(); } ``` 服务除了支持网关那种基于Rest参数的方式之外,还支持基于Rpc方法参数的方式,它包括接口名、方法名、参数名或参数值等多种形式 ```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.getHost() + ":" + server.getPort(); LOG.info("负载均衡用户定制触发:mobile={}, serviceId={}, version={}, region={}, env={}, address={}", mobile, serviceId, version, region, environment, address); if (StringUtils.isNotEmpty(mobile)) { // 手机号以移动138开头,路由到1.0版本的服务上 if (mobile.startsWith("138") && StringUtils.equals(version, "1.0")) { return true; // 手机号以联通133开头,路由到2.0版本的服务上 } else if (mobile.startsWith("133") && StringUtils.equals(version, "1.1")) { return true; } else { // 其它情况,直接拒绝请求 return false; } } return true; } // 根据RPC调用传来的方法参数(例如接口名、方法名、参数名或参数值等),选取执行调用请求的服务实例 // 本示例只作用在discovery-guide-service-a服务上 @SuppressWarnings("unchecked") private boolean applyFromMethod(Server server) { Map attributes = serviceStrategyContextHolder.getRpcAttributes(); String serviceId = pluginAdapter.getServerServiceId(server); String version = pluginAdapter.getServerVersion(server); String region = pluginAdapter.getServerRegion(server); String environment = pluginAdapter.getServerEnvironment(server); String address = server.getHost() + ":" + server.getPort(); LOG.info("负载均衡用户定制触发:attributes={}, serviceId={}, version={}, region={}, env={}, address={}", attributes, serviceId, version, region, environment, address); if (attributes.containsKey(DiscoveryConstant.PARAMETER_MAP)) { Map 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 ``` ### 全链路动态变更元数据的蓝绿灰度发布 利用注册中心的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/discovery/docs/icon-doc/warning.png) 需要注意 ① 并非所有的注册中心都支持动态元数据变更方式,需要使用者自行研究 ② 动态元数据变更方式利用第三方注册中心的Open API达到最终目的,其可能具有一定的延迟性,不如本框架那样具有蓝绿灰度发布实时生效的特征,但比本框架动态变更蓝绿灰度发布简单了一些 ③ 动态元数据变更方式只是让新的元数据驻留在内存里,并不持久化。当服务重启后,服务的元数据仍旧会以初始值为准 ## 全链路蓝绿灰度发布编排建模和流量侦测 ① 获取图形化桌面端 桌面端获取方式有两种方式 - 通过[https://github.com/Nepxion/DiscoveryUI/releases](https://github.com/Nepxion/DiscoveryUI/releases)下载最新版本的discovery-desktop-release - 编译[https://github.com/Nepxion/DiscoveryUI](https://github.com/Nepxion/DiscoveryUI)下的desktop,在target目录下产生discovery-desktop-release ② 启动控制台 - 通过[https://github.com/Nepxion/DiscoveryPlatform](https://github.com/Nepxion/DiscoveryPlatform)下载最新版本的控制台 - 导入IDE或者编译成Spring Boot程序运行 - 运行之前,先修改src/main/resources/bootstrap.properties的相关配置,包括注册中心和配置中心的地址等 ③ 启动图形化桌面端 - 修改config/console.properties中的url,指向控制台的地址 - 在Windows操作系统下,运行startup.bat,在Mac或者Linux操作系统下,运行startup.sh ④ 登录图形化桌面端 登录认证,用户名和密码为admin/admin或者nepxion/nepxion。控制台支持简单的认证,用户名和密码配置在上述控制台的bootstrap.properties中,使用者可以自己扩展AuthenticationResource并注入,实现更专业的认证功能 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop8.jpg) ### 全链路编排建模 全链路编排建模工具,只提供最经典和最常用的蓝绿灰度发布场景功能,并不覆盖框架所有的功能 #### 全链路蓝绿发布编排建模 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop9.jpg) ① 导航栏上选择〔全链路服务蓝绿发布〕 ② 〔全链路服务蓝绿发布〕界面的工具栏上,点击【新建】按钮,弹出【新建配置】对话框。确认下面选项后,点击【确定】按钮后,进行全链路蓝绿发布编排建模 - 〔订阅参数〕项。选择〔局部订阅〕或者〔全局订阅〕,通过下拉菜单〔订阅组名〕和〔订阅服务名〕,〔订阅服务名〕可以选择网关(以网关为发布入口)或者服务(以服务为发布入口)。如果是〔全局订阅〕,则不需要选择〔订阅服务名〕 - 〔部署参数〕项。选择〔域网关模式〕(发布界面上提供只属于〔订阅组〕下的服务列表)或者〔非域网模式〕(发布界面上提供所有服务列表) - 〔发布策略〕项。选择〔版本策略〕或者〔区域策略〕 - 〔路由类型〕项。选择〔蓝 | 绿 | 兜底〕或者〔蓝 | 兜底〕 根据[全链路版本条件匹配蓝绿发布](#全链路版本条件匹配蓝绿发布)示例中的场景 ③ 在〔蓝绿条件〕中,分别输入〔蓝条件〕和〔绿条件〕 - 〔蓝条件〕输入a==1 - 〔绿条件〕输入a==1&&b==2 使用者可以通过〔条件校验〕来判断条件是否正确。例如,在〔绿条件〕区的校验文本框里,输入a=1,执行校验,将提示〔校验结果:false〕,输入a=1;b=2,将提示〔校验结果:true〕 ④ 在〔蓝绿编排〕中,分别选择如下服务以及其版本,并点击【添加】按钮,把路由链路添加到拓扑图上 - 服务discovery-guide-service-a,〔蓝版本〕=1.1,〔绿版本〕=1.0,〔兜底版本〕=1.0 - 服务discovery-guide-service-b,〔蓝版本〕=1.1,〔绿版本〕=1.0,〔兜底版本〕=1.0 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop10.jpg) ⑤ 如果希望内置Header参数,可以〔蓝绿参数〕的文本框中输入 ⑥ 全链路编排建模完毕,点击工具栏上【保存】按钮进行保存,也可以先点击【预览】按钮,在弹出的【预览配置】对话框中,确认规则策略无误后再保存。使用者可以访问Nacos界面查看相关的规则策略是否已经存在 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop11.jpg) ⑦ 对于已经存在的策略配置,可以通过点击工具栏上【打开】按钮,在弹出的【打开配置】对话框中,根据上述逻辑相似,确定〔订阅参数〕项后,选择〔打开远程配置〕(载入Nacos上对应的规则策略)或者〔打开本地配置〕(载入本地硬盘上规则策略文件rule.xml) ⑧ 对于已经存在的策略配置,如果想重置清除掉,点击工具栏上【重置】按钮进行重置清除 #### 全链路灰度发布编排建模 ① 导航栏上选择〔全链路服务灰度发布〕 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop13.jpg) 根据[全链路版本条件权重灰度发布](#全链路版本条件权重灰度发布)示例中的场景 ② 在〔灰度条件〕中,〔灰度条件〕(灰度流量占比)选择95%,〔稳定条件〕(稳定流量占比)会自动切换成5% 其它步骤跟[全链路蓝绿发布编排建模](#全链路蓝绿发布编排建模)相似,但比其简单 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop14.jpg) ### 全链路流量侦测 #### 全链路蓝绿发布流量侦测 ① 导航栏上选择〔全链路服务流量侦测〕 ② 在〔侦测入口〕中,操作如下 - 〔类型〕项。选择〔网关〕或者〔服务〕,本示例的规则策略是配置在网关上,所以选择〔网关〕 - 〔协议〕项。选择〔http://〕或者〔https://〕,视网关或者服务暴露出来的协议类型而定,本示例暴露出来的是http协议,所以选择〔http://〕 - 〔服务〕项。选择一个网关名或者服务名,下拉菜单列表随着〔类型〕项的改变而改变,蓝绿发布规则策略是配置在discovery-guide-gateway上,所以选择它 - 〔实例〕项。选择一个网关实例或者服务实例的IP地址和端口,下拉菜单列表随着〔服务〕的改变而改变 ③ 在〔侦测参数〕中,操作如下 添加〔Header〕项和〔Parameter〕项,也可以〔Cookie〕项,使用者可以任意选择2个 - 〔Header〕项。输入a=1 - 〔Parameter〕项。输入b=2 ④ 在〔侦测链路〕中,操作如下 - 增加服务discovery-guide-service-a - 增加服务discovery-guide-service-b ⑤ 在〔侦测执行〕中,操作如下 - 〔维护〕项。选择〔版本〕、〔区域〕、〔环境〕、〔可用区〕、〔地址〕或者〔组〕,维护表示在拓扑图上聚合调用场景的维度,本示例的规则策略是是基于版本维度进行发布,所以选择〔版本〕 - 〔次数〕项。选择执行侦测的次数,基于网关和服务的性能压力,使用者需要酌情考虑调用次数 - 〔次数〕项。选择执行侦测的同一时刻线程并发数,并发数是对于图形化桌面端而言的 - 〔成功〕项。用来显示侦测成功的百分比 - 〔失败〕项。用来显示侦测失败的百分比 - 〔耗时〕项。用来显示侦测执行的消耗时间 ⑥ 点击工具栏上【开始】按钮开始侦测,在侦测执行过程中,可以点击工具栏上【停止】按钮停止侦测 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop15.jpg) 从上述截图中,可以看到 - 在条件a==1&&b==2的〔绿条件〕下,执行〔网关〕->〔a服务1.0版本〕->〔b服务1.0版本〕的〔绿路由〕 ⑦ 点击工具栏上【查看】按钮查看拓扑图上所有节点配置的规则策略,包括局部配置和全局配置 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop16.jpg) ⑧ 支持直接n-d-version策略路由Header驱动的蓝绿发布流量侦测 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop7.jpg) #### 全链路灰度发布流量侦测 ① 导航栏上选择〔全链路服务流量侦测〕 ② 在〔侦测入口〕中,操作如下 - 〔服务〕项。灰度发布规则策略是配置在discovery-guide-zuul上,所以选择它 ③ 在〔侦测参数〕中,不需要输入任何值 ④ 在〔侦测执行〕中,〔次数〕项的值越大,灰度权重百分比越准确 其它步骤跟[全链路蓝绿发布流量侦测](#全链路蓝绿发布流量侦测)相似,但比其简单 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop17.jpg) 从上述截图中,可以看到 - 执行〔网关〕->〔a服务1.1版本〕->〔b服务1.1版本〕的〔灰度路由〕权重百分比95%左右 - 执行〔网关〕->〔a服务1.0版本〕->〔b服务1.0版本〕的〔稳定路由〕权重百分比5%左右 #### 全链路蓝绿灰度发布混合流量侦测 ① 全链路蓝绿发布 + 灰度发布混合模式下流量侦测 在网关上配置了蓝绿发布规则策略,在a服务上配置了灰度发布规则策略 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop5.jpg) ② 全链路灰度发布 + 蓝绿发布混合模式下流量侦测 在网关上配置了灰度发布规则策略,在a服务上配置了蓝绿发布规则策略 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryDesktop6.jpg) ③ 全链路流量侦测接口 通过discovery-plugin-admin-center-starter内置基于LoadBalanced RestTemplate的接口方法,实现全链路侦测,用于查看全链路中调用的各个服务的版本、区域、环境、可用区、IP地址和端口等是否符合和满足蓝绿灰度条件。使用方式,如下 服务的Rest Endpoint接口 | 操作 | 路径 | 参数 | 方式 | | --- | --- | --- | --- | | 网关为入口 | `http://`[网关IP:PORT]/[A服务名]/inspector/inspect | {"serviceIdList":["B服务名", "C服务名", ...]} | POST | | 服务为入口 | `http://`[A服务IP:PORT]/inspector/inspect | {"serviceIdList":["B服务名", "C服务名", ...]} | POST | ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:内容项中服务名列表不分前后次序 ## 全链路蓝绿灰度发布容灾 ### 发布失败下的版本故障转移 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止蓝绿灰度版本发布人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损 故障转移方式,对版本号进行排序,此解决方案的前置条件是版本号必须是规律的有次序,例如,以时间戳的方式。如果所有服务实例的版本号未设置,那么将转移到未设置版本号的实例上 需要通过如下开关开启该功能 ``` # 启动和关闭版本故障转移。缺失则默认为false spring.application.strategy.version.failover.enabled=true ``` ### 并行发布下的版本偏好 版本偏好,即非蓝绿灰度发布场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施蓝绿灰度版本发布产生混乱,对处于非蓝绿灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;蓝绿灰度状态的服务,还是根据传递的Header版本号进行匹配 偏好方式,对版本号进行排序,此解决方案的前置条件是版本号必须是规律的有次序,例如,以时间戳的方式。如果所有服务实例的版本号未设置,那么将转移到未设置版本号的实例上 需要通过如下开关开启该功能 ``` # 启动和关闭版本偏好。缺失则默认为false spring.application.strategy.version.prefer.enabled=true ``` ## 服务下线场景下全链路蓝绿灰度发布 服务下线场景下,由于Ribbon负载均衡组件存在着缓存机制,当被提供端服务实例已经下线,而消费端服务实例还暂时缓存着它,直到下个心跳周期才会把已下线的服务实例剔除,在此期间,如果发生调用,会造成流量有损 框架提供流量的实时性绝对无损策略。采用下线之前,把服务实例添加到屏蔽名单中,负载均衡不会去寻址该服务实例。下线之后,清除该名单。实现该方式,需要通过DevOps调用配置中心的Open API推送或者在配置中心界面手工修改 ### 全局唯一ID屏蔽 全局唯一ID对应于元数据spring.application.uuid字段,框架会自动把该ID注册到注册中心,不需要用户自己配置,支持通配表达式方式 全局唯一ID的格式为 ``` 年月日(8位)-小时分钟秒(6位)-毫秒(3位)-随机数(4位)-随机数(3位)-随机数(3位) ``` 前半部分精确到毫秒的设计,基本能保证ID的全局唯一,后半部分三重随机数,完全能保证ID的全局唯一。全局唯一失效的前提是,两个服务实例必须是毫秒级的同时启动,同时三次随机碰撞下来,得到完全三个相同的随机数后 增加Spring Cloud Gateway的全局唯一ID屏蔽策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用屏蔽指定全局唯一ID的服务 ```xml 20210601-222214-909-1146-372-698 ``` ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide2-11.jpg) 如果希望每个服务的全局唯一ID分别指定,那么策略内容如下,实现从Spring Cloud Gateway发起的调用屏蔽ID为20210601-222214-909-1146-372-698的a服务,屏蔽ID为20210601-222623-277-4978-633-279的b服务 ```xml {"discovery-guide-service-a":"20210601-222214-909-1146-372-698", "discovery-guide-service-b":"20210601-222623-277-4978-633-279"} ``` 如果忽略服务名,也可以表示成如下方式,即ID为20210601-222214-909-1146-372-698和20210601-222623-277-4978-633-279的服务都被屏蔽 ``` 20210601-222214-909-1146-372-698;20210601-222623-277-4978-633-279 ``` 如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法 ``` 20210601* - 表示屏蔽范围是2021年06月01日注册的实例口 20210601-222214-909-1146-372-69? - 表示屏蔽范围是20210601-222214-909-1146-372-69开头ID的服务 ``` 例如 ``` "discovery-guide-service-b":"20210601*;20210601-222214-909-1146-372-69?" ``` 表示discovery-guide-service-b服务的屏蔽范围是2021年06月01日注册的实例,或者屏蔽范围是20210601-222214-909-1146-372-69开头ID的服务(末尾必须是1个字符),多个用分号隔开 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:跟Header驱动下的IP地址和端口屏蔽策略等效,例如 ``` 1. n-d-id-blacklist=20210601-222214-909-1146-372-698;20210601-222623-277-4978-633-279 2. n-d-id-blacklist={"discovery-guide-service-a":"20210601-222214-909-1146-372-698", "discovery-guide-service-b":"20210601-222623-277-4978-633-279"} ``` ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 小贴士 利用通配符方式实现对指定日期上线的服务实例做屏蔽,示例内容如下,表示2021年6月1日(也可以精确到小时或者分钟)上线的a服务实例和b服务实例都会被屏蔽。该场景的使用意义是,在服务下线之前,使用者担心流量有损,同时使用者知道上一次服务发布的日期,只要该屏蔽策略一生效,负载均衡将实时过滤掉指定日期的服务实例。那么,使用者对这些服务实例无论是优雅停机,还是暴力下线,都不会造成任何流量有损,例如 ```xml {"discovery-guide-service-a":"20210601*", "discovery-guide-service-b":"20210601*"} ``` ### IP地址和端口屏蔽 通过IP地址或者端口或者IP地址+端口进行屏蔽,支持通配表达式方式 增加Zuul的IP地址和端口屏蔽策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用屏蔽指定IP地址和端口,或者指定IP地址,或者指定端口(下面策略以端口为例)的服务 ```xml
3001
``` ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide2-12.jpg) 如果希望每个服务的IP地址或者端口分别指定,那么策略内容如下,实现从Zuul发起的屏蔽屏蔽3001端口的a服务,屏蔽4001端口的b服务 ```xml
{"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4001"}
``` 如果忽略服务名,也可以表示成如下方式,即3001和4001端口的服务都被屏蔽 ```
3001;4001
``` 当所有服务都选同一端口的时候,下面第1条和第2条是等效的 ``` 1.
3001
2.
{"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"}
``` 如果上述表达式还未满足需求,也可以采用通配表达式方式(具体详细用法,参考Spring AntPathMatcher),通过Spring Matcher的通配表达式,支持多个通配*、单个通配?等全部标准表达式用法 ``` * - 表示屏蔽为所有端口 3* - 表示屏蔽范围为3开头的所有端口 ``` 例如 ``` "discovery-guide-service-b":"3*;400?" ``` 表示discovery-guide-service-b服务的屏蔽范围是3开头的所有端口,或者屏蔽范围是400开头的所有端口(末尾必须是1个字符),多个用分号隔开 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:跟Header驱动下的IP地址和端口屏蔽策略等效,例如 ``` 1. n-d-address-blacklist=3001 2. n-d-address-blacklist={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"} ``` ## 异步场景下全链路蓝绿灰度发布 Discovery框架存在着如下全链路传递上下文的场景,包括 - 策略路由Header全链路从网关传递到服务 - 调用链埋点全链路从网关传递到服务 - 业务自定义的上下文的传递 上述上下文会在如下异步场景中丢失,包括 - WebFlux Reactor响应式异步 - Spring异步,@Async注解异步 - Hystrix线程池隔离模式异步 - 线程,线程池异步 - SLF4J日志异步 通过DiscoveryAgent,解决上述痛点。Discovery框架利用DiscoveryAgent字节码增强技术,完美解决各种调用场景下的异步,包括 - Spring Cloud Gateway过滤器中的上下文传递 - Zuul过滤器中的上下文传递 - Feign拦截器中的上下文转发 - RestTemplate拦截器中的上下文转发 - WebClient拦截器中的上下文转发 ### 异步场景下DiscoveryAgent解决方案 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) DiscoveryAgent不仅适用于Discovery框架,也适用于一切具有类似使用场景的基础框架(例如:Dubbo)和业务系统 ThreadLocal的作用是提供线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立。在异步场景下,由于出现线程切换的问题,例如,主线程切换到子线程,会导致线程ThreadLocal上下文丢失。DiscoveryAgent通过Java Agent方式解决这些痛点 涵盖所有Java框架的异步场景,解决如下8个异步场景下丢失线程ThreadLocal上下文的问题 - WebFlux Reactor - `@`Async - Hystrix Thread Pool Isolation - Runnable - Callable - Single Thread - Thread Pool - SLF4J MDC #### 异步跨线程DiscoveryAgent获取 插件获取方式有两种方式 - 通过[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目录 #### 异步跨线程DiscoveryAgent清单 ① discovery-agent-starter-`$`{discovery.version}.jar为Agent引导启动程序,JVM启动时进行加载 ② agent.config为基准扫描目录配置文件 绝大多数情况下不需要修改,当然使用者也可以增加和删除agent.config的基准扫描目录。默认配置如下 ``` # Base thread scan packages agent.plugin.thread.scan.packages=reactor.core.publisher;org.springframework.aop.interceptor;com.netflix.hystrix ``` 基准扫描目录,含义如下 - WebFlux Reactor异步场景下的扫描目录对应为reactor.core.publisher - `@`Async场景下的扫描目录对应为org.springframework.aop.interceptor - Hystrix线程池隔离场景下的扫描目录对应为com.netflix.hystrix ③ plugin/discovery-agent-starter-plugin-strategy-`$`{discovery.version}.jar插件,解决Nepxion Discovery上下文异步场景 ④ plugin/discovery-agent-starter-plugin-mdc-`$`{discovery.version}.jar插件,解决SLF4J MDC日志上下文异步场景 ⑤ 业务系统可以自定义plugin,解决业务自己定义的上下文异步场景 #### 异步跨线程DiscoveryAgent使用 ① 使用示例 - 通过如下-javaagent启动,基本格式,如下 ``` -javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.nepxion.discovery.guide.service.feign ``` ② 参数说明 - C:/opt/discovery-agent:Agent所在的目录,需要对应到实际的目录上 - `-D`thread.scan.packages:Runnable/Callable/Thread/ThreadPool等异步类所在的扫描目录,该目录下的异步类都会被装饰 - 扫描目录最好精细和准确,目录越详细,越可以减少被装饰的对象数,从一定程度上可以提高性能 - 扫描目录如果有多个,用“;”分隔 - 扫描目录如果含有“;”,可能会在某些操作系统中无法被识别,请用`""`进行引入,例如,-Dthread.scan.packages="com.abc;com.xyz" - 扫描目录下没有Runnable/Callable/Thread/ThreadPool等异步类存在,那么thread.scan.packages也不需要配置,最终启动命令行简化为-javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar - `-D`thread.gateway.enabled:Spring Cloud Gateway端策略Header输出到异步子线程。默认开启 - `-D`thread.zuul.enabled:Zuul端策略Header输出到异步子线程。默认开启 - `-D`thread.service.enabled:服务端策略Header输出到异步子线程。默认开启 - `-D`thread.mdc.enabled:SLF4J MDC日志输出到异步子线程。默认开启 - `-D`thread.request.decorator.enabled:异步调用场景下在服务端的Request请求的装饰,当主线程先于子线程执行完的时候,Request会被Destory,导致Header仍旧拿不到,开启装饰,就可以确保拿到。默认为开启,根据实践经验,大多数场景下,需要开启该开关 #### 异步跨线程DiscoveryAgent扩展 - 根据规范开发一个插件,插件提供了钩子函数,在某个类被加载的时候,可以注册一个事件到线程上下文切换事件当中,实现业务自定义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上下文的问题 ### 异步场景下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 ``` 该方案也可以通过[异步场景下DiscoveryAgent解决方案](#异步场景下DiscoveryAgent解决方案)解决 ## 全链路数据库和消息队列蓝绿发布 通过订阅相关参数的变化,实现参数化蓝绿发布,可用于如下场景 ① 基于多DataSource的数据库蓝绿发布 ② 基于多Queue的消息队列蓝绿发布 增加参数化蓝绿发布规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现功能 ① 服务a在版本为1.0的时候,数据库的数据源指向db1;服务a在版本为1.1的时候,数据库的数据源指向db2 ② 服务b在区域为dev的时候,消息队列指向queue1;服务b在区域为qa的时候,消息队列指向queue2 ③ 服务c在环境为env1的时候,数据库的数据源指向db1;服务c在环境为env2的时候,数据库的数据源指向db2 ④ 服务d在可用区为zone1的时候,消息队列指向queue1;服务d在可用区为zone2的时候,消息队列指向queue2 ⑤ 服务c在IP地址和端口为192.168.43.101:1201的时候,数据库的数据源指向db1;服务c在IP地址和端口为192.168.43.102:1201的时候,数据库的数据源指向db2 ```xml ``` 通过事件总线方式,对参数改变动态实现监听,并在此类里自行对接相关的数据库和消息队列中间件的切换和驱动 ```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)里的实现方式 ## 网关动态路由 网关动态路由功能,主要包括 - 路由动态添加 - 路由动态修改 - 路由动态删除 - 路由动态批量更新 - 路由查询 - 路由动态变更后,通过事件总线方式发出事件通知 上述操作,可以通过 - 网关暴露Rest Endpoint接口实施 - 控制台暴露Rest Endpoint接口,对同一个网关下若干个实例批量实施 - 网关订阅配置中心(包括Nacos、Apollo、Consul、Etcd、Redis、Zookeeper)批量实施 ### Spring-Cloud-Gateway网关动态路由 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:Spring Cloud Gateway网关在自动路由模式下,动态路由不能工作 支持Spring Cloud Gateway网关官方断言器和过滤器,也支持用户自定义断言器和过滤器 #### Spring-Cloud-Gateway网关动态路由配置 ① 精简配置 ``` [ { "id": "route0", "uri": "lb://discovery-guide-service-a", "predicates": [ "Path=/discovery-guide-service-a/**,/x/**,/y/**" ], "filters": [ "StripPrefix=1" ] } ] ``` ② 完整配置 ``` [ { "id": "route0", "uri": "lb://discovery-guide-service-a", "predicates": [ "Path=/discovery-guide-service-a/**,/x/**,/y/**" ], "filters": [ "StripPrefix=1" ], "order": 0, "metadata": {} } ] ``` #### Spring-Cloud-Gateway网关自定义动态路由配置 ① 自定义方式描述网关内置断言器和过滤器 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:自定义方式描述网关内置断言器和过滤器的Key必须遵循如下规则 - 对于没有显式args定义的配置,类似Path、StripPrefix这种配置,args名称必须是`_genkey_序号`格式。例如,"_genkey_0": "/discovery-guide-service-a/**" - 对于显式args定义的配置,类似Header、Cookie、Query这种配置,args名称遵照Spring Cloud Gateway内置格式,请查看相关文档或者源码。例如,Header的KV格式为header -> regexp,Cookie的KV格式为name->regexp,Query的KV格式为param->regexp ``` [ { "id": "route0", "uri": "lb://discovery-guide-service-a", "userPredicates": [ { "name": "Path", "args": { "_genkey_0": "/discovery-guide-service-a/**", "_genkey_1": "/x/**", "_genkey_2": "/y/**" } }, { "name": "Header", "args": { "header": "a", "regexp": "1" } }, { "name": "Header", "args": { "header": "b", "regexp": "2" } }, { "name": "Cookie", "args": { "name": "c", "regexp": "3" } }, { "name": "Cookie", "args": { "name": "d", "regexp": "4" } }, { "name": "Query", "args": { "param": "e", "regexp": "5" } }, { "name": "Query", "args": { "param": "f", "regexp": "6" } } ], "userFilters": [ { "name": "StripPrefix", "args": { "_genkey_0": "1" } } ] } ] ``` 在DiscoveryPlatform界面上,格式为 ``` Path={"_genkey_0":"/discovery-guide-service-a/**", "_genkey_1":"/x/**", "_genkey_2":"/y/**"} StripPrefix={"_genkey_0":"1"} Header={"header":"a","regexp":"1"} Header={"header":"b","regexp":"2"} Cookie={"name":"c","regexp":"3"} Cookie={"name":"d","regexp":"4"} Query={"param":"e","regexp":"5"} Query={"param":"f","regexp":"6"} ``` ② 自定义方式描述用户扩展的断言器和过滤器 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:自定义方式描述用户扩展的断言器和过滤器的Key必须遵循如下规则 - List结构,args名称必须是`list的变量名.序号`格式。例如,"whiteList.0": "* swagger-ui.html" - Map结构,args名称必须是`map的变量名.map的key`格式。例如,"userMap.name": "jason" ``` [ { "id": "route0", "uri": "lb://discovery-guide-service-a", "predicates": [ "Path=/discovery-guide-service-a/**,/x/**,/y/**" ], "filters": [ "StripPrefix=1" ], "userPredicates": [], "userFilters": [ { "name": "Authentication", "args": { "secretKey": "abc", "whiteList.0": "* swagger-ui.html", "whiteList.1": "* /swagger-resources/**", "whiteList.2": "* /doc.html", "userMap.name": "jason", "userMap.age": "20", "authInfoCarryStrategy": "AuthWriteToHeader" } } ] } ] ``` 在DiscoveryPlatform界面上,格式为 ``` Authentication={"secretKey":"abc", "whiteList.0":"* swagger-ui.html", "whiteList.1":"* /swagger-resources/**", "whiteList.2":"* /doc.html", "userMap.name":"jason", "userMap.age":"20", "authInfoCarryStrategy":"AuthWriteToHeader"} ``` #### Spring-Cloud-Gateway网关Rest-Endpoint ① Spring Cloud Gateway网关的Rest Endpoint接口 | 操作 | 路径 | 参数 | 方式 | | --- | --- | --- | --- | | 增加网关路由 | `http://`[网关IP:PORT]/spring-cloud-gateway-route/add | 单个动态路由配置 | POST | | 修改网关路由 | `http://`[网关IP:PORT]/spring-cloud-gateway-route/modify | 单个动态路由配置 | POST | | 删除网关路由 | `http://`[网关IP:PORT]/spring-cloud-gateway-route/delete/{routeId} | 无 | DELETE | | 更新全部网关路由 | `http://`[网关IP:PORT]/spring-cloud-gateway-route/update-all | 多个动态路由配置 | POST | | 根据路由Id查看网关路由 | `http://`[网关IP:PORT]/spring-cloud-gateway-route/view/{routeId} | 无 | GET | | 查看全部网关路由| `http://`[网关IP:PORT]/spring-cloud-gateway-route/view-all | 无 | GET | ② 控制台的Rest Endpoint接口 | 操作 | 路径 | 参数 | 方式 | | --- | --- | --- | --- | | 增加网关路由 | `http://`[控制台IP:PORT]/route/add/spring-cloud-gateway/{serviceId} | 单个动态路由配置 | POST | | 修改网关路由 | `http://`[控制台IP:PORT]/route/modify/spring-cloud-gateway/{serviceId} | 单个动态路由配置 | POST | | 删除网关路由 | `http://`[控制台IP:PORT]/route/delete/spring-cloud-gateway/{serviceId}/{routeId} | 无 | DELETE | | 更新全部网关路由 | `http://`[控制台IP:PORT]/route/update-all/spring-cloud-gateway/{serviceId} | 多个动态路由配置 | GET | | 查看全部网关路由| `http://`[控制台IP:PORT]/route/view-all/spring-cloud-gateway/{serviceId} | 无 | GET | #### Spring-Cloud-Gateway网关订阅配置中心 网关订阅配置中心的使用方式,如下 - Key为 - Nacos、Redis、Zookeeper配置中心,Group为{group},DataId为{网关serviceId}-dynamic-route - Apollo、Consul、Etcd配置中心,Key的格式为{group}-{网关serviceId}-dynamic-route - {group}为注册中心元数据group值 - Value参考[Spring-Cloud-Gateway网关动态路由配置](#Spring-Cloud-Gateway网关动态路由配置) ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide7-9.jpg) 支持如下开关开启该动能,默认是关闭的 ``` # 开启和关闭网关订阅配置中心的动态路由策略。缺失则默认为false spring.application.strategy.gateway.dynamic.route.enabled=true ``` 配置中心配置的网关动态路由推送到网关后,网关会自动根据已经存在的路由表进行判断后实施增删改操作,而不是全部清空后再全部插入,这样有助于提高性能和安全性。网关控制台上会打印出如下日志 ```java --- Gateway Dynamic Routes Update Information ---- Total count=3 Added count=1 Modified count=1 Deleted count=1 -------------------------------------------------- ``` #### Spring-Cloud-Gateway网关事件总线通知的订阅 ```java @EventBus public class MySubscriber { @Subscribe public void onGatewayStrategyRouteAdded(GatewayStrategyRouteAddedEvent gatewayStrategyRouteAddedEvent) { System.out.println("增加网关路由=" + gatewayStrategyRouteAddedEvent.getGatewayStrategyRouteEntity()); } @Subscribe public void onGatewayStrategyRouteModified(GatewayStrategyRouteModifiedEvent gatewayStrategyRouteModifiedEvent) { System.out.println("修改网关路由=" + gatewayStrategyRouteModifiedEvent.getGatewayStrategyRouteEntity()); } @Subscribe public void onGatewayStrategyRouteDeleted(GatewayStrategyRouteDeletedEvent gatewayStrategyRouteDeletedEvent) { System.out.println("删除网关路由=" + gatewayStrategyRouteDeletedEvent.getRouteId()); } @Subscribe public void onGatewayStrategyRouteUpdatedAll(GatewayStrategyRouteUpdatedAllEvent gatewayStrategyRouteUpdatedAllEvent) { System.out.println("更新全部网关路由=" + gatewayStrategyRouteUpdatedAllEvent.getGatewayStrategyRouteEntityList()); } } ``` ### Zuul网关动态路由 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:Zuul网关在自动路由模式下,动态路由可以工作 #### Zuul网关动态路由配置 ① 精简配置 ``` [ { "id": "route0", "serviceId": "discovery-guide-service-a", "path": "/discovery-guide-service-a/**" }, { "id": "route1", "serviceId": "discovery-guide-service-a", "path": "/x/**" }, { "id": "route2", "serviceId": "discovery-guide-service-a", "path": "/y/**" } ] ``` 如果希望一个服务只映射一个动态路由路径,则不需要id,可以简化为 ``` [ { "serviceId": "discovery-guide-service-a", "path": "/x/**" } ] ``` ② 完整配置 ``` [ { "id": "route0", "serviceId": "discovery-guide-service-a", "path": "/discovery-guide-service-a/**", "url": null, "stripPrefix": true, "retryable": null, "sensitiveHeaders": [], "customSensitiveHeaders": false }, { "id": "route1", "serviceId": "discovery-guide-service-a", "path": "/x/**", "url": null, "stripPrefix": true, "retryable": null, "sensitiveHeaders": [], "customSensitiveHeaders": false }, { "id": "route2", "serviceId": "discovery-guide-service-a", "path": "/y/**", "url": null, "stripPrefix": true, "retryable": null, "sensitiveHeaders": [], "customSensitiveHeaders": false } ] ``` #### Zuul网关Rest-Endpoint ① Zuul网关的Rest Endpoint接口 | 操作 | 路径 | 参数 | 方式 | | --- | --- | --- | --- | | 增加网关路由 | `http://`[网关IP:PORT]/zuul-route/add | 单个动态路由配置 | POST | | 修改网关路由 | `http://`[网关IP:PORT]/zuul-route/modify | 单个动态路由配置 | POST | | 删除网关路由 | `http://`[网关IP:PORT]/zuul-route/delete/{routeId} | 无 | DELETE | | 更新全部网关路由 | `http://`[网关IP:PORT]/zuul-route/update-all | 多个动态路由配置 | POST | | 根据路由Id查看网关路由 | `http://`[网关IP:PORT]/zuul-route/view/{routeId} | 无 | GET | | 查看全部网关路由| `http://`[网关IP:PORT]/zuul-route/view-all | 无 | GET | ② 控制台的Rest Endpoint接口 | 操作 | 路径 | 参数 | 方式 | | --- | --- | --- | --- | | 增加网关路由 | `http://`[控制台IP:PORT]/route/add/zuul/{serviceId} | 单个动态路由配置 | POST | | 修改网关路由 | `http://`[控制台IP:PORT]/route/modify/zuul/{serviceId} | 单个动态路由配置 | POST | | 删除网关路由 | `http://`[控制台IP:PORT]/route/delete/zuul/{serviceId}/{routeId} | 无 | DELETE | | 更新全部网关路由 | `http://`[控制台IP:PORT]/route/zuul/update-all/{serviceId} | 多个动态路由配置 | GET | | 查看全部网关路由| `http://`[控制台IP:PORT]/route/zuul/view-all/{serviceId} | 无 | GET | #### Zuul网关订阅配置中心 网关订阅配置中心的使用方式,如下 - Key为 - Nacos、Redis、Zookeeper配置中心,Group为{group},DataId为{网关serviceId}-dynamic-route - Apollo、Consul、Etcd配置中心,Key的格式为{group}-{网关serviceId}-dynamic-route - {group}为注册中心元数据group值 - Value参考[Zuul网关动态路由配置](#Zuul网关动态路由配置) ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide7-10.jpg) 支持如下开关开启该动能,默认是关闭的 ``` # 开启和关闭网关订阅配置中心的动态路由策略。缺失则默认为false spring.application.strategy.zuul.dynamic.route.enabled=true ``` 配置中心配置的网关动态路由推送到网关后,网关会自动根据已经存在的路由表进行判断后实施增删改操作,而不是全部清空后再全部插入,这样有助于提高性能和安全性。网关控制台上会打印出如下日志 ```java ----- Zuul Dynamic Routes Update Information ----- Total count=3 Added count=1 Modified count=1 Deleted count=1 -------------------------------------------------- ``` #### Zuul网关事件总线通知的订阅 ```java @EventBus public class MySubscriber { @Subscribe public void onZuulStrategyRouteAdded(ZuulStrategyRouteAddedEvent zuulStrategyRouteAddedEvent) { System.out.println("增加网关路由=" + zuulStrategyRouteAddedEvent.getZuulStrategyRouteEntity()); } @Subscribe public void onZuulStrategyRouteModified(ZuulStrategyRouteModifiedEvent zuulStrategyRouteModifiedEvent) { System.out.println("修改网关路由=" + zuulStrategyRouteModifiedEvent.getZuulStrategyRouteEntity()); } @Subscribe public void onZuulStrategyRouteDeleted(ZuulStrategyRouteDeletedEvent zuulStrategyRouteDeletedEvent) { System.out.println("删除网关路由=" + zuulStrategyRouteDeletedEvent.getRouteId()); } @Subscribe public void onZuulStrategyRouteUpdatedAll(ZuulStrategyRouteUpdatedAllEvent zuulStrategyRouteUpdatedAllEvent) { System.out.println("更新全部网关路由=" + zuulStrategyRouteUpdatedAllEvent.getZuulStrategyRouteEntityList()); } } ``` ## 统一配置订阅执行器 统一配置订阅执行器,基于Nacos、Apollo、Consul、Etcd、Redis、Zookeeper六种配置中心,通过封装适配成同样的写法,通过切换继承类,可切换配置中心,无须修改其它代码 Spring Cloud配置动态刷新机制固化在一个比较单一的场景(例如,通过@Value方式)里,无法满足更灵活更高级的订阅场景,例如,Spring Cloud Gateway和Zuul网关通过改变配置中心的路由信息无法动态刷新路由路径 本框架提供更简单灵活的实现方式,以Nacos为例子,使用者先确定订阅的Group和DataId,在Nacos界面填入这两个参数对应的配置内容,然后通过回调方法处理业务逻辑。具体使用方式,如下 ```java // 把继承类(extends)换成如下任何一个,即可切换配置中心,代码无需任何变动 // 1. NacosProcessor // 2. ApolloProcessor // 3. ConsulProcessor // 4. EtcdProcessor // 5. ZookeeperProcessor // 6. RedisProcessor // Group和DataId自行决定,需要注意 // 1. 对于Nacos、Redis、Zookeeper配置中心,Group和DataId需要和界面相对应 // 2. 对于Apollo、Consul、Etcd配置中心,Key的格式为Group-DataId // 可以同时支持多个配置中心的订阅,需要同时创建多个不同的Processor,同时@Bean方式进入到Spring容器 public class MyConfigProcessor extends NacosProcessor { @Override public void beforeInitialization() { System.out.println("订阅器初始化之前,可以做一些工作"); } @Override public void afterInitialization() { System.out.println("订阅器初始化之后,可以做一些工作"); } @Override public String getGroup() { return "b"; } @Override public String getDataId() { return "a"; } @Override public String getDescription() { // description为日志打印显示而设置,作用是帮助使用者在日志上定位订阅器是否在执行 return "My subscription"; } @Override public void callbackConfig(String config) { // config为配置中心对应键值的内容变更,使用者可以根据此变更对业务模块做回调处理 System.out.println("监听配置改变:config=" + config); } } ``` 统一配置订阅执行器可以单独运行在Spring Boot应用上,它是一个通用的解决方案 - 如果使用者希望脱离Nepxion Discovery以及Spring Cloud框架,使用者只需要引入如下依赖之一即可 - 如果使用者正在使用Nepxion Discovery框架,则跟随它的内置引入即可,不需要额外引入如下依赖之一 ```xml ${project.groupId} discovery-common-nacos ${discovery.version} ${project.groupId} discovery-common-apollo ${discovery.version} ${project.groupId} discovery-common-redis ${discovery.version} ${project.groupId} discovery-common-zookeeper ${discovery.version} ${project.groupId} discovery-common-consul ${discovery.version} ${project.groupId} discovery-common-etcd ${discovery.version} ``` 具体用法和配置,请参考[6.x.x指南示例配置版](https://github.com/Nepxion/DiscoveryGuide/tree/6.x.x-config),分支为6.x.x-config ## 规则策略定义 ### 规则策略格式定义 ![](http://nepxion.gitee.io/discovery/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"}
{"a":"1", "b":"2", "c":"3"}
{"discovery-springcloud-example-a":"20210601-222214-909-1146-372-698", "discovery-springcloud-example-b":"20210601-222623-277-4978-633-279", "discovery-springcloud-example-c":"20210601-222728-133-2597-222-609"}
{"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"}
``` ## 规则策略推送 ### 基于远程配置中心的规则策略订阅推送 Apollo订阅推送界面 ![](http://nepxion.gitee.io/discovery/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/discovery/docs/discovery-doc/Nacos2.jpg) - 参考Nacos官方文档[https://github.com/alibaba/nacos](https://github.com/alibaba/nacos)相关文档,搭建Nacos环境,以及熟悉相关的基本操作 - 添加配置步骤跟Apollo配置界面中的【在页面中添加配置】操作项相似 Redis订阅推送界面 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Redis.jpg) Consul订阅推送界面 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Consul.jpg) Etcd订阅推送界面 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Etcd.jpg) Zookeeper订阅推送界面 略 ### 基于Swagger和Rest的规则策略推送 服务侧单个推送界面 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Swagger1.jpg) 控制平台批量推送界面 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Swagger2.jpg) 除了提供基本的Swagger功能之外,内置模块还对使用者提供扩展 - 自定义Swagger接口利用内置的SwaggerConfiguration来初始化,这样使用者可以不需要定义自己的SwaggerConfiguration。通过如下配置实现 ``` swagger.service.scan.group=your-scan-group swagger.service.scan.packages=your-scan-packages ``` - 自定义内置的基准Docket组名。通过如下配置实现 ``` swagger.service.base.group=your-base-group ``` - 自定义覆盖内置的Swagger配置。通过如下配置实现 ``` # 启动和关闭Swagger。缺失则默认为true swagger.service.enabled=true # Swagger基准Docket组名 swagger.service.base.group=Nepxion Discovery # Swagger自定义Docket组名 swagger.service.scan.group=Admin Center Restful APIs # Swagger自定义扫描目录 swagger.service.scan.packages=your-scan-packages # Swagger描述 swagger.service.description=your-description # Swagger版本 swagger.service.version=6.11.0 # Swagger License名称 swagger.service.license.name=Apache License 2.0 # Swagger License链接 swagger.service.license.url=http://www.apache.org/licenses/LICENSE-2.0 # Swagger联系人名称 swagger.service.contact.name=Nepxion # Swagger联系人网址 swagger.service.contact.url=https://github.com/Nepxion/Discovery # Swagger联系人邮件 swagger.service.contact.email=1394997@qq.com # Swagger服务条件网址 swagger.service.termsOfService.url=http://www.nepxion.com ``` - 自定义基于Access Token Header的Swagger授权,包括全局授权和接口级授权。使用者通过如下方式进行扩展支持,可以选择其中一种,也可以两种并存。当两种并存的时候,全局授权优先于接口级授权 ```java @Configuration @ConditionalOnProperty(value = DiscoverySwaggerConstant.SWAGGER_SERVICE_ENABLED, matchIfMissing = true) public class SwaggerAutoConfiguration { // Access Token Header全局授权 @Bean public List swaggerSecuritySchemes() { return Collections.singletonList(new ApiKey(DiscoveryConstant.N_D_ACCESS_TOKEN, DiscoveryConstant.N_D_ACCESS_TOKEN, "header")); } @Bean public List swaggerSecurityContexts() { return Collections.singletonList( SecurityContext .builder() .securityReferences(Collections.singletonList(new SecurityReference(DiscoveryConstant.N_D_ACCESS_TOKEN, scopes()))) .forPaths(PathSelectors.any()) .build()); } private AuthorizationScope[] scopes() { return new AuthorizationScope[] { new AuthorizationScope("global", "accessAnything") }; } // Access Token Header接口级授权 @Bean public List swaggerHeaderParameters() { return Collections.singletonList( new ParameterBuilder() .name(DiscoveryConstant.N_D_ACCESS_TOKEN) .description("Access Token。格式:" + DiscoveryConstant.BEARER + "空格${access-token}。当全局授权(Authorize)后,此处不必填写") .modelRef(new ModelRef("string")) .parameterType("header") .defaultValue(DiscoveryConstant.BEARER + " ${access-token}") .required(false) .build()); } } ``` 把SwaggerAutoConfiguration加入到src/main/resources/META-INF/spring.factories进行自动装配 ### 基于图形化桌面端和Web端的规则策略推送 参考[全链路蓝绿灰度发布编排建模和流量侦测](#全链路蓝绿灰度发布编排建模和流量侦测) ## 全链路环境隔离和路由 基于服务实例的元数据Metadata的env参数和全链路传递的环境Header值进行对比实现隔离,当从网关传递来的环境Header(n-d-env)值和提供端实例的元数据Metadata环境配置值相等才能调用。环境隔离下,调用端实例找不到符合条件的提供端实例,把流量路由到一个通用或者备份环境 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/IsolationEnvironment.jpg) ### 全链路环境隔离 在网关或者服务端,配置环境元数据,在同一套环境下,env值必须是一样的,这样才能达到在同一个注册中心下,环境隔离的目的 ``` spring.cloud.nacos.discovery.metadata.env=env1 ``` ### 全链路环境路由 在环境隔离执行的时候,如果无法找到对应的环境,则会路由到一个通用或者备份环境,默认为env为common的环境,可以通过如下参数进行更改 ``` # 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common spring.application.strategy.environment.route=common ``` ![](http://nepxion.gitee.io/discovery/docs/icon-doc/warning.png) 需要注意 - 如果存在环境,优先寻址环境的服务实例 - 如果不存在环境,则寻址Common环境的服务实例(未设置元数据Metadata的env参数的服务实例也归为Common环境) - 如果Common环境也不存在,则调用失败 - 如果没有传递环境Header(n-d-env)值,则执行Spring Cloud Ribbon轮询策略 - 环境隔离和路由适用于测试环境,性能压测等场景 ## 全链路可用区亲和性隔离和路由 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/IsolationZone.jpg) ### 全链路可用区亲和性隔离 基于调用端实例和提供端实例的元数据Metadata的zone配置值进行对比实现隔离 ``` spring.cloud.nacos.discovery.metadata.zone=zone ``` 通过如下开关进行开启和关闭 ``` # 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false spring.application.strategy.zone.affinity.enabled=false ``` ### 全链路可用区亲和性路由 在可用区亲和性隔离执行的时候,调用端实例找不到同一可用区的提供端实例,把流量路由到其它可用区或者不归属任何可用区 通过如下开关进行开启和关闭 ``` # 启动和关闭可用区亲和性失败后的路由,即调用端实例没有找到同一个可用区的提供端实例的时候,当开关打开,可路由到其它可用区或者不归属任何可用区,当开关关闭,则直接调用失败。缺失则默认为true spring.application.strategy.zone.route.enabled=true ``` ![](http://nepxion.gitee.io/discovery/docs/icon-doc/warning.png) 需要注意 - 不归属任何可用区,含义是服务实例未设置任何zone元数据值。可用区亲和性路由功能,是为了尽量保证流量不损失 - 本框架提供的可用区亲和性功能适用于一切注册中心 - 如果采用Eureka注册中心,Ribbon在Eureka Client上会自动开启可用区亲和性功能,跟本框架提供的功能相似。它不提供禁止“可用区亲和性失败后的路由”,如果使用者希望实现“找不到相同可用区,直接调用失败”的功能,可以结合本框架上述两个开关来实现 ## 全链路服务隔离和准入 ### 消费端服务隔离 #### 基于组负载均衡隔离 元数据中的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/discovery/docs/discovery-doc/DiscoveryGuide6-1.jpg) 如果加上n-d-service-group=discovery-guide-group的Header,那么两者保持Group相同,则调用通过。这是解决异构系统调用微服务被隔离的一种手段 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide6-2.jpg) ### 注册发现隔离和准入 #### 基于IP地址黑白名单注册准入 微服务启动的时候,禁止指定的IP地址注册到注册中心。支持黑/白名单,白名单表示只允许指定IP地址前缀注册,黑名单表示不允许指定IP地址前缀注册 - 全局过滤,指注册到服务注册发现中心的所有微服务,只有IP地址包含在全局过滤字段的前缀中,都允许注册(对于白名单而言),或者不允许注册(对于黑名单而言) - 局部过滤,指专门针对某个微服务而言,那么真正的过滤条件是全局过滤 + 局部过滤结合在一起 #### 基于最大注册数限制注册准入 微服务启动的时候,一旦微服务集群下注册的实例数目已经达到上限(可配置),将禁止后续的微服务进行注册 - 全局配置值,只下面配置所有的微服务集群,最多能注册多少个 - 局部配置值,指专门针对某个微服务而言,如果该值如存在,全局配置值失效 #### 基于IP地址黑白名单发现准入 微服务启动的时候,禁止指定的IP地址被服务发现。它使用的方式和[基于IP地址黑白名单注册准入](#基于IP地址黑白名单注册准入)一致 #### 自定义注册发现准入 - 集成AbstractRegisterListener,实现自定义禁止注册 - 集成AbstractDiscoveryListener,实现自定义禁止被发现。需要注意,在Consul下,同时会触发service和management两个实例的事件,需要区别判断 - 集成AbstractLoadBalanceListener,实现自定义禁止被负载均衡 ## 全链路服务限流熔断降级权限 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 由于如下功能早于Spring Cloud Alibaba Sentinel而产生,下述功能也可以通过Spring Cloud Alibaba Sentinel功能来实现 Sentinel订阅配置中心的使用方式,如下 - Key为 - Nacos、Redis、Zookeeper配置中心,Group为{group},DataId为{serviceId}-{规则类型} - Apollo、Consul、Etcd配置中心,Key的格式为{group}-{serviceId}-{规则类型} - {group}为注册中心元数据group值 - Value为Json格式的规则 支持远程配置中心和本地规则文件的读取逻辑,即优先读取远程配置,如果不存在或者规则错误,则读取本地规则文件。动态实现远程配置中心对于规则的热刷新 支持如下开关开启该动能,默认是关闭的 ``` # 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展。缺失则默认为false spring.application.strategy.sentinel.datasource.enabled=true ``` ### 原生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/discovery/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/discovery/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/discovery/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/discovery/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/discovery/docs/discovery-doc/DiscoveryGuide7-5.jpg) ### 基于Sentinel-LimitApp扩展的防护 该功能对于上面5种规则都有效,这里以授权规则展开阐述 授权规则中,limitApp,如果有多个,可以通过“,”分隔。"strategy": 0 表示白名单,"strategy": 1 表示黑名单 支持如下开关开启该动能,默认是关闭的 ``` # 启动和关闭Sentinel LimitApp限流等功能。缺失则默认为false spring.application.strategy.sentinel.limit.app.enabled=true ``` #### 基于服务名的防护 修改配置项Sentinel Request Origin Key为服务名Header,修改授权规则中limitApp为对应的服务名,可实现基于服务名的防护 配置项,该配置项默认为n-d-service-id,可以不配置 ``` spring.application.strategy.sentinel.request.origin.key=n-d-service-id ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示所有discovery-guide-service-a服务允许访问discovery-guide-service-b服务 ``` [ { "resource": "sentinel-resource", "limitApp": "discovery-guide-service-a", "strategy": 0 } ] ``` #### 基于组的防护 修改配置项Sentinel Request Origin Key为组Header,修改授权规则中limitApp为对应的组名,可实现基于组的防护 配置项 ``` spring.application.strategy.sentinel.request.origin.key=n-d-service-group ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示隶属my-group组的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "my-group", "strategy": 0 } ] ``` #### 基于版本的防护 修改配置项Sentinel Request Origin Key为版本Header,修改授权规则中limitApp为对应的版本,可实现基于版本的防护机制 配置项 ``` spring.application.strategy.sentinel.request.origin.key=n-d-service-version ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示版本为1.0的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "1.0", "strategy": 0 } ] ``` #### 基于区域的防护 修改配置项Sentinel Request Origin Key为区域Header,修改授权规则中limitApp为对应的区域,可实现基于区域的防护 配置项 ``` spring.application.strategy.sentinel.request.origin.key=n-d-service-region ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示区域为dev的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "dev", "strategy": 0 } ] ``` #### 基于环境的防护 修改配置项Sentinel Request Origin Key为环境Header,修改授权规则中limitApp为对应的环境,可实现基于环境的防护 配置项 ``` spring.application.strategy.sentinel.request.origin.key=n-d-service-env ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示环境为env1的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "env1", "strategy": 0 } ] ``` #### 基于可用区的防护 修改配置项Sentinel Request Origin Key为可用区Header,修改授权规则中limitApp为对应的可用区,可实现基于可用区的防护 配置项 ``` spring.application.strategy.sentinel.request.origin.key=n-d-service-zone ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示可用区为zone1的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "zone1", "strategy": 0 } ] ``` #### 基于IP地址和端口的防护 修改配置项Sentinel Request Origin Key为IP地址和端口Header,修改授权规则中limitApp为对应的区域值,可实现基于IP地址和端口的防护 配置项 ``` spring.application.strategy.sentinel.request.origin.key=n-d-service-address ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示地址和端口为192.168.0.88:8081和192.168.0.88:8082的服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "192.168.0.88:8081,192.168.0.88:8082", "strategy": 0 } ] ``` #### 自定义组合式的防护 通过适配类实现自定义组合式的防护,支持自定义Header、Parameter、Cookie参数的防护,自定义业务参数的防护,以及自定义前两者组合式的防护 ```java // 自定义版本号+地域名,实现组合式熔断 public class MySentinelStrategyRequestOriginAdapter extends DefaultSentinelStrategyRequestOriginAdapter { @Override public String parseOrigin(HttpServletRequest request) { String version = request.getHeader(DiscoveryConstant.N_D_SERVICE_VERSION); String location = request.getHeader("location"); return version + "&" + location; } } ``` 在配置类里@Bean方式进行适配类创建 ```java @Bean public SentinelStrategyRequestOriginAdapter sentinelStrategyRequestOriginAdapter() { return new MySentinelStrategyRequestOriginAdapter(); } ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示版本为1.0且传入Header的location=shanghai,同时满足这两个条件下的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "1.0&shanghai", "strategy": 0 } ] ``` 运行效果 - 当传递的Header中location=shanghai,当全链路调用中,API网关负载均衡discovery-guide-service-a服务到1.0版本后再去调用discovery-guide-service-b服务,最终调用成功 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/DiscoveryGuide7-6.jpg) - 当传递的Header中location=beijing,不满足条件,最终调用在discovery-guide-service-b服务端被拒绝掉 ![](http://nepxion.gitee.io/discovery/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/discovery/docs/discovery-doc/DiscoveryGuide7-8.jpg) ### Sentinel-Rest-Endpoint ① 服务的Rest Endpoint接口 | 操作 | 路径 | 参数 | 方式 | | --- | --- | --- | --- | | 更新流控规则列表 | `http://`[服务IP:PORT]/sentinel-core/update-flow-rules | 多个规则配置 | POST | | 清除流控规则列表 | `http://`[服务IP:PORT]/sentinel-core/clear-flow-rules | 无 | POST | | 查看流控规则列表 | `http://`[服务IP:PORT]/sentinel-core/view-flow-rules | 无 | GET | | 更新降级规则列表 | `http://`[服务IP:PORT]/sentinel-core/update-degrade-rules | 多个规则配置 | POST | | 清除降级规则列表 | `http://`[服务IP:PORT]/sentinel-core/clear-degrade-rules | 无 | POST | | 查看降级规则列表 | `http://`[服务IP:PORT]/sentinel-core/view-degrade-rules | 无 | GET | | 更新授权规则列表 | `http://`[服务IP:PORT]/sentinel-core/update-authority-rules | 多个规则配置 | POST | | 清除授权规则列表 | `http://`[服务IP:PORT]/sentinel-core/clear-authority-rules | 无 | POST | | 查看授权规则列表 | `http://`[服务IP:PORT]/sentinel-core/view-authority-rules | 无 | GET | | 更新系统规则列表 | `http://`[服务IP:PORT]/sentinel-core/update-system-rules | 多个规则配置 | POST | | 清除系统规则列表 | `http://`[服务IP:PORT]/sentinel-core/clear-system-rules | 无 | POST | | 查看系统规则列表 | `http://`[服务IP:PORT]/sentinel-core/view-system-rules | 无 | GET | | 更新热点参数流控规则列表 | `http://`[服务IP:PORT]/sentinel-param/update-param-flow-rules | 多个规则配置 | POST | | 清除热点参数流控规则列表 | `http://`[服务IP:PORT]/sentinel-param/clear-param-flow-rules | 无 | POST | | 查看热点参数流控规则列表 | `http://`[服务IP:PORT]/sentinel-param/view-param-flow-rules | 无 | GET | ② 控制台的Rest Endpoint接口 | 操作 | 路径 | 参数 | 方式 | | --- | --- | --- | --- | | 批量更新哨兵规则列表 | `http://`[控制台IP:PORT]/sentinel/update/{ruleType}/{serviceId} | 多个规则配置 | POST | | 批量清除哨兵规则列表 | `http://`[控制台IP:PORT]/sentinel/clear/{ruleType}/{serviceId} | 无 | POST | | 批量查看哨兵规则列表 | `http://`[控制台IP:PORT]/sentinel/view/{ruleType}/{serviceId} | 无 | GET | ruleType为哨兵规则类型。取值: flow | degrade | authority | system | param-flow ## 全链路监控 ### 全链路调用链监控 #### 蓝绿灰度埋点调用链监控 ① 内置蓝绿灰度埋点 内置蓝绿灰度埋点,包括如下 ``` 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地址和端口屏蔽值 ``` - n-d-service开头的埋点代表是服务自身的属性 - n-d-开头的埋点是蓝绿灰度传递的策略路由值 ② 外置自定义埋点 用户可以自定义外置埋点 - 自定义要传递的调用链参数,例如:traceId,spanId等 - 自定义要传递的业务参数,例如:mobile,user等 ③ 跟调用链中间件集成 - 集成OpenTracing + Jaeger蓝绿灰度全链路监控 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Jaeger2.jpg) ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/JaegerPremium1.jpg) - 集成OpenTracing + SkyWalking蓝绿灰度全链路监控 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/SkyWalking1.jpg) ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/SkyWalking2.jpg) #### 蓝绿灰度埋点Debug辅助监控 Debug辅助监控只是通过普通的System.out.println方式输出,便于开发人员在IDE上调试,在生产环境下不建议开启 对于Debug辅助监控功能的开启和关闭,需要通过如下开关做控制 ``` # 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false spring.application.strategy.monitor.enabled=true # 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.rest.intercept.debug.enabled=true # 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.logger.debug.enabled=true ``` ① 网关端和服务端自身蓝绿灰度埋点Debug辅助监控 ``` ----------------------- Logger Debug ----------------------- trace-id=dade3982ae65e9e1 span-id=997e31021e9fce20 n-d-service-group=discovery-guide-group n-d-service-type=service n-d-service-id=discovery-guide-service-a n-d-service-address=172.27.208.1:3001 n-d-service-version=1.0 n-d-service-region=dev n-d-service-env=env1 n-d-service-zone=zone1 n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} mobile=13812345678 user= ------------------------------------------------------------ ``` ② 服务端Feign、RestTemplate或者WebClient拦截输入的蓝绿灰度埋点Debug辅助监控 ``` --------- Feign Intercept Input Header Information --------- n-d-service-group=discovery-guide-group n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} n-d-service-type=gateway n-d-service-id=discovery-guide-zuul n-d-service-env=default mobile=13812345678 n-d-service-region=default n-d-service-zone=default n-d-service-address=172.27.208.1:5002 n-d-service-version=1.0 ------------------------------------------------------------ ``` ③ 服务端Feign、RestTemplate或者WebClient调用拦截输出的蓝绿灰度埋点Debug辅助监控 ``` -------- Feign Intercept Output Header Information --------- mobile=[13812345678] n-d-service-address=[172.27.208.1:3001] n-d-service-env=[env1] n-d-service-group=[discovery-guide-group] n-d-service-id=[discovery-guide-service-a] n-d-service-region=[dev] n-d-service-type=[service] n-d-service-version=[1.0] n-d-service-zone=[zone1] n-d-version=[{"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}] ------------------------------------------------------------ ``` #### Sentinel熔断埋点调用链监控 - 集成OpenTracing + Jaeger + Sentinel限流熔断降级权限埋点全链路监控 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/Jaeger6.jpg) - 集成OpenTracing + SkyWalking + Sentinel限流熔断降级权限埋点全链路监控 ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/SkyWalking3.jpg) ![](http://nepxion.gitee.io/discovery/docs/discovery-doc/SkyWalking4.jpg) #### 自定义埋点调用链监控 ① 自定义调用链上下文参数的创建,继承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 LinkedHashMap(); 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 LinkedHashMap(); 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.alarm.enabled=true # 启动和关闭日志输出。缺失则默认为false spring.application.strategy.logger.enabled=true # 日志输出中,是否显示MDC前面的Key。缺失则默认为true spring.application.strategy.logger.mdc.key.shown=true # 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.logger.debug.enabled=true # 启动和关闭调用链输出。缺失则默认为false spring.application.strategy.tracer.enabled=true # 启动和关闭调用链的蓝绿灰度信息以独立的Span节点输出,如果关闭,则蓝绿灰度信息输出到原生的Span节点中(SkyWalking不支持原生模式)。缺失则默认为true spring.application.strategy.tracer.separate.span.enabled=true # 启动和关闭调用链的蓝绿灰度规则策略信息输出。缺失则默认为true spring.application.strategy.tracer.rule.output.enabled=true # 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false spring.application.strategy.tracer.exception.detail.output.enabled=true # 启动和关闭类方法上入参和出参输出到调用链。缺失则默认为false spring.application.strategy.tracer.method.context.output.enabled=true # 显示在调用链界面上蓝绿灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION spring.application.strategy.tracer.span.value=NEPXION # 显示在调用链界面上蓝绿灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery # 启动和关闭Sentinel调用链上规则在Span上的输出。缺失则默认为true spring.application.strategy.tracer.sentinel.rule.output.enabled=true # 启动和关闭Sentinel调用链上方法入参在Span上的输出。缺失则默认为false spring.application.strategy.tracer.sentinel.args.output.enabled=true ``` ![](http://nepxion.gitee.io/discovery/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包版本兼容性的场景 ### 全链路日志监控 #### 蓝绿灰度埋点日志监控 蓝绿灰度埋点日志输出,需要使用者配置logback.xml或者log4j.xml日志格式,参考如下 ```xml discovery %date %level [%thread] [%X{trace-id}] [%X{span-id}] [%X{n-d-service-group}] [%X{n-d-service-type}] [%X{n-d-service-app-id}] [%X{n-d-service-id}] [%X{n-d-service-address}] [%X{n-d-service-version}] [%X{n-d-service-region}] [%X{n-d-service-env}] [%X{n-d-service-zone}] [%X{mobile}] [%X{user}] %logger{10} [%file:%line] - %msg%n UTF-8 log/discovery-%d{yyyy-MM-dd}.%i.log 50MB INFO true 0 512 discovery %date %level [%thread] [%X{trace-id}] [%X{span-id}] [%X{n-d-service-group}] [%X{n-d-service-type}] [%X{n-d-service-app-id}] [%X{n-d-service-id}] [%X{n-d-service-address}] [%X{n-d-service-version}] [%X{n-d-service-region}] [%X{n-d-service-env}] [%X{n-d-service-zone}] [%X{mobile}] [%X{user}] %logger{10} [%file:%line] - %msg%n UTF-8 INFO ``` ### 全链路告警监控 #### 蓝绿灰度告警监控 全链路蓝绿灰度实施过程中,使用者需要快速判断蓝绿灰度是否已经生效,可以通过 - Debug开关开启,通过控制台输出去判断相关蓝绿灰度Header是否传递,是否相同 - 依托监控调用链中间件,通过埋点输出去判断相关蓝绿灰度Header是否传递,是否相同 上述方式需要人工观察和干预,并不友好,使用者也可以通过集成如下蓝绿灰度告警监控模块来实现 ① 网关和服务加上下面的类 ```java @EventBus public class MySubscriber { @Subscribe public void onAlarm(StrategyAlarmEvent strategyAlarmEvent) { // 在本告警中告警类型为StrategyConstant.STRATEGY_CONTEXT_ALARM的静态变量值,表示蓝绿灰度上下文告警 String alarmType = strategyAlarmEvent.getAlarmType(); // 通过事件总线把告警数据alarmMap存储到ElasticSearch、MessageQueue、数据库等 Map alarmMap = strategyAlarmEvent.getAlarmMap(); } } ``` 在配置类里@Bean方式进行订阅类创建 ```java @Bean public MySubscriber mySubscriber() { return new MySubscriber(); } ``` 并开启如下开关 ``` # 启动和关闭告警,一旦关闭,蓝绿灰度上下文输出都将关闭。缺失则默认为false spring.application.strategy.alarm.enabled=true ``` ② 通过事件总线把告警数据存储到ElasticSearch、MessageQueue、数据库等 ③ 根据端到端的traceId对应的蓝绿灰度Header是否传递,是否相同,判断蓝绿灰度是否成功 ④ 如果不相同,结合DevOps系统发送告警邮件或者通知 ⑤ 告警数据具体信息列表参考源码: ```java com.nepxion.discovery.plugin.strategy.monitor.DefaultStrategyAlarm ``` 示例如下: ``` {n-d-service-group=discovery-guide-group, n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"}, n-d-service-type=service, n-d-service-id=discovery-guide-service-b, n-d-service-env=env1, mobile=, n-d-service-region=qa, span-id=c37b54d7fec6bd07, n-d-service-zone=zone1, n-d-service-address=192.168.0.107:4001, trace-id=64c79e1ef68eecf3, n-d-service-version=1.0} ``` ## 全链路服务侧注解 服务侧对于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) ## 元数据流量染色 ### 基于Git插件自动创建版本号 通过集成插件git-commit-id-plugin,通过产生git信息文件的方式,获取git.commit.id(最后一次代码的提交ID)或者git.build.version(对应到Maven工程的版本)来自动创建版本号,这样就可以避免使用者手工维护版本号 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:当两者都启用的时候,手工配置的版本号优先级要高于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/discovery/docs/icon-doc/warning.png) 需要注意,一般情况下,上述两个地方的配置都同时保持默认即可。对于一些特殊的用法,两个地方的配置项用法必须保持一致,例如 ``` # 输出到工程根目录下 ${project.basedir}/git.json # 输出成json格式 json ``` 下面配置项必须上面两个配置项的操作逻辑相同 ``` # 输出到工程根目录下的json格式文件 spring.application.git.generator.path=file:git.json ``` 内置基于Swagger的Rest接口,可以供外部查询当前服务的Git信息 | 操作 | 路径 | 参数 | 方式 | | --- | --- | --- | --- | | 获取Git信息的Map格式 | `http://`[IP:PORT]/git/map | 无 | GET | | 获取Git信息的文本格式 | `http://`[IP:PORT]/git/text | 无 | GET | ### 基于服务名前缀自动创建组名 通过指定长度截断或者标志截断服务名的前缀来自动创建组名,这样就可以避免使用者手工维护组名 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/tip.png) 提醒:当两者都启用的时候,手工配置的组名优先级要高于截断方式的组名 - 增加配置项 ``` # 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false spring.application.group.generator.enabled=true # 服务名前缀的截断长度,必须大于0 spring.application.group.generator.length=15 # 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式 spring.application.group.generator.character=- ``` ### 基于运维平台运行参数自动创建版本号 运维平台在启动微服务的时候,可以通过参数方式初始化元数据,框架会自动把它注册到远程注册中心。有如下两种方式 - 通过VM arguments来传递,它的用法是前面加-D。支持上述所有的注册组件,它的限制是变量前面必须要加metadata.,推荐使用该方式。例如:-Dmetadata.version=1.0 - 通过Program arguments来传递,它的用法是前面加--。支持Eureka、Zookeeper和Nacos的增量覆盖,Consul由于使用了全量覆盖的tag方式,不适用改变单个元数据的方式。例如:--spring.cloud.nacos.discovery.metadata.version=1.0 - 两种方式尽量避免同时用 如果使用者希望运维侧去决定版本号,那么推荐一种可行性方案,版本号可以表示为日期戳-序号 - 日期戳表示为当天的日期 - 序号表示为当天的发布次数,一般定义为四位,即从0001-9999。序号由运维平台来维护,当天每发布一个版本,序号自加1 这种表示方式具有很强的可读性意义,例如,20210601-0003,表示某一组服务实例蓝绿灰度的版本为2021年6月1日发布的第三个版本 ### 基于用户自定义创建版本号 参考[流量染色配置](#流量染色配置) ## 自动扫描目录 自动扫描目录功能为省掉手工配置扫描目录而设定的,当使用者手工配置了扫描目录,则采用使用者配置的目录,如果没配置,则采用自动扫描目录的方式 如下配置是手工配置扫描目录的样例 ``` # 路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能 spring.application.strategy.scan.packages=com.nepxion.discovery.guide.service ``` ① 自动扫描目录的配置 ``` # 启动和关闭自动扫描目录,当扫描目录未人工配置的时候,可以通过自动扫描方式决定扫描目录。缺失则默认为true spring.application.strategy.auto.scan.packages.enabled=true # 启动和关闭嵌套扫描,嵌套扫描指扫描非本工程下外部包的目录,可以支持多层嵌套。缺失则默认为false spring.application.strategy.auto.scan.recursion.enabled=false ``` ② 自动扫描目录的逻辑 在假设的场景中,SpringBoot入口设定扫描目录为com.a,com.a目录下有个Spring对象通过ComponentScan方式设定扫描目录为com.b,com.b目录下有个Spring对象通过ComponentScan方式设定扫描目录为com.c,那么最终计算出来的目录为 嵌套扫描下,得到的扫描目录是 ``` SpringBoot入口所在的目录;com.a;com.b;com.c ``` 非嵌套扫描下,得到的扫描目录是 ``` SpringBoot入口所在的目录;com.a ``` ③ 扩展获取自动扫描目录 使用者可以通过如下代码得到自动扫描目录 ```java public class MyService { @Autowired private StrategyPackagesExtractor strategyPackagesExtractor; public void getPackages() { // 获取@SpringBootApplication所在类入口的扫描目录(一般只有一个),返回List类型 strategyPackagesExtractor.getBasePackagesList(); // 获取所有嵌套的扫描目录(包括当前工程的所有类中@SpringBootApplication和@ComponentScan注解设定的扫描目录),返回List类型 strategyPackagesExtractor.getScanningPackagesList(); // 上面两种目录的相加,返回List类型 strategyPackagesExtractor.getAllPackagesList(); } } ``` ## 配置文件 ### 流量染色配置 统一注册中心配置方式,适用于所有注册中心 ``` spring.cloud.discovery.metadata.group=xxx-service-group spring.cloud.discovery.metadata.version=1.0 spring.cloud.discovery.metadata.region=dev spring.cloud.discovery.metadata.env=env1 spring.cloud.discovery.metadata.zone=zone1 ``` 不同注册中心原生配置方式 ``` # 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 ``` ![](http://nepxion.gitee.io/discovery/docs/icon-doc/information.png) 〔Spring Cloud 202x版〕特别提醒 > 对于Spring Cloud 202x版,由于它重构了Consul元数据的方式,需要通过如下方式配置 ``` # Consul config for discovery spring.cloud.consul.discovery.metadata.group=xxx-service-group spring.cloud.consul.discovery.metadata.version=1.0 spring.cloud.consul.discovery.metadata.region=dev spring.cloud.consul.discovery.metadata.env=env1 spring.cloud.consul.discovery.metadata.zone=zone1 ``` > 对于用户自定义的Consul元数据的Key,不能带有包含“.”,“@”等字符,否则服务无法启动,但允许包含“_”,“-”等字符,参考如下配置 ``` # 合法格式 spring.cloud.consul.discovery.metadata.my_data=abc spring.cloud.consul.discovery.metadata.my-data=abc # 非法格式 spring.cloud.consul.discovery.metadata.my.data=abc spring.cloud.consul.discovery.metadata.my@data=abc ``` ### 中间件属性配置 ① 注册中心配置 - Nacos注册中心配置 ``` # Nacos config for discovery spring.cloud.nacos.discovery.server-addr=localhost:8848 # spring.cloud.nacos.discovery.namespace=discovery ``` - Eureka注册中心配置 ``` # Eureka config for discovery eureka.client.serviceUrl.defaultZone=http://localhost:9528/eureka/ eureka.instance.preferIpAddress=true ``` - Consul注册中心配置 ``` # Consul config for discovery spring.cloud.consul.host=localhost spring.cloud.consul.port=8500 spring.cloud.consul.discovery.preferIpAddress=true ``` - Zookeeper注册中心配置 ``` # Zookeeper config for discovery spring.cloud.zookeeper.connectString=localhost:2181 spring.cloud.zookeeper.discovery.instancePort=${server.port} spring.cloud.zookeeper.discovery.root=/spring-cloud spring.cloud.zookeeper.discovery.preferIpAddress=true ``` ② 配置中心配置 蓝绿灰度发布专用配置 - Apollo配置中心配置 ``` # Apollo config for rule app.id=discovery apollo.meta=http://localhost:8080 # apollo.plugin.namespace=application ``` - Nacos配置中心配置 ``` # Nacos config for rule spring.cloud.nacos.config.server-addr=localhost:8848 # spring.cloud.nacos.config.namespace=application ``` - Redis配置中心配置 ``` # Redis config for rule spring.redis.host=localhost spring.redis.port=6379 spring.redis.password= spring.redis.database=0 ``` - Zookeeper配置中心配置 ``` # Zookeeper config for rule zookeeper.connect-string=localhost:2181 zookeeper.retry-count=3 zookeeper.sleep-time=3000 ``` - Consul配置中心配置 ``` # Consul config for rule consul.host=localhost consul.port=8500 consul.timeout=1 consul.token= ``` - Etcd配置中心配置 ``` # Etcd config for rule etcd.server.addr=http://localhost:2379 etcd.username= etcd.password= ``` ③ 监控中心配置 - OpenTracing + Jaeger监控中心配置 ``` # OpenTracing config for jaeger opentracing.jaeger.enabled=true opentracing.jaeger.http-sender.url=http://localhost:14268/api/traces ``` - SkyWalking监控中心配置 ``` -javaagent:C:/opt/skywalking-agent/skywalking-agent.jar -Dskywalking.agent.service_name=discovery-guide-service-a ``` - Spring Boot Admin监控中心配置 ``` # Spring boot admin config spring.boot.admin.client.instance.prefer-ip=true spring.boot.admin.client.url=http://localhost:9728 ``` ④ 异步跨线程Agent配置 ``` -javaagent:C:/opt/discovery-agent/discovery-agent-starter-${discovery.agent.version}.jar -Dthread.scan.packages=com.nepxion.discovery.guide.service.feign ``` ### 功能开关配置 ① 服务端配置 ``` # Plugin core config # 开启和关闭服务注册层面的控制。一旦关闭,服务注册的黑/白名单过滤功能将失效,最大注册数的限制过滤功能将失效。缺失则默认为true spring.application.register.control.enabled=true # 开启和关闭服务发现层面的控制。一旦关闭,服务多版本调用的控制功能将失效,动态屏蔽指定IP地址的服务实例被发现的功能将失效。缺失则默认为true spring.application.discovery.control.enabled=true # 开启和关闭通过Rest方式对规则配置的控制和推送。一旦关闭,只能通过远程配置中心来控制和推送。缺失则默认为true spring.application.config.rest.control.enabled=true # 规则文件的格式,支持xml和json。缺失则默认为xml spring.application.config.format=xml # spring.application.config.format=json # 本地规则文件的路径,支持两种方式:classpath:rule.xml(rule.json) - 规则文件放在resources目录下,便于打包进jar;file:rule.xml(rule.json) - 规则文件放在工程根目录下,放置在外部便于修改。缺失则默认为不装载本地规则 spring.application.config.path=classpath:rule.xml # spring.application.config.path=classpath:rule.json # 为微服务归类的Key,一般通过group字段来归类,例如eureka.instance.metadataMap.group=xxx-group或者eureka.instance.metadataMap.application=xxx-application。缺失则默认为group spring.application.group.key=group # spring.application.group.key=application # 业务系统希望大多数时候Spring、SpringBoot或者SpringCloud的基本配置、调优参数(非业务系统配置参数),不配置在业务端,集成到基础框架里。但特殊情况下,业务系统有时候也希望能把基础框架里配置的参数给覆盖掉,用他们自己的配置 # 对于此类型的配置需求,可以配置在下面的配置文件里。该文件一般放在resource目录下。缺失则默认为spring-application-default.properties spring.application.default.properties.path=spring-application-default.properties # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试。缺失则默认为false spring.application.no.servers.retry.enabled=false # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的次数。缺失则默认为5 spring.application.no.servers.retry.times=5 # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的时间间隔。缺失则默认为2000 spring.application.no.servers.retry.await.time=2000 # 负载均衡下,消费端尝试获取对应提供端服务实例列表为空的时候,通过日志方式通知。缺失则默认为false spring.application.no.servers.notify.enabled=false # 由于Nacos注册中心会自动把服务名处理成GROUP@@SERVICE_ID的格式,导致根据服务名去获取元数据的时候会找不到。通过如下开关开启是否要过滤掉GROUP前缀。缺失则默认为true spring.application.nacos.service.id.filter.enabled=true # Plugin strategy config # 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true spring.application.strategy.zone.avoidance.rule.enabled=true # 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true spring.application.strategy.rest.intercept.enabled=true # 启动和关闭路由策略的时候,对REST方式在异步调用场景下在服务端的Request请求的装饰,当主线程先于子线程执行完的时候,Request会被Destory,导致Header仍旧拿不到,开启装饰,就可以确保拿到。缺失则默认为true spring.application.strategy.rest.request.decorator.enabled=true # 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.rest.intercept.debug.enabled=true # 路由策略过滤器的执行顺序(Order)。缺失则默认为0 spring.application.strategy.service.route.filter.order=0 # 当外界传值Header的时候,服务也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以服务设置为优先,否则以外界传值为优先。缺失则默认为true spring.application.strategy.service.header.priority=true # 启动和关闭Feign上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下 # 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 # 启动和关闭WebClient上核心策略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.web.client.core.header.transmission.enabled=true # 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.context.request.headers=trace-id;span-id # 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于业务系统自定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.business.request.headers=token # 路由策略的时候,执行请求过滤,对指定包含的URI字段进行排除。缺失则默认为/actuator/,如果多个用“;”分隔,不允许出现空格 spring.application.strategy.uri.filter.exclusion=/actuator/ # 启动和关闭路由策略的时候,对RPC方式的调用拦截。缺失则默认为false spring.application.strategy.rpc.intercept.enabled=true # 路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能 spring.application.strategy.scan.packages=com.nepxion.discovery.plugin.example.service.feign # 启动和关闭消费端的服务隔离(基于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.alarm.enabled=true # 启动和关闭日志输出。缺失则默认为false spring.application.strategy.logger.enabled=true # 日志输出中,是否显示MDC前面的Key。缺失则默认为true spring.application.strategy.logger.mdc.key.shown=true # 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.logger.debug.enabled=true # 启动和关闭调用链输出。缺失则默认为false spring.application.strategy.tracer.enabled=true # 启动和关闭调用链的蓝绿灰度信息以独立的Span节点输出,如果关闭,则蓝绿灰度信息输出到原生的Span节点中(SkyWalking不支持原生模式)。缺失则默认为true spring.application.strategy.tracer.separate.span.enabled=true # 启动和关闭调用链的蓝绿灰度规则策略信息输出。缺失则默认为true spring.application.strategy.tracer.rule.output.enabled=true # 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false spring.application.strategy.tracer.exception.detail.output.enabled=true # 启动和关闭类方法上入参和出参输出到调用链。缺失则默认为false spring.application.strategy.tracer.method.context.output.enabled=true # 显示在调用链界面上蓝绿灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION spring.application.strategy.tracer.span.value=NEPXION # 显示在调用链界面上蓝绿灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery # 启动和关闭Sentinel调用链上规则在Span上的输出。缺失则默认为true spring.application.strategy.tracer.sentinel.rule.output.enabled=true # 启动和关闭Sentinel调用链上方法入参在Span上的输出。缺失则默认为false spring.application.strategy.tracer.sentinel.args.output.enabled=true # 开启服务端实现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.datasource.enabled=true # 流控规则文件路径。缺失则默认为classpath:sentinel-flow.json spring.application.strategy.sentinel.flow.path=classpath:sentinel-flow.json # 降级规则文件路径。缺失则默认为classpath:sentinel-degrade.json spring.application.strategy.sentinel.degrade.path=classpath:sentinel-degrade.json # 授权规则文件路径。缺失则默认为classpath:sentinel-authority.json spring.application.strategy.sentinel.authority.path=classpath:sentinel-authority.json # 系统规则文件路径。缺失则默认为classpath:sentinel-system.json spring.application.strategy.sentinel.system.path=classpath:sentinel-system.json # 热点参数流控规则文件路径。缺失则默认为classpath:sentinel-param-flow.json spring.application.strategy.sentinel.param.flow.path=classpath:sentinel-param-flow.json # 启动和关闭Sentinel LimitApp高级限流熔断功能。缺失则默认为false spring.application.strategy.sentinel.limit.app.enabled=true # 执行Sentinel LimitApp高级限流熔断时候,以Http请求中的Header值作为关键Key。缺失则默认为n-d-service-id,即以服务名作为关键Key spring.application.strategy.sentinel.request.origin.key=n-d-service-id # 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common spring.application.strategy.environment.route=common # 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false spring.application.strategy.zone.affinity.enabled=true # 启动和关闭可用区亲和性失败后的路由,即调用端实例没有找到同一个可用区的提供端实例的时候,当开关打开,可路由到其它可用区或者不归属任何可用区,当开关关闭,则直接调用失败。缺失则默认为true spring.application.strategy.zone.route.enabled=true # 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止蓝绿灰度版本发布人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损 # 启动和关闭版本故障转移。缺失则默认为false spring.application.strategy.version.failover.enabled=true # 版本偏好,即非蓝绿灰度发布场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施蓝绿灰度版本发布产生混乱,对处于非蓝绿灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;蓝绿灰度状态的服务,还是根据传递的Header版本号进行匹配 # 启动和关闭版本偏好。缺失则默认为false spring.application.strategy.version.prefer.enabled=true # 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true spring.application.parameter.event.onstart.enabled=true # 启动和关闭自动扫描目录,当扫描目录未人工配置的时候,可以通过自动扫描方式决定扫描目录。缺失则默认为true spring.application.strategy.auto.scan.packages.enabled=true # 启动和关闭嵌套扫描,嵌套扫描指扫描非本工程下外部包的目录,可以支持多层嵌套。缺失则默认为false spring.application.strategy.auto.scan.recursion.enabled=false # 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false spring.application.group.generator.enabled=true # 服务名前缀的截断长度,必须大于0 spring.application.group.generator.length=15 # 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式 spring.application.group.generator.character=- # 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false spring.application.git.generator.enabled=true # 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties spring.application.git.generator.path=classpath:git.properties # spring.application.git.generator.path=classpath:git.json # 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count} spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time} # spring.application.git.version.key={git.build.version}-{git.commit.time} # 启动和关闭Swagger。缺失则默认为true swagger.service.enabled=true # Swagger基准Docket组名 swagger.service.base.group=Nepxion Discovery # Swagger自定义Docket组名 swagger.service.scan.group=Admin Center Restful APIs # Swagger自定义扫描目录 swagger.service.scan.packages=your-scan-packages # Swagger描述 swagger.service.description=your-description # Swagger版本 swagger.service.version=6.11.0 # Swagger License名称 swagger.service.license.name=Apache License 2.0 # Swagger License链接 swagger.service.license.url=http://www.apache.org/licenses/LICENSE-2.0 # Swagger联系人名称 swagger.service.contact.name=Nepxion # Swagger联系人网址 swagger.service.contact.url=https://github.com/Nepxion/Discovery # Swagger联系人邮件 swagger.service.contact.email=1394997@qq.com # Swagger服务条件网址 swagger.service.termsOfService.url=http://www.nepxion.com ``` ② Spring Cloud Gateway端配置 ``` # Plugin core config # 开启和关闭服务注册层面的控制。一旦关闭,服务注册的黑/白名单过滤功能将失效,最大注册数的限制过滤功能将失效。缺失则默认为true spring.application.register.control.enabled=true # 开启和关闭服务发现层面的控制。一旦关闭,服务多版本调用的控制功能将失效,动态屏蔽指定IP地址的服务实例被发现的功能将失效。缺失则默认为true spring.application.discovery.control.enabled=true # 开启和关闭通过Rest方式对规则配置的控制和推送。一旦关闭,只能通过远程配置中心来控制和推送。缺失则默认为true spring.application.config.rest.control.enabled=true # 规则文件的格式,支持xml和json。缺失则默认为xml spring.application.config.format=xml # spring.application.config.format=json # 本地规则文件的路径,支持两种方式:classpath:rule.xml(rule.json) - 规则文件放在resources目录下,便于打包进jar;file:rule.xml(rule.json) - 规则文件放在工程根目录下,放置在外部便于修改。缺失则默认为不装载本地规则 spring.application.config.path=classpath:rule.xml # spring.application.config.path=classpath:rule.json # 为微服务归类的Key,一般通过group字段来归类,例如eureka.instance.metadataMap.group=xxx-group或者eureka.instance.metadataMap.application=xxx-application。缺失则默认为group spring.application.group.key=group # spring.application.group.key=application # 业务系统希望大多数时候Spring、SpringBoot或者SpringCloud的基本配置、调优参数(非业务系统配置参数),不配置在业务端,集成到基础框架里。但特殊情况下,业务系统有时候也希望能把基础框架里配置的参数给覆盖掉,用他们自己的配置 # 对于此类型的配置需求,可以配置在下面的配置文件里。该文件一般放在resource目录下。缺失则默认为spring-application-default.properties spring.application.default.properties.path=spring-application-default.properties # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试。缺失则默认为false spring.application.no.servers.retry.enabled=false # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的次数。缺失则默认为5 spring.application.no.servers.retry.times=5 # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的时间间隔。缺失则默认为2000 spring.application.no.servers.retry.await.time=2000 # 负载均衡下,消费端尝试获取对应提供端服务实例列表为空的时候,通过日志方式通知。缺失则默认为false spring.application.no.servers.notify.enabled=false # 由于Nacos注册中心会自动把服务名处理成GROUP@@SERVICE_ID的格式,导致根据服务名去获取元数据的时候会找不到。通过如下开关开启是否要过滤掉GROUP前缀。缺失则默认为true spring.application.nacos.service.id.filter.enabled=true # Plugin strategy config # 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true spring.application.strategy.zone.avoidance.rule.enabled=true # 路由策略过滤器的执行顺序(Order)。缺失则默认为9000 spring.application.strategy.gateway.route.filter.order=9000 # 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true spring.application.strategy.gateway.header.priority=false # 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true spring.application.strategy.gateway.original.header.ignored=true # 开启和关闭网关订阅配置中心的动态路由策略。缺失则默认为false spring.application.strategy.gateway.dynamic.route.enabled=true # 启动和关闭网关上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下 # 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.consumer.isolation.enabled=true # 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false spring.application.strategy.monitor.enabled=true # 启动和关闭告警,一旦关闭,蓝绿灰度上下文输出都将关闭。缺失则默认为false spring.application.strategy.alarm.enabled=true # 启动和关闭日志输出。缺失则默认为false spring.application.strategy.logger.enabled=true # 日志输出中,是否显示MDC前面的Key。缺失则默认为true spring.application.strategy.logger.mdc.key.shown=true # 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.logger.debug.enabled=true # 启动和关闭调用链输出。缺失则默认为false spring.application.strategy.tracer.enabled=true # 启动和关闭调用链的蓝绿灰度信息以独立的Span节点输出,如果关闭,则蓝绿灰度信息输出到原生的Span节点中(SkyWalking不支持原生模式)。缺失则默认为true spring.application.strategy.tracer.separate.span.enabled=true # 启动和关闭调用链的蓝绿灰度规则策略信息输出。缺失则默认为true spring.application.strategy.tracer.rule.output.enabled=true # 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false spring.application.strategy.tracer.exception.detail.output.enabled=true # 显示在调用链界面上蓝绿灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION spring.application.strategy.tracer.span.value=NEPXION # 显示在调用链界面上蓝绿灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery # 启动和关闭Sentinel调用链上规则在Span上的输出。缺失则默认为true spring.application.strategy.tracer.sentinel.rule.output.enabled=true # 启动和关闭Sentinel调用链上方法入参在Span上的输出。缺失则默认为false spring.application.strategy.tracer.sentinel.args.output.enabled=true # 开启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 # 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展。缺失则默认为false spring.application.strategy.sentinel.datasource.enabled=true # 流控规则文件路径。缺失则默认为classpath:sentinel-flow.json spring.application.strategy.sentinel.flow.path=classpath:sentinel-flow.json # 降级规则文件路径。缺失则默认为classpath:sentinel-degrade.json spring.application.strategy.sentinel.degrade.path=classpath:sentinel-degrade.json # 授权规则文件路径。缺失则默认为classpath:sentinel-authority.json spring.application.strategy.sentinel.authority.path=classpath:sentinel-authority.json # 系统规则文件路径。缺失则默认为classpath:sentinel-system.json spring.application.strategy.sentinel.system.path=classpath:sentinel-system.json # 热点参数流控规则文件路径。缺失则默认为classpath:sentinel-param-flow.json spring.application.strategy.sentinel.param.flow.path=classpath:sentinel-param-flow.json # 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common spring.application.strategy.environment.route=common # 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false spring.application.strategy.zone.affinity.enabled=true # 启动和关闭可用区亲和性失败后的路由,即调用端实例没有找到同一个可用区的提供端实例的时候,当开关打开,可路由到其它可用区或者不归属任何可用区,当开关关闭,则直接调用失败。缺失则默认为true spring.application.strategy.zone.route.enabled=true # 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止蓝绿灰度版本发布人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损 # 启动和关闭版本故障转移。缺失则默认为false spring.application.strategy.version.failover.enabled=true # 版本偏好,即非蓝绿灰度发布场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施蓝绿灰度版本发布产生混乱,对处于非蓝绿灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;蓝绿灰度状态的服务,还是根据传递的Header版本号进行匹配 # 启动和关闭版本偏好。缺失则默认为false spring.application.strategy.version.prefer.enabled=true # 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true spring.application.parameter.event.onstart.enabled=true # 启动和关闭自动扫描目录,当扫描目录未人工配置的时候,可以通过自动扫描方式决定扫描目录。缺失则默认为true spring.application.strategy.auto.scan.packages.enabled=true # 启动和关闭嵌套扫描,嵌套扫描指扫描非本工程下外部包的目录,可以支持多层嵌套。缺失则默认为false spring.application.strategy.auto.scan.recursion.enabled=false # 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false spring.application.group.generator.enabled=true # 服务名前缀的截断长度,必须大于0 spring.application.group.generator.length=15 # 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式 spring.application.group.generator.character=- # 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false spring.application.git.generator.enabled=true # 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties spring.application.git.generator.path=classpath:git.properties # spring.application.git.generator.path=classpath:git.json # 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count} spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time} # spring.application.git.version.key={git.build.version}-{git.commit.time} # 开启和关闭从SkyWalking apm-agent-core里反射获取TraceId并复制。由于SkyWalking对WebFlux上下文Threadlocal处理机制不恰当,导致产生的TraceId在全链路中并不一致,打开这个开关可以保证全链路TraceId都是一致的。缺失则默认为true spring.application.strategy.gateway.skywalking.traceid.enabled=true # 下面配置只适用于网关里直接进行Feign、RestTemplate或者WebClient调用场景 # 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true spring.application.strategy.rest.intercept.enabled=true # 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.rest.intercept.debug.enabled=true # 启动和关闭Feign上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下 # 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 # 启动和关闭WebClient上核心策略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.web.client.core.header.transmission.enabled=true # 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.context.request.headers=trace-id;span-id # 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于业务系统自定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.business.request.headers=user;mobile;location ``` ③ 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 # 由于Nacos注册中心会自动把服务名处理成GROUP@@SERVICE_ID的格式,导致根据服务名去获取元数据的时候会找不到。通过如下开关开启是否要过滤掉GROUP前缀。缺失则默认为true spring.application.nacos.service.id.filter.enabled=true # Plugin strategy config # 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true spring.application.strategy.zone.avoidance.rule.enabled=true # 路由策略过滤器的执行顺序(Order)。缺失则默认为0 spring.application.strategy.zuul.route.filter.order=0 # 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true spring.application.strategy.zuul.header.priority=false # 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true spring.application.strategy.zuul.original.header.ignored=true # 开启和关闭网关订阅配置中心的动态路由策略。缺失则默认为false spring.application.strategy.zuul.dynamic.route.enabled=true # 启动和关闭网关上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下 # 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.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上的输出。缺失则默认为true spring.application.strategy.tracer.sentinel.rule.output.enabled=true # 启动和关闭Sentinel调用链上方法入参在Span上的输出。缺失则默认为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 # 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展。缺失则默认为false spring.application.strategy.sentinel.datasource.enabled=true # 流控规则文件路径。缺失则默认为classpath:sentinel-flow.json spring.application.strategy.sentinel.flow.path=classpath:sentinel-flow.json # 降级规则文件路径。缺失则默认为classpath:sentinel-degrade.json spring.application.strategy.sentinel.degrade.path=classpath:sentinel-degrade.json # 授权规则文件路径。缺失则默认为classpath:sentinel-authority.json spring.application.strategy.sentinel.authority.path=classpath:sentinel-authority.json # 系统规则文件路径。缺失则默认为classpath:sentinel-system.json spring.application.strategy.sentinel.system.path=classpath:sentinel-system.json # 热点参数流控规则文件路径。缺失则默认为classpath:sentinel-param-flow.json spring.application.strategy.sentinel.param.flow.path=classpath:sentinel-param-flow.json # 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common spring.application.strategy.environment.route=common # 启动和关闭可用区亲和性,即同一个可用区的服务才能调用,同一个可用区的条件是调用端实例和提供端实例的元数据Metadata的zone配置值必须相等。缺失则默认为false spring.application.strategy.zone.affinity.enabled=true # 启动和关闭可用区亲和性失败后的路由,即调用端实例没有找到同一个可用区的提供端实例的时候,当开关打开,可路由到其它可用区或者不归属任何可用区,当开关关闭,则直接调用失败。缺失则默认为true spring.application.strategy.zone.route.enabled=true # 版本故障转移,即无法找到相应版本的服务实例,路由到老的稳定版本的实例。其作用是防止蓝绿灰度版本发布人为设置错误,或者对应的版本实例发生灾难性的全部下线,导致流量有损 # 启动和关闭版本故障转移。缺失则默认为false spring.application.strategy.version.failover.enabled=true # 版本偏好,即非蓝绿灰度发布场景下,路由到老的稳定版本的实例。其作用是防止多个网关上并行实施蓝绿灰度版本发布产生混乱,对处于非蓝绿灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;蓝绿灰度状态的服务,还是根据传递的Header版本号进行匹配 # 启动和关闭版本偏好。缺失则默认为false spring.application.strategy.version.prefer.enabled=true # 启动和关闭在服务启动的时候参数订阅事件发送。缺失则默认为true spring.application.parameter.event.onstart.enabled=true # 启动和关闭自动扫描目录,当扫描目录未人工配置的时候,可以通过自动扫描方式决定扫描目录。缺失则默认为true spring.application.strategy.auto.scan.packages.enabled=true # 启动和关闭嵌套扫描,嵌套扫描指扫描非本工程下外部包的目录,可以支持多层嵌套。缺失则默认为false spring.application.strategy.auto.scan.recursion.enabled=false # 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false spring.application.group.generator.enabled=true # 服务名前缀的截断长度,必须大于0 spring.application.group.generator.length=15 # 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式 spring.application.group.generator.character=- # 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false spring.application.git.generator.enabled=true # 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties spring.application.git.generator.path=classpath:git.properties # spring.application.git.generator.path=classpath:git.json # 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count} # spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time} # spring.application.git.version.key={git.build.version}-{git.commit.time} # 下面配置只适用于网关里直接进行Feign、RestTemplate或者WebClient调用场景 # 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true spring.application.strategy.rest.intercept.enabled=true # 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.rest.intercept.debug.enabled=true # 启动和关闭Feign上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下 # 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 # 启动和关闭WebClient上核心策略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.web.client.core.header.transmission.enabled=true # 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.context.request.headers=trace-id;span-id # 路由策略的时候,对REST方式调用拦截的时候(支持Feign、RestTemplate或者WebClient调用),希望把来自外部自定义的Header参数(用于业务系统自定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.business.request.headers=user;mobile;location ``` ### 内置文件配置 框架提供内置文件方式的配置spring-application-default.properties。如果使用者希望对框架做封装,并提供相应的默认配置,可以在src/main/resources目录下放置spring-application-default.properties ![](http://nepxion.gitee.io/discovery/docs/icon-doc/warning.png) 需要注意,该文件在整个服务目录和包中只能出现一次 ## Docker容器化和Kubernetes平台支持 ### Docker容器化 ![](http://nepxion.gitee.io/discovery/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/discovery/docs/discovery-doc/Docker.jpg) ### Kubernetes平台支持 请自行研究 ## 自动化测试 自动化测试,基于Spring Boot/Spring Cloud的自动化测试框架,包括普通调用测试、蓝绿灰度调用测试和扩展调用测试(例如:支持阿里巴巴的Sentinel,FF4j的功能开关等)。通过注解形式,跟Spring Boot内置的测试机制集成,使用简单方便。该自动化测试框架的现实意义,可以把服务注册发现中心、远程配置中心、负载均衡、蓝绿灰度发布、熔断降级限流、功能开关、Feign、RestTemplate或者WebClient调用等中间件或者组件,一条龙组合起来进行自动化测试 自动化测试代码参考[指南示例自动化测试](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 gateway.route0.test.url=http://localhost:5001/x/invoke/gateway gateway.route1.test.url=http://localhost:5001/y/invoke/gateway gateway.route2.test.url=http://localhost:5001/z/invoke/gateway gateway.inspect.url=http://localhost:5001/discovery-guide-service-a/inspector/inspect # Zuul网关配置 zuul.group=discovery-guide-group zuul.service.id=discovery-guide-zuul zuul.test.url=http://localhost:5002/discovery-guide-service-a/invoke/zuul zuul.route0.test.url=http://localhost:5002/x/invoke/zuul zuul.route1.test.url=http://localhost:5002/y/invoke/zuul zuul.route2.test.url=http://localhost:5002/z/invoke/zuul zuul.inspect.url=http://localhost:5002/discovery-guide-service-a/inspector/inspect # 每个测试用例执行循环次数 testcase.loop.times=1 # 测试用例的灰度权重测试开关。由于权重测试需要大量采样调用,会造成整个自动化测试时间很长,可以通过下面开关开启和关闭。缺失则默认为true gray.weight.testcases.enabled=true # 测试用例的灰度权重采样总数。采样总数越大,灰度权重准确率越高,但耗费时间越长 gray.weight.testcase.sample.count=1500 # 测试用例的灰度权重准确率偏离值。采样总数越大,灰度权重准确率偏离值越小 gray.weight.testcase.result.offset=5 ``` ### 测试用例 ![](http://nepxion.gitee.io/discovery/docs/icon-doc/warning.png) 需要注意,当使用Eureka注册中心的时候,因为Spring Cloud内嵌了Eureka可用区亲和性功能,会自动开启该策略,则导致某些自动化测试用例失败。需要把所有服务实例的元数据zone值改成相同或者也可以把该行元数据删除,然后进行自动化测试 #### 测试包引入 ```xml com.nepxion discovery-plugin-test-starter ${discovery.version} ``` ![](http://nepxion.gitee.io/discovery/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打包来执行,则需要增加如下插件 ```xml org.apache.maven.plugins maven-compiler-plugin -parameters ${project.build.sourceEncoding} ${java.version} ${java.version} ``` #### 测试入口程序 结合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(); // 配置类型 FormatType formatType() default FormatType.TEXT_FORMAT; // 组名-服务名组合键值的前缀 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和Apollo为例 - Nacos的Key格式 ``` Group为DEFAULT_GROUP,Data ID为sentinel-authority-${spring.application.name}。每个服务都专享自己的Sentinel规则 ``` - Apollo的Key格式 ``` namespace为application,Key为sentinel-authority。每个服务都专享自己的Sentinel规则 ``` ② 执行测试用例前,把执行限流降级熔断等逻辑的内容(executePath = "sentinel-authority-2.json")推送到远程配置中心 ③ 执行测试用例,通过断言Assert来判断测试结果 ④ 执行测试用例后,把修改过的内容(resetPath = "sentinel-authority-1.json")复原,再推送一次到远程配置中心 ```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 :: testVersionStrategyGray1() ---------- Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testRegionStrategyGray1() ---------- Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testVersionWeightStrategyGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : 1.0 version weight=90%, 1.1 version weight=10% B service desired : 1.0 version weight=20%, 1.1 version weight=80% Result : A service 1.0 version weight=89.6% Result : A service 1.1 version weight=10.4% Result : B service 1.0 version weight=20.1333% Result : B service 1.1 version weight=79.8667% * Passed ---------- Run automation testcase :: testRegionWeightStrategyGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : dev region weight=85%, qa region weight=15% B service desired : dev region weight=85%, qa region weight=15% Result : A service dev region weight=83.7667% Result : A service qa region weight=16.2333% Result : B service dev region weight=86.2% Result : B service qa region weight=13.8% * Passed ---------- Run automation testcase :: testStrategyGray1() ---------- Header : [a:"1", b:"2"] Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] ``` - 路由规则测试报告样例 ``` * Passed ---------- Run automation testcase :: testVersionRuleGray() ---------- Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testRegionRuleGray() ---------- Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testVersionWeightRuleGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : 1.0 version weight=75%, 1.1 version weight=25% B service desired : 1.0 version weight=35%, 1.1 version weight=65% Result : A service 1.0 version weight=75.2667% Result : A service 1.1 version weight=24.7333% Result : B service 1.0 version weight=35.1667% Result : B service 1.1 version weight=64.8333% * Passed ---------- Run automation testcase :: testRegionWeightRuleGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : dev region weight=95%, qa region weight=5% B service desired : dev region weight=95%, qa region weight=5% Result : A service dev region weight=94.9333% Result : A service qa region weight=5.0667% Result : B service dev region weight=95.0667% Result : B service qa region weight=4.9333% * Passed ---------- Run automation testcase :: testVersionCompositeRuleGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : 1.0 version weight=40%, 1.1 version weight=60% Route desired : A Service 1.0 version -> B Service 1.0 version, A Service 1.1 version -> B Service 1.1 version Result : A service 1.0 version weight=39.8333% A service 1.1 version weight=60.1667% * Passed ``` ## 压力测试 压力测试,基于WRK的异步压力测试框架,能用很少的线程压测出很大的并发量,使用简单方便 ### 测试环境 ① 准备两台机器部署Spring Cloud应用 ② 准备一台机器部署网关(Spring Cloud或者Zuul) ③ 准备一台机器部署压测工具 | 服务 | 配置 | 数目 | | --- | --- | --- | | Spring Cloud Gateway | 16C 32G | 1 | | Zuul 1.x | 16C 32G | 1 | | Service | 4C 8G | 2 | ④ 优化方式 - Spring Cloud Gateway,不需要优化 - Zuul 1.x,优化如下 ``` zuul.host.max-per-route-connections=1000 zuul.host.max-total-connections=1000 zuul.semaphore.max-semaphores=5000 ``` ### 测试介绍 - 使用WRK脚本进行性能测试,WRK脚本参考post.lua(位于discovery-guide-test-automation目录下),不带参数运行 - 使用WRK详细说明参考[https://github.com/wg/wrk](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% | ## Star走势图 [![Stargazers over time](https://starchart.cc/Nepxion/Discovery.svg)](https://starchart.cc/Nepxion/Discovery)