# Discovery **Repository Path**: project-3/Discovery ## Basic Information - **Project Name**: Discovery - **Description**: 🐳 Nepxion Discovery is an enhancement for Spring Cloud Discovery with gray release, router, weight, limitation, circuit breaker, degrade, isolation, monitor, tracing 灰度发布、路由、权重、限流、熔断、降级、隔离、监控、追踪 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: http://www.nepxion.com - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 292 - **Created**: 2020-09-03 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README [^_^]: ![](http://nepxion.gitee.io/docs/discovery-doc/Cover.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Banner.png) # Discovery【探索】微服务企业级解决方案 [![Total lines](https://tokei.rs/b1/github/Nepxion/Discovery?category=lines)](https://tokei.rs/b1/github/Nepxion/Discovery?category=lines) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?label=license)](https://github.com/Nepxion/Discovery/blob/master/LICENSE) [![Maven Central](https://img.shields.io/maven-central/v/com.nepxion/discovery.svg?label=maven%20central)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.nepxion%22%20AND%20discovery) [![Javadocs](http://www.javadoc.io/badge/com.nepxion/discovery-plugin-framework.svg)](http://www.javadoc.io/doc/com.nepxion/discovery-plugin-framework) [![Build Status](https://travis-ci.org/Nepxion/Discovery.svg?branch=master)](https://travis-ci.org/Nepxion/Discovery) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/8e39a24e1be740c58b83fb81763ba317)](https://www.codacy.com/project/HaojunRen/Discovery/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Nepxion/Discovery&utm_campaign=Badge_Grade_Dashboard) [![Stars](https://img.shields.io/github/stars/Nepxion/Discovery.svg?label=Stars&tyle=flat&logo=GitHub)](https://github.com/Nepxion/Discovery/stargazers) [![Stars](https://gitee.com/Nepxion/Discovery/badge/star.svg)](https://gitee.com/nepxion/Discovery/stargazers) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-2.3.3.RELEASE-blue?logo=Spring)](https://search.maven.org/artifact/org.springframework.boot/spring-boot) [![Spring Cloud](https://img.shields.io/badge/Spring%20Cloud-Hoxton.SR8-blue?logo=Spring)](https://search.maven.org/search?q=g:org.springframework.cloud%20AND%20a:spring-cloud-dependencies&core=gav) [![Spring Cloud Alibaba](https://img.shields.io/badge/Spring%20Cloud%20Alibaba-2.2.2.RELEASE-blue?logo=Spring)](https://search.maven.org/search?q=g:com.alibaba.cloud%20AND%20a:spring-cloud-alibaba-dependencies&core=gav) ![](http://nepxion.gitee.io/docs/discovery-doc/Logo64.png) Discovery【探索】微服务企业级解决方案 ① Discovery【探索】微服务企业级解决方案文档 - [Discovery【探索】微服务企业级解决方案(PPT版)](http://nepxion.gitee.io/docs/link-doc/discovery-ppt.html) - [Discovery【探索】微服务企业级解决方案(PDF版)](http://nepxion.gitee.io/docs/link-doc/discovery-pdf.html) - [Discovery【探索】微服务企业级解决方案(HTML版)](http://nepxion.gitee.io/docs/link-doc/discovery-html.html) ② Discovery【探索】微服务企业级解决方案源码。请访问Gitee镜像获得最佳体验 - [源码Gitee同步镜像](https://gitee.com/Nepxion/Discovery) - [源码Github原镜像](https://github.com/Nepxion/Discovery) ③ Discovery【探索】微服务企业级解决方案指南示例源码。请访问Gitee镜像获得最佳体验 - [指南Gitee同步镜像](https://gitee.com/Nepxion/DiscoveryGuide) - [指南Github原镜像](https://github.com/Nepxion/DiscoveryGuide) ④ Discovery【探索】微服务框架指南示例说明 - 对于入门级玩家,参考[指南示例极简版](https://github.com/Nepxion/DiscoveryGuide/tree/simple),分支为simple。涉及到指南篇里的灰度路由和发布的基本功能 - 对于熟练级玩家,参考[指南示例精进版](https://github.com/Nepxion/DiscoveryGuide/tree/master),分支为master。除上述《极简版》功能外,涉及到指南篇里的绝大多数高级功能 - 对于骨灰级玩家,参考[指南示例高级版](https://github.com/Nepxion/DiscoveryGuide/tree/premium),分支为premium。除上述《精进版》功能外,涉及到指南篇里的ActiveMQ、MongoDB、RabbitMQ、Redis、RocketMQ、MySQL等高级调用链和灰度调用链的整合 ![](http://nepxion.gitee.io/docs/polaris-doc/Logo64.png) Polaris【北极星】企业级云原生微服务框架 ① Polaris【北极星】企业级云原生微服务框架文档 - [Polaris【北极星】企业级云原生微服务框架(PDF版)](http://nepxion.gitee.io/docs/link-doc/polaris-pdf.html) - [Polaris【北极星】企业级云原生微服务框架(HTML版)](http://nepxion.gitee.io/docs/link-doc/polaris-html.html) ② Polaris【北极星】企业级云原生微服务框架源码。请访问Gitee镜像获得最佳体验 - [源码Gitee同步镜像](https://gitee.com/Nepxion/Polaris) - [源码Github原镜像](https://github.com/Nepxion/Polaris) ③ Polaris【北极星】企业级云原生微服务框架指南示例源码。请访问Gitee镜像获得最佳体验 - [指南Gitee同步镜像](https://gitee.com/Nepxion/PolarisGuide) - [指南Github原镜像](https://github.com/Nepxion/PolarisGuide) ![](http://nepxion.gitee.io/docs/icon-doc/information.png) Discovery【探索】和Polaris【北极星】联合架构图 ![](http://nepxion.gitee.io/docs/polaris-doc/Architecture.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/information.png) Discovery【探索】和Polaris【北极星】联合拓扑图 ![](http://nepxion.gitee.io/docs/polaris-doc/Topology.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/information.png) Polaris【北极星】分层架构图 ![](http://nepxion.gitee.io/docs/polaris-doc/Layer.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/information.png) Discovery【探索】域网关实施图 ![](http://nepxion.gitee.io/docs/polaris-doc/DomainEnable.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/information.png) Discovery【探索】非域网关实施图 ![](http://nepxion.gitee.io/docs/polaris-doc/DomainDisable.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/information.png) Discovery【探索】全局订阅实施图 ![](http://nepxion.gitee.io/docs/polaris-doc/GlobalSub.jpg) ## 简介 Discovery【探索】微服务框架,基于Spring Cloud Discovery服务注册发现、Ribbon负载均衡、Feign和RestTemplate调用等组件全方位增强的企业级微服务开源解决方案,更贴近企业级需求,更具有企业级的插件引入、开箱即用特征 ① 微服务框架支持的基本功能,如下 - 支持阿里巴巴Nacos、Eureka、Consul和Zookeeper四个服务注册发现中心 - 支持阿里巴巴Nacos、携程Apollo和Redis三个远程配置中心 - 支持阿里巴巴Sentinel和Hystrix两个熔断限流降级权限中间件 - 支持Uber Jaeger、Apache Skywalking两个符合OpenTracing规范的调用链中间件 - 支持Prometheus Micrometer和Spring Boot Admin两个指标中间件 - 支持Java Agent解决异步跨线程ThreadLocal上下文传递 - 支持Spring Cloud Gateway、Zuul网关和微服务三大模块的灰度发布和路由等一系列功能 - 支持和兼容Spring Cloud Edgware版、Finchley版、Greenwich版和Hoxton版 ② 微服务框架支持的应用功能,如下 - 基于Header传递的全链路灰度路由,网关为路由触发点。采用配置中心配置路由策略映射在网关过滤器中植入Header信息而实现,路由策略传递到全链路服务中。主要包括 - 匹配路由。包括版本匹配路由、区域匹配路由、IP地址和端口匹配路由 - 权重路由。包括版本权重路由、区域权重路由 - 动态变更元数据路由 - 全局订阅式路由。此方式为基于Header传递和基于规则订阅两种方式的合成方案 - 前端触发路由 - 网关过滤器触发路由 - 负载均衡策略类触发路由 - 并行路由下的版本优选路由 - 异步场景下的触发路由 - 基于规则订阅的全链路灰度发布。采用配置中心配置灰度规则映射在全链路服务而实现,所有服务都订阅一个共享配置。主要包括 - 匹配发布。包括版本匹配发布、区域匹配发布 - 权重发布。包括版本权重发布、区域权重发布 - 基于灰度发布和灰度路由的多种组合式规则策略。主要包括 - 全链路灰度条件命中和灰度匹配组合式策略 - 全链路灰度条件权重和灰度匹配组合式策略 - 前端灰度和网关灰度路由组合式策略 - 全链路灰度权重和灰度匹配组合式规则 - 基于多方式的规则策略推送。主要包括 - 基于远程配置中心的规则策略订阅推送 - 基于Swagger和Rest的规则策略推送 - 基于图形化界面的规则策略推送 - 基于组(Group)和黑白名单的全链路服务隔离和准入。主要包括 - 服务注册发现准入。包括基于组(Group)黑白名单注册准入、基于IP地址黑白名单注册准入、基于最大注册数限制注册准入、基于IP地址黑白名单发现准入 - 消费端服务隔离。包括基于组(Group)负载均衡隔离 - 提供端服务隔离。包括基于组(Group)Header传值策略隔离 - 基于Env的全链路环境隔离和路由。主要包括 - 环境隔离。基于服务实例的元数据Metadata的env参数和全链路传递的环境Header值进行比对实现隔离 - 环境路由。基于调用端实例找不到符合条件的提供端实例,把流量路由到一个通用或者备份环境 - 基于Sentinel的全链路服务限流熔断降级权限。除了支持Sentinel原生五个相关规则外,扩展LimitApp的机制,整合灰度路由,通过动态的Http Header方式实现组合式防护机制。主要包括 - 基于服务名的防护 - 基于灰度组的防护 - 基于灰度版本的防护 - 基于灰度区域的防护 - 基于IP地址和端口的防护 - 基于自定义业务参数的组合式防护机制 - 基于Hystrix的全链路服务限流熔断和灰度融合 - 全链路监控。主要包括 - 全链路调用链监控(Tracing) - 全链路日志监控(Logging) - 全链路指标监控(Metrics) - 全链路Header传递 - 全链路侦测 - 全链路服务侧注解 - 全链路服务侧API权限 - 异步跨线程Agent。主要包括 - 插件获取 - 插件使用 - 插件扩展 - 元数据Metadata自动化策略。主要包括 - 基于服务名前缀自动创建灰度组名 - 基于Git插件自动创建灰度版本号 - 元数据Metadata运维平台策略 - 同城双活多机房切换。基于区域匹配发布或者路由的同城双活多机房切换 - 数据库灰度发布。基于多数据源的数据库灰度发布,内置简单的数据库灰度发布策略 - 灰度路由和发布的自动化测试。主要包括 - 基于Spring Boot/Spring Cloud自动化测试,包括普通调用测试、灰度调用测试和扩展调用测试 - 基于WRK的性能压力测试 - Docker容器化和Kubernetes平台的无缝支持部署 ③ 微服务框架支持的重要中间件,如下 - [**Nacos**] 阿里巴巴中间件部门开发的新一代集服务注册发现中心和配置中心为一体的中间件。它是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施,支持几乎所有主流类型的“服务”的发现、配置和管理,更敏捷和容易地构建、交付和管理微服务平台 - [**Sentinel**] 阿里巴巴中间件部门开发的新一代以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性的分布式系统的流量防卫兵。它承接了阿里巴巴近10年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等 - [**Spring Cloud Alibaba**] 阿里巴巴中间件部门开发的Spring Cloud增强套件,致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过Spring Cloud编程模型轻松使用这些组件来开发分布式应用服务。依托Spring Cloud Alibaba,只需要添加一些注解和少量配置,就可以将Spring Cloud应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统 - [**OpenTracing**] OpenTracing已进入CNCF,正在为全球的分布式追踪系统提供统一的概念、规范、架构和数据标准。它通过提供平台无关、厂商无关的API,使得开发人员能够方便的添加(或更换)追踪系统的实现。对于存在多样化的技术栈共存的调用链中,OpenTracing适配Java、C、Go和.Net等技术栈,实现全链路分布式追踪功能。迄今为止,Uber Jaeger、Twitter Zipkin和Apache Skywalking已经适配了OpenTracing规范。CNCF技术委员会通过OpenTelemetry规范整合基于Tracing的OpenTracing规范(官方推荐Jaeger做Backend)和基于Metrics的OpenSensus规范(官方推荐Prometheus做Backend) ④ 框架支持易用性表现,简单步骤如下 - 引入相关依赖到pom.xml - 设置元数据MetaData。如下四个元数据可以按需设置 - 定义所属组名(group)或者应用名(application),也可以通过服务名前缀来自动产生服务组名 - 定义版本号(version),也可以通过Git插件方式自动产生版本号 - 定义所属区域名(region) - 定义所属环境(env) - 执行采用“约定大于配置”的准则,使用者也可以开启和关闭相关功能项或者属性值,达到最佳配置 - 规则策略文件设置和推送 ## 鸣谢 ![](http://nepxion.gitee.io/docs/icon-doc/information.png) 鸣谢 - 感谢阿里巴巴中间件Nacos、Sentinel和Spring Cloud Alibaba团队,尤其是Nacos负责人@彦林、@于怀,Sentinel负责人@宿何、@子衿,Spring Cloud Alibaba负责人@小马哥、@洛夜、@亦盏的技术支持 - 感谢携程Apollo团队,尤其是@宋顺,特意开发OpenApi包和技术支持 - 感谢代码贡献者,包括@zifeihan,@Ax1an,@WeihuaWang,@张顺,@Esun,@liumapp,@terranhu,@JikaiSun,@HaoHuang,@FanYang,@Ankeway,@liquanjin等 - 感谢为本框架进行测试验证和问题分析定位的同学,包括@张龙,@CongweiXu,@fan,@阿神,@慕紫,@郝俊仁,@Windon,@杨成,@李鹏,@任学会,@郭小伟等 - 感谢为本框架提出宝贵意见和建议的同学 - 感谢阿里巴巴中间件Nacos和Spring Cloud Alibaba团队,纳本框架为相关开源项目 NacosSpring Cloud Alibaba - 感谢使用本框架的公司和企业。不完全统计,目前社区开源项目已经被如下公司使用或者调研
为提供更好的专业级服务,请更多已经使用本框架的公司和企业联系我,也可以在Github Issues上登记 ## 目录 - [简介](#简介) - [鸣谢](#鸣谢) - [请联系我](#请联系我) - [相关版本](#相关版本) - [相关链接](#相关链接) - [源码主页](#源码主页) - [指南主页](#指南主页) - [文档主页](#文档主页) - [现有痛点](#现有痛点) - [名词解释](#名词解释) - [工程架构](#工程架构) - [工程清单](#工程清单) - [架构核心](#架构核心) - [依赖引入](#依赖引入) - [准备工作](#准备工作) - [环境搭建](#环境搭建) - [启动服务](#启动服务) - [环境验证](#环境验证) - [基于Header传递方式的灰度路由策略](#基于Header传递方式的灰度路由策略) - [配置网关灰度路由策略](#配置网关灰度路由策略) - [版本匹配灰度路由策略](#版本匹配灰度路由策略) - [版本权重灰度路由策略](#版本权重灰度路由策略) - [区域匹配灰度路由策略](#区域匹配灰度路由策略) - [区域权重灰度路由策略](#区域权重灰度路由策略) - [IP地址和端口匹配灰度路由策略](#IP地址和端口匹配灰度路由策略) - [动态变更元数据的灰度路由策略](#动态变更元数据的灰度路由策略) - [全局订阅式的灰度路由策略](#全局订阅式的灰度路由策略) - [配置全链路灰度条件命中和灰度匹配组合式策略](#配置全链路灰度条件命中和灰度匹配组合式策略) - [配置全链路灰度条件权重和灰度匹配组合式策略](#配置全链路灰度条件权重和灰度匹配组合式策略) - [配置前端灰度和网关灰度路由组合式策略](#配置前端灰度和网关灰度路由组合式策略) - [通过其它方式设置灰度路由策略](#通过其它方式设置灰度路由策略) - [通过前端传入灰度路由策略](#通过前端传入灰度路由策略) - [通过业务参数在过滤器中自定义灰度路由策略](#通过业务参数在过滤器中自定义灰度路由策略) - [通过业务参数在策略类中自定义灰度路由策略](#通过业务参数在策略类中自定义灰度路由策略) - [并行灰度路由下的版本优选策略](#并行灰度路由下的版本优选策略) - [基于异步场景的全链路灰度路由策略](#基于异步场景的全链路灰度路由策略) - [基于订阅方式的全链路灰度发布规则](#基于订阅方式的全链路灰度发布规则) - [配置全链路灰度匹配规则](#配置全链路灰度匹配规则) - [版本匹配灰度规则](#版本匹配灰度规则) - [区域匹配灰度规则](#区域匹配灰度规则) - [配置全链路灰度权重规则](#配置全链路灰度权重规则) - [全局版本权重灰度规则](#全局版本权重灰度规则) - [局部版本权重灰度规则](#局部版本权重灰度规则) - [全局区域权重灰度规则](#全局区域权重灰度规则) - [局部区域权重灰度规则](#局部区域权重灰度规则) - [配置全链路灰度权重和灰度匹配组合式规则](#配置全链路灰度权重和灰度匹配组合式规则) - [数据库灰度发布规则](#数据库灰度发布规则) - [基于多格式的规则策略定义](#基于多格式的规则策略定义) - [规则策略格式定义](#规则策略格式定义) - [规则策略内容定义](#规则策略内容定义) - [规则策略示例](#规则策略示例) - [基于多方式的规则策略推送](#基于多方式的规则策略推送) - [基于远程配置中心的规则策略订阅推送](#基于远程配置中心的规则策略订阅推送) - [基于Swagger和Rest的规则策略推送](#基于Swagger和Rest的规则策略推送) - [基于图形化界面的规则策略推送](#基于图形化界面的规则策略推送) - [基于组和黑白名单的全链路服务隔离和准入](#基于组和黑白名单的全链路服务隔离和准入) - [服务注册发现准入](#服务注册发现准入) - [基于组黑白名单注册准入](#基于组黑白名单注册准入) - [基于IP地址黑白名单注册准入](#基于IP地址黑白名单注册准入) - [基于最大注册数限制注册准入](#基于最大注册数限制注册准入) - [基于IP地址黑白名单发现准入](#基于IP地址黑白名单发现准入) - [自定义注册发现准入](#自定义注册发现准入) - [消费端服务隔离](#消费端服务隔离) - [基于组负载均衡隔离](#基于组负载均衡隔离) - [提供端服务隔离](#提供端服务隔离) - [基于组Header传值策略隔离](#基于组Header传值策略隔离) - [基于Env的全链路环境隔离和路由](#基于Env的全链路环境隔离和路由) - [环境隔离](#环境隔离) - [环境路由](#环境路由) - [基于Sentinel的全链路服务限流熔断降级权限和灰度融合](#基于Sentinel的全链路服务限流熔断降级权限和灰度融合) - [原生Sentinel注解](#原生Sentinel注解) - [原生Sentinel规则](#原生Sentinel规则) - [流控规则](#流控规则) - [降级规则](#降级规则) - [授权规则](#授权规则) - [系统规则](#系统规则) - [热点参数流控规则](#热点参数流控规则) - [基于灰度路由和Sentinel-LimitApp扩展的防护机制](#基于灰度路由和Sentinel-LimitApp扩展的防护机制) - [基于服务名的防护机制](#基于服务名的防护机制) - [基于灰度组的防护机制](#基于灰度组的防护机制) - [基于灰度版本的防护机制](#基于灰度版本的防护机制) - [基于灰度区域的防护机制](#基于灰度区域的防护机制) - [基于IP地址和端口的防护机制](#基于IP地址和端口的防护机制) - [自定义业务参数的组合式防护机制](#自定义业务参数的组合式防护机制) - [基于Hystrix的全链路服务限流熔断和灰度融合](#基于Hystrix的全链路服务限流熔断和灰度融合) - [全链路监控](#全链路监控) - [全链路调用链监控-Tracing](#全链路调用链监控-Tracing) - [Header输出方式](#Header输出方式) - [调用链输出方式](#调用链输出方式) - [日志输出方式](#日志输出方式) - [全链路指标监控-Metrics](#全链路指标监控-Metrics) - [Prometheus监控方式](#Prometheus监控方式) - [Grafana监控方式](#Grafana监控方式) - [Spring-Boot-Admin监控方式](#Spring-Boot-Admin监控方式) - [全链路Header传递](#全链路Header传递) - [全链路侦测](#全链路侦测) - [全链路服务侧注解](#全链路服务侧注解) - [全链路服务侧API权限](#全链路服务侧API权限) - [异步跨线程Agent](#异步跨线程Agent) - [插件获取](#插件获取) - [插件使用](#插件使用) - [插件扩展](#插件扩展) - [元数据Metadata自动化策略](#元数据Metadata自动化策略) - [基于服务名前缀自动创建灰度组名](#基于服务名前缀自动创建灰度组名) - [基于Git插件自动创建灰度版本号](#基于Git插件自动创建灰度版本号) - [元数据Metadata运维平台策略](#元数据Metadata运维平台策略) - [配置文件](#配置文件) - [基础属性配置](#基础属性配置) - [功能开关配置](#功能开关配置) - [内置文件配置](#内置文件配置) - [Docker容器化和Kubernetes平台支持](#Docker容器化和Kubernetes平台支持) - [Docker容器化](#Docker容器化) - [Kubernetes平台支持](#Kubernetes平台支持) - [自动化测试](#自动化测试) - [架构设计](#架构设计) - [启动控制台](#启动控制台) - [配置文件](#配置文件) - [测试用例](#测试用例) - [测试包引入](#测试包引入) - [测试入口程序](#测试入口程序) - [普通调用测试](#普通调用测试) - [灰度调用测试](#灰度调用测试) - [扩展调用测试](#扩展调用测试) - [测试报告](#测试报告) - [压力测试](#压力测试) - [测试环境](#测试环境) - [测试介绍](#测试介绍) - [测试步骤](#测试步骤) - [附录](#附录) - [中间件服务器下载地址](#中间件服务器下载地址) - [Star走势图](#Star走势图) ## 请联系我 微信、公众号和文档 ![](http://nepxion.gitee.io/docs/zxing-doc/微信-1.jpg)![](http://nepxion.gitee.io/docs/zxing-doc/公众号-1.jpg)![](http://nepxion.gitee.io/docs/zxing-doc/文档-1.jpg) ## 相关版本 | 框架版本 | 框架分支 | 框架状态 | Spring Cloud版本 | Spring Boot版本 | Spring Cloud Alibaba版本 | | --- | --- | --- | --- | --- | --- | | 6.0.7 | master | ![](http://nepxion.gitee.io/docs/icon-doc/confirm_24.png) | Hoxton

Greenwich
Finchley | 2.3.x.RELEASE
2.2.x.RELEASE
2.1.x.RELEASE
2.0.x.RELEASE | 2.2.x.RELEASE

2.1.x.RELEASE
2.0.x.RELEASE | | ~~5.6.0~~ | ~~5.x.x~~ | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) | Greenwich | 2.1.x.RELEASE | 2.1.x.RELEASE | | ~~4.15.0~~ | ~~4.x.x~~ | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) | Finchley | 2.0.x.RELEASE | 2.0.x.RELEASE | | 3.16.7 | 3.x.x | ![](http://nepxion.gitee.io/docs/icon-doc/arrow_up_24.png) | Edgware | 1.5.x.RELEASE | 1.5.x.RELEASE | | ~~2.0.x~~ | ~~2.x.x~~ | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) | Dalston | 1.x.x.RELEASE | 1.5.x.RELEASE | | ~~1.0.x~~ | ~~1.x.x~~ | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) | Camden | 1.x.x.RELEASE | 1.5.x.RELEASE | ![](http://nepxion.gitee.io/docs/icon-doc/confirm_24.png) 表示维护中 | ![](http://nepxion.gitee.io/docs/icon-doc/arrow_up_24.png) 表示不维护,但可用,强烈建议升级 | ![](http://nepxion.gitee.io/docs/icon-doc/delete_24.png) 表示不维护,不可用,已废弃 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:Hoxton SR5或者以上的版本才支持Spring Boot 2.3.x.RELEASE版本 - 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)已废弃 ## 相关链接 ### 源码主页 [Discovery源码主页](https://github.com/Nepxion/Discovery) [Polaris源码主页](https://github.com/Nepxion/Polaris) ### 指南主页 [Discovery指南主页](https://github.com/Nepxion/DiscoveryGuide) [Polaris指南主页](https://github.com/Nepxion/PolarisGuide) ### 文档主页 [文档主页](https://gitee.com/Nepxion/Docs/tree/master/web-doc) ## 现有痛点 现有的Spring Cloud微服务架构的痛点 - 如果你是运维负责人,是否会经常发现,你掌管的测试环境中的服务注册中心,被一些不负责的开发人员把他本地开发环境注册上来,造成测试人员测试失败。你希望可以把本地开发环境注册给屏蔽掉,不让注册 - 如果你是运维负责人,生产环境的某个微服务集群下的某个实例,暂时出了问题,但又不希望它下线。你希望可以把该实例给屏蔽掉,暂时不让它被调用 - 如果你是业务负责人,鉴于业务服务的快速迭代性,微服务集群下的实例发布不同的版本。你希望根据版本管理策略进行路由,提供给下游微服务区别调用,例如访问控制快速基于版本的不同而切换,例如在不同的版本之间进行流量调拨 - 如果你是业务负责人,希望灰度发布功能可以基于业务场景特色定制,例如根据用户手机号进行不同服务器的路由 - 如果你是DBA负责人,希望灰度发布功能可以基于数据库切换上 - 如果你是测试负责人,希望对微服务做A/B测试 ## 名词解释 - E版、F版、G版、H版,即Spring Cloud的Edgware、Finchley、Greenwich、Hoxton的首字母,以此类推 - 灰度发布和灰度路由。灰度发布即基于订阅方式的灰度功能,灰度路由即通过Http Header的传递路由信息的灰度功能,但它也能支持订阅方式 - 匹配灰度和权重灰度。匹配灰度即在灰度的时候,没有过渡过程,用版本匹配的方式流量,直接从旧版本切换到新版本,权重灰度即在灰度的时候,有个过渡过程,可以根据实际情况,先给新版本分配低额流量,给旧版本分配高额流量,对新版本进行监测,如果没有问题,就继续把旧版的流量切换到新版本上 - 规则定义和策略定义。规则定义即通过XML或者Json定义既有格式的规则,策略定义即通过Http Header的策略方式传递路由信息 - 本地版本,即初始化读取本地配置文件获取的版本,也可以是第一次读取远程配置中心获取的版本。本地版本和初始版本是同一个概念 - 动态版本,即灰度发布时的版本。动态版本和灰度版本是同一个概念 - 本地规则,即初始化读取本地配置文件获取的规则,也可以是第一次读取远程配置中心获取的规则。本地规则和初始规则是同一个概念 - 动态规则,即灰度发布时的规则。动态规则和灰度规则是同一个概念 - 事件总线,即基于Google Guava的EventBus构建的组件。通过事件总线可以推送动态规则策略和动态版本的更新和删除 - 远程配置中心,即可以存储规则策略配置XML格式的配置中心,可以包括不限于Nacos,Redis,Apollo - 配置(Config)和规则(Rule)。在本系统中属于同一个概念,例如更新配置,即更新规则;例如远程配置中心存储的配置,即规则XML - 服务端口和管理端口。服务端口即在配置文件的server.port值,管理端口即management.port(E版)值或者management.server.port(F版或以上)值 ![](http://nepxion.gitee.io/docs/icon-doc/tip.png) 灰度发布(规则)和灰度路由(策略) ① 灰度发布(规则)和灰度路由(策略)对比 |   | 灰度发布 | 灰度路由 | | --- | --- | --- | | 驱动域 | 规则 | 策略 | | 驱动方式 | 通过XML或者Json配置直接驱动 | 通过REST或者RPC调用传递Header和参数
+ XML或者Json配置辅助驱动 | | 驱动频率 | 配置更新 | 每次调用时候传递 + 配置更新 | | 扩展性 | 扩展继承三个AbstractXXXListener | 扩展实现DiscoveryEnabledStrategy | | 依赖性 | 依赖配置中心或者本地配置文件 | 依赖每次调用 + 配置中心或者本地配置文件 | | 优点 | 封闭式灰度,无Header驱动,业务
无感知 | 开放式灰度,支持Header驱动,条件型灵活
灰度,灰度生效实时无延迟性 | | 缺点 | 不支持条件型灵活灰度,灰度生效
有延迟性 | 需要业务介入,需要处理好异步调用场景下
Header丢失的问题 | ② 灰度发布(规则)和灰度路由(策略)关系 - 灰度发布(规则)和灰度路由(策略),可以并行在一起工作,也关闭一项,让另一项单独工作 - 灰度发布(规则)和灰度路由(策略),一起工作的时候,先执行规则过滤逻辑,再执行策略过滤逻辑 - 灰度发布(规则)和灰度路由(策略)关闭方式 - 灰度发布(规则)关闭,spring.application.register.control.enabled=false和spring.application.discovery.control.enabled=false - 灰度路由(策略)关闭,spring.application.strategy.control.enabled=false ![](http://nepxion.gitee.io/docs/icon-doc/tip.png) 动态改变规则策略和动态改变版本 ① 动态改变规则策略 微服务启动的时候,由于规则策略(例如:rule.xml)已经配置在本地,使用者希望改变一下规则策略,而不重启微服务,达到规则策略的改变 - 规则策略分为本地规则策略和动态规则策略 - 本地规则策略是通过在本地规则策略(例如:rule.xml)文件定义的,也可以从远程配置中心获取,在微服务启动的时候读取 - 动态规则策略是通过POST方式动态设置,或者由远程配置中心推送设置 - 规则策略初始化的时候,如果接入了远程配置中心,先读取远程规则策略,如果不存在,再读取本地规则策略文件 - 规则策略可以持久化到远程配置中心,一旦微服务死掉后,再次启动,仍旧可以拿到灰度规则策略,所以动态改变规则策略策略属于永久灰度手段 - 规则策略推送到远程配置中心可以分为局部推送和全局推送 - 局部推送是基于Group+ServiceId来推送的,就是同一个Group下同一个ServiceId的服务集群独立拥有一个规则策略配置,如果采用这种方式,需要在每个微服务集群下做一次灰度。优点是独立封闭,本服务集群灰度失败不会影响到其它服务集群,缺点是相对繁琐 - 全局推送是基于Group来推送的(接口参数中的ServiceId由Group来代替),就是同一个Group下所有服务集群共同拥有一个规则策略配置,如果采用这种方式,只需要做一次灰度,所有服务集群都生效。优点是非常简便,缺点具有一定风险,因为这个规则策略配置掌握着所有服务集群的命运。全局推送用于全链路灰度 - 如果既执行了全局推送,又执行了局部推送,那么,当服务运行中,优先接受最后一次推送的规则策略;当服务重新启动的时候,优先读取局部推送的规则策略 ② 动态改变版本 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:动态改变版本,只允许发生在调用链的起点,例如网关,如果没有网关,则取第一个服务,其它层级的服务不能使用该功能 微服务启动的时候,由于版本已经写死在application.properties里,使用者希望改变一下版本,而不重启微服务,达到访问版本的路径改变 - 版本分为本地版本和动态版本 - 本地版本是通过在application.properties里配置的,在微服务启动的时候读取 - 动态版本是通过POST方式动态设置 - 获取版本值的时候,先获取动态版本,如果不存在,再获取本地版本 - 版本不会持久化到远程配置中心,一旦微服务死掉后,再次启动,拿到的还是本地版本,所以动态改变版本策略属于临时灰度手段 ## 工程架构 ### 工程清单 | 工程名 | 描述 | | --- | --- | | discovery-common | 通用模块 | | discovery-common-apollo | 封装Apollo通用操作逻辑 | | discovery-common-nacos | 封装Nacos通用操作逻辑 | | discovery-common-redis | 封装Redis通用操作逻辑 | | discovery-plugin-framework | 核心框架 | | discovery-plugin-framework-eureka | 核心框架服务注册发现的Eureka实现 | | discovery-plugin-framework-consul | 核心框架服务注册发现的Consul实现 | | discovery-plugin-framework-zookeeper | 核心框架服务注册发现的Zookeeper实现 | | discovery-plugin-framework-nacos | 核心框架服务注册发现的Nacos实现 | | discovery-plugin-config-center | 配置中心 | | discovery-plugin-config-center-starter-apollo | 配置中心的Apollo Starter | | discovery-plugin-config-center-starter-nacos | 配置中心的Nacos Starter | | discovery-plugin-config-center-starter-redis | 配置中心的Redis Starter | | discovery-plugin-admin-center | 管理中心 | | discovery-plugin-starter-eureka | 核心框架的Eureka Starter | | discovery-plugin-starter-consul | 核心框架的Consul Starter | | discovery-plugin-starter-zookeeper | 核心框架的Zookeeper Starter | | discovery-plugin-starter-nacos | 核心框架的Nacos Starter | | discovery-plugin-strategy | 路由策略 | | discovery-plugin-strategy-sentinel | 路由策略的Sentinel | | discovery-plugin-strategy-sentinel-monitor | 路由策略的Sentinel监控 | | discovery-plugin-strategy-sentinel-starter-local | 路由策略的Sentinel Local Starter | | discovery-plugin-strategy-sentinel-starter-apollo | 路由策略的Sentinel Apollo Starter | | discovery-plugin-strategy-sentinel-starter-nacos | 路由策略的Sentinel Nacos Starter | | discovery-plugin-strategy-sentinel-starter-opentracing | 路由策略的Sentinel OpenTracing Starter | | discovery-plugin-strategy-sentinel-starter-skywalking | 路由策略的Sentinel Skywalking Starter | | discovery-plugin-strategy-starter-service | 路由策略的Service Starter | | discovery-plugin-strategy-starter-service-sentinel | 路由策略的Service Sentinel Starter | | discovery-plugin-strategy-starter-zuul | 路由策略的Zuul Starter | | discovery-plugin-strategy-starter-gateway | 路由策略的Spring Cloud Gateway Starter | | discovery-plugin-strategy-starter-hystrix | 路由策略下,Hystrix做线程模式的服务隔离必须引入的插件 Starter | | discovery-plugin-strategy-starter-opentracing | 路由策略的OpenTracing Starter | | discovery-plugin-strategy-starter-skywalking | 路由策略的Skywalking Starter | | discovery-plugin-strategy-starter-agent | 路由策略的异步跨线程Agent Starter | | discovery-plugin-strategy-starter-agent-plugin | 路由策略的异步跨线程Agent Plugin Starter | | discovery-plugin-test-starter | 自动化测试 Starter | | discovery-console | 控制平台,集成接口给UI | | discovery-console-starter-apollo | 控制平台的Apollo Starter | | discovery-console-starter-nacos | 控制平台的Nacos Starter | | discovery-console-starter-redis | 控制平台的Redis Starter | | discovery-console-desktop | 图形化灰度发布等桌面程序 | | 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示例 | ### 架构核心 - 灰度方式区别图 ![](http://nepxion.gitee.io/docs/discovery-doc/Difference.jpg) ① 基于网关为触点的Header传递的全链路灰度路由,适用于网关前置部署方式的企业。域网关部署模式下,最适用于该方式;非域网关部署模式下,开启并行灰度路由下的版本优选策略 ② 基于全局订阅方式的全链路灰度发布,适用于网关部署方式比较弱化的企业 ③ 基于全局订阅和Header传递组合式全链路灰度路由,上述两种方式的结合体,是比较理想和节省成本的落地方式 - 服务治理架构图 ![](http://nepxion.gitee.io/docs/discovery-doc/Govern.jpg) - 全局架构图 ![](http://nepxion.gitee.io/docs/discovery-doc/Architecture.jpg) - 模块结构图 ![](http://nepxion.gitee.io/docs/discovery-doc/Module.jpg) ### 依赖引入 ① 服务注册发现依赖引入 服务注册发现中间件的四个插件,必须引入其中一个。该依赖提供灰度发布功能、注册发现中心、管理中心等功能 ```xml com.nepxion discovery-plugin-starter-nacos discovery-plugin-starter-eureka discovery-plugin-starter-consul discovery-plugin-starter-zookeeper ${discovery.version} ``` ② 远程配置中心依赖引入 远程配置中心中间件的三个插件,选择引入其中一个(也可以引入使用者自己的扩展)。该依赖提供配置中心、规则策略解析等功能 ```xml com.nepxion discovery-plugin-config-center-starter-apollo discovery-plugin-config-center-starter-nacos discovery-plugin-config-center-starter-redis ${discovery.version} ``` ③ 路由策略依赖引入 微服务端、网关Zuul端和网关Spring Cloud Gateway端三个路由策略插件,选择引入其中一个。该依赖提供灰度路由功能、Http Header传递等功能 ```xml com.nepxion discovery-plugin-strategy-starter-service discovery-plugin-strategy-starter-zuul discovery-plugin-strategy-starter-gateway ${discovery.version} ``` ④ 防护插件依赖引入 - Sentinel防护插件。只适用于微服务端 ```xml com.nepxion discovery-plugin-strategy-starter-service-sentinel ${discovery.version} ``` - Sentinel防护的数据源插件,选择引入其中一个(也可以引入使用者自己的扩展) ```xml com.nepxion discovery-plugin-strategy-sentinel-starter-nacos discovery-plugin-strategy-sentinel-starter-apollo discovery-plugin-strategy-sentinel-starter-local ${discovery.version} ``` - Hystrix防护插件。Hystrix线程池隔离模式(信号量隔离模式不需要引入)下必须引入该插件,灰度路由Header和调用链Span在Hystrix线程池隔离模式下传递时,通过线程上下文切换会存在丢失Header的问题。通过该插件解决,支持微服务端、网关Zuul端和网关Spring Cloud Gateway端 ```xml com.nepxion discovery-plugin-strategy-starter-hystrix ${discovery.version} ``` ⑤ 控制台依赖引入 远程配置中心中间件的三个插件,选择引入其中一个(也可以引入使用者自己的扩展)。该依赖提供配置中心、规则策略解析等功能 ```xml com.nepxion discovery-console-starter-apollo discovery-console-starter-nacos discovery-console-starter-redis ${discovery.version} ``` ⑥ 调用链插件依赖引入 调用链功能引入,支持微服务端、网关Zuul端和网关Spring Cloud Gateway端 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:该模块支持F版或更高版本,且不能同时引入 ```xml 微服务端引入 com.nepxion discovery-plugin-strategy-sentinel-starter-opentracing discovery-plugin-strategy-sentinel-starter-skywalking ${discovery.version} ``` ⑦ 异步跨线程Agent依赖引入 异步跨线程Agent的引入,通过Java Agent方式启动。灰度路由Header和调用链Span在Hystrix线程池隔离模式下或者线程、线程池、@Async注解等异步调用Feign或者RestTemplate时,通过线程上下文切换会存在丢失Header的问题。通过该插件解决,支持微服务端、网关Zuul端和网关Spring Cloud Gateway端 ```xml com.nepxion discovery-plugin-strategy-starter-agent ${discovery.version} com.nepxion discovery-plugin-strategy-starter-agent-plugin ${discovery.version} ``` ⑧ 自动化测试插件依赖引入 ```xml com.nepxion discovery-plugin-test-starter ${discovery.version} ``` ## 准备工作 为了更好的阐述框架的各项功能,本文围绕指南示例展开,请使用者先进行下面的准备工作。指南示例以Nacos为服务注册中心和配置中心展开介绍,使用者可自行换成其它服务注册中心和配置中心 ### 环境搭建 ① 下载代码 ② Git clone https://github.com/Nepxion/DiscoveryGuide.git ③ 代码导入IDE ④ 下载Nacos服务器 - 从[https://github.com/alibaba/nacos/releases](https://github.com/alibaba/nacos/releases)获取nacos-server-x.x.x.zip,并解压 ⑤ 启动Nacos服务器 - Windows环境下,运行bin目录下的startup.cmd - Linux环境下,运行bin目录下的startup.sh ### 启动服务 - 在IDE中,启动四个应用服务和两个网关服务,控制平台服务和监控平台服务可选,如下 | 类名 | 微服务 | 服务端口 | 版本 | 区域 | 环境 | | --- | --- | --- | --- | --- | -- | | DiscoveryGuideServiceA1.java | A1 | 3001 | 1.0 | dev | env1 | | DiscoveryGuideServiceA2.java | A2 | 3002 | 1.1 | qa | common | | DiscoveryGuideServiceB1.java | B1 | 4001 | 1.0 | qa | env1 | | DiscoveryGuideServiceB2.java | B2 | 4002 | 1.1 | dev | common | | DiscoveryGuideGateway.java | Gateway | 5001 | 1.0 | 无 | 无 | | DiscoveryGuideZuul.java | Zuul | 5002 | 1.0 | 无 | 无 | | DiscoveryGuideConsole.java | Console | 6001 | 1.0 | 无 | 无 | | DiscoveryGuideAdmin.java | Admin | 6002 | 1.0 | 无 | 无 | - 部署拓扑图 ![](http://nepxion.gitee.io/docs/discovery-doc/BasicTopology.jpg) 全链路路径, 如下 ``` API网关 -> 服务A(两个实例) -> 服务B(两个实例) ``` ### 环境验证 ① 通过Postman工具验证 - 导入Postman的测试脚本,[脚本地址](https://github.com/Nepxion/DiscoveryGuide/raw/master/postman.json) - 在Postman中执行目录结构下 ”Nepxion“ -> ”Discovery指南网关接口“ -> ”Gateway网关调用示例“,调用地址为[http://localhost:5001/discovery-guide-service-a/invoke/gateway](http://localhost:5001/discovery-guide-service-a/invoke/gateway),相关的Header值已经预设,供开发者修改。测试通过Spring Cloud Gateway网关的调用结果,结果为如下格式 ``` gateway -> [ID=discovery-guide-service-a][P=Nacos][H=192.168.0.107:3001][V=1.0][R=dev][E=env1][G=discovery-guide-group][TID=48682.7508.15870951148324081][SID=49570.77.15870951148480000] -> [ID=discovery-guide-service-b][P=Nacos][H=192.168.0.107:4001][V=1.0][R=qa][E=env1][G=discovery-guide-group][TID=48682.7508.15870951148324081][SID=49571.85.15870951189970000] ``` - 在Postman中执行目录结构下 ”Nepxion“ -> ”Discovery指南网关接口“ -> ”Zuul网关调用示例“,调用地址为[http://localhost:5002/discovery-guide-service-a/invoke/zuul](http://localhost:5002/discovery-guide-service-a/invoke/zuul),相关的Header值已经预设,供开发者修改。测试通过Zuul网关的调用结果,结果为如下格式 ``` zuul -> [ID=discovery-guide-service-a][P=Nacos][H=192.168.0.107:3001][V=1.0][R=dev][E=env1][G=discovery-guide-group][TID=48682.7508.15870951148324081][SID=49570.77.15870951148480000] -> [ID=discovery-guide-service-b][P=Nacos][H=192.168.0.107:4001][V=1.0][R=qa][E=env1][G=discovery-guide-group][TID=48682.7508.15870951148324081][SID=49571.85.15870951189970000] ``` - 上述步骤在下面每次更改规则策略的时候执行,并验证结果和规则策略的期望值是否相同 ② 通过图形化界面验证 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 该方式有点古老,并不再维护,请斟酌使用 - 下载[源码主页](https://github.com/Nepxion/Discovery)的工程,并导入IDE - 启动源码工程下的discovery-springcloud-example-console/ConsoleApplication - 启动源码工程下的discovery-console-desktop/ConsoleLauncher - 通过admin/admin登录,点击“显示服务拓扑”按钮,将呈现如下界面 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide5-2.jpg) - 在加入上述规则策略前,选中网关节点,右键点击“执行灰度路由”,在弹出路由界面中,依次加入“discovery-guide-service-a”和“discovery-guide-service-b”,点击“执行路由”按钮,将呈现如下界面 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide5-3.jpg) - 在加入上述规则策略后,在路由界面中,再次点击“执行路由”按钮,将呈现如下界面 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide5-4.jpg) ## 基于Header传递方式的灰度路由策略 ![](http://nepxion.gitee.io/docs/icon-doc/information.png) 本章节通过网关为触发点来介绍灰度路由策略功能,使用者也可以不通过网关,直接以微服务为触发点进行实施 ### 配置网关灰度路由策略 在Nacos配置中心,增加网关灰度路由策略 #### 版本匹配灰度路由策略 增加Spring Cloud Gateway的基于版本匹配路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用都走版本为1.0的服务 ```xml 1.0 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-1.jpg) 每个服务调用的版本都可以自行指定,见下面第二条。当所有服务都选同一版本的时候,可以简化成下面第一条 ``` 1. 1.0 2. {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` 如果上述表达式还未满足需求,也可以采用通配符(具体详细用法,参考Spring AntPathMatcher) ``` * - 表示调用范围为所有服务的所有版本 1.* - 表示调用范围为所有服务的1开头的所有版本 ``` 或者 ``` "discovery-guide-service-b":"1.*;1.2.?" ``` 表示discovery-guide-service-b服务的版本调用范围是1开头的所有版本,或者是1.2开头的所有版本(末尾必须是1个字符),多个用分号隔开 版本灰度路由架构图 ![](http://nepxion.gitee.io/docs/discovery-doc/RouteVersion.jpg) #### 版本权重灰度路由策略 增加Spring Cloud Gateway的基于版本权重路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-gateway,策略内容如下,实现从Spring Cloud Gateway发起的调用1.0版本流量调用为90%,1.1流量调用为10% ```xml 1.0=90;1.1=10 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-2.jpg) 每个服务调用的版本权重都可以自行指定,见下面第二条。当所有服务都选相同版本权重的时候,可以简化成下面第一条 ``` 1. 1.0=90;1.1=10 2. {"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=90;1.1=10"} ``` #### 区域匹配灰度路由策略 增加Zuul的基于区域匹配路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用都走区域为dev的服务 ```xml dev ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-3.jpg) 每个服务调用的区域都可以自行指定,见下面第二条。当所有服务都选同一区域的时候,可以简化成下面第一条 ``` 1. dev 2. {"discovery-guide-service-a":"dev", "discovery-guide-service-b":"dev"} ``` 如果上述表达式还未满足需求,也可以采用通配符(具体详细用法,参考Spring AntPathMatcher) ``` * - 表示调用范围为所有服务的所有区域 d* - 表示调用范围为所有服务的d开头的所有区域 ``` 或者 ``` "discovery-guide-service-b":"d*;q?" ``` 表示discovery-guide-service-b服务的区域调用范围是d开头的所有区域,或者是q开头的所有区域(末尾必须是1个字符),多个用分号隔开 区域灰度路由架构图 ![](http://nepxion.gitee.io/docs/discovery-doc/RouteRegion.jpg) #### 区域权重灰度路由策略 增加Zuul的基于区域权重路由的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用dev区域流量调用为85%,qa区域流量调用为15% ```xml dev=85;qa=15 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-4.jpg) 每个服务调用的区域权重都可以自行指定,见下面第二条。当所有服务都选相同区域权重的时候,可以简化成下面第一条 ``` 1. dev=85;qa=15 2. {"discovery-guide-service-a":"dev=85;qa=15", "discovery-guide-service-b":"dev=85;qa=15"} ``` #### IP地址和端口匹配灰度路由策略 增加Zuul的基于IP地址和端口匹配的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现从Zuul发起的调用走指定IP地址和端口,或者指定IP地址,或者指定端口(下面策略以端口为例)的服务 ```xml
3001
``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-5.jpg) 每个服务调用的端口都可以自行指定,见下面第二条。当所有服务都选同一端口的时候,可以简化成下面第一条(单机版不适用于该策略) ``` 1.
3001
2.
{"discovery-guide-service-a":"3001", "discovery-guide-service-b":"3001"}
``` 如果上述表达式还未满足需求,也可以采用通配符(具体详细用法,参考Spring AntPathMatcher) ``` * - 表示调用范围为所有服务的所有端口 3* - 表示调用范围为所有服务的3开头的所有端口 ``` 或者 ``` "discovery-guide-service-b":"3*;400?" ``` 表示discovery-guide-service-b服务的端口调用范围是3开头的所有端口,或者是4开头的所有端口(末尾必须是1个字符),多个用分号隔开 IP地址和端口灰度路由架构图 ![](http://nepxion.gitee.io/docs/discovery-doc/RouteAddress.jpg) #### 动态变更元数据的灰度路由策略 利用注册中心的Open API接口动态变更服务实例的元数据,达到稳定版本和灰度版本流量控制的目的。以Nacos的版本匹配为例 老的稳定版本的服务实例配置元数据 ``` spring.cloud.nacos.discovery.metadata.version=stable ``` 新的灰度版本的服务实例配置元数据 ``` spring.cloud.nacos.discovery.metadata.version=gray ``` 参考如下配置,第一种方式表示所有的服务流量走灰度版本,第二种方式表示a服务流量走灰度版本,b服务流量走稳定版本 ```xml gray ``` ```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/service?serviceName={appId}&metadata=version%3stable' ``` - Eureka Open API变更元数据 ``` curl -X PUT 'http://ip:port/eureka/apps/{appId}/{instanceId}/metadata?version=stable' ``` - Consul Open API变更元数据 自行研究 - Zookeeper Open API变更元数据 自行研究 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 需要注意的是,该方案利用第三方注册中心的Open API达到控制目的,具有一定的延迟性,不如本框架那样具有灰度发布实时生效的特征 #### 全局订阅式的灰度路由策略 通过全链路传递Header实现灰度路由,会存在一定的困难,框架提供另外一种很简单的方式来规避Header传递,但能达到Header传递一样的效果。以版本匹配为例 增加版本匹配的灰度策略,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),策略内容如下,实现a服务走1.0版本,b服务走1.1版本 ```xml {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"} ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-10.jpg) 如果采用上述方式,可以考虑开启下面的开关 ``` # 启动和关闭核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下 # 1. n-d-version # 2. n-d-region # 3. n-d-address # 4. n-d-version-weight # 5. n-d-region-weight # 6. n-d-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 ``` ### 配置全链路灰度条件命中和灰度匹配组合式策略 属于全链路蓝绿部署的范畴。既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务,一般来说,网关已经加了,服务上就不需要加,当不存在的网关的时候,服务就可以考虑加上 支持Spel表达式进行自定义策略,支持所有标准的Spel表达式,包括==,!=,>,>=,<,<=,&&,||等,由于策略保存在XML文件里,对于特殊符号需要转义,见下面表格 ![](http://nepxion.gitee.io/docs/discovery-doc/EscapeCharacter1.jpg) 从Http Header获取到值进行逻辑判断,例如Http Header的Key为a,它的格式表示为#H['a'],H为Header的首字母。假如路由触发的条件为a等于1,b小于等于2,c不等于3,那么表达式可以写为 ![](http://nepxion.gitee.io/docs/discovery-doc/EscapeCharacter2.jpg) 特殊符号必须转义,所以表达式必须改成如下 ![](http://nepxion.gitee.io/docs/discovery-doc/EscapeCharacter3.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) Spel表达式需要注意的地方 - 任何值都大于null。当某个Header未传值,但又指定了该Header大于的表达式,那么正则结果是true。例如,表达式为#H['a'] > '2',但a作为Header未传递进来,即为null,判断结果为false - null满足不等于。当某个Header未传值,但又指定了该Header不等于的表达式,那么正则结果是true。例如,表达式为#H['a'] != '2',但a作为Header未传递进来,即为null,判断结果为true 增加组合式的灰度策略,支持版本匹配、区域匹配、IP地址和端口匹配、版本权重匹配、区域权重匹配。以版本匹配为例,Group为discovery-guide-group,Data Id为discovery-guide-gateway,或者,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现功能 ``` 1. 当外部调用带有的Http Header中的值a=1同时b=2 节点中header="#H['a'] == '1' && #H['b'] == '2'"对应的version-id="version-route1",找到下面 节点中id="version-route1" type="version"的那项,那么路由即为 {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} 2. 当外部调用带有的Http Header中的值a=1 节点中header="#H['a'] == '1'"对应的version-id="version-route2",找到下面 中id="version-route2" type="version"的那项,那么路由即为 {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"} 3. 当外部调用带有的Http Header中的值都不命中,那么执行顺序为 1)如果配置了权重路由(节点下)的策略,则执行权重路由 2)如果权重路由策略未配置,则执行节点中的全局缺省路由,那么路由即为 {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} 3)如果全局缺省路由未配置,则执行Spring Cloud Ribbon轮询策略 4. 必须带有Header。假如不愿意从网关外部传入Header,那么支持策略下内置Header来决策蓝绿和灰度,可以代替外部传入Header,参考如下配置 注意:Spring Cloud Gateway在Finchley版不支持该方式
5. 策略总共支持5种,可以单独一项使用,也可以多项叠加使用 1)version 版本路由 2)region 区域路由 3)address IP地址和端口路由 4)version-weight 版本权重路由 5)region-weight 区域权重路由 6. 策略支持Spring Spel的条件表达式方式 7. 策略支持Spring Matcher的通配符方式 8. 支持并行实施。通过namespace(可以自定义)的Http Header进行发布隔离 ``` 具体示例内容如下 ```xml {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.1"}
``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-8.jpg) 内置基于Swagger的Rest接口,可以校验策略的条件表达式、校验策略的全链路路由 ### 配置全链路灰度条件权重和灰度匹配组合式策略 属于全链路灰度发布的范畴。既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务,一般来说,网关已经加了,服务上就不需要加,当不存在的网关的时候,服务就可以考虑加上 增加组合式的灰度策略,支持版本匹配、区域匹配、IP地址和端口匹配。以版本匹配为例,Group为discovery-guide-group,Data Id为discovery-guide-zuul,策略内容如下,实现功能 - a服务1.0版本向网关提供90%的流量,1.1版本向网关提供10%的流量 - a服务1.0版本只能访问b服务1.0版本,1.1版本只能访问b服务1.1版本 该功能的意义是,网关随机权重调用服务,而服务链路按照版本匹配方式调用 ``` 1. version-route1链路配比10%的流量,version-route2链路配比90%的流量 2. 策略总共支持3种,可以单独一项使用,也可以多项叠加使用 1)version 版本路由 2)region 区域路由 3)address IP地址和端口路由 3. 当带有Header的时候,根据Header的表达式来选择相应的全链路权重策略;如果不带Header,执行默认无条件的全链路权重策略 4. 策略支持Spring Spel的条件表达式方式,用法跟上面一致 5. 策略支持Spring Matcher的通配符方式,用法跟上面一致 6. 支持并行实施。通过namespace(可以自定义)的Http Header进行发布隔离 ``` 具体示例内容如下 ```xml {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-9.jpg) ### 配置前端灰度和网关灰度路由组合式策略 当前端(例如:APP)和后端微服务同时存在多个版本时,可以执行“前端灰度&网关灰度路由组合式策略” 例如:前端存在1.0和2.0版本,微服务存在1.0和2.0版本,由于存在版本不兼容的情况(前端1.0版本只能调用微服务的1.0版本,前端2.0版本只能调用微服务的2.0版本),那么前端调用网关时候,可以通过Header传递它的版本号给网关,网关根据前端版本号,去路由对应版本的微服务 该场景可以用“通过业务参数在过滤器中自定义灰度路由策略”的方案来解决,如下 ```xml {"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} {"discovery-guide-service-a":"1.1", "discovery-guide-service-b":"1.1"} ``` 上述配置,模拟出全链路中,两条独立不受干扰的调用路径 ``` 1. APP v1.0 -> 网关 -> A服务 v1.0 -> B服务 v1.0 2. APP v1.1 -> 网关 -> A服务 v1.1 -> B服务 v1.1 ``` ### 通过其它方式设置灰度路由策略 除了上面通过配置中心发布灰度规路由则外,还有如下三种方式,这三种方式既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务 #### 通过前端传入灰度路由策略 通过前端(Postman)方式传入灰度路由策略,来代替配置中心方式,传递全链路路由策略 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:当配置中心和界面都配置后,以界面传入优先 - 版本匹配策略,Header格式如下任选一个 ``` 1. n-d-version=1.0 2. n-d-version={"discovery-guide-service-a":"1.0", "discovery-guide-service-b":"1.0"} ``` - 版本权重策略,Header格式如下任选一个 ``` 1. n-d-version-weight=1.0=90;1.1=10 2. n-d-version-weight={"discovery-guide-service-a":"1.0=90;1.1=10", "discovery-guide-service-b":"1.0=90;1.1=10"} ``` - 区域匹配策略,Header格式如下任选一个 ``` 1. n-d-region=qa 2. n-d-region={"discovery-guide-service-a":"qa", "discovery-guide-service-b":"qa"} ``` - 区域权重策略,Header格式如下任选一个 ``` 1. n-d-region-weight=dev=99;qa=1 2. n-d-region-weight={"discovery-guide-service-a":"dev=99;qa=1", "discovery-guide-service-b":"dev=99;qa=1"} ``` - IP地址和端口匹配策略,Header格式如下任选一个 ``` 1. n-d-address={"discovery-guide-service-a":"127.0.0.1:3001", "discovery-guide-service-b":"127.0.0.1:4002"} 2. n-d-address={"discovery-guide-service-a":"127.0.0.1", "discovery-guide-service-b":"127.0.0.1"} 3. n-d-address={"discovery-guide-service-a":"3001", "discovery-guide-service-b":"4002"} ``` - 环境隔离下动态环境匹配策略 ``` 1. n-d-env=env1 ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-6.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide2-7.jpg) 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。需要通过如下开关做控制 ``` # 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true spring.application.strategy.gateway.header.priority=false # 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true spring.application.strategy.gateway.original.header.ignored=true # 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true spring.application.strategy.zuul.header.priority=false # 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true spring.application.strategy.zuul.original.header.ignored=true ``` #### 通过业务参数在过滤器中自定义灰度路由策略 通过过滤器传递Http Header的方式传递全链路灰度路由策略 下面代码既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务,一般来说,网关已经加了,服务就不需要加,当不存在的网关的时候,服务上就可以考虑。继承GatewayStrategyRouteFilter、ZuulStrategyRouteFilter或者ServiceStrategyRouteFilter,覆盖掉如下方法中的一个或者多个,通过@Bean方式覆盖框架内置的过滤类 ```java public String getRouteVersion(); public String getRouteRegion(); public String getRouteAddress(); public String getRouteVersionWeight(); public String getRouteRegionWeight(); ``` GatewayStrategyRouteFilter示例 在代码里根据不同的Header选择不同的路由路径,示例提供全链路条件命中和全链路随机权重两种方式 ```java // 适用于A/B Testing或者更根据某业务参数决定灰度路由路径。可以结合配置中心分别配置A/B两条路径,可以动态改变并通知 // 当Header中传来的用户为张三,执行一条路由路径;为李四,执行另一条路由路径 public class MyGatewayStrategyRouteFilter extends DefaultGatewayStrategyRouteFilter { 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\"}"; @Value("${a.route.version:" + DEFAULT_A_ROUTE_VERSION + "}") private String aRouteVersion; @Value("${b.route.version:" + DEFAULT_B_ROUTE_VERSION + "}") private String bRouteVersion; // 全链路条件命中 @Override public String getRouteVersion() { String user = strategyContextHolder.getHeader("user"); if (StringUtils.equals(user, "zhangsan")) { return aRouteVersion; } else if (StringUtils.equals(user, "lisi")) { return bRouteVersion; } return super.getRouteVersion(); } // 全链路随机权重 /*@Override public String getRouteVersion() { List> list = new ArrayList>(); list.add(new ImmutablePair(DEFAULT_A_ROUTE_VERSION, 30D)); list.add(new ImmutablePair(DEFAULT_B_ROUTE_VERSION, 70D)); MapWeightRandom weightRandom = new MapWeightRandom(list); return weightRandom.random(); }*/ } ``` 在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类 ```java @Bean public GatewayStrategyRouteFilter gatewayStrategyRouteFilter() { return new MyGatewayStrategyRouteFilter(); } ``` ZuulStrategyRouteFilter示例 在代码里根据不同的Header选择不同的路由路径,示例提供全链路条件命中和全链路随机权重两种方式 ```java // 适用于A/B Testing或者更根据某业务参数决定灰度路由路径。可以结合配置中心分别配置A/B两条路径,可以动态改变并通知 // 当Header中传来的用户为张三,执行一条路由路径;为李四,执行另一条路由路径 public class MyZuulStrategyRouteFilter extends DefaultZuulStrategyRouteFilter { private static final 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\"}"; @Value("${a.route.version:" + DEFAULT_A_ROUTE_VERSION + "}") private String aRouteVersion; @Value("${b.route.version:" + DEFAULT_B_ROUTE_VERSION + "}") private String bRouteVersion; // 全链路条件命中 @Override public String getRouteVersion() { String user = strategyContextHolder.getHeader("user"); if (StringUtils.equals(user, "zhangsan")) { return aRouteVersion; } else if (StringUtils.equals(user, "lisi")) { return bRouteVersion; } return super.getRouteVersion(); } // 全链路随机权重 /*@Override public String getRouteVersion() { List> list = new ArrayList>(); list.add(new ImmutablePair(DEFAULT_A_ROUTE_VERSION, 30D)); list.add(new ImmutablePair(DEFAULT_B_ROUTE_VERSION, 70D)); MapWeightRandom weightRandom = new MapWeightRandom(list); return weightRandom.random(); }*/ } ``` 在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类 ```java @Bean public ZuulStrategyRouteFilter zuulStrategyRouteFilter() { return new MyZuulStrategyRouteFilter(); } ``` ServiceStrategyRouteFilter示例 在代码里根据不同的Header选择不同的路由路径,示例提供全链路条件命中和全链路随机权重两种方式 ```java // 适用于A/B Testing或者更根据某业务参数决定灰度路由路径。可以结合配置中心分别配置A/B两条路径,可以动态改变并通知 // 当Header中传来的用户为张三,执行一条路由路径;为李四,执行另一条路由路径 public class MyServiceStrategyRouteFilter extends DefaultServiceStrategyRouteFilter { private static final String DEFAULT_A_ROUTE_VERSION = "{\"discovery-guide-service-b\":\"1.1\"}"; private static final String DEFAULT_B_ROUTE_VERSION = "{\"discovery-guide-service-b\":\"1.0\"}"; @Value("${a.route.version:" + DEFAULT_A_ROUTE_VERSION + "}") private String aRouteVersion; @Value("${b.route.version:" + DEFAULT_B_ROUTE_VERSION + "}") private String bRouteVersion; // 全链路条件命中 @Override public String getRouteVersion() { String user = strategyContextHolder.getHeader("user"); if (StringUtils.equals(user, "zhangsan")) { return aRouteVersion; } else if (StringUtils.equals(user, "lisi")) { return bRouteVersion; } return super.getRouteVersion(); } // 全链路随机权重 /*@Override public String getRouteVersion() { List> list = new ArrayList>(); list.add(new ImmutablePair(DEFAULT_A_ROUTE_VERSION, 30D)); list.add(new ImmutablePair(DEFAULT_B_ROUTE_VERSION, 70D)); MapWeightRandom weightRandom = new MapWeightRandom(list); return weightRandom.random(); }*/ } ``` 在配置类里@Bean方式进行过滤类创建,覆盖框架内置的过滤类 ```java @Bean public ServiceStrategyRouteFilter serviceStrategyRouteFilter() { return new MyServiceStrategyRouteFilter(); } ``` #### 通过业务参数在策略类中自定义灰度路由策略 通过策略方式自定义灰度路由策略。下面代码既适用于Zuul和Spring Cloud Gateway网关,也适用于Service微服务,同时全链路中网关和服务都必须加该方式,DefaultDiscoveryEnabledStrategy可以有多个,框架会组合判断 ```java // 实现了组合策略,版本路由策略+区域路由策略+IP和端口路由策略+自定义策略 public class MyDiscoveryEnabledStrategy extends DefaultDiscoveryEnabledStrategy { private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledStrategy.class); // 对REST调用传来的Header参数(例如:mobile)做策略 @Override public boolean apply(Server server) { String mobile = strategyContextHolder.getHeader("mobile"); String serviceId = pluginAdapter.getServerServiceId(server); String version = pluginAdapter.getServerVersion(server); String region = pluginAdapter.getServerRegion(server); String environment = pluginAdapter.getServerEnvironment(server); String address = server.getHostPort(); LOG.info("负载均衡用户定制触发:mobile={}, serviceId={}, version={}, region={}, env={}, address={}", mobile, serviceId, version, region, environment, address); if (StringUtils.isNotEmpty(mobile)) { // 手机号以移动138开头,路由到1.0版本的服务上 if (mobile.startsWith("138") && StringUtils.equals(version, "1.0")) { return true; // 手机号以联通133开头,路由到2.0版本的服务上 } else if (mobile.startsWith("133") && StringUtils.equals(version, "1.1")) { return true; } else { // 其它情况,直接拒绝请求 return false; } } return true; } } ``` 在配置类里@Bean方式进行策略类创建 ```java @Bean public DiscoveryEnabledStrategy discoveryEnabledStrategy() { return new MyDiscoveryEnabledStrategy(); } ``` 在网关和服务中支持基于Rest Header传递的自定义灰度路由策略,除此之外,服务还支持基于Rpc方法参数传递的自定义灰度路由策略,它包括接口名、方法名、参数名或参数值等多种形式。下面的示例表示在服务中同时开启基于Rest Header传递和Rpc方法参数传递的自定义组合式灰度路由策略,DefaultDiscoveryEnabledStrategy可以有多个,框架会组合判断 ```java // 实现了组合策略,版本路由策略+区域路由策略+IP和端口路由策略+自定义策略 public class MyDiscoveryEnabledStrategy implements DiscoveryEnabledStrategy { private static final Logger LOG = LoggerFactory.getLogger(MyDiscoveryEnabledStrategy.class); @Autowired private PluginAdapter pluginAdapter; @Autowired private ServiceStrategyContextHolder serviceStrategyContextHolder; @Override public boolean apply(Server server) { boolean enabled = applyFromHeader(server); if (!enabled) { return false; } return applyFromMethod(server); } // 根据REST调用传来的Header参数(例如:mobile),选取执行调用请求的服务实例 private boolean applyFromHeader(Server server) { String mobile = serviceStrategyContextHolder.getHeader("mobile"); String serviceId = pluginAdapter.getServerServiceId(server); String version = pluginAdapter.getServerVersion(server); String region = pluginAdapter.getServerRegion(server); String environment = pluginAdapter.getServerEnvironment(server); String address = server.getHostPort(); LOG.info("负载均衡用户定制触发:mobile={}, serviceId={}, version={}, region={}, env={}, address={}", mobile, serviceId, version, region, environment, address); if (StringUtils.isNotEmpty(mobile)) { // 手机号以移动138开头,路由到1.0版本的服务上 if (mobile.startsWith("138") && StringUtils.equals(version, "1.0")) { return true; // 手机号以联通133开头,路由到2.0版本的服务上 } else if (mobile.startsWith("133") && StringUtils.equals(version, "1.1")) { return true; } else { // 其它情况,直接拒绝请求 return false; } } return true; } // 根据RPC调用传来的方法参数(例如接口名、方法名、参数名或参数值等),选取执行调用请求的服务实例 // 本示例只作用在discovery-guide-service-a服务上 @SuppressWarnings("unchecked") private boolean applyFromMethod(Server server) { Map attributes = serviceStrategyContextHolder.getRpcAttributes(); String serviceId = pluginAdapter.getServerServiceId(server); String version = pluginAdapter.getServerVersion(server); String region = pluginAdapter.getServerRegion(server); String environment = pluginAdapter.getServerEnvironment(server); String address = server.getHostPort(); LOG.info("负载均衡用户定制触发:attributes={}, serviceId={}, version={}, region={}, env={}, address={}", attributes, serviceId, version, region, environment, address); if (attributes.containsKey(DiscoveryConstant.PARAMETER_MAP)) { Map parameterMap = (Map) attributes.get(DiscoveryConstant.PARAMETER_MAP); String value = parameterMap.get("value").toString(); if (StringUtils.isNotEmpty(value)) { // 输入值包含dev,路由到dev区域的服务上 if (value.contains("dev") && StringUtils.equals(region, "dev")) { return true; // 输入值包含qa,路由到qa区域的服务上 } else if (value.contains("qa") && StringUtils.equals(region, "qa")) { return true; } else { // 其它情况,直接通过请求 return true; } } } return true; } } ``` 需要通过如下开关开启该功能 ``` # 启动和关闭路由策略的时候,对RPC方式的调用拦截。缺失则默认为false spring.application.strategy.rpc.intercept.enabled=true ``` ### 并行灰度路由下的版本优选策略 防止多个网关上并行实时灰度路由产生混乱,对处于非灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;灰度状态的服务,还是根据传递的Header版本号进行匹配 优选的方式,对版本号进行排序,取第一个版本号,所以此方案的前置条件是必须版本号是规律的有次序,例如,以时间戳的方式 需要通过如下开关开启该功能 ``` # 启动和关闭调用对端服务,是否执行调用它的时候只取它的老的稳定版本的实例的策略。缺失则默认为false spring.application.strategy.version.filter.enabled=true ``` ### 基于异步场景的全链路灰度路由策略 当若干个服务之间调用,存在异步场景,如下 - 调用时候,启用了Hystrix线程池隔离机制 - 线程池里的线程触发调用 - 新创建单个线程触发调用 参考Hystrix线程池隔离模式下的解决方案 参考异步跨线程Agent的解决方案 ## 基于订阅方式的全链路灰度发布规则 在Nacos配置中心,增加全链路灰度发布规则 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:该功能和网关灰度路由和灰度权重功能会叠加,为了不影响演示效果,请先清除网关灰度路由的策略 ### 配置全链路灰度匹配规则 #### 版本匹配灰度规则 增加版本匹配的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现a服务1.0版本只能访问b服务1.0版本,a服务1.1版本只能访问b服务1.1版本 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide3-1.jpg) #### 区域匹配灰度规则 增加区域匹配的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现dev区域的a服务只能访问dev区域的b服务,qa区域的a服务只能访问qa区域的b服务 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide3-2.jpg) ### 配置全链路灰度权重规则 #### 全局版本权重灰度规则 增加全局版本权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现版本为1.0的服务提供90%的流量,版本为1.1的服务提供10%的流量 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide4-1.jpg) #### 局部版本权重灰度规则 增加局部版本权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现a服务1.0版本提供90%的流量,1.1版本提供10%的流量;b服务1.0版本提供20%的流量,1.1版本提供80%的流量 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide4-2.jpg) #### 全局区域权重灰度规则 增加全局区域权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现区域为dev的服务提供90%的流量,区域为qa的服务提供10%的流量 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide4-3.jpg) #### 局部区域权重灰度规则 增加局部区域权重的灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现a服务dev区域提供90%的流量,qa区域提供10%的流量;b服务dev区域提供20%的流量,qa区域提供80%的流量 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide4-4.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:局部权重优先级高于全局权重,版本权重优先级高于区域权重 请执行Postman操作,请仔细观察服务被随机权重调用到的概率 ### 配置全链路灰度权重和灰度匹配组合式规则 增加组合式的灰度规则,支持版本匹配和区域匹配。以版本匹配为例,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现功能 - a服务1.0版本向网关提供90%的流量,1.1版本向网关提供10%的流量 - a服务1.0版本只能访问b服务1.0版本,1.1版本只能访问b服务1.1版本 该功能的意义是,网关随机权重调用服务,而服务链路按照版本匹配方式调用 ```xml ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide5-1.jpg) ### 数据库灰度发布规则 通过订阅业务参数的变化,实现参数化灰度发布,例如,多数据源的数据库切换的灰度发布 增加参数化灰度规则,Group为discovery-guide-group,Data Id为discovery-guide-group(全局发布,两者都是组名),规则内容如下,实现功能 - a服务指向测试数据库(database的value为qa) - b服务指向生产数据库(database的value为prod) - a服务上线后,执行数据库灰度发布,改对应value为prod,结合ShardingSphere等多数据源中间件,实现实时无缝切换,即可实现数据库灰度发布 ```xml ``` ## 基于多格式的规则策略定义 ### 规则策略格式定义 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:服务名大小写规则 - 在配置文件(application.properties、application.yaml等)里,定义服务名(spring.application.name)不区分大小写 - 在规则文件(XML、Json)里,引用的服务名必须小写 - 在Nacos、Apollo、Redis等远程配置中心的Key,包含的服务名必须小写 ### 规则策略内容定义 规则策略的格式是XML或者Json,存储于本地文件或者远程配置中心,可以通过远程配置中心修改的方式达到规则策略动态化。其核心代码参考discovery-plugin-framework以及它的扩展、discovery-plugin-config-center以及它的扩展和discovery-plugin-admin-center等 ### 规则策略示例 XML最全的示例如下,Json示例见源码discovery-springcloud-example-service工程下的rule.json ```xml {"discovery-springcloud-example-a":"1.0", "discovery-springcloud-example-b":"1.0", "discovery-springcloud-example-c":"1.0;1.2"} {"discovery-springcloud-example-a":"qa;dev", "discovery-springcloud-example-b":"dev", "discovery-springcloud-example-c":"qa"}
{"discovery-springcloud-example-a":"192.168.43.101:1100", "discovery-springcloud-example-b":"192.168.43.101:1201", "discovery-springcloud-example-c":"192.168.43.101:1300"}
{"discovery-springcloud-example-a":"1.0=90;1.1=10", "discovery-springcloud-example-b":"1.0=90;1.1=10", "discovery-springcloud-example-c":"1.0=90;1.1=10"} {"discovery-springcloud-example-a":"dev=85;qa=15", "discovery-springcloud-example-b":"dev=85;qa=15", "discovery-springcloud-example-c":"dev=85;qa=15"}
{"discovery-springcloud-example-a":"1.0", "discovery-springcloud-example-b":"1.0", "discovery-springcloud-example-c":"1.0;1.2"} {"discovery-springcloud-example-a":"1.1", "discovery-springcloud-example-b":"1.1", "discovery-springcloud-example-c":"1.2"} {"discovery-springcloud-example-a":"qa;dev", "discovery-springcloud-example-b":"dev", "discovery-springcloud-example-c":"qa"} {"discovery-springcloud-example-a":"qa", "discovery-springcloud-example-b":"qa", "discovery-springcloud-example-c":"qa"} {"discovery-springcloud-example-a":"192.168.43.101:1100", "discovery-springcloud-example-b":"192.168.43.101:1201", "discovery-springcloud-example-c":"192.168.43.101:1300"} {"discovery-springcloud-example-a":"192.168.43.101:1101", "discovery-springcloud-example-b":"192.168.43.101:1201", "discovery-springcloud-example-c":"192.168.43.101:1301"} {"discovery-springcloud-example-a":"1.0=90;1.1=10", "discovery-springcloud-example-b":"1.0=90;1.1=10", "discovery-springcloud-example-c":"1.0=90;1.1=10"} {"discovery-springcloud-example-a":"1.0=10;1.1=90", "discovery-springcloud-example-b":"1.0=10;1.1=90", "discovery-springcloud-example-c":"1.0=10;1.1=90"} {"discovery-springcloud-example-a":"dev=85;qa=15", "discovery-springcloud-example-b":"dev=85;qa=15", "discovery-springcloud-example-c":"dev=85;qa=15"} {"discovery-springcloud-example-a":"dev=15;qa=85", "discovery-springcloud-example-b":"dev=15;qa=85", "discovery-springcloud-example-c":"dev=15;qa=85"}
``` ## 基于多方式的规则策略推送 ### 基于远程配置中心的规则策略订阅推送 Apollo订阅推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Apollo1.jpg) ① 参考Apollo官方文档[https://github.com/ctripcorp/apollo](https://github.com/ctripcorp/apollo)相关文档,搭建Apollo环境,以及熟悉相关的基本操作 ② 根据上图,做如下步骤操作 - 设置页面中AppId和配置文件里面app.id一致 - 设置页面中Namespace和配置文件里面apollo.plugin.namespace一致,如果配置文件里不设置,那么页面默认采用内置的“application” - 在页面中添加配置 - 局部配置方式:一个服务集群(eureka.instance.metadataMap.group和spring.application.name都相同的服务)对应一个配置文件,通过group+serviceId方式添加,Key为“group-serviceId”,Value为Xml或者Json格式的规则策略内容。group取值于配置文件里的eureka.instance.metadataMap.group配置项,serviceId取值于spring.application.name配置项目 - 全局配置方式:一组服务集群(eureka.instance.metadataMap.group相同,但spring.application.name可以不相同的服务)对应一个配置文件,通过group方式添加,Key为“group-group”,Value为Xml或者Json格式的规则内容。group取值于配置文件里的eureka.instance.metadataMap.group配置项 - 强烈建议局部配置方式和全局配置方式不要混用,否则连使用者自己都无法搞清楚到底是哪种配置方式在起作用 - 其他更多参数,例如evn, cluster等,请自行参考Apollo官方文档,保持一致 ③ 特别注意 - 局部配置方式建议使用Apollo的私有(private)配置方式,全局配置方式必须采用Apollo的共享(public)配置方式 - 如果业务配置和灰度配置在同一个namespace里且namespace只有一个,灰度配置可以通过apollo.bootstrap.namespaces或者apollo.plugin.namespace来指定(如果namespace为application则都不需要配置) - 如果业务配置和灰度配置不在同一个namespace里或者业务配置横跨几个namespace,灰度配置必须通过apollo.plugin.namespace来指定唯一的namespace Nacos订阅推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Nacos2.jpg) - 参考Nacos官方文档[https://github.com/alibaba/nacos](https://github.com/alibaba/nacos)相关文档,搭建Nacos环境,以及熟悉相关的基本操作 - 添加配置步骤跟Apollo配置界面中的“在页面中添加配置”操作项相似 Redis订阅推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Redis.jpg) ### 基于Swagger和Rest的规则策略推送 服务侧单个推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Swagger1.jpg) 控制平台批量推送界面 ![](http://nepxion.gitee.io/docs/discovery-doc/Swagger2.jpg) ### 基于图形化界面的规则策略推送 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 下面两种方式有点古老,并不再维护,请斟酌使用 基于图形化桌面程序的灰度发布 ![](http://nepxion.gitee.io/docs/discovery-doc/Console1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Console2.jpg) ① 桌面程序对Windows和Mac操作系统都支持,但在Mac操作系统中界面显示有点瑕疵,但不影响功能使用 ② Clone [https://github.com/Nepxion/Discovery.git](https://github.com/Nepxion/Discovery.git)获取源码 ③ 通过IDE启动 - 运行discovery-console-desktop\ConsoleLauncher.java启动 ④ 通过脚本启动 - 在discovery-console-desktop目录下执行mvn clean install,target目录下将产生discovery-console-desktop-[版本号]-release的目录 - 进入discovery-console-desktop-[版本号]-release,请修改config/console.properties中的url,该地址指向控制平台的地址 - 运行“Discovery灰度发布控制台.bat”,启动桌面程序 - 如果您是操作系统,请参考“Discovery灰度发布控制台.bat”,自行编写“Discovery灰度发布控制台.sh”脚本,启动桌面程序 ⑤ 操作界面 - 登录认证,用户名和密码为admin/admin或者nepxion/nepxion。顺便说一下,控制台支持简单的认证,用户名和密码配置在discovery-springcloud-example-console\bootstrap.properties中,您可以自己扩展AuthenticationResource并注入,实现更专业的认证功能 ![](http://nepxion.gitee.io/docs/discovery-doc/Console0.jpg) - 点击“显示服务拓扑”按钮,弹出“服务集群组过滤”对话框,列表是以服务所在的集群组列表(例如:eureka.instance.metadataMap.group=example-service-group),选择若干个并点击“确定”按钮,如果使用者想获取全部的服务集群(可能会耗性能),则直接点击“取消”按钮 ![](http://nepxion.gitee.io/docs/discovery-doc/Console4.jpg) - 从服务注册发现中心获取服务拓扑 ![](http://nepxion.gitee.io/docs/discovery-doc/Console5.jpg) - 执行灰度路由,选择一个服务,右键菜单“执行灰度路由” ![](http://nepxion.gitee.io/docs/discovery-doc/Console6.jpg) - 通过“服务列表”切换,或者点击增加和删除服务按钮,确定灰度路由路径,点击“执行路由” ![](http://nepxion.gitee.io/docs/discovery-doc/Console7.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Console2.jpg) - 推送模式设置,“异步推送”和“同步推送”,前者是推送完后立刻返回,后者是推送完后等待推送结果(包括规则XML解析的异常等都能在界面上反映出来);“规则推送到远程配置中心”和“规则推送到服务或者服务集群”,前者是推送到配置中心(持久化),后者是推送到一个或者多个服务机器的内存(非持久化,重启后丢失) ![](http://nepxion.gitee.io/docs/discovery-doc/Console8.jpg) - 执行灰度发布,选择一个服务或者服务组,右键菜单“执行灰度发布”,前者是通过单个服务实例执行灰度发布,后者是通过一组服务实例执行灰度发布 ![](http://nepxion.gitee.io/docs/discovery-doc/Console9.jpg) - 灰度发布,包括“更改版本”和“更改规则”,前者通过更改版本号去适配灰度规则中的版本匹配关系,后者直接修改规则。“更改版本”是推送到一个或者多个服务机器的内存(非持久化,重启后丢失),“更改规则”是根据不同的推送模式,两种方式都支持 ![](http://nepxion.gitee.io/docs/discovery-doc/Console10.jpg) - 全链路灰度发布,所有在同一个集群组(例如:eureka.instance.metadataMap.group=example-service-group)里的服务统一做灰度发布,即一个规则配置搞定所有服务的灰度发布。点击“全链路灰度发布”按钮,弹出“全链路灰度发布”对话框 ![](http://nepxion.gitee.io/docs/discovery-doc/Console11.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Console12.jpg) - 刷新灰度状态,选择一个服务或者服务组,右键菜单“刷新灰度状态”,查看某个服务或者服务组是否正在做灰度发布 ![](http://nepxion.gitee.io/docs/discovery-doc/Console13.jpg) ⑥ 操作视频 - 灰度发布-版本访问策略 - 请访问[https://pan.baidu.com/s/1eq_N56VbgSCaTXYQ5aKqiA](https://pan.baidu.com/s/1eq_N56VbgSCaTXYQ5aKqiA),获取更清晰的视频,注意一定要下载下来看,不要在线看,否则不清晰 - 请访问[http://www.iqiyi.com/w_19rzwzovrl.html](http://www.iqiyi.com/w_19rzwzovrl.html),视频清晰度改成720P,然后最大化播放 - 灰度发布-版本权重策略 - 请访问[https://pan.baidu.com/s/1VXPatJ6zrUeos7uTQwM3Kw](https://pan.baidu.com/s/1VXPatJ6zrUeos7uTQwM3Kw),获取更清晰的视频,注意一定要下载下来看,不要在线看,否则不清晰 - 请访问[http://www.iqiyi.com/w_19rzs9pll1.html](http://www.iqiyi.com/w_19rzs9pll1.html),视频清晰度改成720P,然后最大化播放 - 灰度发布-全链路策略 - 请访问[https://pan.baidu.com/s/1XQSKCZUykc6t04xzfrFHsg](https://pan.baidu.com/s/1XQSKCZUykc6t04xzfrFHsg),获取更清晰的视频,注意一定要下载下来看,不要在线看,否则不清晰 - 请访问[http://www.iqiyi.com/w_19s1e0zf95.html](http://www.iqiyi.com/w_19s1e0zf95.html),视频清晰度改成720P,然后最大化播放 基于图形化Web程序的灰度发布 - 参考[图形化Web](https://github.com/Nepxion/DiscoveryUI) - 操作过程跟“基于图形化桌面程序的灰度发布”类似 ![](http://nepxion.gitee.io/docs/discovery-doc/Console14.jpg) ## 基于组和黑白名单的全链路服务隔离和准入 ### 服务注册发现准入 #### 基于组黑白名单注册准入 微服务启动的时候,它所属的Group不在Group的黑名单或者在白名单里,才允许被注册。只需要在网关或者服务端,开启如下配置即可 ``` # 启动和关闭注册的服务隔离(基于Group黑/白名单的策略)。缺失则默认为false spring.application.strategy.register.isolation.enabled=true ``` 黑/白名单通过如下方式配置 ``` spring.application.strategy.register.isolation.group.blacklist= spring.application.strategy.register.isolation.group.whitelist= ``` #### 基于IP地址黑白名单注册准入 微服务启动的时候,禁止指定的IP地址注册到注册中心。支持黑/白名单,白名单表示只允许指定IP地址前缀注册,黑名单表示不允许指定IP地址前缀注册 - 全局过滤,指注册到服务注册发现中心的所有微服务,只有IP地址包含在全局过滤字段的前缀中,都允许注册(对于白名单而言),或者不允许注册(对于黑名单而言) - 局部过滤,指专门针对某个微服务而言,那么真正的过滤条件是全局过滤 + 局部过滤结合在一起 #### 基于最大注册数限制注册准入 微服务启动的时候,一旦微服务集群下注册的实例数目已经达到上限(可配置),将禁止后续的微服务进行注册 - 全局配置值,只下面配置所有的微服务集群,最多能注册多少个 - 局部配置值,指专门针对某个微服务而言,如果该值如存在,全局配置值失效 #### 基于IP地址黑白名单发现准入 微服务启动的时候,禁止指定的IP地址被服务发现。它使用的方式和[基于IP地址黑白名单注册准入](#基于IP地址黑白名单注册准入)一致 #### 自定义注册发现准入 - 集成AbstractRegisterListener,实现自定义禁止注册 - 集成AbstractDiscoveryListener,实现自定义禁止被发现。注意,在Consul下,同时会触发service和management两个实例的事件,需要区别判断 - 集成AbstractLoadBalanceListener,实现自定义禁止被负载均衡 ### 消费端服务隔离 #### 基于组负载均衡隔离 元数据中的Group在一定意义上代表着系统ID或者系统逻辑分组,基于Group策略意味着只有同一个系统中的服务才能调用 基于Group是否相同的策略,即消费端拿到的提供端列表,两者的Group必须相同。只需要在网关或者服务端,开启如下配置即可 ``` # 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false spring.application.strategy.consumer.isolation.enabled=true ``` 通过修改discovery-guide-service-b的Group名为其它名称,执行Postman调用,将发现从discovery-guide-service-a无法拿到discovery-guide-service-b的任何实例。意味着在discovery-guide-service-a消费端进行了隔离 ### 提供端服务隔离 #### 基于组Header传值策略隔离 元数据中的Group在一定意义上代表着系统ID或者系统逻辑分组,基于Group策略意味着只有同一个系统中的服务才能调用 基于Group是否相同的策略,即服务端被消费端调用,两者的Group必须相同,否则拒绝调用,异构系统可以通过Header方式传递n-d-service-group值进行匹配。只需要在服务端(不适用网关),开启如下配置即可 ``` # 启动和关闭提供端的服务隔离(基于Group是否相同的策略)。缺失则默认为false spring.application.strategy.provider.isolation.enabled=true # 灰度路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截和消费端的服务隔离两项工作 spring.application.strategy.scan.packages=com.nepxion.discovery.guide.service.feign ``` 在Postman调用,执行[http://localhost:4001/invoke/abc](http://localhost:4001/invoke/abc),去调用discovery-guide-service-b服务,将出现如下异常。意味着在discovery-guide-service-b提供端进行了隔离 ``` Reject to invoke because of isolation with different service group ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide6-1.jpg) 如果加上n-d-service-group=discovery-guide-group的Header,那么两者保持Group相同,则调用通过。这是解决异构系统调用微服务被隔离的一种手段 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide6-2.jpg) ## 基于Env的全链路环境隔离和路由 基于服务实例的元数据Metadata的env参数和全链路传递的环境Header值进行比对实现隔离,当从网关传递来的环境Header(n-d-env)值和提供端实例的元数据Metadata环境配置值相等才能调用。环境隔离下,调用端实例找不到符合条件的提供端实例,把流量路由到一个通用或者备份环境 ![](http://nepxion.gitee.io/docs/discovery-doc/IsolationEnvironment.jpg) ### 环境隔离 在网关或者服务端,配置环境元数据,在同一套环境下,env值必须是一样的,这样才能达到在同一个注册中心下,环境隔离的目的 ``` spring.cloud.nacos.discovery.metadata.env=env1 ``` ### 环境路由 在环境隔离执行的时候,如果无法找到对应的环境,则会路由到一个通用或者备份环境,默认为env为common的环境,可以通过如下参数进行更改 ``` # 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common spring.application.environment.route=common ``` 整个隔离和路由的逻辑如下 - 如果存在环境,优先寻址环境的服务实例 - 如果不存在环境,则寻址Common环境的服务实例(未设置元数据Metadata的env参数的服务实例也归为Common环境) - 如果Common环境也不存在,则调用失败 - 如果没有传递环境Header(n-d-env)值,则执行Spring Cloud Ribbon轮询策略 ## 基于Sentinel的全链路服务限流熔断降级权限和灰度融合 集成Sentinel熔断隔离限流降级平台 ![](http://nepxion.gitee.io/docs/discovery-doc/Sentinel3.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Sentinel4.jpg) 通过集成Sentinel,在服务端实现该功能 ![](http://nepxion.gitee.io/docs/icon-doc/information.png) 下面方式也可以通过Spring Cloud Alibaba Sentinel功能来实现 封装NacosDataSource和ApolloDataSource,支持Nacos和Apollo两个远程配置中心,零代码实现Sentinel功能。更多的远程配置中心,请参照Sentinel官方的DataSource并自行集成 - Nacos的Key格式 ``` Group为元数据中配置的[组名],Data Id为[服务名]-[规则类型] ``` - Apollo的Key格式 ``` [组名]-[服务名]-[规则类型] ``` 支持远程配置中心和本地规则文件的读取逻辑,即优先读取远程配置,如果不存在或者规则错误,则读取本地规则文件。动态实现远程配置中心对于规则的热刷新 支持如下开关开启该动能,默认是关闭的 ``` # 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展和调用链埋点输出。缺失则默认为false spring.application.strategy.sentinel.enabled=true ``` ### 原生Sentinel注解 参照下面代码,为接口方法增加@SentinelResource注解,value为sentinel-resource,blockHandler和fallback是防护其作用后需要执行的方法 ```java @RestController @ConditionalOnProperty(name = DiscoveryConstant.SPRING_APPLICATION_NAME, havingValue = "discovery-guide-service-b") public class BFeignImpl extends AbstractFeignImpl implements BFeign { private static final Logger LOG = LoggerFactory.getLogger(BFeignImpl.class); @Override @SentinelResource(value = "sentinel-resource", blockHandler = "handleBlock", fallback = "handleFallback") public String invoke(@PathVariable(value = "value") String value) { value = doInvoke(value); LOG.info("调用路径:{}", value); return value; } public String handleBlock(String value, BlockException e) { return value + "-> B server sentinel block, cause=" + e.getClass().getName() + ", rule=" + e.getRule() + ", limitApp=" + e.getRuleLimitApp(); } public String handleFallback(String value) { return value + "-> B server sentinel fallback"; } } ``` ### 原生Sentinel规则 原生Sentinel规则的用法,请参照Sentinel官方文档 #### 流控规则 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-flow,规则内容如下 ``` [ { "resource": "sentinel-resource", "limitApp": "default", "grade": 1, "count": 1, "strategy": 0, "refResource": null, "controlBehavior": 0, "warmUpPeriodSec": 10, "maxQueueingTimeMs": 500, "clusterMode": false, "clusterConfig": null } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-1.jpg) #### 降级规则 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-degrade,规则内容如下 ``` [ { "resource": "sentinel-resource", "limitApp": "default", "count": 2, "timeWindow": 10, "grade": 0, "passCount": 0 } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-2.jpg) #### 授权规则 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下 ``` [ { "resource": "sentinel-resource", "limitApp": "discovery-guide-service-a", "strategy": 0 } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-3.jpg) #### 系统规则 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-system,规则内容如下 ``` [ { "resource": null, "limitApp": null, "highestSystemLoad": -1.0, "highestCpuUsage": -1.0, "qps": 200.0, "avgRt": -1, "maxThread": -1 } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-4.jpg) #### 热点参数流控规则 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-param-flow,规则内容如下 ``` [ { "resource": "sentinel-resource", "limitApp": "default", "grade": 1, "paramIdx": 0, "count": 1, "controlBehavior": 0, "maxQueueingTimeMs": 0, "burstCount": 0, "durationInSec": 1, "paramFlowItemList": [], "clusterMode": false } ] ``` ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-5.jpg) ### 基于灰度路由和Sentinel-LimitApp扩展的防护机制 该方式对于上面5种规则都有效,这里以授权规则展开阐述 授权规则中,limitApp,如果有多个,可以通过“,”分隔。"strategy": 0 表示白名单,"strategy": 1 表示黑名单 #### 基于服务名的防护机制 修改配置项Sentinel Request Origin Key为服务名的Header名称,修改授权规则中limitApp为对应的服务名,可实现基于服务名的防护机制 配置项,该配置项默认为n-d-service-id,可以不配置 ``` spring.application.strategy.service.sentinel.request.origin.key=n-d-service-id ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示所有discovery-guide-service-a服务允许访问discovery-guide-service-b服务 ``` [ { "resource": "sentinel-resource", "limitApp": "discovery-guide-service-a", "strategy": 0 } ] ``` #### 基于灰度组的防护机制 修改配置项Sentinel Request Origin Key为灰度组的Header名称,修改授权规则中limitApp为对应的组名,可实现基于组名的防护机制 配置项 ``` spring.application.strategy.service.sentinel.request.origin.key=n-d-service-group ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示隶属my-group组的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "my-group", "strategy": 0 } ] ``` #### 基于灰度版本的防护机制 修改配置项Sentinel Request Origin Key为灰度版本的Header名称,修改授权规则中limitApp为对应的版本,可实现基于版本的防护机制 配置项 ``` spring.application.strategy.service.sentinel.request.origin.key=n-d-service-version ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示版本为1.0的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "1.0", "strategy": 0 } ] ``` #### 基于灰度区域的防护机制 修改配置项Sentinel Request Origin Key为灰度区域的Header名称,修改授权规则中limitApp为对应的区域,可实现基于区域的防护机制 配置项 ``` spring.application.strategy.service.sentinel.request.origin.key=n-d-service-region ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示区域为dev的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "dev", "strategy": 0 } ] ``` #### 基于IP地址和端口的防护机制 修改配置项Sentinel Request Origin Key为灰度区域的Header名称,修改授权规则中limitApp为对应的区域值,可实现基于IP地址和端口的防护机制 配置项 ``` spring.application.strategy.service.sentinel.request.origin.key=n-d-service-address ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示地址和端口为192.168.0.88:8081和192.168.0.88:8082的服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "192.168.0.88:8081,192.168.0.88:8082", "strategy": 0 } ] ``` 支持如下开关开启该动能,默认是关闭的 ``` # 启动和关闭Sentinel LimitApp限流等功能。缺失则默认为false spring.application.strategy.service.sentinel.limit.app.enabled=true ``` #### 自定义业务参数的组合式防护机制 通过适配类实现自定义业务参数的组合式防护机制 ```java // 自定义版本号+用户名,实现组合式熔断 public class MyServiceSentinelRequestOriginAdapter extends DefaultServiceSentinelRequestOriginAdapter { @Override public String parseOrigin(HttpServletRequest request) { String version = request.getHeader(DiscoveryConstant.N_D_SERVICE_VERSION); String user = request.getHeader("user"); return version + "&" + user; } } ``` 在配置类里@Bean方式进行适配类创建 ```java @Bean public ServiceSentinelRequestOriginAdapter ServiceSentinelRequestOriginAdapter() { return new MyServiceSentinelRequestOriginAdapter(); } ``` 增加服务discovery-guide-service-b的规则,Group为discovery-guide-group,Data Id为discovery-guide-service-b-sentinel-authority,规则内容如下,表示版本为1.0且传入的Http Header的user=zhangsan,同时满足这两个条件下的所有服务都允许访问服务discovery-guide-service-b ``` [ { "resource": "sentinel-resource", "limitApp": "1.0&zhangsan", "strategy": 0 } ] ``` 运行效果 - 当传递的Http Header中user=zhangsan,当全链路调用中,API网关负载均衡discovery-guide-service-a服务到1.0版本后再去调用discovery-guide-service-b服务,最终调用成功 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-6.jpg) - 当传递的Http Header中user=lisi,不满足条件,最终调用在discovery-guide-service-b服务端被拒绝掉 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-7.jpg) - 当传递的Http Header中user=zhangsan,满足条件之一,当全链路调用中,API网关负载均衡discovery-guide-service-a服务到1.1版本后再去调用discovery-guide-service-b服务,不满足version=1.0的条件,最终调用在discovery-guide-service-b服务端被拒绝掉 ![](http://nepxion.gitee.io/docs/discovery-doc/DiscoveryGuide7-8.jpg) ## 基于Hystrix的全链路服务限流熔断和灰度融合 通过引入Hystrix组件实现服务限流熔断的功能。灰度路由Header和调用链Span在Hystrix线程池隔离模式(信号量模式不需要引入)下传递时,通过线程上下文切换会存在丢失Header的问题,通过下述步骤解决,同时适用于网关端和服务端 - Pom引入 ```xml com.nepxion discovery-plugin-strategy-starter-hystrix ${discovery.version} ``` - 配置开启 ``` # 开启服务端实现Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失。缺失则默认为false spring.application.strategy.hystrix.threadlocal.supported=true ``` 该方案也可以被异步跨线程Agent代替 ## 全链路监控 ### 全链路调用链监控-Tracing 调用链监控,在本文主要指灰度调用链监控。快速入门操作,请访问操作视频[Nepxion Discovery 灰度发布路由调用链](https://pan.baidu.com/s/1PbksbZKVY7reBrnVb3RS6Q),注意一定要下载下来看,不要在线看,否则不清晰 灰度调用链主要包括如下12个参数,以n-d-service开头的是必须的,其它是可选的或者按照场景而定。使用者可以自行定义要传递的调用链参数,例如:traceId, spanId等;也可以自行定义要传递的业务调用链参数,例如:mobile, user等 ``` 1. n-d-service-group - 服务所属组或者应用 2. n-d-service-type - 服务类型,分为“网关”和“服务” 3. n-d-service-id - 服务ID 4. n-d-service-address - 服务地址,包括Host和Port 5. n-d-service-version - 服务版本 6. n-d-service-region - 服务所属区域 7. n-d-service-env - 服务所属环境 8. n-d-version - 版本路由值 9. n-d-region - 区域路由值 10. n-d-address - 地址路由值 11. n-d-version-weight - 版本权重路由值 12. n-d-region-weight - 区域权重路由值 ``` 灰度调用链输出分为Header方式、调用链方式、日志MDC方式,三种方式可以并存使用。调用链方式支持WebMvc和WebFlux #### Header输出方式 - Spring Cloud Gateway网关端自行会传输Header值(参考Discovery源码中的AbstractGatewayStrategyRouteFilter.java) - Zuul网关端自行会传输Header值(参考Discovery源码中的AbstractZuulStrategyRouteFilter.java) - 服务端通过Feign和RestTemplate拦截器传输Header值(参考Discovery源码中的FeignStrategyInterceptor.java和RestTemplateStrategyInterceptor.java) #### 调用链输出方式 调用链输出方式以OpenUber Jaeger为例来说明 - 从[网盘文档](https://pan.baidu.com/s/1i57rXaNKPuhGRqZ2MONZOA)获取,Windows操作系统下解压后运行jaeger.bat,Mac和Lunix操作系统请自行研究 - 执行Postman调用后,访问[http://localhost:16686](http://localhost:16686)查看灰度调用链 - 灰度调用链支持WebMvc和WebFlux两种方式,以NEPXION字样的标记来标识 - 支持对Sentinel自动埋点 ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:因为OpenTracing规范不仅仅只被Jaeger所遵守,所以框架并没有直接引入Jaeger包,需要使用者自行引入,可以参照指南示例中的pom.xml引用 ```xml io.opentracing.contrib opentracing-spring-cloud-starter io.opentracing.contrib opentracing-spring-jaeger-starter ``` 集成Opentracing + Uber Jaeger调用链平台 ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger2.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger3.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger4.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger5.jpg) 集成Sentinel + 灰度全链路监控 ![](http://nepxion.gitee.io/docs/discovery-doc/Jaeger6.jpg) 集成主流中间件 + 灰度全链路监控 代码请从[指南示例高级版](https://github.com/Nepxion/DiscoveryGuide)获取,分支为premium。运行出下图强大效果的前提,需要事先搭建Nacos、Jaeger、ActiveMQ、MongoDB、RabbitMQ、Redis、RocketMQ以及MySQL数据库等环境 使用者如果不想搭建环境,想直接观看效果,可以直接把[离线数据](https://github.com/Nepxion/DiscoveryGuide/raw/master/tracing.json)导入到Jaeger界面(JSON File栏,拖进去即可),观看到下图效果 ![](http://nepxion.gitee.io/docs/discovery-doc/JaegerPremium1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/JaegerPremium2.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/JaegerPremium3.jpg) 集成Opentracing + Apache Skywalking调用链平台 ![](http://nepxion.gitee.io/docs/discovery-doc/Skywalking1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Skywalking2.jpg) ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 请注意如下配置,将决定终端界面的显示 - 如果开启,灰度信息输出到独立的Span节点中,意味着在界面显示中,灰度信息通过独立的NEPXION Span节点来显示 - 如果关闭,灰度信息输出到原生的Span节点中,意味着在界面显示中,灰度信息会和原生Span节点的调用信息、协议信息等合在一起 ``` # 启动和关闭调用链的灰度信息以独立的Span节点输出,如果关闭,则灰度信息输出到原生的Span节点中(Skywalking不支持原生模式)。缺失则默认为true spring.application.strategy.tracer.separate.span.enabled=true ``` 自定义调用链上下文参数的创建(该类不是必须的),继承DefaultStrategyTracerAdapter ```java // 自定义调用链上下文参数的创建 // 对于getTraceId和getSpanId方法,在OpenTracing等调用链中间件引入的情况下,由调用链中间件决定,在这里定义不会起作用;在OpenTracing等调用链中间件未引入的情况下,在这里定义才有效,下面代码中表示从Http Header中获取,并全链路传递 // 对于getCustomizationMap方法,表示输出到调用链中的定制化业务参数,可以同时输出到日志和OpenTracing等调用链中间件,下面代码中表示从Http Header中获取,并全链路传递 public class MyStrategyTracerAdapter extends DefaultStrategyTracerAdapter { @Override public String getTraceId() { return StringUtils.isNotEmpty(strategyContextHolder.getHeader(DiscoveryConstant.TRACE_ID)) ? strategyContextHolder.getHeader(DiscoveryConstant.TRACE_ID) : StringUtils.EMPTY; } @Override public String getSpanId() { return StringUtils.isNotEmpty(strategyContextHolder.getHeader(DiscoveryConstant.SPAN_ID)) ? strategyContextHolder.getHeader(DiscoveryConstant.SPAN_ID) : StringUtils.EMPTY; } @Override public Map getCustomizationMap() { Map customizationMap = new HashMap(); customizationMap.put("mobile", StringUtils.isNotEmpty(strategyContextHolder.getHeader("mobile")) ? strategyContextHolder.getHeader("mobile") : StringUtils.EMPTY); customizationMap.put("user", StringUtils.isNotEmpty(strategyContextHolder.getHeader("user")) ? strategyContextHolder.getHeader("user") : StringUtils.EMPTY); return customizationMap; } } ``` 在配置类里@Bean方式进行调用链类创建,覆盖框架内置的调用链类 ```java @Bean public StrategyTracerAdapter strategyTracerAdapter() { return new MyStrategyTracerAdapter(); } ``` 自定义类方法上入参和出参输出到调用链(该类不是必须的),继承ServiceStrategyMonitorAdapter ```java // 自定义类方法上入参和出参输出到调用链 // parameterMap格式: // key为入参名 // value为入参值 public class MyServiceStrategyMonitorAdapter implements ServiceStrategyMonitorAdapter { @Override public Map getCustomizationMap(ServiceStrategyMonitorInterceptor interceptor, MethodInvocation invocation, Map parameterMap, Object returnValue) { Map customizationMap = new HashMap(); customizationMap.put(DiscoveryConstant.PARAMETER, parameterMap.toString()); customizationMap.put(DiscoveryConstant.RETURN, returnValue != null ? returnValue.toString() : null); return customizationMap; } } ``` 在配置类里@Bean方式进行创建 ```java @Bean public ServiceStrategyMonitorAdapter serviceStrategyMonitorAdapter() { return new MyServiceStrategyMonitorAdapter(); } ``` 业务方法上获取TraceId和SpanId ```java public class MyClass { @Autowired private StrategyMonitorContext strategyMonitorContext; public void doXXX() { String traceId = strategyMonitorContext.getTraceId(); String spanId = strategyMonitorContext.getSpanId(); ... } } ``` 对于调用链功能的开启和关闭,需要通过如下开关做控制 ``` # 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false spring.application.strategy.monitor.enabled=true # 启动和关闭日志输出。缺失则默认为false spring.application.strategy.logger.enabled=true # 日志输出中,是否显示MDC前面的Key。缺失则默认为true spring.application.strategy.logger.mdc.key.shown=true # 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.logger.debug.enabled=true # 启动和关闭调用链输出。缺失则默认为false spring.application.strategy.tracer.enabled=true # 启动和关闭调用链的灰度信息以独立的Span节点输出,如果关闭,则灰度信息输出到原生的Span节点中(Skywalking不支持原生模式)。缺失则默认为true spring.application.strategy.tracer.separate.span.enabled=true # 启动和关闭调用链的灰度规则策略信息输出。缺失则默认为true spring.application.strategy.tracer.rule.output.enabled=true # 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false spring.application.strategy.tracer.exception.detail.output.enabled=true # 启动和关闭类方法上入参和出参输出到调用链。缺失则默认为false spring.application.strategy.tracer.method.context.output.enabled=true ``` 对于灰度Span输出在调用链界面上的显示,提供如下配置 ``` # 显示在调用链界面上灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION spring.application.strategy.tracer.span.value=NEPXION # 显示在调用链界面上灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery ``` 对Sentinel自动埋点,有如下两个参数默认处于关闭状态,但因为原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去 ``` # 启动和关闭Sentinel调用链上规则在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为true spring.application.strategy.tracer.sentinel.rule.output.enabled=true # 启动和关闭Sentinel调用链上方法入参在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为false spring.application.strategy.tracer.sentinel.args.output.enabled=true ``` ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:OpenTracing对Finchley版的Spring Cloud Gateway的reactor-core包存在版本兼容性问题,如果使用者希望Finchley版的Spring Cloud Gateway上使用OpenTracing,需要做如下改造 ```xml com.nepxion discovery-plugin-strategy-starter-gateway ${discovery.version} io.projectreactor reactor-core io.projectreactor reactor-core 3.2.3.RELEASE ``` 上述方式也适用于其它引入了低版本reactor-core包版本兼容性的场景 #### 日志输出方式 可以单独输出,也可以结合调用链一起组合输出,使用方式跟调用链方式类似 参考在IDE控制台打印的结果 ![](http://nepxion.gitee.io/docs/discovery-doc/Tracer.jpg) ### 全链路指标监控-Metrics #### Prometheus监控方式 ![](http://nepxion.gitee.io/docs/discovery-doc/Prometheus.jpg) #### Grafana监控方式 ![](http://nepxion.gitee.io/docs/discovery-doc/Grafana.jpg) #### Spring-Boot-Admin监控方式 ![](http://nepxion.gitee.io/docs/discovery-doc/Admin1.jpg) ![](http://nepxion.gitee.io/docs/discovery-doc/Admin7.jpg) ## 全链路Header传递 框架会默认把相关的Header,进行全链路传递,可以通过如下配置进行。除此之外,凡是以“n-d-”开头的任何Header,框架都会默认全链路传递 ``` # 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true spring.application.strategy.rest.intercept.enabled=true # 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.rest.intercept.debug.enabled=true # 灰度路由策略的时候,对REST方式调用拦截的时候(支持Feign或者RestTemplate调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.context.request.headers=trace-id;span-id # 灰度路由策略的时候,对REST方式调用拦截的时候(支持Feign或者RestTemplate调用),希望把来自外部自定义的Header参数(用于业务系统子定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.business.request.headers=user;mobile ``` ## 全链路侦测 通过内置基于LoadBalanced RestTemplate方式的/inspector/inspect接口方法,实现全链路侦测,可以查看全链路中调用的各个服务的版本、区域、环境、IP地址等是否符合预期,是否满足灰度条件,该接口可以集成到使用者的界面中,就可以规避通过Postman工具或者调用链系统去判断,有利于节省人工成本。使用方式 - 执行Post请求 - 请求的路径:http://[网关URL]/[服务名1]/inspector/inspect - 请求的内容:{"serviceIdList":["服务名2", "服务名3", ....]}。服务名不分前后次序 ## 全链路服务侧注解 服务侧对于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能,默认映射到RestController类(含有@RestController注解),并配合如下的扫描路径才能工作 ``` # 灰度路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能 spring.application.strategy.scan.packages=com.nepxion.discovery.guide.service.feign ``` 当使用者不希望只局限于RestController类(含有@RestController注解)方式,而要求在任何类中实现上述功能,那么框架提供@ServiceStrategy注解,使用者把它加在类头部即可,可以达到和@RestController注解同样的效果 ## 全链路服务侧API权限 服务侧对于RPC方式的调用,可以加入API权限控制,通过在接口或者类名上加@Permission注解,或者在接口或者类的方法名上加@Permission注解,实现API权限控制。如果两者都加,以前者为优先 - 实现权限自动扫描入库 - 实现提供显式基于注解的权限验证,参数通过注解传递;实现提供基于Rest请求的权限验证,参数通过Header传递 - 实现提供入库方法和权限判断方法的扩展,这两者需要自行实现 请参考[权限代码](https://github.com/Nepxion/DiscoveryGuide/blob/master/discovery-guide-service/src/main/java/com/nepxion/discovery/guide/service/permission) ## 异步跨线程Agent 灰度路由Header和调用链Span在Hystrix线程池隔离模式下或者线程、线程池、@Async注解等异步调用Feign或者RestTemplate时,通过线程上下文切换会存在丢失Header的问题,通过下述步骤解决,同时适用于网关端和服务端。该方案可以替代Hystrix线程池隔离模式下的解决方案,也适用于其它有相同使用场景的基础框架和业务场景,例如:Dubbo ### 插件获取 插件获取方式有两种方式 - 通过[https://github.com/Nepxion/Discovery/releases](https://github.com/Nepxion/Discovery/releases)下载最新版本的Discovery Agent - 编译[https://github.com/Nepxion/Discovery](https://github.com/Nepxion/Discovery)的master分支,执行mvn clean install,产生discovery-agent目录 ### 插件使用 - discovery-plugin-strategy-starter-agent-{discovery.version}.jar为Agent引导启动程序,JVM启动时进行加载;discovery-agent/plugin目录包含discovery-plugin-strategy-starter-agent-plugin-{discovery.version}.jar为nepxion-discovery自带的实现方案,业务系统可以自定义plugin,解决业务自己定义的上下文跨线程传递 - 通过如下-javaagent启动 ```xml -javaagent:/discovery-agent/discovery-plugin-strategy-starter-agent-{discovery.version}.jar -Dthread.scan.packages=com.abc;com.xyz -Dthread.request.decorator.enabled=true ``` 参数说明 - /discovery-agent:Agent所在的目录,需要对应到实际的目录上 - thread.scan.packages:Runnable,Callable对象所在的扫描目录,该目录下的Runnable,Callable对象都会被装饰。该目录最好精细和准确,这样可以减少被装饰的对象数,提高性能,目录如果有多个,用“;”分隔 - thread.request.decorator.enabled:异步调用场景下在服务端的Request请求的装饰,当主线程先于子线程执行完的时候,Request会被Destory,导致Header仍旧拿不到,开启装饰,就可以确保拿到。默认为关闭,根据实践经验,大多数场景下,需要开启这个开关 ```xml 扫描目录thread.scan.packages定义,该参数只作用于服务侧,网关侧不需要加 1. @Async场景下的扫描目录为org.springframework.aop.interceptor 2. Hystrix线程池隔离场景下的扫描目录为com.netflix.hystrix 3. 线程、线程池的扫描目录为自定义Runnable,Callable对象所在类的目录 ``` ### 插件扩展 - 根据规范开发一个插件,插件提供了钩子函数,在某个类被加载的时候,可以注册一个事件到线程上下文切换事件当中,实现业务自定义ThreadLocal的跨线程传递。参考:discovery-plugin-strategy-starter-agent-plugin模块的com.nepxion.discovery.plugin.strategy.starter.agent.plugin.service下的实现方式 - plugin目录为放置需要在线程切换时进行ThreadLocal传递的自定义插件。业务自定义插件开发完后,放入到plugin目录下即可 ## 元数据Metadata自动化策略 ### 基于服务名前缀自动创建灰度组名 通过指定长度截断或者标志截断服务名的前缀来自动创建灰度组名,这样就可以避免使用者手工维护灰度组名。当两者都启用的时候,截断方式的组名优先级要高于手工配置的组名 - 增加配置项 ``` # 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false spring.application.group.generator.enabled=true # 服务名前缀的截断长度,必须大于0 spring.application.group.generator.length=15 # 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式 spring.application.group.generator.character=- ``` ### 基于Git插件自动创建灰度版本号 通过集成插件git-commit-id-plugin,通过产生git信息文件的方式,获取git.commit.id(最后一次代码的提交ID)或者git.build.version(对应到Maven工程的版本)来自动创建灰度版本号,这样就可以避免使用者手工维护灰度版本号。当两者都启用的时候,Git插件方式的版本号优先级要高于手工配置的版本号 - 增加Git编译插件 需要在4个工程下的pom.xml里增加git-commit-id-plugin 默认配置 ```xml pl.project13.maven git-commit-id-plugin revision true yyyyMMdd ``` 特色配置 ```xml pl.project13.maven git-commit-id-plugin revision true ${project.basedir}/.git ${project.build.outputDirectory}/git.json json true false false yyyyMMdd ``` 更多的配置方式,参考[https://github.com/git-commit-id/maven-git-commit-id-plugin/blob/master/maven/docs/using-the-plugin.md](https://github.com/git-commit-id/maven-git-commit-id-plugin/blob/master/maven/docs/using-the-plugin.md) 可能需要增加下面的配置,保证Git相关文件被打包进去 ```xml src/main/java **/*.xml **/*.json **/*.properties src/main/resources **/*.xml **/*.json **/*.properties ``` - 增加配置项 ``` # 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false spring.application.git.generator.enabled=true # 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties spring.application.git.generator.path=classpath:git.properties # spring.application.git.generator.path=classpath:git.json # 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count} spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time} # spring.application.git.version.key={git.build.version}-{git.commit.time} ``` 下面是可供选择的Git字段,比较实际意义的字段为git.commit.id,git.commit.id.abbrev,git.build.version,git.total.commit.count ``` git.branch=master git.build.host=Nepxion git.build.time=2019-10-21-10\:07\:41 git.build.user.email=1394997@qq.com git.build.user.name=Nepxion git.build.version=1.0.0 git.closest.tag.commit.count= git.closest.tag.name= git.commit.id=04d7e45b11b975db37bdcdbc5a97c02e9d80e5fa git.commit.id.abbrev=04d7e45 git.commit.id.describe=04d7e45-dirty git.commit.id.describe-short=04d7e45-dirty git.commit.message.full=\u4FEE\u6539\u914D\u7F6E git.commit.message.short=\u4FEE\u6539\u914D\u7F6E git.commit.time=2019-10-21T09\:09\:25+0800 git.commit.user.email=1394997@qq.com git.commit.user.name=Nepxion git.dirty=true git.local.branch.ahead=0 git.local.branch.behind=0 git.remote.origin.url=https\://github.com/Nepxion/DiscoveryGuide.git git.tags= git.total.commit.count=765 ``` ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:一般情况下,上述两个地方的配置都同时保持默认即可。对于一些特色化的用法,两个地方的配置项用法必须保持一致,例如 ``` # 输出到工程根目录下 ${project.basedir}/git.json # 输出成json格式 json ``` 下面配置项必须上面两个配置项的操作逻辑相同 ``` # 输出到工程根目录下的json格式文件 spring.application.git.generator.path=file:git.json ``` 内置基于Swagger的Rest接口,可以供外部查询当前服务的Git信息 ## 元数据Metadata运维平台策略 外部系统(例如:运维发布平台)在远程启动微服务的时候,可以通过参数传递来动态改变元数据或者增加运维特色的参数,最后注册到远程配置中心。有如下两种方式 - 通过Program arguments来传递,它的用法是前面加“--”。支持Eureka、Zookeeper和Nacos的增量覆盖,Consul由于使用了全量覆盖的tag方式,不适用改变单个元数据的方式。例如:--spring.cloud.nacos.discovery.metadata.version=1.0 - 通过VM arguments来传递,它的用法是前面加“-D”。支持上述所有的注册组件,它的限制是变量前面必须要加“metadata.”,推荐使用该方式。例如:-Dmetadata.version=1.0 - 两种方式尽量避免同时用 ## 配置文件 ### 基础属性配置 不同的服务注册发现组件对应的不同的配置值,请仔细阅读 ``` # Eureka config for discovery eureka.instance.metadataMap.group=xxx-service-group eureka.instance.metadataMap.version=1.0 eureka.instance.metadataMap.region=dev eureka.instance.metadataMap.env=env1 # 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 # 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 # Nacos config for discovery spring.cloud.nacos.discovery.metadata.group=example-service-group spring.cloud.nacos.discovery.metadata.version=1.0 spring.cloud.nacos.discovery.metadata.region=dev spring.cloud.nacos.discovery.metadata.env=env1 # Management config # E版配置方式 # 关闭访问Rest接口时候的权限验证 management.security.enabled=false management.port=5100 # F版或以上配置方式 management.server.port=5100 ``` ### 功能开关配置 服务端配置 ``` # Plugin core config # 开启和关闭服务注册层面的控制。一旦关闭,服务注册的黑/白名单过滤功能将失效,最大注册数的限制过滤功能将失效。缺失则默认为true spring.application.register.control.enabled=true # 开启和关闭服务发现层面的控制。一旦关闭,服务多版本调用的控制功能将失效,动态屏蔽指定IP地址的服务实例被发现的功能将失效。缺失则默认为true spring.application.discovery.control.enabled=true # 开启和关闭通过Rest方式对规则配置的控制和推送。一旦关闭,只能通过远程配置中心来控制和推送。缺失则默认为true spring.application.config.rest.control.enabled=true # 规则文件的格式,支持xml和json。缺失则默认为xml spring.application.config.format=xml # spring.application.config.format=json # 本地规则文件的路径,支持两种方式:classpath:rule.xml(rule.json) - 规则文件放在resources目录下,便于打包进jar;file:rule.xml(rule.json) - 规则文件放在工程根目录下,放置在外部便于修改。缺失则默认为不装载本地规则 spring.application.config.path=classpath:rule.xml # spring.application.config.path=classpath:rule.json # 为微服务归类的Key,一般通过group字段来归类,例如eureka.instance.metadataMap.group=xxx-group或者eureka.instance.metadataMap.application=xxx-application。缺失则默认为group spring.application.group.key=group # spring.application.group.key=application # 业务系统希望大多数时候Spring、SpringBoot或者SpringCloud的基本配置、调优参数(非业务系统配置参数),不配置在业务端,集成到基础框架里。但特殊情况下,业务系统有时候也希望能把基础框架里配置的参数给覆盖掉,用他们自己的配置 # 对于此类型的配置需求,可以配置在下面的配置文件里。该文件一般放在resource目录下。缺失则默认为spring-application-default.properties spring.application.default.properties.path=spring-application-default.properties # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试。缺失则默认为false spring.application.no.servers.retry.enabled=false # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的次数。缺失则默认为5 spring.application.no.servers.retry.times=5 # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的时间间隔。缺失则默认为2000 spring.application.no.servers.retry.await.time=2000 # 负载均衡下,消费端尝试获取对应提供端服务实例列表为空的时候,通过日志方式通知。缺失则默认为false spring.application.no.servers.notify.enabled=false # Plugin strategy config # 开启和关闭路由策略的控制。一旦关闭,路由策略功能将失效。缺失则默认为true spring.application.strategy.control.enabled=true # 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true spring.application.strategy.zone.avoidance.rule.enabled=true # 启动和关闭路由策略的时候,对REST方式的调用拦截。缺失则默认为true spring.application.strategy.rest.intercept.enabled=true # 启动和关闭Feign上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下 # 1. n-d-version # 2. n-d-region # 3. n-d-address # 4. n-d-version-weight # 5. n-d-region-weight # 6. n-d-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-env (不属于灰度蓝绿范畴的Header,只要外部传入就会全程传递) spring.application.strategy.rest.template.core.header.transmission.enabled=true # 启动和关闭路由策略的时候,对REST方式在异步调用场景下在服务端的Request请求的装饰,当主线程先于子线程执行完的时候,Request会被Destory,导致Header仍旧拿不到,开启装饰,就可以确保拿到。缺失则默认为false spring.application.strategy.rest.request.decorator.enabled=true # 启动和关闭Header传递的Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.rest.intercept.debug.enabled=true # 路由策略的时候,对REST方式调用拦截的时候(支持Feign或者RestTemplate调用),希望把来自外部自定义的Header参数(用于框架内置上下文Header,例如:trace-id, span-id等)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.context.request.headers=trace-id;span-id # 路由策略的时候,对REST方式调用拦截的时候(支持Feign或者RestTemplate调用),希望把来自外部自定义的Header参数(用于业务系统子定义Header,例如:mobile)传递到服务里,那么配置如下值。如果多个用“;”分隔,不允许出现空格 spring.application.strategy.business.request.headers=token # 启动和关闭路由策略的时候,对RPC方式的调用拦截。缺失则默认为false spring.application.strategy.rpc.intercept.enabled=true # 路由策略的时候,需要指定对业务RestController类的扫描路径。此项配置作用于RPC方式的调用拦截、消费端的服务隔离和调用链三项功能 spring.application.strategy.scan.packages=com.nepxion.discovery.plugin.example.service.feign # 启动和关闭注册的服务隔离(基于Group黑/白名单的策略)。缺失则默认为false spring.application.strategy.register.isolation.enabled=true # 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false spring.application.strategy.consumer.isolation.enabled=true # 启动和关闭提供端的服务隔离(基于Group是否相同的策略)。缺失则默认为false spring.application.strategy.provider.isolation.enabled=true # 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false spring.application.strategy.monitor.enabled=true # 启动和关闭日志输出。缺失则默认为false spring.application.strategy.logger.enabled=true # 日志输出中,是否显示MDC前面的Key。缺失则默认为true spring.application.strategy.logger.mdc.key.shown=true # 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.logger.debug.enabled=true # 启动和关闭调用链输出。缺失则默认为false spring.application.strategy.tracer.enabled=true # 启动和关闭调用链的灰度信息以独立的Span节点输出,如果关闭,则灰度信息输出到原生的Span节点中(Skywalking不支持原生模式)。缺失则默认为true spring.application.strategy.tracer.separate.span.enabled=true # 启动和关闭调用链的灰度规则策略信息输出。缺失则默认为true spring.application.strategy.tracer.rule.output.enabled=true # 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false spring.application.strategy.tracer.exception.detail.output.enabled=true # 启动和关闭类方法上入参和出参输出到调用链。缺失则默认为false spring.application.strategy.tracer.method.context.output.enabled=true # 显示在调用链界面上灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION spring.application.strategy.tracer.span.value=NEPXION # 显示在调用链界面上灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery # 启动和关闭Sentinel调用链上规则在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为true spring.application.strategy.tracer.sentinel.rule.output.enabled=true # 启动和关闭Sentinel调用链上方法入参在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为false spring.application.strategy.tracer.sentinel.args.output.enabled=true # 开启服务端实现Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失。缺失则默认为false spring.application.strategy.hystrix.threadlocal.supported=true # 启动和关闭Sentinel限流降级熔断权限等原生功能的数据来源扩展和调用链埋点输出。缺失则默认为false spring.application.strategy.sentinel.enabled=true # 流控规则文件路径。缺失则默认为classpath:sentinel-flow.json spring.application.strategy.sentinel.flow.path=classpath:sentinel-flow.json # 降级规则文件路径。缺失则默认为classpath:sentinel-degrade.json spring.application.strategy.sentinel.degrade.path=classpath:sentinel-degrade.json # 授权规则文件路径。缺失则默认为classpath:sentinel-authority.json spring.application.strategy.sentinel.authority.path=classpath:sentinel-authority.json # 系统规则文件路径。缺失则默认为classpath:sentinel-system.json spring.application.strategy.sentinel.system.path=classpath:sentinel-system.json # 热点参数流控规则文件路径。缺失则默认为classpath:sentinel-param-flow.json spring.application.strategy.sentinel.param.flow.path=classpath:sentinel-param-flow.json # 服务端执行规则时候,以Http请求中的Header值作为关键Key。缺失则默认为n-d-service-id,即以服务名作为关键Key spring.application.strategy.service.sentinel.request.origin.key=n-d-service-id # 启动和关闭Sentinel LimitApp限流等功能。缺失则默认为false spring.application.strategy.service.sentinel.limit.app.enabled=true # 防止多个网关上并行实施灰度路由产生混乱,对处于非灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;灰度状态的服务,还是根据传递的Header版本号进行匹配 # 启动和关闭调用对端服务,是否执行调用它的时候只取它的老的稳定版本的实例的策略。缺失则默认为false spring.application.strategy.version.filter.enabled=true # 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common spring.application.environment.route=common # 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false spring.application.group.generator.enabled=true # 服务名前缀的截断长度,必须大于0 spring.application.group.generator.length=15 # 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式 spring.application.group.generator.character=- # 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false spring.application.git.generator.enabled=true # 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties spring.application.git.generator.path=classpath:git.properties # spring.application.git.generator.path=classpath:git.json # 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count} spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time} # spring.application.git.version.key={git.build.version}-{git.commit.time} ``` Spring Cloud Gateway端配置 ``` # Plugin core config # 开启和关闭服务注册层面的控制。一旦关闭,服务注册的黑/白名单过滤功能将失效,最大注册数的限制过滤功能将失效。缺失则默认为true spring.application.register.control.enabled=true # 开启和关闭服务发现层面的控制。一旦关闭,服务多版本调用的控制功能将失效,动态屏蔽指定IP地址的服务实例被发现的功能将失效。缺失则默认为true spring.application.discovery.control.enabled=true # 开启和关闭通过Rest方式对规则配置的控制和推送。一旦关闭,只能通过远程配置中心来控制和推送。缺失则默认为true spring.application.config.rest.control.enabled=true # 规则文件的格式,支持xml和json。缺失则默认为xml spring.application.config.format=xml # spring.application.config.format=json # 本地规则文件的路径,支持两种方式:classpath:rule.xml(rule.json) - 规则文件放在resources目录下,便于打包进jar;file:rule.xml(rule.json) - 规则文件放在工程根目录下,放置在外部便于修改。缺失则默认为不装载本地规则 spring.application.config.path=classpath:rule.xml # spring.application.config.path=classpath:rule.json # 为微服务归类的Key,一般通过group字段来归类,例如eureka.instance.metadataMap.group=xxx-group或者eureka.instance.metadataMap.application=xxx-application。缺失则默认为group spring.application.group.key=group # spring.application.group.key=application # 业务系统希望大多数时候Spring、SpringBoot或者SpringCloud的基本配置、调优参数(非业务系统配置参数),不配置在业务端,集成到基础框架里。但特殊情况下,业务系统有时候也希望能把基础框架里配置的参数给覆盖掉,用他们自己的配置 # 对于此类型的配置需求,可以配置在下面的配置文件里。该文件一般放在resource目录下。缺失则默认为spring-application-default.properties spring.application.default.properties.path=spring-application-default.properties # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试。缺失则默认为false spring.application.no.servers.retry.enabled=false # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的次数。缺失则默认为5 spring.application.no.servers.retry.times=5 # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的时间间隔。缺失则默认为2000 spring.application.no.servers.retry.await.time=2000 # 负载均衡下,消费端尝试获取对应提供端服务实例列表为空的时候,通过日志方式通知。缺失则默认为false spring.application.no.servers.notify.enabled=false # Plugin strategy config # 开启和关闭路由策略的控制。一旦关闭,路由策略功能将失效。缺失则默认为true spring.application.strategy.control.enabled=true # 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true spring.application.strategy.zone.avoidance.rule.enabled=true # 路由策略过滤器的执行顺序(Order)。缺失则默认为9000 spring.application.strategy.gateway.route.filter.order=9000 # 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true spring.application.strategy.gateway.header.priority=false # 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true spring.application.strategy.gateway.original.header.ignored=true # 启动和关闭网关上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下 # 1. n-d-version # 2. n-d-region # 3. n-d-address # 4. n-d-version-weight # 5. n-d-region-weight # 6. n-d-env (不属于灰度蓝绿范畴的Header,只要外部传入就会全程传递) spring.application.strategy.gateway.core.header.transmission.enabled=true # 启动和关闭注册的服务隔离(基于Group黑/白名单的策略)。缺失则默认为false spring.application.strategy.register.isolation.enabled=true # 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false spring.application.strategy.consumer.isolation.enabled=true # 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false spring.application.strategy.monitor.enabled=true # 启动和关闭日志输出。缺失则默认为false spring.application.strategy.logger.enabled=true # 日志输出中,是否显示MDC前面的Key。缺失则默认为true spring.application.strategy.logger.mdc.key.shown=true # 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.logger.debug.enabled=true # 启动和关闭调用链输出。缺失则默认为false spring.application.strategy.tracer.enabled=true # 启动和关闭调用链的灰度信息以独立的Span节点输出,如果关闭,则灰度信息输出到原生的Span节点中(Skywalking不支持原生模式)。缺失则默认为true spring.application.strategy.tracer.separate.span.enabled=true # 启动和关闭调用链的灰度规则策略信息输出。缺失则默认为true spring.application.strategy.tracer.rule.output.enabled=true # 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false spring.application.strategy.tracer.exception.detail.output.enabled=true # 显示在调用链界面上灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION spring.application.strategy.tracer.span.value=NEPXION # 显示在调用链界面上灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery # 启动和关闭Sentinel调用链上规则在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为true spring.application.strategy.tracer.sentinel.rule.output.enabled=true # 启动和关闭Sentinel调用链上方法入参在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为false spring.application.strategy.tracer.sentinel.args.output.enabled=true # 开启Spring Cloud Gateway网关上实现Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失。缺失则默认为false spring.application.strategy.hystrix.threadlocal.supported=true # 防止多个网关上并行实施灰度路由产生混乱,对处于非灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;灰度状态的服务,还是根据传递的Header版本号进行匹配 # 启动和关闭调用对端服务,是否执行调用它的时候只取它的老的稳定版本的实例的策略。缺失则默认为false spring.application.strategy.version.filter.enabled=true # 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common spring.application.environment.route=common # 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false spring.application.group.generator.enabled=true # 服务名前缀的截断长度,必须大于0 spring.application.group.generator.length=15 # 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式 spring.application.group.generator.character=- # 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false spring.application.git.generator.enabled=true # 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties spring.application.git.generator.path=classpath:git.properties # spring.application.git.generator.path=classpath:git.json # 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count} spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time} # spring.application.git.version.key={git.build.version}-{git.commit.time} ``` Zuul端配置 ``` # Plugin core config # 开启和关闭服务注册层面的控制。一旦关闭,服务注册的黑/白名单过滤功能将失效,最大注册数的限制过滤功能将失效。缺失则默认为true spring.application.register.control.enabled=true # 开启和关闭服务发现层面的控制。一旦关闭,服务多版本调用的控制功能将失效,动态屏蔽指定IP地址的服务实例被发现的功能将失效。缺失则默认为true spring.application.discovery.control.enabled=true # 开启和关闭通过Rest方式对规则配置的控制和推送。一旦关闭,只能通过远程配置中心来控制和推送。缺失则默认为true spring.application.config.rest.control.enabled=true # 规则文件的格式,支持xml和json。缺失则默认为xml spring.application.config.format=xml # spring.application.config.format=json # 本地规则文件的路径,支持两种方式:classpath:rule.xml(rule.json) - 规则文件放在resources目录下,便于打包进jar;file:rule.xml(rule.json) - 规则文件放在工程根目录下,放置在外部便于修改。缺失则默认为不装载本地规则 spring.application.config.path=classpath:rule.xml # spring.application.config.path=classpath:rule.json # 为微服务归类的Key,一般通过group字段来归类,例如eureka.instance.metadataMap.group=xxx-group或者eureka.instance.metadataMap.application=xxx-application。缺失则默认为group spring.application.group.key=group # spring.application.group.key=application # 业务系统希望大多数时候Spring、SpringBoot或者SpringCloud的基本配置、调优参数(非业务系统配置参数),不配置在业务端,集成到基础框架里。但特殊情况下,业务系统有时候也希望能把基础框架里配置的参数给覆盖掉,用他们自己的配置 # 对于此类型的配置需求,可以配置在下面的配置文件里。该文件一般放在resource目录下。缺失则默认为spring-application-default.properties spring.application.default.properties.path=spring-application-default.properties # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试。缺失则默认为false spring.application.no.servers.retry.enabled=false # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的次数。缺失则默认为5 spring.application.no.servers.retry.times=5 # 负载均衡下,消费端尝试获取对应提供端初始服务实例列表为空的时候,进行重试的时间间隔。缺失则默认为2000 spring.application.no.servers.retry.await.time=2000 # 负载均衡下,消费端尝试获取对应提供端服务实例列表为空的时候,通过日志方式通知。缺失则默认为false spring.application.no.servers.notify.enabled=false # Plugin strategy config # 开启和关闭路由策略的控制。一旦关闭,路由策略功能将失效。缺失则默认为true spring.application.strategy.control.enabled=true # 开启和关闭Ribbon默认的ZoneAvoidanceRule负载均衡策略。一旦关闭,则使用RoundRobin简单轮询负载均衡策略。缺失则默认为true spring.application.strategy.zone.avoidance.rule.enabled=true # 路由策略过滤器的执行顺序(Order)。缺失则默认为0 spring.application.strategy.zuul.route.filter.order=0 # 当外界传值Header的时候,网关也设置并传递同名的Header,需要决定哪个Header传递到后边的服务去。如果下面开关为true,以网关设置为优先,否则以外界传值为优先。缺失则默认为true spring.application.strategy.zuul.header.priority=false # 当以网关设置为优先的时候,网关未配置Header,而外界配置了Header,仍旧忽略外界的Header。缺失则默认为true spring.application.strategy.zuul.original.header.ignored=true # 启动和关闭网关上核心策略Header传递,缺失则默认为true。当全局订阅启动时,可以关闭核心策略Header传递,这样可以节省传递数据的大小,一定程度上可以提升性能。核心策略Header,包含如下 # 1. n-d-version # 2. n-d-region # 3. n-d-address # 4. n-d-version-weight # 5. n-d-region-weight # 6. n-d-env (不属于灰度蓝绿范畴的Header,只要外部传入就会全程传递) spring.application.strategy.zuul.core.header.transmission.enabled=true # 启动和关闭注册的服务隔离(基于Group黑/白名单的策略)。缺失则默认为false spring.application.strategy.register.isolation.enabled=true # 启动和关闭消费端的服务隔离(基于Group是否相同的策略)。缺失则默认为false spring.application.strategy.consumer.isolation.enabled=true # 启动和关闭监控,一旦关闭,调用链和日志输出都将关闭。缺失则默认为false spring.application.strategy.monitor.enabled=true # 启动和关闭日志输出。缺失则默认为false spring.application.strategy.logger.enabled=true # 日志输出中,是否显示MDC前面的Key。缺失则默认为true spring.application.strategy.logger.mdc.key.shown=true # 启动和关闭Debug日志打印,注意:每调用一次都会打印一次,会对性能有所影响,建议压测环境和生产环境关闭。缺失则默认为false spring.application.strategy.logger.debug.enabled=true # 启动和关闭调用链输出。缺失则默认为false spring.application.strategy.tracer.enabled=true # 启动和关闭调用链的灰度信息以独立的Span节点输出,如果关闭,则灰度信息输出到原生的Span节点中(Skywalking不支持原生模式)。缺失则默认为true spring.application.strategy.tracer.separate.span.enabled=true # 启动和关闭调用链的灰度规则策略信息输出。缺失则默认为true spring.application.strategy.tracer.rule.output.enabled=true # 启动和关闭调用链的异常信息是否以详细格式输出。缺失则默认为false spring.application.strategy.tracer.exception.detail.output.enabled=true # 显示在调用链界面上灰度Span的名称,建议改成具有公司特色的框架产品名称。缺失则默认为NEPXION spring.application.strategy.tracer.span.value=NEPXION # 显示在调用链界面上灰度Span Tag的插件名称,建议改成具有公司特色的框架产品的描述。缺失则默认为Nepxion Discovery spring.application.strategy.tracer.span.tag.plugin.value=Nepxion Discovery # 启动和关闭Sentinel调用链上规则在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为true spring.application.strategy.tracer.sentinel.rule.output.enabled=true # 启动和关闭Sentinel调用链上方法入参在Span上的输出,注意:原生的Sentinel不是Spring技术栈,下面参数必须通过-D方式或者System.setProperty方式等设置进去。缺失则默认为false spring.application.strategy.tracer.sentinel.args.output.enabled=true # 开启Zuul网关上实现Hystrix线程隔离模式做服务隔离时,必须把spring.application.strategy.hystrix.threadlocal.supported设置为true,同时要引入discovery-plugin-strategy-starter-hystrix包,否则线程切换时会发生ThreadLocal上下文对象丢失。缺失则默认为false spring.application.strategy.hystrix.threadlocal.supported=true # 防止多个网关上并行实施灰度路由产生混乱,对处于非灰度状态的服务,调用它的时候,只取它的老的稳定版本的实例;灰度状态的服务,还是根据传递的Header版本号进行匹配 # 启动和关闭调用对端服务,是否执行调用它的时候只取它的老的稳定版本的实例的策略。缺失则默认为false spring.application.strategy.version.filter.enabled=true # 流量路由到指定的环境下。不允许为保留值default,缺失则默认为common spring.application.environment.route=common # 开启和关闭使用服务名前缀来作为服务组名。缺失则默认为false spring.application.group.generator.enabled=true # 服务名前缀的截断长度,必须大于0 spring.application.group.generator.length=15 # 服务名前缀的截断标志。当截断长度配置了,则取截断长度方式,否则取截断标志方式 spring.application.group.generator.character=- # 开启和关闭使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为false spring.application.git.generator.enabled=true # 插件git-commit-id-plugin产生git信息文件的输出路径,支持properties和json两种格式,支持classpath:xxx和file:xxx两种路径,这些需要和插件里的配置保持一致。缺失则默认为classpath:git.properties spring.application.git.generator.path=classpath:git.properties # spring.application.git.generator.path=classpath:git.json # 使用Git信息中的字段单个或者多个组合来作为服务版本号。缺失则默认为{git.commit.time}-{git.total.commit.count} # spring.application.git.version.key={git.commit.id.abbrev}-{git.commit.time} # spring.application.git.version.key={git.build.version}-{git.commit.time} ``` ### 内置文件配置 框架提供内置文件方式的配置spring-application-default.properties。如果使用者希望对框架做封装,并提供相应的默认配置,可以在src/main/resources目录下放置spring-application-default.properties ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:该文件在整个服务目录和包中只能出现一次 ## Docker容器化和Kubernetes平台支持 ### Docker容器化 ![](http://nepxion.gitee.io/docs/icon-doc/information.png) Spring 2.3.x支持Docker分层部署,步骤也更简单,请参考Polaris【北极星】企业级云原生微服务框架里的介绍 ① 搭建Windows10操作系统或者Linux操作系统下的Docker环境 - Windows10环境下,具体步骤参考[Docker安装步骤](https://github.com/Nepxion/Thunder/blob/master/thunder-spring-boot-docker-example/README.md) - Linux环境请自行研究 ② 需要在4个工程下的pom.xml里增加spring-boot-maven-plugin和docker-maven-plugin ```xml org.springframework.boot spring-boot-maven-plugin true com.nepxion.discovery.guide.gateway.DiscoveryGuideGateway JAR repackage false com.spotify docker-maven-plugin ${ImageName} openjdk:8-jre-alpine ["java", "-jar", "/${project.build.finalName}.jar"] ${ExposePort} / ${project.build.directory} ${project.build.finalName}.jar ``` ③ 拷贝discovery-guide-docker目录下的所有脚本文件到根目录下 ④ 所有脚本文件下的MIDDLEWARE_HOST=10.0.75.1改成使用者本地物理IP地址(Docker是不能去连接容器外地址为localhost的中间件服务器) ⑤ 全自动部署和运行Docker化的服务。在根目录下 - 一键运行install-docker-gateway.bat或者.sh,把Spring Cloud Gateway网关全自动部署且运行起来 - 一键运行install-docker-zuul.bat或者.sh,把Zuul网关全自动部署且运行起来 - 一键运行install-docker-service-xx.bat或者.sh,把微服务全自动部署且运行起来。注意,必须依次运行,即等上一个部署完毕后才能执行下一个 - 一键运行install-docker-console.bat或者.sh,把控制平台全自动部署且运行起来 - 一键运行install-docker-admin.bat或者.sh,把监控平台全自动部署且运行起来 ⑥ 上述步骤为演示步骤,和DevOps平台结合在一起,更为完美 ![](http://nepxion.gitee.io/docs/discovery-doc/Docker.jpg) ### Kubernetes平台支持 请自行研究 ## 自动化测试 自动化测试,基于Spring Boot/Spring Cloud的自动化测试框架,包括普通调用测试、灰度调用测试和扩展调用测试(例如:支持阿里巴巴的Sentinel,FF4j的功能开关等)。通过注解形式,跟Spring Boot内置的测试机制集成,使用简单方便。该自动化测试框架的现实意义,可以把服务注册发现中心、远程配置中心、负载均衡、灰度发布、熔断降级限流、功能开关、Feign或者RestTemplate调用等中间件或者组件,一条龙组合起来进行自动化测试 自动化测试代码参考[指南示例自动化测试](https://github.com/Nepxion/DiscoveryGuide/tree/master/discovery-guide-test-automation) ### 架构设计 通过Matrix Aop框架,实现TestAutoScanProxy和TestInterceptor拦截测试用例,实现配置内容的自动化推送 ### 启动控制台 运行[指南示例](https://github.com/Nepxion/DiscoveryGuide)下的DiscoveryGuideConsole.java控制台服务,它是连接服务注册发现中心、远程配置中心和服务的纽带,自动化测试利用控制台实现配置的自动更新和清除 ### 配置文件 ``` # 自动化测试框架内置配置 # 测试用例类的扫描路径 spring.application.test.scan.packages=com.nepxion.discovery.guide.test # 测试用例的配置内容推送时,是否打印配置日志。缺失则默认为true spring.application.test.config.print.enabled=true # 测试用例的配置内容推送后,等待生效的时间。推送远程配置中心后,再通知各服务更新自身的配置缓存,需要一定的时间,缺失则默认为3000 spring.application.test.config.operation.await.time=8000 # 测试用例的配置内容推送的控制台地址。控制台是连接服务注册发现中心、远程配置中心和服务的纽带 spring.application.test.console.url=http://localhost:6001/ # 业务测试配置 # Spring Cloud Gateway网关配置 gateway.group=discovery-guide-group gateway.service.id=discovery-guide-gateway gateway.test.url=http://localhost:5001/discovery-guide-service-a/invoke/gateway # Zuul网关配置 zuul.group=discovery-guide-group zuul.service.id=discovery-guide-zuul zuul.test.url=http://localhost:5002/discovery-guide-service-a/invoke/zuul # 每个测试用例执行循环次数 testcase.loop.times=1 # 测试用例的灰度权重测试开关。由于权重测试需要大量采样调用,会造成整个自动化测试时间很长,可以通过下面开关开启和关闭。缺失则默认为true gray.weight.testcases.enabled=true # 测试用例的灰度权重采样总数。采样总数越大,灰度权重准确率越高,但耗费时间越长 gray.weight.testcase.sample.count=1500 # 测试用例的灰度权重准确率偏离值。采样总数越大,灰度权重准确率偏离值越小 gray.weight.testcase.result.offset=5 ``` ### 测试用例 #### 测试包引入 ```xml com.nepxion discovery-plugin-test-starter ${discovery.version} org.apache.maven.plugins maven-compiler-plugin -parameters ${project.build.sourceEncoding} ${java.version} ${java.version} ``` ![](http://nepxion.gitee.io/docs/icon-doc/warning.png) 注意:对于带有注解@DTestConfig的测试用例,要用到Spring的Spel语法格式(即group = "#group", serviceId = "#serviceId"),需要引入Java8的带"-parameters"编译方式,见上面的参数设置 在IDE环境里需要设置"-parameters"的Compiler Argument - Eclipse加"-parameters"参数:https://www.concretepage.com/java/jdk-8/java-8-reflection-access-to-parameter-names-of-method-and-constructor-with-maven-gradle-and-eclipse-using-parameters-compiler-argument - Idea加"-parameters"参数:http://blog.csdn.net/royal_lr/article/details/52279993 #### 测试入口程序 结合Spring Boot Junit,TestApplication.class为测试框架内置应用启动程序,DiscoveryGuideTestConfiguration用于初始化所有测试用例类。在测试方法上面加入JUnit的@Test注解 ```java @RunWith(SpringRunner.class) @SpringBootTest(classes = { TestApplication.class, DiscoveryGuideTestConfiguration.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class DiscoveryGuideTest { @Autowired private DiscoveryGuideTestCases discoveryGuideTestCases; private static long startTime; @BeforeClass public static void beforeTest() { startTime = System.currentTimeMillis(); } @AfterClass public static void afterTest() { LOG.info("* Finished automation test in {} seconds", (System.currentTimeMillis() - startTime) / 1000); } @Test public void testNoGray() throws Exception { discoveryGuideTestCases.testNoGray(gatewayTestUrl); discoveryGuideTestCases.testNoGray(zuulTestUrl); } @Test public void testVersionStrategyGray() throws Exception { discoveryGuideTestCases.testVersionStrategyGray1(gatewayGroup, gatewayServiceId, gatewayTestUrl); discoveryGuideTestCases.testVersionStrategyGray1(zuulGroup, zuulServiceId, zuulTestUrl); } } ``` ```java @Configuration public class DiscoveryGuideTestConfiguration { @Bean public DiscoveryGuideTestCases discoveryGuideTestCases() { return new DiscoveryGuideTestCases(); } } ``` #### 普通调用测试 在测试方法上面增加注解@DTest,通过断言Assert来判断测试结果。注解@DTest内容如下 ```java @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface DTest { } ``` 代码如下 ```java public class DiscoveryGuideTestCases { @Autowired private TestRestTemplate testRestTemplate; @DTest public void testNoGray(String testUrl) { int noRepeatCount = 0; List resultList = new ArrayList(); for (int i = 0; i < 4; i++) { String result = testRestTemplate.getForEntity(testUrl, String.class).getBody(); LOG.info("Result{} : {}", i + 1, result); if (!resultList.contains(result)) { noRepeatCount++; } resultList.add(result); } Assert.assertEquals(noRepeatCount, 4); } } ``` #### 灰度调用测试 在测试方法上面增加注解@DTestConfig,通过断言Assert来判断测试结果。注解DTestConfig注解内容如下 ```java @Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface DTestConfig { // 组名 String group(); // 服务名 String serviceId(); // 组名-服务名组合键值的前缀 String prefix() default StringUtils.EMPTY; // 组名-服务名组合键值的后缀 String suffix() default StringUtils.EMPTY; // 执行配置的文件路径。测试用例运行前,会把该文件里的内容推送到远程配置中心或者服务 String executePath(); // 重置配置的文件路径。测试用例运行后,会把该文件里的内容推送到远程配置中心或者服务。该文件内容是最初的默认配置 // 如果该注解属性为空,则直接删除从配置中心删除组名-服务名组合键值 String resetPath() default StringUtils.EMPTY; } ``` 代码如下 ```java public class DiscoveryGuideTestCases { @Autowired private TestRestTemplate testRestTemplate; @DTestConfig(group = "#group", serviceId = "#serviceId", executePath = "gray-strategy-version-1.xml", resetPath = "gray-default.xml") public void testVersionStrategyGray(String group, String serviceId, String testUrl) { for (int i = 0; i < 4; i++) { String result = testRestTemplate.getForEntity(testUrl, String.class).getBody(); LOG.info("Result{} : {}", i + 1, result); int index = result.indexOf("[V=1.0]"); int lastIndex = result.lastIndexOf("[V=1.0]"); Assert.assertNotEquals(index, -1); Assert.assertNotEquals(lastIndex, -1); Assert.assertNotEquals(index, lastIndex); } } } ``` 灰度配置文件gray-strategy-version-1.xml的内容如下 ```xml 1.0 ``` 灰度配置文件gray-default.xml的内容如下 ```xml ``` #### 扩展调用测试 除了支持灰度自动化测试外,使用者可扩展出以远程配置中心内容做变更的自动化测试。以阿里巴巴的Sentinel为例子,测试实现方式如下 ① 远程配置中心约定 - Nacos的Key格式 ``` Group为代码中的{group},Data ID为{serviceId}-{suffix},即{serviceId}-sentinel ``` - Apollo的Key格式 ``` {group}-{serviceId}-sentinel ``` ② 执行测试用例前,把执行限流降级熔断等逻辑的内容(executePath = "sentinel-test.xml")推送到远程配置中心 ③ 执行测试用例,通过断言Assert来判断测试结果 ④ 执行测试用例后,把修改过的内容(resetPath = "sentinel-default.xml")复原,再推送一次到远程配置中心 ```java public class DiscoveryGuideTestCases { @Autowired private TestRestTemplate testRestTemplate; @DTestConfig(group = "#group", serviceId = "#serviceId", suffix = "sentinel" executePath = "sentinel-test.xml", resetPath = "sentinel-default.xml") public void testSentinel(String group, String serviceId, String testUrl) { ... } } ``` ### 测试报告 - 路由策略测试报告样例 ``` ---------- Run automation testcase :: testNoGray() ---------- Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testEnabledStrategyGray1() ---------- Header : [mobile:"138"] Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testVersionStrategyGray() ---------- Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testRegionStrategyGray() ---------- Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testVersionWeightStrategyGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : 1.0 version weight=90%, 1.1 version weight=10% B service desired : 1.0 version weight=20%, 1.1 version weight=80% Result : A service 1.0 version weight=89.6% Result : A service 1.1 version weight=10.4% Result : B service 1.0 version weight=20.1333% Result : B service 1.1 version weight=79.8667% * Passed ---------- Run automation testcase :: testRegionWeightStrategyGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : dev region weight=85%, qa region weight=15% B service desired : dev region weight=85%, qa region weight=15% Result : A service dev region weight=83.7667% Result : A service qa region weight=16.2333% Result : B service dev region weight=86.2% Result : B service qa region weight=13.8% * Passed ``` - 路由规则测试报告样例 ``` ---------- Run automation testcase :: testStrategyCustomizationGray() ---------- Header : [a:"1", b:"2"] Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testVersionRuleGray() ---------- Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testRegionRuleGray() ---------- Result1 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result2 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] Result3 : gateway -> discovery-guide-service-a[192.168.0.107:3002][V=1.1][R=qa][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4001][V=1.0][R=qa][G=discovery-guide-group] Result4 : gateway -> discovery-guide-service-a[192.168.0.107:3001][V=1.0][R=dev][G=discovery-guide-group] -> discovery-guide-service-b[192.168.0.107:4002][V=1.1][R=dev][G=discovery-guide-group] * Passed ---------- Run automation testcase :: testVersionWeightRuleGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : 1.0 version weight=75%, 1.1 version weight=25% B service desired : 1.0 version weight=35%, 1.1 version weight=65% Result : A service 1.0 version weight=75.2667% Result : A service 1.1 version weight=24.7333% Result : B service 1.0 version weight=35.1667% Result : B service 1.1 version weight=64.8333% * Passed ---------- Run automation testcase :: testRegionWeightRuleGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : dev region weight=95%, qa region weight=5% B service desired : dev region weight=95%, qa region weight=5% Result : A service dev region weight=94.9333% Result : A service qa region weight=5.0667% Result : B service dev region weight=95.0667% Result : B service qa region weight=4.9333% * Passed ---------- Run automation testcase :: testVersionCompositeRuleGray() ---------- Sample count=3000 Weight result offset desired=5% A service desired : 1.0 version weight=40%, 1.1 version weight=60% Route desired : A Service 1.0 version -> B Service 1.0 version, A Service 1.1 version -> B Service 1.1 version Result : A service 1.0 version weight=39.8333% A service 1.1 version weight=60.1667% * Passed ``` ## 压力测试 压力测试,基于WRK的异步压力测试框架,能用很少的线程压测出很大的并发量,使用简单方便 ### 测试环境 ① 准备两台机器部署Spring Cloud应用 ② 准备一台机器部署网关(Spring Cloud或者Zuul) ③ 准备一台机器部署压测工具 | 服务 | 配置 | 数目 | | --- | --- | --- | | Spring Cloud Gateway | 16C 32G | 1 | | Zuul 1.x | 16C 32G | 1 | | Service | 4C 8G | 2 | ④ 优化方式 - Spring Cloud Gateway,不需要优化 - Zuul 1.x,优化如下 ``` zuul.host.max-per-route-connections=1000 zuul.host.max-total-connections=1000 zuul.semaphore.max-semaphores=5000 ``` ### 测试介绍 - 使用WRK脚本进行性能测试,WRK脚本示例,请参考[压测脚本](https://github.com/Nepxion/DiscoveryGuide/tree/master/discovery-guide-test-automation/post.lua),下面的测试命令行可以不必带脚本参数 - 使用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% | ## 附录 ### 中间件服务器下载地址 ① Consul - Consul服务器版本不限制,推荐用最新版本,从[https://releases.hashicorp.com/consul/](https://releases.hashicorp.com/consul/)获取 - 功能界面主页,[http://localhost:8500](http://localhost:8500) ② Zookeeper - Spring Cloud F版或以上,必须采用Zookeeper服务器的3.5.x服务器版本(或者更高),从[http://zookeeper.apache.org/releases.html#download](http://zookeeper.apache.org/releases.html#download)获取 - Spring Cloud E版,Zookeeper服务器版本不限制 ③ Eureka - 跟Spring Cloud版本保持一致,自行搭建服务器 - 功能界面主页,[http://localhost:9528](http://localhost:9528) ④ Apollo - Apollo服务器版本,推荐用最新版本,从[https://github.com/ctripcorp/apollo/releases](https://github.com/ctripcorp/apollo/releases)获取 - 功能界面主页,[http://localhost:8088](http://localhost:8088) ⑤ Nacos - Nacos服务器版本,推荐用最新版本,从[https://github.com/alibaba/nacos/releases](https://github.com/alibaba/nacos/releases)获取 - 功能界面主页,[http://localhost:8848/nacos/index.html](http://localhost:8848/nacos/index.html) ⑥ Redis - Redis服务器版本,推荐用最新版本,从[https://redis.io/](https://redis.io/)获取 ⑦ Sentinel - Sentinel服务器版本,推荐用最新版本,从[https://github.com/alibaba/Sentinel/releases](https://github.com/alibaba/Sentinel/releases)获取 - 功能界面主页,[http://localhost:8075/#/dashboard](http://localhost:8075/#/dashboard) ⑧ Prometheus - Prometheus服务器版本,推荐用最新版本,从[https://github.com/prometheus/prometheus/releases](https://github.com/prometheus/prometheus/releases)获取 - 功能界面主页,[http://localhost:9090](http://localhost:9090) ⑨ Grafana - Grafana服务器版本,推荐用最新版本,从[https://grafana.com/grafana/download?platform=windows](https://grafana.com/grafana/download?platform=windows)获取 - 功能界面主页,[http://localhost:3000](http://localhost:3000) ⑩ Jaeger - Jaeger服务器版本,推荐用最新版本,从[https://github.com/jaegertracing/jaeger/releases](https://github.com/jaegertracing/jaeger/releases)获取 - 功能界面主页,[http://localhost:16686](http://localhost:16686) ⑪ Zipkin - Zipkin服务器版本,推荐用最新版本,从[https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec](https://search.maven.org/remote_content?g=io.zipkin&a=zipkin-server&v=LATEST&c=exec)获取 - 功能界面主页,[http://localhost:9411/zipkin](http://localhost:9411/zipkin) ⑫ Spring Boot Admin - 跟Spring Boot版本保持一致,自行搭建服务器。从[https://github.com/codecentric/spring-boot-admin](https://github.com/codecentric/spring-boot-admin)获取 - 功能界面主页,[http://localhost:6002](http://localhost:6002) ## Star走势图 [![Stargazers over time](https://starchart.cc/Nepxion/Discovery.svg)](https://starchart.cc/Nepxion/Discovery)