Spring Cloud Dalston.RELEASE中文文档
Spring Cloud Dalston.RELEASE中文文档Spring Cloud目录特性云原生应用程序Spring Cloud上下文:应用程序上下文服务引导应用程序上下文应用程序上下文层次结构改变引导位置Properties覆盖远程Properties的值自定义引导配置自定义引导属性源环境变化刷新范围加密和解密端点Spring Cloud C...
Spring Cloud Dalston.RELEASE中文文档
Spring Cloud
- 特性
- 云原生应用程序
- Spring Cloud Config
- Spring Cloud Netflix
- Spring Cloud Stream
- Binder实现
- Spring Cloud Bus
- Spring Cloud Sleuth
- Spring Cloud Consul
- Spring Cloud Zookeeper
- Spring Cloud Security
- Spring Cloud为Cloud Foundry
- Spring Cloud Contract
- 附录:配置纲要Properties
Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致了样板模式, 使用Spring Cloud开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式环境中运行良好,包括开发人员自己的笔记本电脑,裸机数据中心,以及Cloud Foundry等托管平台。
版本:Dalston.RELEASE
特性
Spring Cloud专注于提供良好的开箱即用经验的典型用例和可扩展性机制覆盖。
-
分布式/版本化配置
-
服务注册和发现
-
路由
-
service - to - service调用
-
负载均衡
-
断路器
-
分布式消息传递
云原生应用程序
云原生是一种应用开发风格,鼓励在持续交付和价值驱动开发领域轻松采用最佳实践。相关的学科是建立12-factor Apps,其中开发实践与交付和运营目标相一致,例如通过使用声明式编程和管理和监控。Spring Cloud以多种具体方式促进这些开发风格,起点是一组功能,分布式系统中的所有组件都需要或需要时轻松访问。
许多这些功能都由Spring Boot覆盖,我们在Spring Cloud中建立。更多的由Spring Cloud提供为两个库:Spring Cloud Context和Spring Cloud Commons。Spring Cloud上下文为Spring Cloud应用程序(引导上下文,加密,刷新范围和环境端点)的ApplicationContext
提供实用程序和特殊服务。Spring Cloud Commons是一组在不同的Spring Cloud实现中使用的抽象和常用类(例如Spring Cloud Netflix vs. Spring Cloud Consul)。
如果由于“非法密钥大小”而导致异常,并且您正在使用Sun的JDK,则需要安装Java加密扩展(JCE)无限强度管理策略文件。有关详细信息,请参阅以下链接:
将文件解压缩到JDK / jre / lib / security文件夹(无论您使用的是哪个版本的JRE / JDK x64 / x86)。
注意
| Spring Cloud根据非限制性Apache 2.0许可证发布。如果您想为文档的这一部分做出贡献,或者发现错误,请在github中找到项目中的源代码和问题跟踪器。 |
Spring Cloud上下文:应用程序上下文服务
Spring Boot对于如何使用Spring构建应用程序有一个看法:例如它具有常规配置文件的常规位置,以及用于常见管理和监视任务的端点。Spring Cloud建立在此之上,并添加了一些可能系统中所有组件将使用或偶尔需要的功能。
引导应用程序上下文
一个Spring Cloud应用程序通过创建一个“引导”上下文来进行操作,这个上下文是主应用程序的父上下文。开箱即用,负责从外部源加载配置属性,还解密本地外部配置文件中的属性。这两个上下文共享一个Environment
,这是任何Spring应用程序的外部属性的来源。Bootstrap属性的优先级高,因此默认情况下不能被本地配置覆盖。
引导上下文使用与主应用程序上下文不同的外部配置约定,因此使用bootstrap.yml
application.yml
(或.properties
)代替引导和主上下文的外部配置。例:
spring: application: name: foo cloud: config: uri: ${SPRING_CONFIG_URI:http://localhost:8888}
如果您的应用程序需要服务器上的特定于应用程序的配置,那么设置spring.application.name
(在bootstrap.yml
或application.yml
)中是个好主意。
您可以通过设置spring.cloud.bootstrap.enabled=false
(例如在系统属性中)来完全禁用引导过程。
应用程序上下文层次结构
如果您从SpringApplication
或SpringApplicationBuilder
构建应用程序上下文,则将Bootstrap上下文添加为该上下文的父级。这是一个Spring的功能,即子上下文从其父进程继承属性源和配置文件,因此与不使用Spring Cloud Config构建相同上下文相比,“主”应用程序上下文将包含其他属性源。额外的财产来源是:
-
“bootstrap”:如果在Bootstrap上下文中找到任何
PropertySourceLocators
,则可选CompositePropertySource
显示为高优先级,并且具有非空属性。一个例子是来自Spring Cloud Config服务器的属性。有关如何自定义此属性源的内容的说明,请参阅 下文。 -
“applicationConfig:[classpath:bootstrap.yml]”(如果Spring配置文件处于活动状态,则为朋友)。如果您有一个
bootstrap.yml
(或属性),那么这些属性用于配置引导上下文,然后在父进程设置时将它们添加到子上下文中。它们的优先级低于application.yml
(或属性)以及作为创建Spring Boot应用程序的过程的正常部分添加到子级的任何其他属性源。有关如何自定义这些属性源的内容的说明,请参阅下文。
由于属性源的排序规则,“引导”条目优先,但请注意,这些条目不包含来自bootstrap.yml
的任何数据,它具有非常低的优先级,但可用于设置默认值。
您可以通过简单地设置您创建的任何ApplicationContext
的父上下文来扩展上下文层次结构,例如使用自己的界面,或使用SpringApplicationBuilder
方便方法(parent()
,child()
和sibling()
)。引导环境将是您创建自己的最高级祖先的父级。层次结构中的每个上下文都将有自己的“引导”属性源(可能为空),以避免无意中将值从父级升级到其后代。层次结构中的每个上下文(原则上)也可以具有不同的spring.application.name
,因此如果存在配置服务器,则不同的远程属性源。普通的Spring应用程序上下文行为规则适用于属性解析:子环境中的属性通过名称和属性源名称覆盖父项中的属性(如果子级具有与父级名称相同的属性源,一个来自父母的孩子不包括在孩子中)。
请注意,SpringApplicationBuilder
允许您在整个层次结构中共享Environment
,但这不是默认值。因此,兄弟情境尤其不需要具有相同的资料或财产来源,尽管它们与父母共享共同点。
改变引导位置Properties
可以使用spring.cloud.bootstrap.name
(默认“引导”)或spring.cloud.bootstrap.location
(默认为空)指定bootstrap.yml
(或.properties
)位置,例如在系统属性中。这些属性的行为类似于具有相同名称的spring.config.*
变体,实际上它们用于通过在其Environment
中设置这些属性来设置引导ApplicationContext
。如果在正在构建的上下文中有活动的配置文件(来自spring.profiles.active
或通过Environment
API)),则该配置文件中的属性也将被加载,就像常规的Spring Boot应用程序,例如来自bootstrap-development.properties
的“开发”简介。
覆盖远程Properties的值
通过引导上下文添加到应用程序的属性源通常是“远程”(例如从配置服务器),并且默认情况下,不能在本地覆盖,除了在命令行上。如果要允许您的应用程序使用自己的系统属性或配置文件覆盖远程属性,则远程属性源必须通过设置spring.cloud.config.allowOverride=true
(在本地设置本身不起作用)授予权限。一旦设置了该标志,就会有一些更精细的设置来控制远程属性与系统属性和应用程序本地配置的位置:spring.cloud.config.overrideNone=true
覆盖任何本地属性源,spring.cloud.config.overrideSystemProperties=false
如果只有系统属性和env var应该覆盖远程设置,而不是本地配置文件。
自定义引导配置
可以通过在org.springframework.cloud.bootstrap.BootstrapConfiguration
键下添加条目/META-INF/spring.factories
来训练引导上下文来执行任何您喜欢的操作。这是用于创建上下文的Spring @Configuration
类的逗号分隔列表。您可以在此处创建要用于自动装配的主应用程序上下文的任何bean,并且还有ApplicationContextInitializer
类型的@Beans
的特殊合同。如果要控制启动顺序(默认顺序为“最后”),可以使用@Order
标记类。
警告
| 添加自定义BootstrapConfiguration 时,请注意,您添加的类不是错误的@ComponentScanned 到您的“主”应用程序上下文中,可能不需要它们。对于您的@ComponentScan 或@SpringBootApplication 注释配置类尚未涵盖的启动配置类,请使用单独的包名称。 |
引导过程通过将初始化器注入主SpringApplication
实例(即正常的Spring Boot启动顺序,无论是作为独立应用程序运行还是部署在应用程序服务器中)结束。首先,从spring.factories
中找到的类创建引导上下文,然后在ApplicationContextInitializer
类型的所有@Beans
添加到主SpringApplication
开始之前。
自定义引导属性源
引导过程添加的外部配置的默认属性源是Config Server,但您可以通过将PropertySourceLocator
类型的bean添加到引导上下文(通过spring.factories
)添加其他源。您可以使用此方法从其他服务器或数据库中插入其他属性。
作为一个例子,请考虑以下微不足道的自定义定位器:
@Configuration public class CustomPropertySourceLocator implements PropertySourceLocator {
@Override public PropertySource<?> locate(Environment environment) { return new MapPropertySource("customProperty", Collections.<String, Object>singletonMap("property.from.sample.custom.source", "worked as intended")); }
}
传入的Environment
是要创建的ApplicationContext
的Environment
,即为我们提供额外的属性来源的。它将已经具有正常的Spring Boot提供的资源来源,因此您可以使用它们来定位特定于此Environment
的属性源(例如通过将其绑定在spring.application.name
上,如在默认情况下所做的那样Config Server属性源定位器)。
如果你在这个类中创建一个jar,然后添加一个META-INF/spring.factories
包含:
org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomPropertySourceLocator
那么“customProperty”PropertySource
将显示在其类路径中包含该jar的任何应用程序中。
环境变化
应用程序将收听EnvironmentChangeEvent
,并以几种标准方式进行更改(用户可以以常规方式添加ApplicationListeners
附加ApplicationListeners
)。当观察到EnvironmentChangeEvent
时,它将有一个已更改的键值列表,应用程序将使用以下内容:
-
重新绑定上下文中的任何
@ConfigurationProperties
bean -
为
logging.level.*
中的任何属性设置记录器级别
请注意,配置客户端不会通过默认轮询查找Environment
中的更改,通常我们不建议检测更改的方法(尽管可以使用@Scheduled
注释进行设置)。如果您有一个扩展的客户端应用程序,那么最好将EnvironmentChangeEvent
广播到所有实例,而不是让它们轮询更改(例如使用Spring Cloud总线)。
EnvironmentChangeEvent
涵盖了大量的刷新用例,只要您真的可以更改Environment
并发布事件(这些API是公开的,部分内核为Spring)。您可以通过访问/configprops
端点(普通Spring Boot执行器功能)来验证更改是否绑定到@ConfigurationProperties
bean。例如,DataSource
可以在运行时更改其maxPoolSize
(由Spring Boot创建的默认DataSource
是一个@ConfigurationProperties
bean),并且动态增加容量。重新绑定@ConfigurationProperties
不会覆盖另一大类用例,您需要更多的控制刷新,并且您需要更改在整个ApplicationContext
上是原子的。为了解决这些担忧,我们有@RefreshScope
。
刷新范围
当配置更改时,标有@RefreshScope
的Spring @Bean
将得到特殊处理。这解决了状态bean在初始化时只注入配置的问题。例如,如果通过Environment
更改数据库URL时DataSource
有开放连接,那么我们可能希望这些连接的持有人能够完成他们正在做的工作。然后下一次有人从游泳池借用一个连接,他得到一个新的URL。
刷新范围bean是在使用时初始化的懒惰代理(即当调用一个方法时),并且作用域作为初始值的缓存。要强制bean重新初始化下一个方法调用,您只需要使其缓存条目无效。
RefreshScope
是上下文中的一个bean,它有一个公共方法refreshAll()
来清除目标缓存中的范围内的所有bean。还有一个refresh(String)
方法可以按名称刷新单个bean。此功能在/refresh
端点(通过HTTP或JMX)中公开。
注意
| @RefreshScope (技术上)在@Configuration 类上工作,但可能会导致令人惊讶的行为:例如,这并不 意味着该类中定义的所有@Beans 本身都是@RefreshScope 。具体来说,任何取决于这些bean的东西都不能依赖它们在刷新启动时被更新,除非它本身在@RefreshScope (在其中将重新刷新并重新注入其依赖关系),那么它们将从刷新的@Configuration )重新初始化。 |
加密和解密
Spring Cloud具有一个用于在本地解密属性值的Environment
预处理器。它遵循与Config Server相同的规则,并通过encrypt.*
具有相同的外部配置。因此,您可以使用{cipher}*
格式的加密值,只要有一个有效的密钥,那么在主应用程序上下文获取Environment
之前,它们将被解密。要在应用程序中使用加密功能,您需要在您的类路径中包含Spring安全性RSA(Maven协调“org.springframework.security:spring-security-rsa”),并且还需要全面强大的JCE扩展你的JVM
如果由于“非法密钥大小”而导致异常,并且您正在使用Sun的JDK,则需要安装Java加密扩展(JCE)无限强度管理策略文件。有关详细信息,请参阅以下链接:
将文件解压缩到JDK / jre / lib / security文件夹(无论您使用的是哪个版本的JRE / JDK x64 / x86)。
端点
对于Spring Boot执行器应用程序,还有一些额外的管理端点:
-
POST到
/env
以更新Environment
并重新绑定@ConfigurationProperties
和日志级别 -
/refresh
重新加载引导带上下文并刷新@RefreshScope
bean -
/restart
关闭ApplicationContext
并重新启动(默认情况下禁用) -
/pause
和/resume
调用Lifecycle
方法(stop()
和start()
ApplicationContext
)
Spring Cloud Commons:普通抽象
诸如服务发现,负载平衡和断路器之类的模式适用于所有Spring Cloud客户端可以独立于实现(例如通过Eureka或Consul发现)的消耗的共同抽象层。
@EnableDiscoveryClient
Commons提供@EnableDiscoveryClient
注释。这通过META-INF/spring.factories
查找DiscoveryClient
接口的实现。Discovery Client的实现将在org.springframework.cloud.client.discovery.EnableDiscoveryClient
键下的spring.factories
中添加一个配置类。DiscoveryClient
实现的示例是Spring Cloud Netflix Eureka,Spring Cloud Consul发现和Spring Cloud Zookeeper发现。
默认情况下,DiscoveryClient
的实现将使用远程发现服务器自动注册本地Spring Boot服务器。可以通过在@EnableDiscoveryClient
中设置autoRegister=false
来禁用此功能。
ServiceRegistry
Commons现在提供了一个ServiceRegistry
接口,它提供了诸如register(Registration)
和deregister(Registration)
之类的方法,允许您提供定制的注册服务。Registration
是一个标记界面。
@Configuration @EnableDiscoveryClient(autoRegister=false) public class MyConfiguration { private ServiceRegistry registry;
public MyConfiguration(ServiceRegistry registry) { this.registry = registry; } // called via some external process, such as an event or a custom actuator endpoint public void register() { Registration registration = constructRegistration(); this.registry.register(registration); }
}
每个ServiceRegistry
实现都有自己的Registry
实现。
服务部门自动注册
默认情况下,ServiceRegistry
实现将自动注册正在运行的服务。要禁用该行为,有两种方法。您可以设置@EnableDiscoveryClient(autoRegister=false)
永久禁用自动注册。您还可以设置spring.cloud.service-registry.auto-registration.enabled=false
以通过配置禁用该行为。
服务注册执行器端点
Commons提供/service-registry
致动器端点。该端点依赖于Spring应用程序上下文中的Registration
bean。通过GET调用/service-registry/instance-status
将返回Registration
的状态。具有String
主体的同一端点的POST将将当前Registration
的状态更改为新值。请参阅您正在使用的ServiceRegistry
实现的文档,以获取更新状态的允许值和为状态获取的值。
Spring RestTemplate作为负载平衡器客户端
RestTemplate
可以自动配置为使用功能区。要创建负载平衡RestTemplate
创建RestTemplate
@Bean
并使用@LoadBalanced
限定符。
警告
| 通过自动配置不再创建RestTemplate bean。它必须由单个应用程序创建。 |
@Configuration public class MyConfiguration {
@LoadBalanced @Bean RestTemplate restTemplate() { return new RestTemplate(); }
}
public class MyClass {
@Autowired
private RestTemplate restTemplate;
public String doOtherStuff() {
String results = restTemplate.getForObject("http://stores/stores", String.class);
return results;
}
}
URI需要使用虚拟主机名(即服务名称,而不是主机名)。Ribbon客户端用于创建完整的物理地址。有关 如何设置RestTemplate
的详细信息,请参阅 RibbonAutoConfiguration。
重试失败的请求
负载平衡RestTemplate
可以配置为重试失败的请求。默认情况下,该逻辑被禁用,您可以通过将Spring重试添加到应用程序的类路径来启用它。负载平衡RestTemplate
将符合与重试失败请求相关的一些Ribbon配置值。如果要在类路径中使用Spring重试来禁用重试逻辑,则可以设置spring.cloud.loadbalancer.retry.enabled=false
。您可以使用的属性是client.ribbon.MaxAutoRetries
,client.ribbon.MaxAutoRetriesNextServer
和client.ribbon.OkToRetryOnAllOperations
。请参阅Ribbon文档 ,了解属性的具体内容。
注意
| 上述示例中的client 应替换为您的Ribbon客户端名称。 |
多个RestTemplate对象
如果你想要一个没有负载平衡的RestTemplate
,创建一个RestTemplate
bean并注入它。要创建@Bean
时,使用@LoadBalanced
限定符来访问负载平衡RestTemplate
。
重要
| 请注意下面示例中的普通RestTemplate 声明的@Primary 注释,以消除不合格的@Autowired 注入。 |
@Configuration public class MyConfiguration {
@LoadBalanced @Bean RestTemplate loadBalanced() { return new RestTemplate(); } @Primary @Bean RestTemplate restTemplate() { return new RestTemplate(); }
}
public class MyClass {
@Autowired
private RestTemplate restTemplate;
@Autowired
@LoadBalanced
private RestTemplate loadBalanced;
public String doOtherStuff() {
return loadBalanced.getForObject("http://stores/stores", String.class);
}
public String doStuff() {
return restTemplate.getForObject("http://example.com", String.class);
}
}
提示
| 如果您看到错误java.lang.IllegalArgumentException: Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89 ,请尝试注入RestOperations 或设置spring.aop.proxyTargetClass=true 。 |
忽略网络接口
有时,忽略某些命名网络接口是有用的,因此可以将其从服务发现注册中排除(例如,在Docker容器中运行)。可以设置正则表达式的列表,这将导致所需的网络接口被忽略。以下配置将忽略“docker0”接口和以“veth”开头的所有接口。
spring: cloud: inetutils: ignoredInterfaces: - docker0 - veth.*
您还可以强制使用正则表达式列表中指定的网络地址:
spring: cloud: inetutils: preferredNetworks: - 192.168 - 10.0
您也可以强制仅使用站点本地地址。有关更多详细信息,请参阅Inet4Address.html.isSiteLocalAddress())是什么是站点本地地址。
spring: cloud: inetutils: useOnlySiteLocalInterfaces: true
Spring Cloud Config
Dalston.RELEASE
Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持。使用Config Server,您可以在所有环境中管理应用程序的外部属性。客户端和服务器上的概念映射与Spring Environment
和PropertySource
抽象相同,因此它们与Spring应用程序非常契合,但可以与任何以任何语言运行的应用程序一起使用。随着应用程序通过从开发人员到测试和生产的部署流程,您可以管理这些环境之间的配置,并确定应用程序具有迁移时需要运行的一切。服务器存储后端的默认实现使用git,因此它轻松支持标签版本的配置环境,以及可以访问用于管理内容的各种工具。很容易添加替代实现,并使用Spring配置将其插入。
快速开始
启动服务器:
$ cd spring-cloud-config-server $ ../mvnw spring-boot:run
该服务器是一个Spring Boot应用程序,所以您可以从IDE运行它,而不是喜欢(主类是ConfigServerApplication
)。然后尝试一个客户端:
$ curl localhost:8888/foo/development {"name":"development","label":"master","propertySources":[ {"name":"https://github.com/scratches/config-repo/foo-development.properties","source":{"bar":"spam"}}, {"name":"https://github.com/scratches/config-repo/foo.properties","source":{"foo":"bar"}} ]}
定位资源的默认策略是克隆一个git仓库(在spring.cloud.config.server.git.uri
),并使用它来初始化一个迷你SpringApplication
。小应用程序的Environment
用于枚举属性源并通过JSON端点发布。
HTTP服务具有以下格式的资源:
/{application}/{profile}[/{label}] /{application}-{profile}.yml /{label}/{application}-{profile}.yml /{application}-{profile}.properties /{label}/{application}-{profile}.properties
其中“应用程序”作为SpringApplication
中的spring.config.name
注入(即常规的Spring Boot应用程序中通常是“应用程序”),“配置文件”是活动配置文件(或逗号分隔列表的属性),“label”是可选的git标签(默认为“master”)。
Spring Cloud Config服务器从git存储库(必须提供)为远程客户端提供配置:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
客户端使用
要在应用程序中使用这些功能,只需将其构建为依赖于spring-cloud-config-client的Spring引导应用程序(例如,查看配置客户端或示例应用程序的测试用例)。添加依赖关系的最方便的方法是通过Spring Boot启动器org.springframework.cloud:spring-cloud-starter-config
。还有一个Maven用户的父pom和BOM(spring-cloud-starter-parent
)和用于Gradle和Spring CLI用户的Spring IO版本管理属性文件。示例Maven配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<!-- repositories also needed for snapshots and milestones -->
那么你可以创建一个标准的Spring Boot应用程序,像这个简单的HTTP服务器:
@SpringBootApplication @RestController public class Application {@RequestMapping("/") public String home() { return "Hello World!"; } public static void main(String[] args) { SpringApplication.run(Application.class, args); }
}
当它运行它将从端口8888上的默认本地配置服务器接收外部配置,如果它正在运行。要修改启动行为,您可以使用bootstrap.properties
(如application.properties
)更改配置服务器的位置,但用于应用程序上下文的引导阶段),例如
spring.cloud.config.uri: http://myconfigserver.com
引导属性将在/env
端点中显示为高优先级属性源,例如
$ curl localhost:8080/env { "profiles":[], "configService:https://github.com/spring-cloud-samples/config-repo/bar.properties":{"foo":"bar"}, "servletContextInitParams":{}, "systemProperties":{...}, ... }
(名为“configService:<远程存储库的URL> / <文件名>”的属性源包含值为“bar”的属性“foo”,是最高优先级)。
注意
| 属性源名称中的URL是git存储库,而不是配置服务器URL。 |
Spring Cloud Config服务器
服务器为外部配置(名称值对或等效的YAML内容)提供了基于资源的HTTP。服务器可以使用@EnableConfigServer
注释轻松嵌入到Spring Boot应用程序中。所以这个应用程序是一个配置服务器:
@SpringBootApplication
@EnableConfigServer
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}
像所有的默认端口8080上运行的所有Spring Boot应用程序一样,但您可以通过各种方式将其切换到常规端口8888。最简单的也是设置一个默认配置库,它是通过启动它的spring.config.name=configserver
(在Config Server jar中有一个configserver.yml
)。另一个是使用你自己的application.properties
,例如
server.port: 8888
spring.cloud.config.server.git.uri: file://${user.home}/config-repo
其中${user.home}/config-repo
是包含YAML和属性文件的git仓库。
注意
| 在Windows中,如果文件URL为绝对驱动器前缀,例如file:///${user.home}/config-repo ,则需要额外的“/”。 |
提示
|
以下是上面示例中创建git仓库的方法: $ cd $HOME $ mkdir config-repo $ cd config-repo $ git init . $ echo info.foo: bar > application.properties $ git add -A . $ git commit -m "Add application.properties" |
警告
| 使用本地文件系统进行git存储库仅用于测试。使用服务器在生产环境中托管配置库。 |
警告
| 如果您只保留文本文件,则配置库的初始克隆将会快速有效。如果您开始存储二进制文件,尤其是较大的文件,则可能会遇到服务器中第一个配置请求和/或内存不足错误的延迟。 |
环境库
您要在哪里存储配置服务器的配置数据?管理此行为的策略是EnvironmentRepository
,服务于Environment
对象。此Environment
是Spring Environment
(包括propertySources
作为主要功能)的域的浅层副本。Environment
资源由三个变量参数化:
-
{application}
映射到客户端的“spring.application.name”; -
{profile}
映射到客户端上的“spring.profiles.active”(逗号分隔列表); 和 -
{label}
这是一个服务器端功能,标记“版本”的配置文件集。
存储库实现通常表现得像一个Spring Boot应用程序从“spring.config.name”等于{application}
参数加载配置文件,“spring.profiles.active”等于{profiles}
参数。配置文件的优先级规则也与常规启动应用程序相同:活动配置文件优先于默认配置,如果有多个配置文件,则最后一个获胜(例如向Map
添加条目)。
示例:客户端应用程序具有此引导配置:
spring:
application:
name: foo
profiles:
active: dev,mysql
(通常使用Spring Boot应用程序,这些属性也可以设置为环境变量或命令行参数)。
如果存储库是基于文件的,则服务器将从application.yml
创建Environment
(在所有客户端之间共享),foo.yml
(以foo.yml
优先))。如果YAML文件中有文件指向Spring配置文件,那么应用的优先级更高(按照列出的配置文件的顺序),并且如果存在特定于配置文件的YAML(或属性)文件,那么这些文件也应用于优先级高于默认值。较高优先级转换为Environment
之前列出的PropertySource
。(这些规则与独立的Spring Boot应用程序相同。)
Git后端
EnvironmentRepository
的默认实现使用Git后端,这对于管理升级和物理环境以及审核更改非常方便。要更改存储库的位置,可以在Config Server中设置“spring.cloud.config.server.git.uri”配置属性(例如application.yml
)。如果您使用file:
前缀进行设置,则应从本地存储库中工作,以便在没有服务器的情况下快速方便地启动,但在这种情况下,服务器将直接在本地存储库上进行操作,而不会克隆如果它不是裸机,因为配置服务器永远不会更改“远程”资源库)。要扩展Config Server并使其高度可用,您需要将服务器的所有实例指向同一个存储库,因此只有共享文件系统才能正常工作。即使在这种情况下,最好使用共享文件系统存储库的ssh:
协议,以便服务器可以将其克隆并使用本地工作副本作为缓存。
该存储库实现将HTTP资源的{label}
参数映射到git标签(提交ID,分支名称或标签)。如果git分支或标签名称包含斜杠(“/”),则应使用特殊字符串“(_)”指定HTTP URL中的标签,以避免与其他URL路径模糊。例如,如果标签为foo/bar
,则替换斜杠将导致标签看起来像foo(_)bar
。如果您使用像curl这样的命令行客户端(例如使用引号将其从shell中转出来),请小心URL中的方括号。
Git URI中的占位符
Spring Cloud Config服务器支持一个Git仓库URL,其中包含{application}
和{profile}
(以及{label}
)的占位符,如果需要,请记住标签应用为git标签)。因此,您可以使用(例如)轻松支持“每个应用程序的一个repo”策略:
spring:
cloud:
config:
server:
git:
uri: https://github.com/myorg/{application}
或使用类似模式但使用{profile}
的“每个配置文件一个”策略。
模式匹配和多个存储库
还可以通过应用程序和配置文件名称的模式匹配来支持更复杂的需求。模式格式是带有通配符的{application}/{profile}
名称的逗号分隔列表(可能需要引用以通配符开头的模式)。例:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
repos:
simple: https://github.com/simple/config-repo
special:
pattern: special*/dev*,*special*/dev*
uri: https://github.com/special/config-repo
local:
pattern: local*
uri: file:/home/configsvc/config-repo
如果{application}/{profile}
不匹配任何模式,它将使用在“spring.cloud.config.server.git.uri”下定义的默认uri。在上面的例子中,对于“简单”存储库,模式是simple/*
(即所有配置文件中只匹配一个名为“简单”的应用程序)。“本地”存储库与所有配置文件中以“local”开头的所有应用程序名称匹配(将/*
后缀自动添加到任何没有配置文件匹配器的模式)。
注意
| 在上述“简单”示例中使用的“单行”快捷方式只能在唯一要设置的属性为URI的情况下使用。如果您需要设置其他任何内容(凭据,模式等),则需要使用完整的表单。 |
repo中的pattern
属性实际上是一个数组,因此您可以使用属性文件中的YAML数组(或[0]
,[1]
等后缀)绑定到多个模式。如果要运行具有多个配置文件的应用程序,则可能需要执行此操作。例:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
repos:
development:
pattern:
- */development
- */staging
uri: https://github.com/development/config-repo
staging:
pattern:
- */qa
- */production
uri: https://github.com/staging/config-repo
注意
| Spring Cloud将猜测包含不在* 中的配置文件的模式意味着您实际上要匹配从此模式开始的配置文件列表(因此*/staging 是["*/staging", "*/staging,*"] )。这是常见的,您需要在本地的“开发”配置文件中运行应用程序,但也可以远程运行“云”配置文件。 |
每个存储库还可以选择将配置文件存储在子目录中,搜索这些目录的模式可以指定为searchPaths
。例如在顶层:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
searchPaths: foo,bar*
在此示例中,服务器搜索顶级和“foo /”子目录以及名称以“bar”开头的任何子目录中的配置文件。
默认情况下,首次请求配置时,服务器克隆远程存储库。服务器可以配置为在启动时克隆存储库。例如在顶层:
spring:
cloud:
config:
server:
git:
uri: https://git/common/config-repo.git
repos:
team-a:
pattern: team-a-*
cloneOnStart: true
uri: http://git/team-a/config-repo.git
team-b:
pattern: team-b-*
cloneOnStart: false
uri: http://git/team-b/config-repo.git
team-c:
pattern: team-c-*
uri: http://git/team-a/config-repo.git
在此示例中,服务器在启动之前克隆了team-a的config-repo,然后它接受任何请求。所有其他存储库将不被克隆,直到请求从存储库配置。
注意
| 在配置服务器启动时设置要克隆的存储库可以帮助在配置服务器启动时快速识别错误配置的源(例如,无效的存储库URI)。配置源不启用cloneOnStart 时,配置服务器可能会成功启动配置错误或无效的配置源,而不会检测到错误,直到应用程序从该配置源请求配置为止。 |
认证
要在远程存储库上使用HTTP基本身份验证,请分别添加“username”和“password”属性(不在URL中),例如
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
username: trolley
password: strongpassword
如果您不使用HTTPS和用户凭据,当您将密钥存储在默认目录(~/.ssh
)中,并且uri指向SSH位置时,SSH也应该开箱即用,例如“ git@github.com:配置/云配置”。必须在~/.ssh/known_hosts
文件中存在Git服务器的条目,并且它是ssh-rsa
格式。其他格式(如ecdsa-sha2-nistp256
)不受支持。为了避免意外,您应该确保Git服务器的known_hosts
文件中只有一个条目,并且与您提供给配置服务器的URL匹配。如果您在URL中使用了主机名,那么您希望在known_hosts
文件中具有这一点,而不是IP。使用JGit访问存储库,因此您发现的任何文档都应适用。HTTPS代理设置可以~/.git/config
设置,也可以通过系统属性(-Dhttps.proxyHost
和-Dhttps.proxyPort
)与任何其他JVM进程相同。
提示
| 如果您不知道~/.git 目录使用git config --global 来处理设置的位置(例如git config --global http.sslVerify false )。 |
使用AWS CodeCommit进行认证
AWS CodeCommit认证也可以完成。当从命令行使用Git时,AWS CodeCommit使用身份验证助手。该帮助器不与JGit库一起使用,因此如果Git URI与AWS CodeCommit模式匹配,则将创建用于AWS CodeCommit的JGit CredentialProvider。AWS CodeCommit URI始终看起来像 https://git-codecommit.$ {AWS_REGION} .amazonaws.com / $ {repopath}。
如果您使用AWS CodeCommit URI提供用户名和密码,那么这些URI必须 是用于访问存储库的AWS accessKeyId和 secretAccessKey。如果不指定用户名和密码,则将使用AWS默认凭据提供程序链检索accessKeyId和secretAccessKey 。
如果您的Git URI与CodeCommit URI模式(上述)匹配,则必须在用户名和密码或默认凭据提供程序链支持的某个位置中提供有效的AWS凭据。AWS EC2实例可以使用EC2实例的 IAM角色。
注意:aws-java-sdk-core jar是一个可选的依赖关系。如果aws-java-sdk-core jar不在您的类路径上,则无论git服务器URI如何,都将不会创建AWS代码提交凭据提供程序。
Git搜索路径中的占位符
Spring Cloud Config服务器还支持具有{application}
和{profile}
(以及{label}
(如果需要))占位符的搜索路径。例:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
searchPaths: '{application}'
在资源库中搜索与目录(以及顶级)相同名称的文件。通配符在具有占位符的搜索路径中也是有效的(搜索中包含任何匹配的目录)。
力拉入Git存储库
如前所述Spring Cloud Config服务器克隆远程git存储库,如果某种方式本地副本变脏(例如,通过操作系统进程更改文件夹内容),则Spring Cloud Config服务器无法从远程存储库更新本地副本。
要解决这个问题,有一个force-pull
属性,如果本地副本是脏的,将使Spring Cloud Config Server强制从远程存储库拉。例:
spring:
cloud:
config:
server:
git:
uri: https://github.com/spring-cloud-samples/config-repo
force-pull: true
如果您有多个存储库配置,则可以为每个存储库配置force-pull
属性。例:
spring:
cloud:
config:
server:
git:
uri: https://git/common/config-repo.git
force-pull: true
repos:
team-a:
pattern: team-a-*
uri: http://git/team-a/config-repo.git
force-pull: true
team-b:
pattern: team-b-*
uri: http://git/team-b/config-repo.git
force-pull: true
team-c:
pattern: team-c-*
uri: http://git/team-a/config-repo.git
注意
| force-pull 属性的默认值为false 。 |
版本控制后端文件系统使用
警告
| 使用基于VCS的后端(git,svn)文件被检出或克隆到本地文件系统。默认情况下,它们放在系统临时目录中,前缀为config-repo- 。在linux上,例如可以是/tmp/config-repo-<randomid> 。一些操作系统会定期清除临时目录。这可能会导致意外的行为,例如缺少属性。为避免此问题,请通过将spring.cloud.config.server.git.basedir 或spring.cloud.config.server.svn.basedir 设置为不驻留在系统临时结构中的目录来更改Config Server使用的目录。 |
文件系统后端
配置服务器中还有一个不使用Git的“本机”配置文件,只是从本地类路径或文件系统加载配置文件(您想要指向的任何静态URL“spring.cloud.config.server .native.searchLocations“)。要使用本机配置文件,只需使用“spring.profiles.active = native”启动Config Server。
注意
| 请记住使用file: 前缀的文件资源(缺省没有前缀通常是classpath)。与任何Spring Boot配置一样,您可以嵌入${} 样式的环境占位符,但请记住,Windows中的绝对路径需要额外的“/”,例如file:///${user.home}/config-repo |
警告
| searchLocations 的默认值与本地Spring Boot应用程序(所以[classpath:/, classpath:/config, file:./, file:./config] )相同。这不会将application.properties 从服务器暴露给所有客户端,因为在发送到客户端之前,服务器中存在的任何属性源都将被删除。 |
提示
| 文件系统后端对于快速入门和测试是非常好的。要在生产中使用它,您需要确保文件系统是可靠的,并在配置服务器的所有实例中共享。 |
搜索位置可以包含{application}
,{profile}
和{label}
的占位符。以这种方式,您可以隔离路径中的目录,并选择一个有用的策略(例如每个应用程序的子目录或每个配置文件的子目录)。
如果您不在搜索位置使用占位符,则该存储库还将HTTP资源的{label}
参数附加到搜索路径上的后缀,因此属性文件将从每个搜索位置加载并具有相同名称的子目录作为标签(标记的属性在Spring环境中优先)。因此,没有占位符的默认行为与添加以/{label}/. For example `file:/tmp/config
结尾的搜索位置与file:/tmp/config,file:/tmp/config/{label}
相同
Vault后端
Spring Cloud Config服务器还支持Vault作为后端。
有关Vault的更多信息,请参阅Vault快速入门指南。
要使配置服务器使用Vault后端,您必须使用vault
配置文件运行配置服务器。例如在配置服务器的application.properties
中,您可以添加spring.profiles.active=vault
。
默认情况下,配置服务器将假定您的Vault服务器正在运行于http://127.0.0.1:8200
。它还将假定后端名称为secret
,密钥为application
。所有这些默认值都可以在配置服务器的application.properties
中配置。以下是可配置Vault属性的表。所有属性前缀为spring.cloud.config.server.vault
。
名称 | 默认值 |
---|---|
host | 127.0.0.1 |
port | 8200 |
scheme | HTTP |
backend | 秘密 |
defaultKey | 应用 |
profileSeparator | , |
所有可配置的属性可以在org.springframework.cloud.config.server.environment.VaultEnvironmentRepository
找到。
运行配置服务器后,可以向服务器发出HTTP请求,以从Vault后端检索值。为此,您需要为Vault服务器创建一个令牌。
首先放置一些数据给你Vault。例如
$ vault write secret/application foo=bar baz=bam
$ vault write secret/myapp foo=myappsbar
现在,将HTTP请求发送给您的配置服务器以检索值。
$ curl -X "GET" "http://localhost:8888/myapp/default" -H "X-Config-Token: yourtoken"
在提出上述要求后,您应该会看到类似的回复。
{
"name":"myapp",
"profiles":[
"default"
],
"label":null,
"version":null,
"state":null,
"propertySources":[
{
"name":"vault:myapp",
"source":{
"foo":"myappsbar"
}
},
{
"name":"vault:application",
"source":{
"baz":"bam",
"foo":"bar"
}
}
]
}
多个Properties来源
使用Vault时,您可以为应用程序提供多个属性源。例如,假设您已将数据写入Vault中的以下路径。
secret/myApp,dev
secret/myApp
secret/application,dev
secret/application
写入secret/application
的Properties可 用于使用配置服务器的所有应用程序。名称为myApp
的应用程序将具有写入secret/myApp
和secret/application
的任何属性。当myApp
启用dev
配置文件时,写入所有上述路径的属性将可用,列表中第一个路径中的属性优先于其他路径。
与所有应用共享配置
基于文件的存储库
使用基于文件(即git,svn和native)的存储库,文件名为application*
的资源在所有客户端应用程序(所以application.properties
,application.yml
,application-*.properties
等)之间共享)。您可以使用这些文件名的资源来配置全局默认值,并根据需要将其覆盖应用程序特定的文件。
#_property_overrides [属性覆盖]功能也可用于设置全局默认值,并且允许占位符应用程序在本地覆盖它们。
提示
| 使用“本机”配置文件(本地文件系统后端),建议您使用不属于服务器自身配置的显式搜索位置。否则,默认搜索位置中的application* 资源将被删除,因为它们是服务器的一部分。 |
Vault服务器
当使用Vault作为后端时,可以通过将配置放在secret/application
中与所有应用程序共享配置。例如,如果您运行此Vault命令
$ vault write secret/application foo=bar baz=bam
使用配置服务器的所有应用程序都可以使用属性foo
和baz
。
复合环境库
在某些情况下,您可能希望从多个环境存储库中提取配置数据。为此,只需在配置服务器的应用程序属性或YAML文件中启用多个配置文件即可。例如,如果您要从Git存储库以及SVN存储库中提取配置数据,那么您将为配置服务器设置以下属性。
spring:
profiles:
active: git, svn
cloud:
config:
server:
svn:
uri: file:///path/to/svn/repo
order: 2
git:
uri: file:///path/to/git/repo
order: 1
除了指定URI的每个repo之外,还可以指定order
属性。order
属性允许您指定所有存储库的优先级顺序。order
属性的数值越低,优先级越高。存储库的优先顺序将有助于解决包含相同属性的值的存储库之间的任何潜在冲突。
注意
| 从环境仓库检索值时的任何类型的故障将导致整个复合环境的故障。 |
注意
| 当使用复合环境时,重要的是所有repos都包含相同的标签。如果您有类似于上述的环境,并且使用标签master 请求配置数据,但是SVN repo不包含称为master 的分支,则整个请求将失败。 |
自定义复合环境库
除了使用来自Spring Cloud的环境存储库之外,还可以提供自己的EnvironmentRepository
bean作为复合环境的一部分。要做到这一点,你的bean必须实现EnvironmentRepository
接口。如果要在复合环境中控制自定义EnvironmentRepository
的优先级,您还应该实现Ordered
接口并覆盖getOrdered
方法。如果您不实现Ordered
接口,那么您的EnvironmentRepository
将被赋予最低优先级。
属性覆盖
配置服务器具有“覆盖”功能,允许操作员为应用程序使用普通的Spring Boot钩子不会意外更改的所有应用程序提供配置属性。要声明覆盖,只需将名称/值对的地图添加到spring.cloud.config.server.overrides
。例如
spring:
cloud:
config:
server:
overrides:
foo: bar
将导致配置客户端的所有应用程序独立于自己的配置读取foo=bar
。(当然,应用程序可以以任何方式使用Config Server中的数据,因此覆盖不可强制执行,但如果它们是Spring Cloud Config客户端,则它们确实提供有用的默认行为。)
提示
| 通过使用反斜杠(“\”)来转义“$”或“{”,例如\${app.foo:bar} 解析,可以转义正常的Spring具有“$ {}”的环境占位符到“bar”,除非应用程序提供自己的“app.foo”。请注意,在YAML中,您不需要转义反斜杠本身,而是在您执行的属性文件中配置服务器上的覆盖。 |
您可以通过在远程存储库中设置标志spring.cloud.config.overrideNone=true
(默认为false),将客户端中所有覆盖的优先级更改为更为默认值,允许应用程序在环境变量或系统属性中提供自己的值。
健康指标
配置服务器附带运行状况指示器,检查配置的EnvironmentRepository
是否正常工作。默认情况下,它要求EnvironmentRepository
应用程序名称为app
,default
配置文件和EnvironmentRepository
实现提供的默认标签。
您可以配置运行状况指示器以检查更多应用程序以及自定义配置文件和自定义标签,例如
spring:
cloud:
config:
server:
health:
repositories:
myservice:
label: mylabel
myservice-dev:
name: myservice
profiles: development
您可以通过设置spring.cloud.config.server.health.enabled=false
来禁用运行状况指示器。
安全
您可以以任何对您有意义的方式(从物理网络安全性到OAuth2承载令牌)保护您的Config Server,并且Spring Security和Spring Boot可以轻松做任何事情。
要使用默认的Spring Boot配置的HTTP Basic安全性,只需在类路径中包含Spring Security(例如通过spring-boot-starter-security
)。默认值为“user”的用户名和随机生成的密码,这在实践中不会非常有用,因此建议您配置密码(通过security.user.password
)并对其进行加密(请参阅下文的说明怎么做)。
加密和解密
重要
| 先决条件:要使用加密和解密功能,您需要在JVM中安装全面的JCE(默认情况下不存在)。您可以从Oracle下载“Java加密扩展(JCE)无限强度管理策略文件”,并按照安装说明(实际上将JRE lib / security目录中的2个策略文件替换为您下载的文件)。 |
如果远程属性源包含加密内容(以{cipher}
开头的值),则在通过HTTP发送到客户端之前,它们将被解密。这种设置的主要优点是,当它们“静止”时,属性值不必是纯文本(例如在git仓库中)。如果值无法解密,则从属性源中删除该值,并添加具有相同键的附加属性,但以“无效”作为前缀。和“不适用”的值(通常为“<n / a>”)。这主要是为了防止密码被用作密码并意外泄漏。
如果要为config客户端应用程序设置远程配置存储库,可能会包含一个application.yml
,例如:
spring:
datasource:
username: dbuser
password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'
.properties文件中的加密值不能用引号括起来,否则不会解密该值:
spring.datasource.username: dbuser spring.datasource.password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ
您可以安全地将此纯文本推送到共享git存储库,并且保密密码。
服务器还暴露了/encrypt
和/decrypt
端点(假设这些端点将被保护,并且只能由授权代理访问)。如果您正在编辑远程配置文件,可以使用Config Server通过POST到/encrypt
端点来加密值,例如
$ curl localhost:8888/encrypt -d mysecret 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
注意
| 如果要加密的值具有需要进行URL编码的字符,则应使用--data-urlencode 选项curl 来确保它们已正确编码。 |
逆向操作也可通过/decrypt
获得(如果服务器配置了对称密钥或全密钥对):
$ curl localhost:8888/decrypt -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda mysecret
提示
| 如果您使用curl进行测试,则使用--data-urlencode (而不是-d )或设置显式Content-Type: text/plain ,以确保在有特殊字符时正确地对数据进行编码('+'特别是棘手)。 |
将加密的值添加到{cipher}
前缀,然后再将其放入YAML或属性文件中,然后再提交并将其推送到远程可能不安全的存储区。
/encrypt
和/decrypt
端点也都接受/*/{name}/{profiles}
形式的路径,当客户端调用到主环境资源时,可以用于每个应用程序(名称)和配置文件控制密码。
注意
| 为了以这种细微的方式控制密码,您还必须提供一种TextEncryptorLocator 类型的@Bean ,可以为每个名称和配置文件创建不同的加密器。默认提供的不会这样做(所有加密使用相同的密钥)。 |
spring
命令行客户端(安装了Spring Cloud CLI扩展)也可以用于加密和解密,例如
$ spring encrypt mysecret --key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda $ spring decrypt --key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda mysecret
要在文件中使用密钥(例如用于加密的RSA公钥),使用“@”键入键值,并提供文件路径,例如
$ spring encrypt mysecret --key @${HOME}/.ssh/id_rsa.pub AQAjPgt3eFZQXwt8tsHAVv/QHiY5sI2dRcR+...
关键参数是强制性的(尽管有一个--
前缀)。
密钥管理
Config Server可以使用对称(共享)密钥或非对称密钥(RSA密钥对)。非对称选择在安全性方面是优越的,但是使用对称密钥往往更方便,因为它只是配置的一个属性值。
要配置对称密钥,您只需要将encrypt.key
设置为一个秘密字符串(或使用环境变量ENCRYPT_KEY
将其从纯文本配置文件中删除)。
要配置非对称密钥,您可以将密钥设置为PEM编码的文本值(encrypt.key
),也可以通过密钥库设置密钥(例如由JDK附带的keytool
实用程序创建)。密钥库属性为encrypt.keyStore.*
,*
等于
-
location
(aResource
位置), -
password
(解锁密钥库)和 -
alias
(以识别商店中使用的密钥)。
使用公钥进行加密,需要私钥进行解密。因此,原则上您只能在服务器中配置公钥,如果您只想进行加密(并准备使用私钥本地解密值)。实际上,您可能不想这样做,因为它围绕所有客户端传播密钥管理流程,而不是将其集中在服务器中。另一方面,如果您的配置服务器真的相对不安全,并且只有少数客户端需要加密的属性,这是一个有用的选项。
创建用于测试的密钥库
要创建一个密钥库进行测试,您可以执行以下操作:
$ keytool -genkeypair -alias mytestkey -keyalg RSA \ -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" \ -keypass changeme -keystore server.jks -storepass letmein
将server.jks
文件放在类路径(例如)中,然后在您的application.yml
中配置服务器:
encrypt:
keyStore:
location: classpath:/server.jks
password: letmein
alias: mytestkey
secret: changeme
使用多个键和键旋转
除了加密属性值中的{cipher}
前缀之外,配置服务器在(Base64编码)密文开始前查找{name:value}
前缀(零或多个)。密钥被传递给TextEncryptorLocator
,它可以执行找到密码的TextEncryptor
所需的任何逻辑。如果配置了密钥库(encrypt.keystore.location
),默认定位器将使用“key”前缀提供的别名,即使用如下密码查找存储中的密钥:
foo:
bar: `{cipher}{key:testkey}...`
定位器将寻找一个名为“testkey”的键。也可以通过前缀中的{secret:…}
值提供一个秘密,但是如果不是默认值,则使用密钥库密码(这是您在构建密钥库时获得的,并且不指定密码)。如果你这样做 提供一个秘密建议你也加密使用自定义SecretLocator
的秘密。
如果密钥只用于加密几个字节的配置数据(即它们没有在其他地方使用),则密码转换几乎不是必需的,但是如果存在安全漏洞,有时您可能需要更改密钥实例。在这种情况下,所有客户端都需要更改其源配置文件(例如,以git格式),并在所有密码中使用新的{key:…}
前缀,当然事先检查密钥别名在配置服务器密钥库中是否可用。
提示
| 如果要让Config Server处理所有加密以及解密,也可以将{name:value} 前缀添加到发布到/encrypt 端点的明文中。 |
服务加密Properties
有时您希望客户端在本地解密配置,而不是在服务器中进行配置。在这种情况下,您仍然可以拥有/加密和解密端点(如果您提供encrypt.*
配置来定位密钥),但是您需要使用spring.cloud.config.server.encrypt.enabled=false
明确地关闭传出属性的解密。如果您不关心端点,那么如果您既不配置密钥也不配置使能的标志,则应该起作用。
服务替代格式
来自环境端点的默认JSON格式对于Spring应用程序的消费是完美的,因为它直接映射到Environment
抽象。如果您喜欢,可以通过向资源路径(“.yml”,“.yaml”或“.properties”)添加后缀来使用与YAML或Java属性相同的数据。这对于不关心JSON端点的结构的应用程序或其提供的额外的元数据的应用程序来说可能是有用的,例如,不使用Spring的应用程序可能会受益于此方法的简单性。
YAML和属性表示有一个额外的标志(作为一个布尔查询参数resolvePlaceholders
提供)),以标示Spring ${…}
形式的源文档中的占位符,应在输出中解析可能在渲染之前。对于不了解Spring占位符惯例的消费者来说,这是一个有用的功能。
注意
| 使用YAML或属性格式存在局限性,主要是与元数据的丢失有关。JSON被构造为属性源的有序列表,例如,名称与源相关联。即使源的起源具有多个源,并且原始源文件的名称丢失,YAML和属性表也合并成一个映射。YAML表示不一定是后台存储库中YAML源的忠实表示:它是由平面属性源的列表构建的,并且必须对键的形式进行假设。 |
服务纯文本
您的应用程序可能需要通用的纯文本配置文件,而不是使用Environment
抽象(或YAML中的其他替代表示形式或属性格式)。配置服务器通过/{name}/{profile}/{label}/{path}
附加的端点提供这些服务,其中“name”,“profile”和“label”的含义与常规环境端点相同,但“path”是文件名(例如log.xml
)。此端点的源文件位于与环境端点相同的方式:与属性或YAML文件相同的搜索路径,而不是聚合所有匹配的资源,只返回匹配的第一个。
找到资源后,使用正确格式(${…}
)的占位符将使用有效的Environment
解析为应用程序名称,配置文件和标签提供。以这种方式,资源端点与环境端点紧密集成。例如,如果您有一个GIT(或SVN)资源库的布局:
application.yml nginx.conf
其中nginx.conf
如下所示:
server { listen 80; server_name ${nginx.server.name}; }
和application.yml
这样:
nginx:
server:
name: example.com
---
spring:
profiles: development
nginx:
server:
name: develop.com
那么/foo/default/master/nginx.conf
资源如下所示:
server { listen 80; server_name example.com; }
和/foo/development/master/nginx.conf
这样:
server { listen 80; server_name develop.com; }
注意
| 就像环境配置的源文件一样,“配置文件”用于解析文件名,因此,如果您想要一个特定于配置文件的文件,则/*/development/*/logback.xml 将由一个名为logback-development.xml 的文件解析(优先于logback.xml )。 |
嵌入配置服务器
配置服务器最好作为独立应用程序运行,但如果需要,可以将其嵌入到另一个应用程序中。只需使用@EnableConfigServer
注释。在这种情况下可以使用的可选属性是spring.cloud.config.server.bootstrap
,它是一个标志,表示服务器应该从其自己的远程存储库配置自身。该标志默认关闭,因为它可能会延迟启动,但是当嵌入在另一个应用程序中时,以与其他应用程序相同的方式初始化是有意义的。
注意
| 应该是显而易见的,但请记住,如果您使用引导标志,配置服务器将需要在bootstrap.yml 中配置其名称和存储库URI。 |
要更改服务器端点的位置,您可以(可选)设置spring.cloud.config.server.prefix
,例如“/ config”,以提供前缀下的资源。前缀应该开始但不以“/”结尾。它被应用于配置服务器中的@RequestMappings
(即Spring Boot前缀server.servletPath
和server.contextPath
)之下。
如果您想直接从后端存储库(而不是从配置服务器)读取应用程序的配置,这基本上是一个没有端点的嵌入式配置服务器。如果不使用@EnableConfigServer
注释(仅设置spring.cloud.config.server.bootstrap=true
),则可以完全关闭端点。
推送通知和Spring Cloud Bus
许多源代码存储库提供程序(例如Github,Gitlab或Bitbucket)将通过webhook通知您存储库中的更改。您可以通过提供商的用户界面将webhook配置为URL和一组感兴趣的事件。例如, Github 将使用包含提交列表的JSON主体和“X-Github-Event”等于“push”的头文件发送到webhook。如果在spring-cloud-config-monitor
库中添加依赖关系并激活配置服务器中的Spring Cloud Bus,则启用“/ monitor”端点。
当Webhook被激活时,配置服务器将发送一个RefreshRemoteApplicationEvent
针对他认为可能已经改变的应用程序。变更检测可以进行策略化,但默认情况下,它只是查找与应用程序名称匹配的文件的更改(例如,“foo.properties”针对的是“foo”应用程序,“application.properties”针对所有应用程序) 。如果要覆盖该行为的策略是PropertyPathNotificationExtractor
,它接受请求标头和正文作为参数,并返回更改的文件路径列表。
默认配置与Github,Gitlab或Bitbucket配合使用。除了来自Github,Gitlab或Bitbucket的JSON通知之外,您还可以通过使用表单编码的身体参数path={name}
通过POST为“/ monitor”来触发更改通知。这将广播到匹配“{name}”模式的应用程序(可以包含通配符)。
注意
| 只有在配置服务器和客户端应用程序中激活spring-cloud-bus 时才会传送RefreshRemoteApplicationEvent 。 |
注意
| 默认配置还检测本地git存储库中的文件系统更改(在这种情况下不使用webhook,但是一旦编辑配置文件,将会播放刷新)。 |
Spring Cloud Config客户端
Spring Boot应用程序可以立即利用Spring配置服务器(或应用程序开发人员提供的其他外部属性源),并且还将获取与Environment
更改事件相关的一些其他有用功能。
配置第一引导
这是在类路径上具有Spring Cloud Config Client的任何应用程序的默认行为。配置客户端启动时,它将通过配置服务器(通过引导配置属性spring.cloud.config.uri
)绑定,并使用远程属性源初始化Spring Environment
。
这样做的最终结果是所有想要使用Config Server的客户端应用程序需要bootstrap.yml
(或环境变量),服务器地址位于spring.cloud.config.uri
(默认为“http:// localhost:8888” )。
发现第一个引导
如果您正在使用DiscoveryClient实现,例如Spring Cloud Netflix和Eureka服务发现或Spring Cloud Consul(Spring Cloud Zookeeper不支持此功能),那么您可以使用Config Server如果您想要发现服务注册,但在默认的“配置优先”模式下,客户端将无法利用注册。
如果您希望使用DiscoveryClient
找到配置服务器,可以通过设置spring.cloud.config.discovery.enabled=true
(默认为“false”)来实现。最终的结果是,客户端应用程序都需要具有适当发现配置的bootstrap.yml
(或环境变量)。例如,使用Spring Cloud Netflix,您需要定义Eureka服务器地址,例如eureka.client.serviceUrl.defaultZone
。使用此选项的价格是启动时额外的网络往返,以定位服务注册。好处是配置服务器可以更改其坐标,只要发现服务是一个固定点。默认的服务标识是“configserver”,但您可以使用spring.cloud.config.discovery.serviceId
在客户端进行更改(在服务器上以服务的通常方式更改,例如设置spring.application.name
)。
发现客户端实现都支持某种元数据映射(例如Eureka,我们有eureka.instance.metadataMap
)。可能需要在其服务注册元数据中配置Config Server的一些其他属性,以便客户端可以正确连接。如果使用HTTP Basic安全配置服务器,则可以将凭据配置为“用户名”和“密码”。并且如果配置服务器具有上下文路径,您可以设置“configPath”。例如,对于作为Eureka客户端的配置服务器:
eureka:
instance:
...
metadataMap:
user: osufhalskjrtl
password: lviuhlszvaorhvlo5847
configPath: /config
配置客户端快速失败
在某些情况下,如果服务无法连接到配置服务器,则可能希望启动服务失败。如果这是所需的行为,请设置引导配置属性spring.cloud.config.failFast=true
,客户端将以异常停止。
配置客户端重试
如果您希望配置服务器在您的应用程序启动时可能偶尔不可用,您可以要求它在发生故障后继续尝试。首先,您需要设置spring.cloud.config.failFast=true
,然后您需要添加spring-retry
和spring-boot-starter-aop
到您的类路径。默认行为是重试6次,初始退避间隔为1000ms,指数乘数为1.1,用于后续退避。您可以使用spring.cloud.config.retry.*
配置属性配置这些属性(和其他)。
提示
| 要完全控制重试,请使用ID“configServerRetryInterceptor”添加RetryOperationsInterceptor 类型的@Bean 。Spring重试有一个RetryInterceptorBuilder 可以轻松创建一个。 |
查找远程配置资源
配置服务从/{name}/{profile}/{label}
提供属性源,客户端应用程序中的默认绑定
-
“name”=
${spring.application.name}
-
“profile”=
${spring.profiles.active}
(实际上是Environment.getActiveProfiles()
) -
“label”=“master”
所有这些都可以通过设置spring.cloud.config.*
(其中*
是“name”,“profile”或“label”)来覆盖。“标签”可用于回滚到以前版本的配置; 使用默认的Config Server实现,它可以是git标签,分支名称或提交ID。标签也可以以逗号分隔的列表形式提供,在这种情况下,列表中的项目会逐个尝试,直到成功。例如,当您可能希望将配置标签与您的分支对齐,但使其成为可选(例如spring.cloud.config.label=myfeature,develop
)时,这对于在特征分支上工作时可能很有用。
安全
如果您在服务器上使用HTTP基本安全性,那么客户端只需要知道密码(如果不是默认用户名)。您可以通过配置服务器URI,或通过单独的用户名和密码属性,例如
spring:
cloud:
config:
uri: https://user:secret@myconfig.mycompany.com
要么
spring:
cloud:
config:
uri: https://myconfig.mycompany.com
username: user
password: secret
spring.cloud.config.password
和spring.cloud.config.username
值覆盖URI中提供的任何内容。
如果您在Cloud Foundry部署应用程序,则提供密码的最佳方式是通过服务凭证(例如URI),因为它甚至不需要在配置文件中。在Cloud Foundry上为本地工作的用户提供的服务的一个例子,名为“configserver”:
spring:
cloud:
config:
uri: ${vcap.services.configserver.credentials.uri:http://user:password@localhost:8888}
如果您使用另一种形式的安全性,则可能需要向ConfigServicePropertySourceLocator
提供RestTemplate
(例如,通过在引导上下文中获取它并注入一个)。ConfigServicePropertySourceLocator
提供{470 /}(例如通过在引导上下文中获取它并注入)。
健康指标
Config Client提供一个尝试从Config Server加载配置的Spring Boot运行状况指示器。可以通过设置health.config.enabled=false
来禁用运行状况指示器。由于性能原因,响应也被缓存。默认缓存生存时间为5分钟。要更改该值,请设置health.config.time-to-live
属性(以毫秒为单位)。
提供自定义RestTemplate
在某些情况下,您可能需要从客户端自定义对配置服务器的请求。通常这涉及传递特殊的Authorization
标头来对服务器的请求进行身份验证。要提供自定义RestTemplate
,请按照以下步骤操作。
-
设置
spring.cloud.config.enabled=false
以禁用现有的配置服务器属性源。 -
使用
PropertySourceLocator
实现创建一个新的配置bean。
@Configuration public class CustomConfigServiceBootstrapConfiguration { @Bean public ConfigClientProperties configClientProperties() { ConfigClientProperties client = new ConfigClientProperties(this.environment); client.setEnabled(false); return client; }
@Bean public ConfigServicePropertySourceLocator configServicePropertySourceLocator() { ConfigClientProperties clientProperties = configClientProperties(); ConfigServicePropertySourceLocator configServicePropertySourceLocator = new ConfigServicePropertySourceLocator(clientProperties); configServicePropertySourceLocator.setRestTemplate(customRestTemplate(clientProperties)); return configServicePropertySourceLocator; }
}
-
在
resources/META-INF
中创建一个名为spring.factories
的文件,并指定您的自定义配置。
org.springframework.cloud.bootstrap.BootstrapConfiguration = com.my.config.client.CustomConfigServiceBootstrapConfiguration
Vault
当使用Vault作为配置服务器的后端时,客户端将需要为服务器提供一个令牌,以从Vault中检索值。可以通过在bootstrap.yml
中设置spring.cloud.config.token
在客户端中提供此令牌。
spring:
cloud:
config:
token: YourVaultToken
Vault
Vault中的嵌套密钥
Vault支持将键嵌入存储在Vault中的值。例如
echo -n '{"appA": {"secret": "appAsecret"}, "bar": "baz"}' | vault write secret/myapp -
此命令将向您的Vault编写一个JSON对象。要在Spring中访问这些值,您将使用传统的点(。)注释。例如
@Value("${appA.secret}")
String name = "World";
上述代码将name
变量设置为appAsecret
。
Spring Cloud Netflix
Dalston.RELEASE
该项目通过自动配置为Spring Boot应用程序提供Netflix OSS集成,并绑定到Spring环境和其他Spring编程模型成语。通过几个简单的注释,您可以快速启用和配置应用程序中的常见模式,并通过经过测试的Netflix组件构建大型分布式系统。提供的模式包括服务发现(Eureka),断路器(Hystrix),智能路由(Zuul)和客户端负载平衡(Ribbon)。
服务发现:Eureka客户端
服务发现是基于微服务架构的关键原则之一。尝试配置每个客户端或某种形式的约定可能非常困难,可以非常脆弱。Netflix服务发现服务器和客户端是Eureka。可以将服务器配置和部署为高可用性,每个服务器将注册服务的状态复制到其他服务器。
如何包含Eureka客户端
要在您的项目中包含Eureka客户端,请使用组org.springframework.cloud
和工件ID spring-cloud-starter-eureka
的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面。
注册Eureka
当客户端注册Eureka时,它提供关于自身的元数据,例如主机和端口,健康指示符URL,主页等。Eureka从属于服务的每个实例接收心跳消息。如果心跳失败超过可配置的时间表,则通常将该实例从注册表中删除。
示例eureka客户端:
@Configuration @ComponentScan @EnableAutoConfiguration @EnableEurekaClient @RestController public class Application {
@RequestMapping("/") public String home() { return "Hello world"; } public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }
}
(即完全正常的Spring Boot应用程序)。在这个例子中,我们明确地使用@EnableEurekaClient
,但只有Eureka可用,你也可以使用@EnableDiscoveryClient
。需要配置才能找到Eureka服务器。例:
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/
其中“defaultZone”是一个魔术字符串后备值,为任何不表示首选项的客户端提供服务URL(即它是有用的默认值)。
从Environment
获取的默认应用程序名称(服务ID),虚拟主机和非安全端口分别为${spring.application.name}
,${spring.application.name}
和${server.port}
。
@EnableEurekaClient
将应用程序同时进入一个Eureka“实例”(即注册自己)和一个“客户端”(即它可以查询注册表以查找其他服务)。实例行为由eureka.instance.*
配置键驱动,但是如果您确保您的应用程序具有spring.application.name
(这是Eureka服务ID或VIP的默认值),那么默认值将是正常的。
有关可配置选项的更多详细信息,请参阅EurekaInstanceConfigBean和EurekaClientConfigBean。
使用Eureka服务器进行身份验证
如果其中一个eureka.client.serviceUrl.defaultZone
网址中包含一个凭据(如http://user:password@localhost:8761/eureka
)),HTTP基本身份验证将自动添加到您的eureka客户端。对于更复杂的需求,您可以创建DiscoveryClientOptionalArgs
类型的@Bean
,并将ClientFilter
实例注入到其中,所有这些都将应用于从客户端到服务器的调用。
注意
| 由于Eureka中的限制,不可能支持每个服务器的基本身份验证凭据,所以只能使用第一个找到的集合。 |
状态页和健康指标
Eureka实例的状态页面和运行状况指示器分别默认为“/ info”和“/ health”,它们是Spring Boot执行器应用程序中有用端点的默认位置。如果您使用非默认上下文路径或servlet路径(例如server.servletPath=/foo
)或管理端点路径(例如management.contextPath=/admin
),则需要更改这些,即使是执行器应用程序。例:
eureka: instance: statusPageUrlPath: ${management.context-path}/info healthCheckUrlPath: ${management.context-path}/health
这些链接显示在客户端使用的元数据中,并在某些情况下用于决定是否将请求发送到应用程序,因此如果它们是准确的,这是有帮助的。
注册安全应用程序
如果您的应用程序想通过HTTPS联系,则可以分别在EurekaInstanceConfig
,即 eureka.instance.[nonSecurePortEnabled,securePortEnabled]=[false,true]
中设置两个标志。这将使Eureka发布实例信息显示安全通信的明确偏好。Spring Cloud DiscoveryClient
将始终为以这种方式配置的服务返回一个https://…;
URI,并且Eureka(本机)实例信息将具有安全的健康检查URL。
由于Eureka内部的工作方式,它仍然会发布状态和主页的非安全网址,除非您也明确地覆盖。您可以使用占位符来配置eureka实例URL,例如
eureka: instance: statusPageUrl: https://${eureka.hostname}/info healthCheckUrl: https://${eureka.hostname}/health homePageUrl: https://${eureka.hostname}/
(请注意,${eureka.hostname}
是仅在稍后版本的Eureka中可用的本地占位符,您也可以使用Spring占位符实现同样的功能,例如使用${eureka.instance.hostName}
。
注意
| 如果您的应用程序在代理服务器后面运行,并且SSL终止服务在代理中(例如,如果您运行在Cloud Foundry或其他平台作为服务),则需要确保代理“转发”头部被截取并处理应用程序。Spring Boot应用程序中的嵌入式Tomcat容器会自动执行“X-Forwarded - \ *”标头的显式配置。你这个错误的一个迹象就是你的应用程序本身所呈现的链接是错误的(错误的主机,端口或协议)。 |
Eureka的健康检查
默认情况下,Eureka使用客户端心跳来确定客户端是否启动。除非另有规定,否则发现客户端将不会根据Spring Boot执行器传播应用程序的当前运行状况检查状态。这意味着成功注册后Eureka将永远宣布申请处于“UP”状态。通过启用Eureka运行状况检查可以改变此行为,从而将应用程序状态传播到Eureka。因此,每个其他应用程序将不会在“UP”之外的状态下将流量发送到应用程序。
eureka: client: healthcheck: enabled: true
警告
| eureka.client.healthcheck.enabled=true 只能在application.yml 中设置。设置bootstrap.yml 中的值将导致不期望的副作用,例如在具有UNKNOWN 状态的eureka中注册。 |
如果您需要更多的控制健康检查,您可以考虑实施自己的com.netflix.appinfo.HealthCheckHandler
。
Eureka实例和客户端的元数据
值得花点时间了解Eureka元数据的工作原理,以便您可以在平台上使用它。有主机名,IP地址,端口号,状态页和运行状况检查等标准元数据。这些发布在服务注册表中,由客户使用,以直接的方式联系服务。额外的元数据可以添加到eureka.instance.metadataMap
中的实例注册中,并且这将在远程客户端中可访问,但一般不会更改客户端的行为,除非意识到元数据的含义。下面描述了几个特殊情况,其中Spring Cloud已经为元数据映射指定了含义。
在Cloudfoundry上使用Eureka
Cloudfoundry有一个全局路由器,所以同一个应用程序的所有实例都具有相同的主机名(在具有相似架构的其他PaaS解决方案中也是如此)。这不一定是使用Eureka的障碍,但如果您使用路由器(建议,甚至是强制性的,具体取决于您的平台的设置方式),则需要明确设置主机名和端口号(安全或非安全),以便他们使用路由器。您可能还需要使用实例元数据,以便您可以区分客户端上的实例(例如,在自定义负载平衡器中)。默认情况下,eureka.instance.instanceId
为vcap.application.instance_id
。例如:
eureka: instance: hostname: ${vcap.application.uris[0]} nonSecurePort: 80
根据Cloudfoundry实例中安全规则的设置方式,您可以注册并使用主机VM的IP地址进行直接的服务到服务调用。此功能尚未在Pivotal Web Services(PWS)上提供。
在AWS上使用Eureka
如果应用程序计划将部署到AWS云,那么Eureka实例必须被配置为AWS意识到,这可以通过定制来完成EurekaInstanceConfigBean方式如下:
@Bean
@Profile("!default")
public EurekaInstanceConfigBean eurekaInstanceConfig(InetUtils inetUtils) {
EurekaInstanceConfigBean b = new EurekaInstanceConfigBean(inetUtils);
AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka");
b.setDataCenterInfo(info);
return b;
}
更改Eureka实例ID
香草Netflix Eureka实例注册了与其主机名相同的ID(即每个主机只有一个服务)。Spring Cloud Eureka提供了一个明智的默认,如下所示:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}}
。例如myhost:myappname:8080
。
使用Spring Cloud,您可以通过在eureka.instance.instanceId
中提供唯一的标识符来覆盖此。例如:
eureka: instance: instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
使用这个元数据和在localhost上部署的多个服务实例,随机值将在那里进行,以使实例是唯一的。在Cloudfoundry中,vcap.application.instance_id
将在Spring Boot应用程序中自动填充,因此不需要随机值。
使用EurekaClient
一旦您拥有@EnableDiscoveryClient
(或@EnableEurekaClient
)的应用程序,您就可以使用它来从Eureka服务器发现服务实例。一种方法是使用本机com.netflix.discovery.EurekaClient
(而不是Spring云DiscoveryClient
),例如
@Autowired private EurekaClient discoveryClient;
public String serviceUrl() {
InstanceInfo instance = discoveryClient.getNextServerFromEureka(“STORES”, false);
return instance.getHomePageUrl();
}
提示
|
不要使用 |
本机Netflix EurekaClient的替代方案
您不必使用原始的Netflix EurekaClient
,通常在某种包装器后面使用它更为方便。Spring Cloud支持Feign(REST客户端构建器),还支持Spring RestTemplate
使用逻辑Eureka服务标识符(VIP)而不是物理URL。要使用固定的物理服务器列表配置Ribbon,您可以将<client>.ribbon.listOfServers
设置为逗号分隔的物理地址(或主机名)列表,其中<client>
是客户端的ID。
您还可以使用org.springframework.cloud.client.discovery.DiscoveryClient
,它为Netflix不具体的发现客户端提供简单的API,例如
@Autowired private DiscoveryClient discoveryClient;
public String serviceUrl() {
List<ServiceInstance> list = discoveryClient.getInstances(“STORES”);
if (list != null && list.size() > 0 ) {
return list.get(0).getUri();
}
return null;
}
为什么注册服务这么慢?
作为一个实例也包括定期心跳到注册表(通过客户端的serviceUrl
),默认持续时间为30秒。在实例,服务器和客户端在其本地缓存中都具有相同的元数据(因此可能需要3个心跳)之前,客户端才能发现服务。您可以使用eureka.instance.leaseRenewalIntervalInSeconds
更改期限,这将加快客户端连接到其他服务的过程。在生产中,最好坚持使用默认值,因为服务器内部有一些计算可以对租赁更新期进行假设。
区
如果您已将Eureka客户端部署到多个区域,您可能希望这些客户端在使用另一个区域中的服务之前,利用同一区域内的服务。为此,您需要正确配置您的Eureka客户端。
首先,您需要确保将Eureka服务器部署到每个区域,并且它们是彼此的对等体。有关详细信息,请参阅区域和区域部分 。
接下来,您需要告知Eureka您的服务所在的区域。您可以使用metadataMap
属性来执行此操作。例如,如果service 1
部署到zone 1
和zone 2
,则需要在service 1
中设置以下Eureka属性
1区服务1
eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true
第2区的服务1
eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true
服务发现:Eureka服务器
如何包含Eureka服务器
要在项目中包含Eureka服务器,请使用组org.springframework.cloud
和工件id spring-cloud-starter-eureka-server
的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面。
如何运行Eureka服务器
示例eureka服务器;
@SpringBootApplication @EnableEurekaServer public class Application {
public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }
}
服务器具有一个带有UI的主页,并且根据/eureka/*
下的正常Eureka功能的HTTP API端点。
提示
|
由于Gradle的依赖关系解决规则和父母的bom功能缺乏,只要依靠spring-cloud-starter-eureka-server就可能导致应用程序启动失败。要解决这个问题,必须添加Spring Boot Gradle插件,并且必须导入Spring云启动器父母bom:
的build.gradle
|
apply plugin: “spring-boot”
dependencyManagement {
imports {
mavenBom “org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE”
}
}
高可用性,区域和地区
Eureka服务器没有后端存储,但是注册表中的服务实例都必须发送心跳以保持其注册更新(因此可以在内存中完成)。客户端还具有eureka注册的内存缓存(因此,他们不必为注册表提供每个服务请求)。
默认情况下,每个Eureka服务器也是一个Eureka客户端,并且需要(至少一个)服务URL来定位对等体。如果您不提供该服务将运行和工作,但它将淋浴您的日志与大量的噪音无法注册对等体。
关于区域和区域的客户端Ribbon支持的详细信息,请参见下文。
独立模式
只要存在某种监视器或弹性运行时间(例如Cloud Foundry),两个高速缓存(客户机和服务器)和心跳的组合使独立的Eureka服务器对故障具有相当的弹性。在独立模式下,您可能更喜欢关闭客户端行为,因此不会继续尝试并且无法访问其对等体。例:
server: port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://
e
u
r
e
k
a
.
i
n
s
t
a
n
c
e
.
h
o
s
t
n
a
m
e
:
{eureka.instance.hostname}:
eureka.instance.hostname:{server.port}/eureka/
请注意,serviceUrl
指向与本地实例相同的主机。
同行意识
通过运行多个实例并请求他们相互注册,可以使Eureka更具弹性和可用性。事实上,这是默认的行为,所以你需要做的只是为对方添加一个有效的serviceUrl
,例如
--- spring: profiles: peer1 eureka: instance: hostname: peer1 client: serviceUrl: defaultZone: http://peer2/eureka/
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer1/eureka/
在这个例子中,我们有一个YAML文件,可以通过在不同的Spring配置文件中运行,在2台主机(peer1和peer2)上运行相同的服务器。您可以使用此配置来测试单个主机上的对等体感知(通过操作/etc/hosts
来解析主机名,在生产中没有太多价值)。事实上,如果您在一台知道自己的主机名的机器上运行(默认情况下使用java.net.InetAddress
查找),则不需要eureka.instance.hostname
。
您可以向系统添加多个对等体,只要它们至少一个边缘彼此连接,则它们将在它们之间同步注册。如果对等体在物理上分离(在数据中心内或多个数据中心之间),则系统原则上可以分裂脑型故障。
喜欢IP地址
在某些情况下,Eureka优先发布服务的IP地址而不是主机名。将eureka.instance.preferIpAddress
设置为true
,并且当应用程序向eureka注册时,它将使用其IP地址而不是其主机名。
断路器:Hystrix客户端
较低级别的服务中的服务故障可能导致用户级联故障。当对特定服务的呼叫达到一定阈值时(Hystrix中的默认值为5秒内的20次故障),电路打开,不进行通话。在错误和开路的情况下,开发人员可以提供后备。
开放式电路会停止级联故障,并允许不必要的或失败的服务时间来愈合。回退可以是另一个Hystrix保护的调用,静态数据或一个正常的空值。回退可能被链接,所以第一个回退使得一些其他业务电话又回到静态数据。
如何加入Hystrix
要在项目中包含Hystrix,请使用组org.springframework.cloud
和artifact id spring-cloud-starter-hystrix
的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面。
示例启动应用程序:
@SpringBootApplication @EnableCircuitBreaker public class Application {public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); }
}
@Component
public class StoreIntegration {
@HystrixCommand(fallbackMethod = "defaultStores")
public Object getStores(Map<String, Object> parameters) {
//do stuff that might fail
}
public Object defaultStores(Map<String, Object> parameters) {
return /* something useful */;
}
}
@HystrixCommand
由名为“javanica”的Netflix contrib库提供 。Spring Cloud在连接到Hystrix断路器的代理中使用该注释自动包装Spring bean。断路器计算何时打开和关闭电路,以及在发生故障时应该做什么。
要配置@HystrixCommand
,您可以使用commandProperties
属性列出@HystrixProperty
注释。请参阅 这里 了解更多详情。有关 可用属性的详细信息,请参阅Hystrix维基。
传播安全上下文或使用Spring范围
如果您希望某些线程本地上下文传播到@HystrixCommand
,默认声明将不起作用,因为它在线程池中执行命令(超时)。您可以使用某些配置或直接在注释中使用与使用相同的线程来调用Hystrix,方法是要求使用不同的“隔离策略”。例如:
@HystrixCommand(fallbackMethod = "stubMyService",
commandProperties = {
@HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE")
}
)
...
如果您使用@SessionScope
或@RequestScope
,同样的事情也适用。您将知道何时需要执行此操作,因为运行时异常说它找不到范围的上下文。
您还可以将hystrix.shareSecurityContext
属性设置为true
。这样做会自动配置一个Hystrix并发策略插件钩子,他将SecurityContext
从主线程传送到Hystrix命令使用的钩子。Hystrix不允许注册多个hystrix并发策略,因此可以通过将自己的HystrixConcurrencyStrategy
声明为Spring bean来实现扩展机制。Spring Cloud将在Spring上下文中查找您的实现,并将其包装在自己的插件中。
健康指标
连接断路器的状态也暴露在呼叫应用程序的/health
端点中。
{
"hystrix": {
"openCircuitBreakers": [
"StoreIntegration::getStoresByLocationLink"
],
"status": "CIRCUIT_OPEN"
},
"status": "UP"
}
Hystrix指标流
要使Hystrix指标流包含对spring-boot-starter-actuator
的依赖。这将使/hystrix.stream
作为管理端点。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
断路器:Hystrix仪表板
Hystrix的主要优点之一是它收集关于每个HystrixCommand的一套指标。Hystrix仪表板以有效的方式显示每个断路器的运行状况。
Hystrix超时和Ribbon客户
当使用包含Ribbon客户端的Hystrix命令时,您需要确保您的Hystrix超时配置为长于配置的Ribbon超时,包括可能进行的任何潜在的重试。例如,如果您的Ribbon连接超时为一秒钟,并且Ribbon客户端可能会重试该请求三次,那么您的Hystrix超时应该略超过三秒钟。
如何包含Hystrix仪表板
要在项目中包含Hystrix仪表板,请使用组org.springframework.cloud
和工件ID spring-cloud-starter-hystrix-dashboard
的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面。
要运行Hystrix仪表板使用@EnableHystrixDashboard
注释您的Spring Boot主类。然后访问/hystrix
,并将仪表板指向Hystrix客户端应用程序中的单个实例/hystrix.stream
端点。
Turbine
从个人实例看,Hystrix数据在系统整体健康方面不是非常有用。Turbine是将所有相关/hystrix.stream
端点聚合到Hystrix仪表板中使用的/turbine.stream
的应用程序。个人实例位于Eureka。运行Turbine就像使用@EnableTurbine
注释(例如使用spring-cloud-starter-turbine设置类路径)注释主类一样简单。来自Turbine 1维基的所有文档配置属性都适用。唯一的区别是turbine.instanceUrlSuffix
不需要预先添加的端口,除非turbine.instanceInsertPort=false
自动处理。
注意
| 默认情况下,Turbine通过在Eureka中查找其homePageUrl 条目,然后将/hystrix.stream 附加到注册的实例上查找/hystrix.stream 端点。这意味着如果spring-boot-actuator 在自己的端口上运行(这是默认值),则对/hystrix.stream 的调用将失败。要使涡轮机找到正确端口的Hystrix流,您需要向实例的元数据中添加management.port : |
eureka: instance: metadata-map: management.port: ${management.port:8081}
配置密钥turbine.appConfig
是涡轮机将用于查找实例的尤里卡服务列表。涡轮流然后在Hystrix仪表板中使用如下URL:http://my.turbine.sever:8080/turbine.stream?cluster=<CLUSTERNAME>;
(如果名称为“默认值”,则可以省略群集参数)。cluster
参数必须与turbine.aggregator.clusterConfig
中的条目相匹配。从eureka返回的值是大写字母,因此如果有一个名为“customers”的Eureka注册了一个应用程序,我们预计此示例可以正常工作:
turbine: aggregator: clusterConfig: CUSTOMERS appConfig: customers
clusterName
可以通过turbine.clusterNameExpression
中的SPEL表达式以root身份InstanceInfo
进行自定义。默认值为appName
,这意味着Eureka serviceId最终作为集群密钥(即客户的InstanceInfo
具有appName
“CUSTOMERS”)。一个不同的例子是turbine.clusterNameExpression=aSGName
,它将从AWS ASG名称获取集群名称。另一个例子:
turbine: aggregator: clusterConfig: SYSTEM,USER appConfig: customers,stores,ui,admin clusterNameExpression: metadata['cluster']
在这种情况下,来自4个服务的集群名称从其元数据映射中提取,并且预期具有包含“SYSTEM”和“USER”的值。
要为所有应用程序使用“默认”集群,您需要一个字符串文字表达式(带单引号,并且如果它在YAML中也使用双引号进行转义):
turbine: appConfig: customers,stores clusterNameExpression: "'default'"
Spring Cloud提供了一个spring-cloud-starter-turbine
,它具有运行Turbine服务器所需的所有依赖关系。只需创建一个Spring Boot应用程序并用@EnableTurbine
注释它。
注意
| 默认情况下,Spring Cloud允许Turbine使用主机和端口允许每个主机在每个群集中进行多个进程。如果你想建成Turbine本地Netflix的行为,它不会允许每个主机上的多个过程,每簇(关键实例ID是主机名),然后将该属性设置turbine.combineHostPort=false 。 |
Turbine Stream
在某些环境中(例如,在PaaS设置中),从所有分布式Hystrix命令中提取度量的经典Turbine模型不起作用。在这种情况下,您可能希望让Hystrix命令将度量标准推送到Turbine,并且Spring Cloud可以使用消息传递。您需要在客户端上执行的所有操作都为您选择的spring-cloud-netflix-hystrix-stream
和spring-cloud-starter-stream-*
添加依赖关系(有关经纪人的详细信息,请参阅Spring Cloud Stream文档,以及如何配置客户端凭据,但是应该为当地经纪人开箱即用)。
在服务器端只需创建一个Spring Boot应用程序并使用@EnableTurbineStream
进行注释,默认情况下将在8989端口(将您的Hystrix仪表板指向该端口,任何路径)。您可以使用server.port
或turbine.stream.port
自定义端口。如果类路径中还有spring-boot-starter-web
和spring-boot-starter-actuator
,那么您可以通过提供不同的management.port
在单独端口(默认情况下使用Tomcat)打开Actuator端点。
然后,您可以将Hystrix仪表板指向Turbine Stream服务器,而不是单个Hystrix流。如果Turbine Stream在myhost上的端口8989上运行,则将http://myhost:8989
放在Hystrix仪表板中的流输入字段中。电路将以各自的serviceId为前缀,后跟一个点,然后是电路名称。
Spring Cloud提供了一个spring-cloud-starter-turbine-stream
,它具有您需要的Turbine Stream服务器运行所需的所有依赖项,只需添加您选择的Stream binder,例如spring-cloud-starter-stream-rabbit
。您需要Java 8来运行应用程序,因为它是基于Netty的。
客户端负载平衡器:Ribbon
Ribbon是一个客户端负载均衡器,它可以很好地控制HTTP和TCP客户端的行为。Feign已经使用Ribbon,所以如果您使用@FeignClient
,则本节也适用。
Ribbon中的中心概念是指定客户端的概念。每个负载平衡器是组合的组合的一部分,它们一起工作以根据需要联系远程服务器,并且集合具有您将其作为应用程序开发人员(例如使用@FeignClient
注释)的名称。Spring Cloud使用RibbonClientConfiguration
为每个命名的客户端根据需要创建一个新的合奏作为ApplicationContext
。这包含(除其他外)ILoadBalancer
,RestClient
和ServerListFilter
。
如何加入Ribbon
要在项目中包含Ribbon,请使用组org.springframework.cloud
和工件ID spring-cloud-starter-ribbon
的起始器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面。
自定义Ribbon客户端
您可以使用<client>.ribbon.*
中的外部属性来配置Ribbon客户端的某些位,这与使用Netflix API本身没有什么不同,只能使用Spring Boot配置文件。本机选项可以在CommonClientConfigKey
(功能区内核心部分)中作为静态字段进行检查。
Spring Cloud还允许您通过使用@RibbonClient
声明其他配置(位于RibbonClientConfiguration
之上)来完全控制客户端。例:
@Configuration
@RibbonClient(name = "foo", configuration = FooConfiguration.class)
public class TestConfiguration {
}
在这种情况下,客户端由RibbonClientConfiguration
中已经存在的组件与FooConfiguration
中的任何组件组成(后者通常会覆盖前者)。
警告
| FooConfiguration 必须是@Configuration ,但请注意,它不在主应用程序上下文的@ComponentScan 中,否则将由所有@RibbonClients 共享。如果您使用@ComponentScan (或@SpringBootApplication ),则需要采取措施避免包含(例如将其放在一个单独的,不重叠的包中,或者指定要在@ComponentScan )。 |
Spring Cloud Netflix默认情况下为Ribbon(BeanType
beanName:ClassName
)提供以下bean:
-
IClientConfig
ribbonClientConfig:DefaultClientConfigImpl
-
IRule
ribbonRule:ZoneAvoidanceRule
-
IPing
ribbonPing:NoOpPing
-
ServerList<Server>
ribbonServerList:ConfigurationBasedServerList
-
ServerListFilter<Server>
ribbonServerListFilter:ZonePreferenceServerListFilter
-
ILoadBalancer
ribbonLoadBalancer:ZoneAwareLoadBalancer
-
ServerListUpdater
ribbonServerListUpdater:PollingServerListUpdater
创建一个类型的bean并将其放置在@RibbonClient
配置(例如上面的FooConfiguration
)中)允许您覆盖所描述的每个bean。例:
@Configuration
public class FooConfiguration {
@Bean
public IPing ribbonPing(IClientConfig config) {
return new PingUrl();
}
}
这用PingUrl
代替NoOpPing
。
使用属性自定义Ribbon客户端
从版本1.2.0开始,Spring Cloud Netflix现在支持使用属性与Ribbon文档兼容来自定义Ribbon客户端。
这允许您在不同环境中更改启动时的行为。
支持的属性如下所示,应以<clientName>.ribbon.
为前缀:
-
NFLoadBalancerClassName
:应实施ILoadBalancer
-
NFLoadBalancerRuleClassName
:应实施IRule
-
NFLoadBalancerPingClassName
:应实施IPing
-
NIWSServerListClassName
:应实施ServerList
-
NIWSServerListFilterClassName
应实施ServerListFilter
注意
| 在这些属性中定义的类优先于使用@RibbonClient(configuration=MyRibbonConfig.class) 定义的bean和由Spring Cloud Netflix提供的默认值。 |
要设置服务名称users
的IRule
,您可以设置以下内容:
users: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
在Eureka中使用Ribbon
当Eureka与Ribbon结合使用(即两者都在类路径上)时,ribbonServerList
将被扩展为DiscoveryEnabledNIWSServerList
,扩展名为Eureka的服务器列表。它还用NIWSDiscoveryPing
替换IPing
接口,代理到Eureka以确定服务器是否启动。默认情况下安装的ServerList
是一个DomainExtractingServerList
,其目的是使物理元数据可用于负载平衡器,而不使用AWS AMI元数据(这是Netflix依赖的)。默认情况下,服务器列表将使用实例元数据(如远程客户端集合eureka.instance.metadataMap.zone
)中提供的“区域”信息构建,如果缺少,则可以使用服务器主机名中的域名作为代理用于区域(如果设置了标志approximateZoneFromHostname
)。一旦区域信息可用,它可以在ServerListFilter
中使用。默认情况下,它将用于定位与客户端相同区域的服务器,因为默认值为ZonePreferenceServerListFilter
。默认情况下,客户端的区域与远程实例的方式相同,即通过eureka.instance.metadataMap.zone
。
注意
| 设置客户端区域的正统“archaius”方式是通过一个名为“@zone”的配置属性,如果可用,Spring Cloud将优先使用所有其他设置(请注意,该键必须被引用)在YAML配置中)。 |
注意
| 如果没有其他的区域数据源,则基于客户端配置(与实例配置相反)进行猜测。我们将eureka.client.availabilityZones (从区域名称映射到区域列表),并将实例自己的区域的第一个区域(即eureka.client.region ,其默认为“us-east-1”为与本机Netflix的兼容性)。 |
示例:如何使用Ribbon不使用Eureka
Eureka是一种方便的方式来抽象远程服务器的发现,因此您不必在客户端中对其URL进行硬编码,但如果您不想使用它,Ribbon和Feign仍然是适用的。假设您已经为“商店”申请了@RibbonClient
,并且Eureka未被使用(甚至不在类路径上)。Ribbon客户端默认为已配置的服务器列表,您可以提供这样的配置
stores: ribbon: listOfServers: example.com,google.com
示例:在Ribbon中禁用Eureka使用
设置属性ribbon.eureka.enabled = false
将明确禁用在Ribbon中使用Eureka。
ribbon: eureka: enabled: false
直接使用Ribbon API
您也可以直接使用LoadBalancerClient
。例:
public class MyClass { @Autowired private LoadBalancerClient loadBalancer;
public void doStuff() { ServiceInstance instance = loadBalancer.choose("stores"); URI storesUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort())); // ... do something with the URI }
}
缓存Ribbon配置
每个Ribbon命名的客户端都有一个相应的子应用程序上下文,Spring Cloud维护,这个应用程序上下文在第一个请求中被延迟加载到命名的客户端。可以通过指定Ribbon客户端的名称,在启动时,可以更改此延迟加载行为,从而热切加载这些子应用程序上下文。
ribbon: eager-load: enabled: true clients: client1, client2, client3
声明性REST客户端:Feign
Feign是一个声明式的Web服务客户端。这使得Web服务客户端的写入更加方便 要使用Feign创建一个界面并对其进行注释。它具有可插入注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud增加了对Spring MVC注释的支持,并使用Spring Web中默认使用的HttpMessageConverters
。Spring Cloud集成Ribbon和Eureka以在使用Feign时提供负载均衡的http客户端。
如何加入Feign
要在您的项目中包含Feign,请使用组org.springframework.cloud
和工件ID spring-cloud-starter-feign
的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面。
示例spring boot应用
@Configuration @ComponentScan @EnableAutoConfiguration @EnableEurekaClient @EnableFeignClients public class Application {
public static void main(String[] args) { SpringApplication.run(Application.class, args); }
}
@FeignClient("stores") public interface StoreClient { @RequestMapping(method = RequestMethod.GET, value = "/stores") List<Store> getStores();
@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json") Store update(@PathVariable("storeId") Long storeId, Store store);
}
在@FeignClient
注释中,String值(以上“存储”)是一个任意的客户端名称,用于创建Ribbon负载平衡器(有关Ribbon支持的详细信息,请参阅下文))。您还可以使用url
属性(绝对值或只是主机名)指定URL。应用程序上下文中的bean的名称是该接口的完全限定名称。要指定您自己的别名值,您可以使用@FeignClient
注释的qualifier
值。
以上的Ribbon客户端将会发现“商店”服务的物理地址。如果您的应用程序是Eureka客户端,那么它将解析Eureka服务注册表中的服务。如果您不想使用Eureka,您可以简单地配置外部配置中的服务器列表(例如,参见 上文)。
覆盖Feign默认值
Spring Cloud的Feign支持的中心概念是指定的客户端。每个假装客户端都是组合的组件的一部分,它们一起工作以根据需要联系远程服务器,并且该集合具有您将其作为应用程序开发人员使用@FeignClient
注释的名称。Spring Cloud根据需要,使用FeignClientsConfiguration
为每个已命名的客户端创建一个新的集合ApplicationContext
。这包含(除其他外)feign.Decoder
,feign.Encoder
和feign.Contract
。
Spring Cloud可以通过使用@FeignClient
声明额外的配置(FeignClientsConfiguration
)来完全控制假客户端。例:
@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}
在这种情况下,客户端由FeignClientsConfiguration
中的组件与FooConfiguration
中的任何组件组成(后者将覆盖前者)。
注意
| FooConfiguration 不需要使用@Configuration 注释。但是,如果是,则请注意将其从任何@ComponentScan 中排除,否则将包含此配置,因为它将成为feign.Decoder ,feign.Encoder ,feign.Contract 等的默认来源,指定时。这可以通过将其放置在任何@ComponentScan 或@SpringBootApplication 的单独的不重叠的包中,或者可以在@ComponentScan 中明确排除。 |
注意
| serviceId 属性现在已被弃用,有利于name 属性。 |
警告
| 以前,使用url 属性,不需要name 属性。现在需要使用name 。 |
name
和url
属性支持占位符。
@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}
Spring Cloud Netflix默认为feign(BeanType
beanName:ClassName
)提供以下bean:
-
Decoder
feignDecoder:ResponseEntityDecoder
(其中包含SpringDecoder
) -
Encoder
feignEncoder:SpringEncoder
-
Logger
feignLogger:Slf4jLogger
-
Contract
feignContract:SpringMvcContract
-
Feign.Builder
feignBuilder:HystrixFeign.Builder
-
Client
feignClient:如果Ribbon启用,则为LoadBalancerFeignClient
,否则将使用默认的feign客户端。
可以通过将feign.okhttp.enabled
或feign.httpclient.enabled
设置为true
,并将它们放在类路径上来使用OkHttpClient和ApacheHttpClient feign客户端。
Spring Cloud Netflix 默认情况下不提供以下bean,但是仍然从应用程序上下文中查找这些类型的bean以创建假客户机:
-
Logger.Level
-
Retryer
-
ErrorDecoder
-
Request.Options
-
Collection<RequestInterceptor>
-
SetterFactory
创建一个类型的bean并将其放置在@FeignClient
配置(例如上面的FooConfiguration
)中)允许您覆盖所描述的每个bean。例:
@Configuration public class FooConfiguration { @Bean public Contract feignContract() { return new feign.Contract.Default(); }
@Bean public BasicAuthRequestInterceptor basicAuthRequestInterceptor() { return new BasicAuthRequestInterceptor("user", "password"); }
}
这将SpringMvcContract
替换为feign.Contract.Default
,并将RequestInterceptor
添加到RequestInterceptor
的集合中。
可以在@EnableFeignClients
属性defaultConfiguration
中以与上述相似的方式指定默认配置。不同之处在于,此配置将适用于所有假客户端。
注意
| 如果您需要在RequestInterceptor`s you will need to either set the thread isolation strategy for Hystrix to `SEMAPHORE 中使用ThreadLocal 绑定变量,或在Feign中禁用Hystrix。 |
application.yml
# To disable Hystrix in Feign
feign:
hystrix:
enabled: false
To set thread isolation to SEMAPHORE
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
手动创建Feign客户端
在某些情况下,可能需要以上述方法不可能自定义您的Feign客户端。在这种情况下,您可以使用Feign Builder API创建客户端 。下面是一个创建两个具有相同接口的Feign客户端的示例,但是使用单独的请求拦截器配置每个客户端。
@Import(FeignClientsConfiguration.class) class FooController {
private FooClient fooClient; private FooClient adminClient; @Autowired public FooController( Decoder decoder, Encoder encoder, Client client) { this.fooClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) .target(FooClient.class, "http://PROD-SVC"); this.adminClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) .target(FooClient.class, "http://PROD-SVC"); }
}
注意
| 在上面的例子中,FeignClientsConfiguration.class 是Spring Cloud Netflix提供的默认配置。 |
注意
| PROD-SVC 是客户端将要求的服务的名称。 |
Feign Hystrix支持
如果Hystrix在类路径上,feign.hystrix.enabled=true
,Feign将用断路器包装所有方法。还可以返回com.netflix.hystrix.HystrixCommand
。这样就可以使用无效模式(调用.toObservable()
或.observe()
或异步使用(调用.queue()
))。
要在每个客户端基础上禁用Hystrix支持创建一个带有“原型”范围的香草Feign.Builder
,例如:
@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}
警告
| 在Spring Cloud达尔斯顿发布之前,如果Hystrix在类路径Feign中默认将所有方法包装在断路器中。这种默认行为在Spring Cloud达尔斯顿改变了赞成选择加入的方式。 |
Feign Hystrix回退
Hystrix支持回退的概念:当电路断开或出现错误时执行的默认代码路径。要为给定的@FeignClient
启用回退,请将fallback
属性设置为实现回退的类名。
@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
protected interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
static class HystrixClientFallback implements HystrixClient {
@Override
public Hello iFailSometimes() {
return new Hello(“fallback”);
}
}
如果需要访问导致回退触发的原因,可以使用@FeignClient
内的fallbackFactory
属性。
@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello iFailSometimes();
}
@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
@Override
public HystrixClient create(Throwable cause) {
return new HystrixClientWithFallBackFactory() {
@Override
public Hello iFailSometimes() {
return new Hello("fallback; reason was: " + cause.getMessage());
}
};
}
}
警告
| 在Feign中执行回退以及Hystrix回退的工作方式存在局限性。当前返回com.netflix.hystrix.HystrixCommand 和rx.Observable 的方法目前不支持回退。 |
Feign和@Primary
当使用Feign与Hystrix回退时,在同一类型的ApplicationContext
中有多个bean。这将导致@Autowired
不起作用,因为没有一个bean,或者标记为主。要解决这个问题,Spring Cloud Netflix将所有Feign实例标记为@Primary
,所以Spring Framework将知道要注入哪个bean。在某些情况下,这可能是不可取的。要关闭此行为,将@FeignClient
的primary
属性设置为false。
@FeignClient(name = "hello", primary = false)
public interface HelloClient {
// methods here
}
Feign继承支持
Feign通过单继承接口支持样板apis。这样就可以将常用操作分成方便的基本界面。
public interface UserService {
@RequestMapping(method = RequestMethod.GET, value ="/users/{id}") User getUser(@PathVariable("id") long id);
}
@RestController
public class UserResource implements UserService {
}
package project.user;
@FeignClient(“users”)
public interface UserClient extends UserService {
}
注意
| 通常不建议在服务器和客户端之间共享接口。它引入了紧耦合,并且实际上并不适用于当前形式的Spring MVC(方法参数映射不被继承)。 |
Feign请求/响应压缩
您可以考虑为Feign请求启用请求或响应GZIP压缩。您可以通过启用其中一个属性来执行此操作:
feign.compression.request.enabled=true
feign.compression.response.enabled=true
Feign请求压缩为您提供与您为Web服务器设置的设置相似的设置:
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
这些属性可以让您对压缩介质类型和最小请求阈值长度有选择性。
Feign日志记录
为每个创建的Feign客户端创建一个记录器。默认情况下,记录器的名称是用于创建Feign客户端的接口的完整类名。Feign日志记录仅响应DEBUG
级别。
logging.level.project.user.UserClient: DEBUG
您可以为每个客户端配置的Logger.Level
对象告诉Feign记录多少。选择是:
-
NONE
,无记录(DEFAULT)。 -
BASIC
,只记录请求方法和URL以及响应状态代码和执行时间。 -
HEADERS
,记录基本信息以及请求和响应标头。 -
FULL
,记录请求和响应的头文件,正文和元数据。
例如,以下将Logger.Level
设置为FULL
:
@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
外部配置:Archaius
Netflix客户端配置库Archaius 它是所有Netflix OSS组件用于配置的库。Archaius是Apache Commons Configuration项目的扩展。它允许通过轮询源进行更改或将源更改推送到客户端来进行配置更新。Archaius使用Dynamic <Type> Property类作为属性的句柄。
class ArchaiusTest { DynamicStringProperty myprop = DynamicPropertyFactory .getInstance() .getStringProperty("my.prop");
void doSomething() { OtherClass.someMethod(myprop.get()); }
}
Archaius具有自己的一组配置文件和加载优先级。Spring应用程序一般不应直接使用Archaius,但本地仍然需要配置Netflix工具。Spring Cloud具有Spring环境桥,所以Archaius可以从Spring环境读取属性。这允许Spring Boot项目使用正常的配置工具链,同时允许他们在文档中大部分配置Netflix工具。
路由器和过滤器:Zuul
路由在微服务体系结构的一个组成部分。例如,/
可以映射到您的Web应用程序,/api/users
映射到用户服务,并将/api/shop
映射到商店服务。Zuul是Netflix的基于JVM的路由器和服务器端负载均衡器。
Netflix使用Zuul进行以下操作:
-
认证
-
洞察
-
压力测试
-
金丝雀测试
-
动态路由
-
服务迁移
-
负载脱落
-
安全
-
静态响应处理
-
主动/主动流量管理
Zuul的规则引擎允许基本上写任何JVM语言编写规则和过滤器,内置Java和Groovy。
注意
| 配置属性zuul.max.host.connections 已被两个新属性zuul.host.maxTotalConnections 和zuul.host.maxPerRouteConnections 替换,分别默认为200和20。 |
注意
| 所有路由的默认Hystrix隔离模式(ExecutionIsolationStrategy)为SEMAPHORE。如果此隔离模式是首选,则zuul.ribbonIsolationStrategy 可以更改为THREAD。 |
如何加入Zuul
要在您的项目中包含Zuul,请使用组org.springframework.cloud
和artifact id spring-cloud-starter-zuul
的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面。
嵌入式Zuul反向代理
Spring Cloud已经创建了一个嵌入式Zuul代理,以简化UI应用程序想要代理对一个或多个后端服务的呼叫的非常常见的用例的开发。此功能对于用户界面对其所需的后端服务进行代理是有用的,避免了对所有后端独立管理CORS和验证问题的需求。
要启用它,使用@EnableZuulProxy
注释Spring Boot主类,并将本地调用转发到相应的服务。按照惯例,具有ID“用户”的服务将接收来自位于/users
(具有前缀stripped)的代理的请求。代理使用Ribbon来定位一个通过发现转发的实例,并且所有请求都以 hystrix命令执行,所以故障将显示在Hystrix指标中,一旦电路打开,代理将不会尝试联系服务。
注意
| Zuul启动器不包括发现客户端,因此对于基于服务ID的路由,您还需要在类路径中提供其中一个路由(例如Eureka)。 |
要跳过自动添加的服务,请将zuul.ignored-services
设置为服务标识模式列表。如果一个服务匹配一个被忽略的模式,而且包含在明确配置的路由映射中,那么它将被无符号。例:
zuul:
ignoredServices: '*'
routes:
users: /myusers/**
在此示例中,除 “用户” 之外,所有服务都被忽略。
要扩充或更改代理路由,可以添加如下所示的外部配置:
zuul:
routes:
users: /myusers/**
这意味着对“/ myusers”的http呼叫转发到“用户”服务(例如“/ myusers / 101”转发到“/ 101”)。
要获得对路由的更细粒度的控制,您可以独立地指定路径和serviceId:
zuul:
routes:
users:
path: /myusers/**
serviceId: users_service
这意味着对“/ myusers”的http呼叫转发到“users_service”服务。路由必须有一个“路径”,可以指定为蚂蚁样式模式,所以“/ myusers / *”只匹配一个级别,但“/ myusers / **”分层匹配。
后端的位置可以被指定为“serviceId”(用于发现的服务)或“url”(对于物理位置),例如
zuul:
routes:
users:
path: /myusers/**
url: http://example.com/users_service
这些简单的URL路由不会被执行为HystrixCommand
,也不能使用Ribbon对多个URL进行负载平衡。为此,请指定service-route并为serviceId配置Ribbon客户端(目前需要在Ribbon中禁用Eureka支持:详见上文),例如
zuul:
routes:
users:
path: /myusers/**
serviceId: users
ribbon:
eureka:
enabled: false
users:
ribbon:
listOfServers: example.com,google.com
您可以使用regexmapper在serviceId和路由之间提供约定。它使用名为group的正则表达式从serviceId中提取变量并将它们注入到路由模式中。
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
这意味着serviceId“myusers-v1”将被映射到路由“/ v1 / myusers / **”。任何正则表达式都被接受,但所有命名组都必须存在于servicePattern和routePattern中。如果servicePattern与serviceId不匹配,则使用默认行为。在上面的示例中,serviceId“myusers”将被映射到路由“/ myusers / **”(检测不到版本)此功能默认禁用,仅适用于已发现的服务。
要为所有映射添加前缀,请将zuul.prefix
设置为一个值,例如/api
。默认情况下,请求被转发之前,代理前缀被删除(使用zuul.stripPrefix=false
关闭此行为)。您还可以关闭从各路线剥离服务特定的前缀,例如
zuul:
routes:
users:
path: /myusers/**
stripPrefix: false
注意
| zuul.stripPrefix 仅适用于zuul.prefix 中设置的前缀。它对给定路由path 中定义的前缀有影响。 |
在本示例中,对“/ myusers / 101”的请求将转发到“/ myusers / 101”上的“users”服务。
zuul.routes
条目实际上绑定到类型为ZuulProperties
的对象。如果您查看该对象的属性,您将看到它还具有“可重试”标志。将该标志设置为“true”使Ribbon客户端自动重试失败的请求(如果需要,可以使用Ribbon客户端配置修改重试操作的参数)。
默认情况下,将X-Forwarded-Host
标头添加到转发的请求中。关闭set zuul.addProxyHeaders = false
。默认情况下,前缀路径被删除,对后端的请求会拾取一个标题“X-Forwarded-Prefix”(上述示例中的“/ myusers”)。
如果您设置默认路由(“/”),则@EnableZuulProxy
的应用程序可以作为独立服务器,例如zuul.route.home: /
将路由所有流量(即“/ **”)到“home”服务。
如果需要更细粒度的忽略,可以指定要忽略的特定模式。在路由位置处理开始时评估这些模式,这意味着前缀应包含在模式中以保证匹配。忽略的模式跨越所有服务,并取代任何其他路由规范。
zuul:
ignoredPatterns: /**/admin/**
routes:
users: /myusers/**
这意味着诸如“/ myusers / 101”的所有呼叫将被转发到“用户”服务上的“/ 101”。但是包含“/ admin /”的呼叫将无法解决。
警告
| 如果您需要您的路由保留订单,则需要使用YAML文件,因为使用属性文件将会丢失订购。例如: |
zuul:
routes:
users:
path: /myusers/**
legacy:
path: /**
如果要使用属性文件,则legacy
路径可能会在users
路径前面展开,从而使users
路径不可达。
Zuul Http客户端
zuul使用的默认HTTP客户端现在由Apache HTTP Client支持,而不是不推荐使用的Ribbon RestClient
。要分别使用RestClient
或使用okhttp3.OkHttpClient
集合ribbon.restclient.enabled=true
或ribbon.okhttp.enabled=true
。
Cookie和敏感标题
在同一个系统中的服务之间共享标题是可行的,但是您可能不希望敏感标头泄漏到外部服务器的下游。您可以在路由配置中指定被忽略头文件列表。Cookies起着特殊的作用,因为它们在浏览器中具有明确的语义,并且它们总是被视为敏感的。如果代理的消费者是浏览器,则下游服务的cookie也会导致用户出现问题,因为它们都被混淆(所有下游服务看起来都是来自同一个地方)。
如果您对服务的设计非常谨慎,例如,如果只有一个下游服务设置了Cookie,那么您可能可以让他们从后台一直到调用者。另外,如果您的代理设置cookie和所有后台服务都是同一系统的一部分,那么简单地共享它们就可以自然(例如使用Spring Session将它们链接到一些共享状态)。除此之外,由下游服务设置的任何Cookie可能对呼叫者来说都不是很有用,因此建议您将(至少)“Set-Cookie”和“Cookie”设置为不属于您的域名。即使是属于您域名的路线,请尝试仔细考虑允许Cookie在代理之间流动的含义。
灵敏头可以配置为每个路由的逗号分隔列表,例如
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders: Cookie,Set-Cookie,Authorization
url: https://downstream
注意
| 这是sensitiveHeaders 的默认值,因此您不需要设置它,除非您希望它不同。注意这是Spring Cloud Netflix 1.1中的新功能(1.0中,用户无法控制标题,所有Cookie都在两个方向上流动)。 |
sensitiveHeaders
是一个黑名单,默认值不为空,所以要使Zuul发送所有标题(“被忽略”除外),您必须将其显式设置为空列表。如果您要将Cookie或授权标头传递到后端,这是必要的。例:
zuul:
routes:
users:
path: /myusers/**
sensitiveHeaders:
url: https://downstream
也可以通过设置zuul.sensitiveHeaders
来全局设置敏感标题。如果在路由上设置sensitiveHeaders
,则将覆盖全局sensitiveHeaders
设置。
被忽略的标题
除了每个路由的敏感标头,您还可以为与下游服务交互期间应该丢弃的值(请求和响应)设置全局值为zuul.ignoredHeaders
。默认情况下,如果Spring安全性不在类路径上,则它们是空的,否则它们被初始化为由Spring Security指定的一组众所周知的“安全性”头(例如涉及缓存)。在这种情况下的假设是下游服务可能也添加这些头,我们希望代理的值。为了不丢弃这些众所周知的安全标头,只要Spring安全性在类路径上,您可以将zuul.ignoreSecurityHeaders
设置为false
。如果您禁用Spring安全性中的HTTP安全性响应头,并希望由下游服务提供的值,这可能很有用
路线端点
如果您在Spring Boot执行器中使用@EnableZuulProxy
,您将启用(默认情况下)另一个端点,通过HTTP可用/routes
。到此端点的GET将返回映射路由的列表。POST将强制刷新现有路由(例如,如果服务目录中有更改)。您可以通过将endpoints.routes.enabled
设置为false
来禁用此端点。
注意
| 路由应自动响应服务目录中的更改,但POST到/路由是强制更改立即发生的一种方式。 |
扼杀模式和本地前进
迁移现有应用程序或API时的常见模式是“扼杀”旧端点,用不同的实现慢慢替换它们。Zuul代理是一个有用的工具,因为您可以使用它来处理来自旧端点的客户端的所有流量,但将一些请求重定向到新端点。
示例配置:
zuul:
routes:
first:
path: /first/**
url: http://first.example.com
second:
path: /second/**
url: forward:/second
third:
path: /third/**
url: forward:/3rd
legacy:
path: /**
url: http://legacy.example.com
在这个例子中,我们扼杀了“遗留”应用程序,该应用程序映射到所有与其他模式不匹配的请求。/first/**
中的路径已被提取到具有外部URL的新服务中。并且/second/**
中的路径被转发,以便它们可以在本地处理,例如具有正常的Spring @RequestMapping
。/third/**
中的路径也被转发,但具有不同的前缀(即/third/foo
转发到/3rd/foo
)。
注意
| 被忽略的模式并不完全被忽略,它们只是不被代理处理(因此它们也被有效地转发到本地)。 |
通过Zuul上传文件
如果您@EnableZuulProxy
您可以使用代理路径上传文件,只要文件很小,它就应该工作。对于大文件,有一个替代路径绕过“/ zuul / *”中的Spring DispatcherServlet
(以避免多部分处理)。也就是说,如果zuul.routes.customers=/customers/**
则可以将大文件发送到“/ zuul / customers / *”。servlet路径通过zuul.servletPath
进行外部化。如果代理路由引导您通过Ribbon负载均衡器,例如,超大文件也将需要提升超时设置
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
请注意,要使用大型文件进行流式传输,您需要在请求中使用分块编码(某些浏览器默认情况下不会执行)。例如在命令行:
$ curl -v -H "Transfer-Encoding: chunked" \ -F "file=@mylarge.iso" localhost:9999/zuul/simple/file
查询字符串编码
处理传入的请求时,查询参数被解码,因此可以在Zuul过滤器中进行修改。然后在路由过滤器中构建后端请求时重新编码它们。如果使用Javascript的encodeURIComponent()
方法编码,结果可能与原始输入不同。虽然这在大多数情况下不会出现任何问题,但一些Web服务器可以用复杂查询字符串的编码来挑选。
要强制查询字符串的原始编码,可以将特殊标志传递给ZuulProperties
,以便查询字符串与HttpServletRequest::getQueryString
方法相同:
zuul:
forceOriginalQueryStringEncoding: true
注意:此特殊标志仅适用于SimpleHostRoutingFilter
,您可以使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)
轻松覆盖查询参数,因为查询字符串现在直接在原始的HttpServletRequest
上获取。
普通嵌入Zuul
如果您使用@EnableZuulServer
(而不是@EnableZuulProxy
),您也可以运行不带代理的Zuul服务器,或者有选择地切换代理平台的部分。您添加到ZuulFilter
类型的应用程序的任何bean都将自动安装,与@EnableZuulProxy
一样,但不会自动添加任何代理过滤器。
在这种情况下,仍然通过配置“zuul.routes。*”来指定进入Zuul服务器的路由,但没有服务发现和代理,所以“serviceId”和“url”设置将被忽略。例如:
zuul:
routes:
api: /api/**
将“/ api / **”中的所有路径映射到Zuul过滤器链。
禁用Zuul过滤器
Spring Cloud的Zuul在代理和服务器模式下默认启用了多个ZuulFilter
bean。有关启用的可能过滤器,请参阅zuul过滤器包。如果要禁用它,只需设置zuul.<SimpleClassName>.<filterType>.disable=true
。按照惯例,filters
之后的包是Zuul过滤器类型。例如,禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
设置zuul.SendResponseFilter.post.disable=true
。
为路线提供Hystrix回退
当Zuul中给定路由的电路跳闸时,您可以通过创建类型为ZuulFallbackProvider
的bean来提供回退响应。在这个bean中,您需要指定回退的路由ID,并提供返回的ClientHttpResponse
作为后备。这是一个非常简单的ZuulFallbackProvider
实现。
class MyFallbackProvider implements ZuulFallbackProvider { @Override public String getRoute() { return "customers"; }
@Override public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; }
}
这里是路由配置的样子。
zuul:
routes:
customers: /customers/**
如果您希望为所有路由提供默认的回退,您可以创建一个类型为ZuulFallbackProvider
的bean,并且getRoute
方法返回*
或null
。
class MyFallbackProvider implements ZuulFallbackProvider { @Override public String getRoute() { return "*"; }
@Override public ClientHttpResponse fallbackResponse() { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; }
}
Zuul开发人员指南
有关Zuul如何工作的一般概述,请参阅Zuul维基。
Zuul Servlet
Zuul被实现为Servlet。对于一般情况,Zuul嵌入到Spring调度机制中。这允许Spring MVC控制路由。在这种情况下,Zuul被配置为缓冲请求。如果需要通过Zuul不缓冲请求(例如大文件上传),Servlet也将安装在Spring调度程序之外。默认情况下,它位于/zuul
。可以使用zuul.servlet-path
属性更改此路径。
Zuul RequestContext
要在过滤器之间传递信息,Zuul使用a RequestContext
。其数据按照每个请求的ThreadLocal
进行。关于路由请求,错误以及实际HttpServletRequest
和HttpServletResponse
的路由信息。RequestContext
扩展ConcurrentHashMap
,所以任何东西都可以存储在上下文中。FilterConstants
包含由Spring Cloud Netflix安装的过滤器使用的密钥(稍后再安装)。
@EnableZuulProxy
与@EnableZuulServer
Spring Cloud Netflix根据使用何种注释来启用Zuul安装多个过滤器。@EnableZuulProxy
是@EnableZuulServer
的超集。换句话说,@EnableZuulProxy
包含@EnableZuulServer
安装的所有过滤器。“代理”中的其他过滤器启用路由功能。如果你想要一个“空白”Zuul,你应该使用@EnableZuulServer
。
@EnableZuulServer
过滤器
创建从Spring Boot配置文件加载路由定义的SimpleRouteLocator
。
安装了以下过滤器(正常Spring豆类):
前置过滤器
-
ServletDetectionFilter
:检测请求是否通过Spring调度程序。使用键FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY
设置布尔值。 -
FormBodyWrapperFilter
:解析表单数据,并对下游请求进行重新编码。 -
DebugFilter
:如果设置debug
请求参数,则此过滤器将RequestContext.setDebugRouting()
和RequestContext.setDebugRequest()
设置为true。
路由过滤器
-
SendForwardFilter
:此过滤器使用ServletRequestDispatcher
转发请求。转发位置存储在RequestContext
属性FilterConstants.FORWARD_TO_KEY
中。这对于转发到当前应用程序中的端点很有用。
过滤器:
-
SendResponseFilter
:将代理请求的响应写入当前响应。
错误过滤器:
-
SendErrorFilter
:如果RequestContext.getThrowable()
不为null,则转发到/错误(默认情况下)。可以通过设置error.path
属性来更改默认转发路径(/error
)。
@EnableZuulProxy
过滤器
创建从DiscoveryClient
(如Eureka)以及属性加载路由定义的DiscoveryClientRouteLocator
。每个serviceId
从DiscoveryClient
创建路由。随着新服务的添加,路由将被刷新。
除了上述过滤器之外,还安装了以下过滤器(正常Spring豆类):
前置过滤器
-
PreDecorationFilter
:此过滤器根据提供的RouteLocator
确定在哪里和如何路由。它还为下游请求设置各种与代理相关的头。
路由过滤器
-
RibbonRoutingFilter
:此过滤器使用Ribbon,Hystrix和可插拔HTTP客户端发送请求。服务ID位于RequestContext
属性FilterConstants.SERVICE_ID_KEY
中。此过滤器可以使用不同的HTTP客户端。他们是:-
Apache
HttpClient
。这是默认的客户端。 -
Squareup
OkHttpClient
v3。通过在类路径上设置com.squareup.okhttp3:okhttp
库并设置ribbon.okhttp.enabled=true
来启用此功能。 -
Netflix Ribbon HTTP客户端。这可以通过设置
ribbon.restclient.enabled=true
来启用。这个客户端有限制,比如它不支持PATCH方法,还有内置的重试。
-
-
SimpleHostRoutingFilter
:此过滤器通过Apache HttpClient发送请求到预定的URL。URL位于RequestContext.getRouteHost()
。
自定义Zuul过滤示例
以下大部分以下“如何撰写”示例都包含示例Zuul过滤器项目。还有一些操作该存储库中的请求或响应正文的例子。
如何编写预过滤器
前置过滤器用于设置RequestContext
中的数据,用于下游的过滤器。主要用例是设置路由过滤器所需的信息。
public class QueryParamPreFilter extends ZuulFilter { @Override public int filterOrder() { return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration }
@Override public String filterType() { return PRE_TYPE; } @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded && !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); if (request.getParameter("foo") != null) { // put the serviceId in `RequestContext` ctx.put(SERVICE_ID_KEY, request.getParameter("foo")); } return null; }
}
上面的过滤器从foo
请求参数填充SERVICE_ID_KEY
。实际上,做这种直接映射并不是一个好主意,而是从foo
的值来查看服务ID。
现在填写SERVICE_ID_KEY
,PreDecorationFilter
将不会运行,RibbonRoutingFilter
将会。如果您想要路由到完整的网址,请改用ctx.setRouteHost(url)
。
要修改路由过滤器将转发的路径,请设置REQUEST_URI_KEY
。
如何编写路由过滤器
路由过滤器在预过滤器之后运行,并用于向其他服务发出请求。这里的大部分工作是将请求和响应数据转换到客户端所需的模型。
public class OkHttpRoutingFilter extends ZuulFilter { @Autowired private ProxyRequestHelper helper;
@Override public String filterType() { return ROUTE_TYPE; } @Override public int filterOrder() { return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse(); } @Override public Object run() { OkHttpClient httpClient = new OkHttpClient.Builder() // customize .build(); RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String method = request.getMethod(); String uri = this.helper.buildZuulRequestURI(request); Headers.Builder headers = new Headers.Builder(); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); Enumeration<String> values = request.getHeaders(name); while (values.hasMoreElements()) { String value = values.nextElement(); headers.add(name, value); } } InputStream inputStream = request.getInputStream(); RequestBody requestBody = null; if (inputStream != null && HttpMethod.permitsRequestBody(method)) { MediaType mediaType = null; if (headers.get("Content-Type") != null) { mediaType = MediaType.parse(headers.get("Content-Type")); } requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream)); } Request.Builder builder = new Request.Builder() .headers(headers.build()) .url(uri) .method(method, requestBody); Response response = httpClient.newCall(builder.build()).execute(); LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>(); for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) { responseHeaders.put(entry.getKey(), entry.getValue()); } this.helper.setResponse(response.code(), response.body().byteStream(), responseHeaders); context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running return null; }
}
上述过滤器将Servlet请求信息转换为OkHttp3请求信息,执行HTTP请求,然后将OkHttp3响应信息转换为Servlet响应。警告:此过滤器可能有错误,但功能不正确。
如何编写过滤器
后置过滤器通常操纵响应。在下面的过滤器中,我们添加一个随机UUID
作为X-Foo
头。其他操作,如转换响应体,要复杂得多,计算密集。
public class AddResponseHeaderFilter extends ZuulFilter { @Override public String filterType() { return POST_TYPE; }
@Override public int filterOrder() { return SEND_RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletResponse servletResponse = context.getResponse(); servletResponse.addHeader("X-Foo", UUID.randomUUID().toString()); return null; }
}
Zuul错误如何工作
如果在Zuul过滤器生命周期的任何部分抛出异常,则会执行错误过滤器。SendErrorFilter
只有RequestContext.getThrowable()
不是null
才会运行。然后在请求中设置特定的javax.servlet.error.*
属性,并将请求转发到Spring Boot错误页面。
Zuul渴望应用程序上下文加载
Zuul内部使用Ribbon调用远程URL,并且Ribbon客户端默认在第一次调用时由Spring Cloud加载。可以使用以下配置更改Zuul的此行为,并将导致在应用程序启动时,子Ribbon相关的应用程序上下文正在加载。
zuul: ribbon: eager-load: enabled: true
Polyglot支持Sidecar
你有没有非jvm的语言你想利用Eureka,Ribbon和配置服务器?Netflix Prana启发了Spring Cloud Netflix Sidecar 。它包含一个简单的http api来获取给定服务的所有实例(即主机和端口)。您还可以通过从Eureka获取其路由条目的嵌入式Zuul代理来代理服务调用。可以通过主机查找或通过Zuul代理访问Spring Cloud Config服务器。非jvm应用程序应该执行健康检查,以便Sidecar可以向应用程序启动或关闭时向eureka报告。
要在项目中包含Sidecar,使用组org.springframework.cloud
和artifact id spring-cloud-netflix-sidecar
的依赖关系。
要启用Sidecar,请使用@EnableSidecar
创建Spring Boot应用程序。此注释包括@EnableCircuitBreaker
,@EnableDiscoveryClient
和@EnableZuulProxy
。在与非jvm应用程序相同的主机上运行生成的应用程序。
要配置侧车将sidecar.port
和sidecar.health-uri
添加到application.yml
。sidecar.port
属性是非jvm应用程序正在侦听的端口。这样,Sidecar可以使用Eureka正确注册该应用。sidecar.health-uri
是可以在非jvm应用程序上访问的,可以模拟Spring Boot健康指标。它应该返回一个json文档,如下所示:
{
"status":"UP"
}
以下是Sidecar应用程序的application.yml示例:
server:
port: 5678
spring:
application:
name: sidecar
sidecar:
port: 8000
health-uri: http://localhost:8000/health.json
DiscoveryClient.getInstances()
方法的api为/hosts/{serviceId}
。以下是/hosts/customers
在不同主机上返回两个实例的示例响应。这个api可以访问http://localhost:5678/hosts/{serviceId}
的非jvm应用程序(如果侧面在端口5678上)。
[
{
"host": "myhost",
"port": 9000,
"uri": "http://myhost:9000",
"serviceId": "CUSTOMERS",
"secure": false
},
{
"host": "myhost2",
"port": 9000,
"uri": "http://myhost2:9000",
"serviceId": "CUSTOMERS",
"secure": false
}
]
Zuul代理自动将eureka中已知的每个服务的路由添加到/<serviceId>
,因此客户服务可在/customers
获得。非jvm应用程序可以通过http://localhost:5678/customers
访问客户服务(假设边界正在侦听端口5678)。
如果配置服务器已注册到Eureka,则非jvm应用程序可以通过Zuul代理访问它。如果ConfigServer的serviceId为configserver
且端口5678为Sidecar,则可以在 http:// localhost:5678 / configserver
非jvm应用程序可以利用Config Server返回YAML文档的功能。例如,调用http://sidecar.local.spring.io:5678/configserver/default-master.yml 可能会导致一个YAML文档,如下所示
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
password: password
info:
description: Spring Cloud Samples
url: https://github.com/spring-cloud-samples
RxJava与Spring MVC
Spring Cloud Netflix包括RxJava。
RxJava是Reactive Extensions的Java VM实现:用于通过使用observable序列来构建异步和基于事件的程序的库。
Spring Cloud Netflix支持从Spring MVC控制器返回rx.Single
对象。它还支持对服务器发送事件(SSE)使用rx.Observable
对象。如果您的内部API已经使用RxJava构建(参见Feign Hystrix支持示例),这可能非常方便。
以下是使用rx.Single
的一些示例:
@RequestMapping(method = RequestMethod.GET, value = "/single")
public Single<String> single() {
return Single.just("single value");
}
@RequestMapping(method = RequestMethod.GET, value = “/singleWithResponse”)
public ResponseEntity<Single<String>> singleWithResponse() {
return new ResponseEntity<>(Single.just(“single value”),
HttpStatus.NOT_FOUND);
}
@RequestMapping(method = RequestMethod.GET, value = “/singleCreatedWithResponse”)
public Single<ResponseEntity<String>> singleOuterWithResponse() {
return Single.just(new ResponseEntity<>(“single value”, HttpStatus.CREATED));
}
@RequestMapping(method = RequestMethod.GET, value = “/throw”)
public Single<Object> error() {
return Single.error(new RuntimeException(“Unexpected”));
}
如果您有Observable
而不是单个,则可以使用.toSingle()
或.toList().toSingle()
。这里有些例子:
@RequestMapping(method = RequestMethod.GET, value = "/single")
public Single<String> single() {
return Observable.just("single value").toSingle();
}
@RequestMapping(method = RequestMethod.GET, value = “/multiple”)
public Single<List<String>> multiple() {
return Observable.just(“multiple”, “values”).toList().toSingle();
}
@RequestMapping(method = RequestMethod.GET, value = “/responseWithObservable”)
public ResponseEntity<Single<String>> responseWithObservable() {
Observable<String> observable = Observable.just("single value");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(APPLICATION_JSON_UTF8);
return new ResponseEntity<>(observable.toSingle(), headers, HttpStatus.CREATED);
}
@RequestMapping(method = RequestMethod.GET, value = “/timeout”)
public Observable<String> timeout() {
return Observable.timer(1, TimeUnit.MINUTES).map(new Func1<Long, String>() {
@Override
public String call(Long aLong) {
return “single value”;
}
});
}
如果您有流式端点和客户端,SSE可以是一个选项。要将rx.Observable
转换为Spring SseEmitter
使用RxResponse.sse()
。这里有些例子:
@RequestMapping(method = RequestMethod.GET, value = "/sse")
public SseEmitter single() {
return RxResponse.sse(Observable.just("single value"));
}
@RequestMapping(method = RequestMethod.GET, value = “/messages”)
public SseEmitter messages() {
return RxResponse.sse(Observable.just(“message 1”, “message 2”, “message 3”));
}
@RequestMapping(method = RequestMethod.GET, value = “/events”)
public SseEmitter event() {
return RxResponse.sse(APPLICATION_JSON_UTF8,
Observable.just(new EventDto(“Spring io”, getDate(2016, 5, 19)),
new EventDto(“SpringOnePlatform”, getDate(2016, 8, 1))));
}
指标:Spectator,Servo和Atlas
当一起使用时,Spectator / Servo和Atlas提供了近乎实时的操作洞察平台。
Netflix的度量收集库Spectator和Servo Atlas是用于管理维度时间序列数据的Netflix指标后端。
Servo为Netflix服务了好几年,仍然可以使用,但逐渐被淘汰出局Spectator,这只适用于Java 8. Spring Cloud Netflix提供了支持,但Java 8鼓励基于应用的应用程序使用Spectator。
维度与层次度量
Spring Boot执行器指标是层次结构,指标只能由名称分隔。这些名称通常遵循将密钥/值属性对(维)嵌入到以句点分隔的名称中的命名约定。考虑以下两个端点(root和star-star)的指标:
{
"counter.status.200.root": 20,
"counter.status.400.root": 3,
"counter.status.200.star-star": 5,
}
第一个指标给出了每单位时间内针对根端点的成功请求的归一化计数。但是如果系统有20个端点,并且想要获得针对所有端点的成功请求计数呢?一些分级度量后端将允许您指定一个通配符,例如counter.status.200.
,它将读取所有20个指标并聚合结果。或者,您可以提供HandlerInterceptorAdapter
拦截并记录所有成功请求的counter.status.200.all
等指标,而不考虑端点,但现在您必须编写20 + 1个不同的指标。同样,如果您想知道服务中所有端点的成功请求总数,您可以指定一个通配符,例如counter.status.2
.*
。
即使在分级度量后端的通配符支持的情况下,命名一致性也是困难的。具体来说,这些标签在名称字符串中的位置可能会随着时间而滑落,从而导致查询错 例如,假设我们为上述HTTP方法添加了一个额外的维度。那么counter.status.200.root
成为counter.status.200.method.get.root
等等。我们的counter.status.200.*
突然不再具有相同的语义。此外,如果新的维度在整个代码库中不均匀地应用,某些查询可能会变得不可能。这可以很快失控。
Netflix指标被标记(又称维度)。每个指标都有一个名称,但是这个单一的命名度量可以包含多个统计信息和“标签”键/值对,这允许更多的查询灵活性。实际上统计本身就是记录在一个特殊的标签上。
使用Netflix Servo或Spectator记录,上述根端点的计时器包含每个状态码的4个统计信息,其中计数统计信息与Spring Boot执行器计数器相同。如果到目前为止,我们遇到了HTTP 200和400,将有8个可用数据点:
{
"root(status=200,stastic=count)": 20,
"root(status=200,stastic=max)": 0.7265630630000001,
"root(status=200,stastic=totalOfSquares)": 0.04759702862580789,
"root(status=200,stastic=totalTime)": 0.2093076914666667,
"root(status=400,stastic=count)": 1,
"root(status=400,stastic=max)": 0,
"root(status=400,stastic=totalOfSquares)": 0,
"root(status=400,stastic=totalTime)": 0,
}
默认度量集合
没有任何附加依赖或配置,基于Spring Cloud的服务将自动配置Servo MonitorRegistry
,并开始收集每个Spring MVC请求的指标。默认情况下,将为每个MVC请求记录名称为rest
的Servo定时器,其标记为:
-
HTTP方法
-
HTTP状态(例如200,400,500)
-
URI(如果URI为空,则为“root”),为Atlas
-
异常类名称,如果请求处理程序抛出异常
-
如果在请求上设置了匹配
netflix.metrics.rest.callerHeader
的密钥的请求头,则呼叫者。netflix.metrics.rest.callerHeader
没有默认键。如果您希望收集来电者信息,则必须将其添加到应用程序属性中。
设置netflix.metrics.rest.metricName
属性将度量值的名称从rest
更改为您提供的名称。
如果Spring AOP已启用,并且org.aspectj:aspectjweaver
存在于您的运行时类路径上,则Spring Cloud还将收集每个使用RestTemplate
进行的客户端调用的指标。将为每个具有以下标签的MVC请求记录名称为restclient
的Servo定时器:
-
HTTP方法
-
HTTP状态(例如200,400,500),如果响应返回为空,则为“CLIENT_ERROR”;如果在执行
RestTemplate
方法期间发生IOException
,则为“IO_ERROR” -
URI,为Atlas
-
客户名称
警告
| 避免在RestTemplate 内使用硬编码的url参数。定位动态端点时使用URL变量。这将避免ServoMonitorCache 将每个网址视为唯一密钥的潜在“GC覆盖限制达到”问题。 |
// recommended
String orderid = "1";
restTemplate.getForObject("http://testeurekabrixtonclient/orders/{orderid}", String.class, orderid)
// avoid
restTemplate.getForObject(“http://testeurekabrixtonclient/orders/1”, String.class)
指标集:Spectator
要启用Spectator指标,请在spring-boot-starter-spectator
上包含依赖关系:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-spectator</artifactId>
</dependency>
在Spectator说明中,仪表是一个命名,打字和标记的配置,而指标表示给定仪表在某个时间点的值。Spectator米由注册表创建和控制,注册表目前有几个不同的实现。Spectator提供4米类型:计数器,定时器,量规和分配摘要。
Spring Cloud Spectator集成为您配置可注入的com.netflix.spectator.api.Registry
实例。具体来说,它配置一个ServoRegistry
实例,以统一REST度量标准的集合,并将度量标准导出到Servo API下的Atlas后端。实际上,这意味着您的代码可能会使用Servo显示器和Spectator米的混合,并且都将由Spring Boot Actuator MetricReader
实例舀取,并将两者都发送到Atlas后端
Spectator柜台
计数器用于测量某些事件发生的速率。
// create a counter with a name and a set of tags
Counter counter = registry.counter("counterName", "tagKey1", "tagValue1", ...);
counter.increment(); // increment when an event occurs
counter.increment(10); // increment by a discrete amount
计数器记录单个时间归一化统计量。
Spectator计时器
一个计时器用于测量一些事件需要多长时间。Spring Cloud自动记录Spring MVC请求和有条件RestTemplate
请求的定时器,稍后可用于为请求相关指标创建仪表板,如延迟:
// create a timer with a name and a set of tags
Timer timer = registry.timer("timerName", "tagKey1", "tagValue1", ...);
// execute an operation and time it at the same time
T result = timer.record(() -> fooReturnsT());
// alternatively, if you must manually record the time
Long start = System.nanoTime();
T result = fooReturnsT();
timer.record(System.nanoTime() - start, TimeUnit.NANOSECONDS);
计时器同时记录4个统计信息:count,max,totalOfSquares和totalTime。如果您在每次记录时间时在计数器上调用了increment()
一次,计数统计量将始终与计数器提供的单个归一化值相匹配,因此对于单个操作,不需要单独计数和分时。
对于长时间运行的操作,Spectator提供了一个特殊的LongTaskTimer
。
Spectator量规
量规用于确定一些当前值,如队列的大小或处于运行状态的线程数。由于仪表被采样,它们不提供关于这些值在样品之间如何波动的信息。
仪器的正常使用包括在初始化中使用标识符注册仪表,对要采样的对象的引用,以及基于对象获取或计算数值的功能。对对象的引用被单独传递,Spectator注册表将保留对该对象的弱引用。如果对象被垃圾回收,则Spectator将自动删除注册。见注 Spectator是关于潜在的内存泄漏的文件中,如果这个API被滥用。
// the registry will automatically sample this gauge periodically
registry.gauge("gaugeName", pool, Pool::numberOfRunningThreads);
// manually sample a value in code at periodic intervals – last resort!
registry.gauge(“gaugeName”, Arrays.asList(“tagKey1”, “tagValue1”, …), 1000);
Spectator分发摘要
分发摘要用于跟踪事件的分布情况。它类似于一个计时器,但更普遍的是,大小不一定是一段时间。例如,分发摘要可用于测量服务器的请求的有效载荷大小。
// the registry will automatically sample this gauge periodically
DistributionSummary ds = registry.distributionSummary("dsName", "tagKey1", "tagValue1", ...);
ds.record(request.sizeInBytes());
指标集:Servo
警告
| 如果您的代码在Java 8上编译,请使用Spectator而不是Servo,因为Spectator注定要从长期来替换Servo。 |
在Servo语言中,监视器是一个命名,键入和标记的配置,而指标表示给定监视器在某个时间点的值。Servo显示器在逻辑上相当于Spectator米。Servo显示器由MonitorRegistry
创建和控制。尽管有上述警告,Servo确实具有比Spectator有米的更广泛的监视器选项。
Spring Cloud集成为您配置可注入的com.netflix.servo.MonitorRegistry
实例。在Servo中创建了相应的Monitor
类型后,记录数据的过程完全类似于Spectator。
更多推荐
所有评论(0)