目录
Spring Cloud为开发人员提供了用于快速构建分布式系统中某些常见模式的工具(例如,配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调产生了样板模式,并且使用Spring云开发人员可以快速支持实现这些模式的服务和应用程序。它们可以在任何分布式环境中正常工作,包括开发人员自己的笔记本电脑,裸机数据中心和受管理的平台,例如Cloud Foundry。
版本:Greenwich.SR5
Cloud Native是一种应用程序开发风格,可鼓励在持续交付和价值驱动型开发领域轻松采用最佳实践。一个相关的学科是构建12要素应用程序,其中开发实践与交付和运营目标保持一致,例如,通过使用声明性编程,管理和监视。Spring Cloud通过多种特定方式促进了这些发展方式。起点是一组功能,分布式系统中的所有组件都需要轻松访问这些功能。
其中Spring Cloud建立在Spring Boot上,涵盖了许多这些功能。Spring Cloud作为两个库提供了更多功能:Spring Cloud上下文和Spring Cloud Commons。Spring Cloud上下文为Spring Cloud应用程序的ApplicationContext
提供了实用程序和特殊服务(引导上下文,加密,刷新作用域和环境端点)。Spring Cloud Commons是在不同的Spring Cloud实现中使用的一组抽象和通用类(例如Spring Cloud Netflix和Spring Cloud Consul)。
如果由于“密钥大小非法”而导致异常,并且使用Sun的JDK,则需要安装Java密码术扩展(JCE)无限强度管辖权策略文件。有关更多信息,请参见以下链接:
将文件解压缩到您使用的JRE / JDK x64 / x86版本的JDK / jre / lib / security文件夹中。
注意 | |
---|---|
Spring Cloud是根据非限制性Apache 2.0许可证发行的。如果您想为文档的这一部分做出贡献或发现错误,可以在github上找到源代码和项目跟踪工具。 |
Spring Boot对于如何使用Spring来构建应用程序有自己的看法。例如,它具有用于公共配置文件的常规位置,并具有用于公共管理和监视任务的端点。Spring Cloud以此为基础,并添加了一些功能,可能系统中的所有组件都将使用或偶尔需要这些功能。
Spring Cloud应用程序通过创建“ bootstrap ”上下文来运行,该上下文是主应用程序的父上下文。它负责从外部源加载配置属性,并负责解密本地外部配置文件中的属性。这两个上下文共享一个Environment
,它是任何Spring应用程序的外部属性的来源。默认情况下,引导程序属性(不是bootstrap.properties
,而是引导程序阶段加载的属性)具有较高的优先级,因此它们不能被本地配置覆盖。
引导上下文使用与主应用程序上下文不同的约定来定位外部配置。可以使用bootstrap.yml
来代替application.yml
(或.properties
),而将引导程序和外部环境的外部配置很好地分开。以下清单显示了一个示例:
bootstrap.yml。
spring: application: name: foo cloud: config: uri: ${SPRING_CONFIG_URI:http://localhost:8888}
如果您的应用程序需要来自服务器的任何特定于应用程序的配置,则最好设置spring.application.name
(在bootstrap.yml
或application.yml
中)。为了将属性spring.application.name
用作应用程序的上下文ID,必须在bootstrap.[properties | yml]
中进行设置。
如果要检索特定的配置文件配置,还应该在bootstrap.[properties | yml]
中设置spring.profiles.active
。
您可以通过设置spring.cloud.bootstrap.enabled=false
来完全禁用引导过程(例如,在系统属性中)。
如果从SpringApplication
或SpringApplicationBuilder
构建应用程序上下文,那么Bootstrap上下文将作为父级添加到该上下文。Spring的一个功能是子上下文从其父级继承属性源和配置文件,因此与构建没有Spring Cloud Config的相同上下文相比,“ 主 ”应用程序上下文包含其他属性源。其他属性来源是:
PropertySourceLocators
并且具有非空属性,则会以高优先级显示可选的CompositePropertySource
。一个示例是Spring Cloud Config服务器中的属性。有关如何自定义此属性源内容的说明,请参见“ 第2.6节“自定义Bootstrap Property源” ”。bootstrap.yml
(或.properties
),则这些属性用于配置Bootstrap上下文。然后,当它们的父级被设置时,它们被添加到子级上下文。它们的优先级低于application.yml
(或.properties
)以及创建Spring Boot应用程序过程中正常添加到子级的任何其他属性源的优先级。有关如何自定义这些属性源内容的说明,请参见“ 第2.3节“更改引导程序Properties”的位置 ”。由于属性源的排序规则,“ bootstrap ”条目优先。但是,请注意,这些不包含来自bootstrap.yml
的任何数据,该数据的优先级非常低,但可用于设置默认值。
您可以通过设置创建的任何ApplicationContext
的父上下文来扩展上下文层次结构,例如,使用其自己的界面或使用SpringApplicationBuilder
便捷方法(parent()
,child()
和sibling()
)。引导上下文是您自己创建的最高级祖先的父级。层次结构中的每个上下文都有其自己的“ bootstrap ”(可能为空)属性源,以避免无意间将价值从父辈提升到子孙后代。如果有配置服务器,则层次结构中的每个上下文原则上也可以具有不同的spring.application.name
,因此也具有不同的远程属性源。正常的Spring应用程序上下文行为规则适用于属性解析:子上下文的属性按名称以及属性源名称覆盖父级属性。(如果子项具有与父项同名的属性源,则子项中不包括来自父项的值)。
请注意,SpringApplicationBuilder
可让您在整个层次结构中共享Environment
,但这不是默认设置。因此,同级上下文尤其不需要具有相同的配置文件或属性源,即使它们可能与其父级共享相同的值。
可以通过设置spring.cloud.bootstrap.name
(默认值:bootstrap
),spring.cloud.bootstrap.location
(默认值:空)或spring.cloud.bootstrap.additional-location
(默认值:空)来指定bootstrap.yml
(或.properties
)位置。 —例如,在系统属性中。这些属性的行为类似于具有相同名称的spring.config.*
变体。使用spring.cloud.bootstrap.location
将替换默认位置,并且仅使用指定的位置。要将位置添加到默认位置列表中,可以使用spring.cloud.bootstrap.additional-location
。实际上,它们是通过在引导程序Environment
中设置这些属性来设置引导程序ApplicationContext
的。如果存在有效的配置文件(通过spring.profiles.active
或通过您正在构建的上下文中的Environment
API),该配置文件中的属性也会被加载,这与常规Spring Boot应用程序中的加载情况相同-例如,从bootstrap-development.properties
中获取development
个人资料。
通过引导上下文添加到应用程序中的属性源通常是“ 远程的 ”(例如,来自Spring Cloud Config Server)。默认情况下,不能在本地覆盖它们。如果要让您的应用程序使用其自己的系统属性或配置文件覆盖远程属性,则远程属性源必须通过设置spring.cloud.config.allowOverride=true
来授予其权限(在本地设置无效)。设置该标志后,将使用两个更细粒度的设置来控制远程属性相对于系统属性和应用程序本地配置的位置:
spring.cloud.config.overrideNone=true
:从任何本地属性源覆盖。spring.cloud.config.overrideSystemProperties=false
:只有系统属性,命令行参数和环境变量(而不是本地配置文件)才应覆盖远程设置。通过将项添加到名为org.springframework.cloud.bootstrap.BootstrapConfiguration
的项下的/META-INF/spring.factories
中,可以将引导上下文设置为执行您喜欢的任何操作。它包含用于创建上下文的Spring @Configuration
类的逗号分隔列表。您可以在此处创建要用于主应用程序上下文进行自动装配的任何beans。@Beans
类型为ApplicationContextInitializer
的特殊合同。如果要控制启动顺序,则可以用@Order
批注标记类(默认顺序为last
)。
警告 | |
---|---|
当添加自定义 |
引导过程结束时,将初始化程序注入到主要的SpringApplication
实例中(这是正常的Spring Boot启动顺序,无论它是作为独立应用程序运行还是部署在应用程序服务器中)。首先,从spring.factories
中找到的类创建引导上下文。然后,在启动之前,将类型为ApplicationContextInitializer
的所有@Beans
添加到主SpringApplication
。
引导过程添加的外部配置的默认属性来源是Spring Cloud Config服务器,但是您可以通过将类型PropertySourceLocator
的beans添加到引导上下文(通过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
的那个,换句话说,就是我们为其提供其他属性源的那个。它已经有其正常的Spring Boot提供的属性源,因此您可以使用这些属性来定位特定于此Environment
的属性源(例如,通过在spring.application.name
上键入它,这与默认设置相同)。 Spring Cloud Config服务器属性源定位符)。
如果您创建一个包含此类的jar,然后添加包含以下内容的META-INF/spring.factories
,则customProperty
PropertySource
会出现在任何在其类路径中包含该jar的应用程序中:
org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomPropertySourceLocator
如果要使用Spring Boot来配置日志设置,则应将此配置放在`bootstrap。[yml | 属性](如果您希望将其应用于所有事件)。
注意 | |
---|---|
为了使Spring Cloud正确初始化日志记录配置,您不能使用自定义前缀。例如,初始化记录系统时,Spring Cloud无法识别使用 |
应用程序侦听EnvironmentChangeEvent
并以几种标准方式对更改做出反应(用户可以通过常规方式将其他ApplicationListeners
作为@Beans
添加)。观察到EnvironmentChangeEvent
时,它会列出已更改的键值,并且应用程序将这些键值用于:
@ConfigurationProperties
beanslogging.level.*
中的所有属性设置记录器级别请注意,默认情况下,Config Client不轮询Environment
中的更改。通常,我们不建议您使用这种方法来检测更改(尽管您可以使用@Scheduled
注释对其进行设置)。如果您具有横向扩展的客户端应用程序,则最好向所有实例广播EnvironmentChangeEvent
,而不是让它们轮询更改(例如,使用Spring Cloud Bus)。
只要您可以实际更改Environment
并发布事件,EnvironmentChangeEvent
就涵盖了一大类刷新用例。请注意,这些API是公共的,并且是核心Spring的一部分)。您可以通过访问/configprops
端点(正常的Spring Boot Actuator功能)来验证更改是否绑定到@ConfigurationProperties
beans。例如,DataSource
可以在运行时更改其maxPoolSize
(由Spring Boot创建的默认DataSource
是@ConfigurationProperties
bean)并动态地增加容量。重新绑定@ConfigurationProperties
并不涵盖另一类用例,在这种情况下,您需要对刷新有更多的控制,并且需要对整个ApplicationContext
进行原子更改。为了解决这些问题,我们有@RefreshScope
。
进行配置更改时,标记为@RefreshScope
的Spring @Bean
将得到特殊处理。此功能解决了状态beans的问题,该状态仅在初始化时才注入配置。例如,如果通过Environment
更改数据库URL时DataSource
具有打开的连接,则您可能希望这些连接的持有者能够完成他们正在做的事情。然后,下次某物从池中借用一个连接时,它将获得一个具有新URL的连接。
有时,甚至可能必须将@RefreshScope
批注应用到只能初始化一次的某些beans上。如果bean是“不可变的”,则必须用@RefreshScope
注释bean或在属性键spring.cloud.refresh.extra-refreshable
下指定类名。
重要 | |
---|---|
如果您自己创建一个 |
刷新作用域beans是惰性代理,它们在使用时(即在调用方法时)进行初始化,并且作用域充当初始化值的缓存。若要强制bean在下一个方法调用上重新初始化,必须使它的缓存条目无效。
RefreshScope
在上下文中是bean,并具有公用的refreshAll()
方法,可通过清除目标缓存来刷新作用域中的所有beans。/refresh
端点公开了此功能(通过HTTP或JMX)。要按名称刷新单个bean,还有一个refresh(String)
方法。
要公开/refresh
端点,您需要在应用程序中添加以下配置:
management: endpoints: web: exposure: include: refresh
注意 | |
---|---|
|
Spring Cloud具有Environment
预处理器,用于在本地解密属性值。它遵循与Config Server相同的规则,并且通过encrypt.*
具有相同的外部配置。因此,您可以使用{cipher}*
形式的加密值,并且只要存在有效密钥,就可以在主应用程序上下文获得Environment
设置之前对它们进行解密。要在应用程序中使用加密功能,您需要在类路径中包含Spring Security RSA(Maven坐标:“ org.springframework.security:spring-security-rsa”),并且还需要JVM中的全功能JCE扩展。
如果由于“密钥大小非法”而导致异常,并且使用Sun的JDK,则需要安装Java密码术扩展(JCE)无限强度管辖权策略文件。有关更多信息,请参见以下链接:
将文件解压缩到您使用的JRE / JDK x64 / x86版本的JDK / jre / lib / security文件夹中。
对于Spring Boot Actuator应用程序,可以使用一些其他管理端点。您可以使用:
POST
到/actuator/env
以更新Environment
并重新绑定@ConfigurationProperties
和日志级别。/actuator/refresh
重新加载引导上下文并刷新@RefreshScope
beans。/actuator/restart
关闭ApplicationContext
并重新启动(默认情况下禁用)。/actuator/pause
和/actuator/resume
用于调用Lifecycle
方法(ApplicationContext
中的stop()
和start()
)。注意 | |
---|---|
如果禁用 |
服务发现,负载平衡和断路器之类的模式将它们带到一个通用的抽象层,可以由所有Spring Cloud客户端使用,而与实现无关(例如,使用Eureka或Consul进行的发现) )。
Spring Cloud 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
来禁用此行为。
注意 | |
---|---|
不再需要 |
公用创建了Spring Boot HealthIndicator
,DiscoveryClient
实现可以通过实现DiscoveryHealthIndicator
来参与。要禁用复合HealthIndicator
,请设置spring.cloud.discovery.client.composite-indicator.enabled=false
。基于DiscoveryClient
的通用HealthIndicator
是自动配置的(DiscoveryClientHealthIndicator
)。要禁用它,请设置spring.cloud.discovery.client.health-indicator.enabled=false
。要禁用DiscoveryClientHealthIndicator
的描述字段,请设置spring.cloud.discovery.client.health-indicator.include-description=false
。否则,它可能会像已卷起的HealthIndicator
中的description
一样冒泡。
DiscoveryClient
接口扩展了Ordered
。当使用多个发现客户端时,这很有用,因为它允许您定义返回的发现客户端的顺序,类似于如何订购由Spring应用程序加载的beans。默认情况下,任何DiscoveryClient
的顺序都设置为0
。如果要为自定义DiscoveryClient
实现设置不同的顺序,则只需覆盖getOrder()
方法,以便它返回适合您的设置的值。除此之外,您可以使用属性来设置Spring Cloud提供的DiscoveryClient
实现的顺序,其中包括ConsulDiscoveryClient
,EurekaDiscoveryClient
和ZookeeperDiscoveryClient
。为此,您只需要将spring.cloud.{clientIdentifier}.discovery.order
(对于Eureka,则为eureka.client.order
)属性设置为所需的值。
Commons现在提供一个ServiceRegistry
接口,该接口提供诸如register(Registration)
和deregister(Registration)
之类的方法,这些方法使您可以提供自定义的注册服务。Registration
是标记界面。
以下示例显示了正在使用的ServiceRegistry
:
@Configuration @EnableDiscoveryClient(autoRegister=false) public class MyConfiguration { private ServiceRegistry registry; public MyConfiguration(ServiceRegistry registry) { this.registry = registry; } // called through some external process, such as an event or a custom actuator endpoint public void register() { Registration registration = constructRegistration(); this.registry.register(registration); } }
每个ServiceRegistry
实现都有自己的Registry
实现。
ZookeeperRegistration
与ZookeeperServiceRegistry
一起使用EurekaRegistration
与EurekaServiceRegistry
一起使用ConsulRegistration
与ConsulServiceRegistry
一起使用如果您使用的是ServiceRegistry
接口,则将需要为使用的ServiceRegistry
实现传递正确的Registry
实现。
默认情况下,ServiceRegistry
实现会自动注册正在运行的服务。要禁用该行为,可以设置:* @EnableDiscoveryClient(autoRegister=false)
以永久禁用自动注册。* spring.cloud.service-registry.auto-registration.enabled=false
通过配置禁用行为。
Spring Cloud Commons提供了一个/service-registry
执行器端点。该端点依赖于Spring应用程序上下文中的Registration
bean。使用GET调用/service-registry
会返回Registration
的状态。对具有JSON正文的同一终结点使用POST会将当前Registration
的状态更改为新值。JSON正文必须包含带有首选值的status
字段。请参阅更新状态时用于允许值的ServiceRegistry
实现的文档以及为状态返回的值。例如,Eureka的受支持状态为UP
,DOWN
,OUT_OF_SERVICE
和UNKNOWN
。
RestTemplate
可以自动配置为在后台使用负载均衡器客户端。要创建负载均衡的RestTemplate
,请创建RestTemplate
@Bean
并使用@LoadBalanced
限定符,如以下示例所示:
@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。
重要 | |
---|---|
为了使用负载均衡的 |
警告 | |
---|---|
如果要使用 |
WebClient
可以自动配置为使用负载均衡器客户端。要创建负载均衡的WebClient
,请创建WebClient.Builder
@Bean
并使用@LoadBalanced
限定符,如以下示例所示:
@Configuration public class MyConfiguration { @Bean @LoadBalanced public WebClient.Builder loadBalancedWebClientBuilder() { return WebClient.builder(); } } public class MyClass { @Autowired private WebClient.Builder webClientBuilder; public Mono<String> doOtherStuff() { return webClientBuilder.build().get().uri("http://stores/stores") .retrieve().bodyToMono(String.class); } }
URI需要使用虚拟主机名(即服务名,而不是主机名)。Ribbon客户端用于创建完整的物理地址。
重要 | |
---|---|
如果要使用 |
提示 | |
---|---|
在下面使用的 |
可以配置负载均衡的RestTemplate
以重试失败的请求。默认情况下,禁用此逻辑。您可以通过在应用程序的类路径中添加Spring重试来启用它。负载平衡的RestTemplate
遵循与重试失败的请求有关的某些Ribbon配置值。您可以使用client.ribbon.MaxAutoRetries
,client.ribbon.MaxAutoRetriesNextServer
和client.ribbon.OkToRetryOnAllOperations
属性。如果要通过对类路径使用Spring重试来禁用重试逻辑,则可以设置spring.cloud.loadbalancer.retry.enabled=false
。有关这些属性的作用的说明,请参见Ribbon文档。
如果要在重试中实现BackOffPolicy
,则需要创建LoadBalancedRetryFactory
类型的bean并覆盖createBackOffPolicy
方法:
@Configuration public class MyConfiguration { @Bean LoadBalancedRetryFactory retryFactory() { return new LoadBalancedRetryFactory() { @Override public BackOffPolicy createBackOffPolicy(String service) { return new ExponentialBackOffPolicy(); } }; } }
注意 | |
---|---|
前面示例中的 |
如果要向重试功能中添加一个或多个RetryListener
实现,则需要创建类型为LoadBalancedRetryListenerFactory
的bean,并返回要用于给定服务的RetryListener
数组,如以下示例所示:
@Configuration public class MyConfiguration { @Bean LoadBalancedRetryListenerFactory retryListenerFactory() { return new LoadBalancedRetryListenerFactory() { @Override public RetryListener[] createRetryListeners(String service) { return new RetryListener[]{new RetryListener() { @Override public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) { //TODO Do you business... return true; } @Override public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { //TODO Do you business... } @Override public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { //TODO Do you business... } }}; } }; } }
如果您想要一个RestTemplate
而不是负载均衡的,请创建一个RestTemplate
bean并注入它。要访问负载均衡的RestTemplate
,请在创建@Bean
时使用@LoadBalanced
限定符,如以下示例所示:
@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("https://example.com", String.class); } }
重要 | |
---|---|
注意,在前面的示例中,在普通的 |
提示 | |
---|---|
如果看到诸如 |
如果要使WebClient
负载不均衡,请创建一个WebClient
bean并注入它。要访问负载均衡的WebClient
,请在创建@Bean
时使用@LoadBalanced
限定符,如以下示例所示:
@Configuration public class MyConfiguration { @LoadBalanced @Bean WebClient.Builder loadBalanced() { return WebClient.builder(); } @Primary @Bean WebClient.Builder webClient() { return WebClient.builder(); } } public class MyClass { @Autowired private WebClient.Builder webClientBuilder; @Autowired @LoadBalanced private WebClient.Builder loadBalanced; public Mono<String> doOtherStuff() { return loadBalanced.build().get().uri("http://stores/stores") .retrieve().bodyToMono(String.class); } public Mono<String> doStuff() { return webClientBuilder.build().get().uri("http://example.com") .retrieve().bodyToMono(String.class); } }
可以将WebClient
配置为使用ReactiveLoadBalancer
。如果将org.springframework.cloud:spring-cloud-loadbalancer
添加到项目中,并且spring-webflux
在类路径中,则会自动配置ReactorLoadBalancerExchangeFilterFunction
。以下示例说明如何配置WebClient
以在后台使用无功负载均衡器:
public class MyClass { @Autowired private ReactorLoadBalancerExchangeFilterFunction lbFunction; public Mono<String> doOtherStuff() { return WebClient.builder().baseUrl("http://stores") .filter(lbFunction) .build() .get() .uri("/stores") .retrieve() .bodyToMono(String.class); } }
URI需要使用虚拟主机名(即服务名,而不是主机名)。ReactorLoadBalancerClient
用于创建完整的物理地址。
如果您的项目中没有org.springframework.cloud:spring-cloud-loadbalancer
,但是确实有spring-cloud-starter-netflix-ribbon,则仍可以将WebClient
与LoadBalancerClient
结合使用。如果spring-webflux
在类路径中,将自动配置LoadBalancerExchangeFilterFunction
。但是请注意,这是在后台使用非反应性客户端。以下示例显示如何配置WebClient
以使用负载均衡器:
public class MyClass { @Autowired private LoadBalancerExchangeFilterFunction lbFunction; public Mono<String> doOtherStuff() { return WebClient.builder().baseUrl("http://stores") .filter(lbFunction) .build() .get() .uri("/stores") .retrieve() .bodyToMono(String.class); } }
URI需要使用虚拟主机名(即服务名,而不是主机名)。LoadBalancerClient
用于创建完整的物理地址。
警告:现在不建议使用此方法。我们建议您将WebFlux与电抗性负载平衡器一起 使用。
您还可以使用@LoadBalancerClient
批注传递您自己的负载平衡器客户端配置,并传递负载平衡器客户端的名称和配置类,如下所示:
@Configuration @LoadBalancerClient(value = "stores", configuration = StoresLoadBalancerClientConfiguration.class) public class MyConfiguration { @Bean @LoadBalanced public WebClient.Builder loadBalancedWebClientBuilder() { return WebClient.builder(); } }
也可以通过@LoadBalancerClients
注释将多个配置(对于一个以上的负载均衡器客户端)一起传递,如下所示:
@Configuration @LoadBalancerClients({@LoadBalancerClient(value = "stores", configuration = StoresLoadBalancerClientConfiguration.class), @LoadBalancerClient(value = "customers", configuration = CustomersLoadBalancerClientConfiguration.class)}) public class MyConfiguration { @Bean @LoadBalanced public WebClient.Builder loadBalancedWebClientBuilder() { return WebClient.builder(); } }
有时,忽略某些命名的网络接口很有用,以便可以将它们从服务发现注册中排除(例如,在Docker容器中运行时)。可以设置正则表达式列表,以使所需的网络接口被忽略。以下配置将忽略docker0
接口以及所有以veth
开头的接口:
application.yml。
spring: cloud: inetutils: ignoredInterfaces: - docker0 - veth.*
您还可以通过使用正则表达式列表来强制仅使用指定的网络地址,如以下示例所示:
bootstrap.yml。
spring: cloud: inetutils: preferredNetworks: - 192.168 - 10.0
您也可以只使用站点本地地址,如以下示例所示:.application.yml
spring: cloud: inetutils: useOnlySiteLocalInterfaces: true
有关构成站点本地地址的详细信息,请参见Inet4Address.html.isSiteLocalAddress()。
Spring Cloud Commons提供了beans用于创建Apache HTTP客户端(ApacheHttpClientFactory
)和OK HTTP客户端(OkHttpClientFactory
)。仅当OK HTTP jar位于类路径上时,才创建OkHttpClientFactory
bean。此外,Spring Cloud Commons提供了beans用于创建两个客户端使用的连接管理器:ApacheHttpClientConnectionManagerFactory
用于Apache HTTP客户端,OkHttpClientConnectionPoolFactory
用于OK HTTP客户端。如果您想自定义在下游项目中如何创建HTTP客户端,则可以提供自己的beans实现。另外,如果您提供类型为HttpClientBuilder
或OkHttpClient.Builder
的bean,则默认工厂将使用这些构建器作为返回到下游项目的构建器的基础。您还可以通过将spring.cloud.httpclientfactories.apache.enabled
或spring.cloud.httpclientfactories.ok.enabled
设置为false
来禁用这些beans的创建。
Spring Cloud Commons提供了一个/features
执行器端点。该端点返回类路径上可用的功能以及是否已启用它们。返回的信息包括功能类型,名称,版本和供应商。
“功能”有两种类型:抽象和命名。
抽象功能是定义接口或抽象类并创建实现的功能,例如DiscoveryClient
,LoadBalancerClient
或LockService
。抽象类或接口用于在上下文中找到该类型的bean。显示的版本为bean.getClass().getPackage().getImplementationVersion()
。
命名功能是没有实现的特定类的功能,例如“ Circuit Breaker”,“ API Gateway”,“ Spring Cloud Bus”等。这些功能需要一个名称和一个bean类型。
任何模块都可以声明任意数量的HasFeature
beans,如以下示例所示:
@Bean public HasFeatures commonsFeatures() { return HasFeatures.abstractFeatures(DiscoveryClient.class, LoadBalancerClient.class); } @Bean public HasFeatures consulFeatures() { return HasFeatures.namedFeatures( new NamedFeature("Spring Cloud Bus", ConsulBusAutoConfiguration.class), new NamedFeature("Circuit Breaker", HystrixCommandAspect.class)); } @Bean HasFeatures localFeatures() { return HasFeatures.builder() .abstractFeature(Foo.class) .namedFeature(new NamedFeature("Bar Feature", Bar.class)) .abstractFeature(Baz.class) .build(); }
这些beans中的每一个都应放入受到适当保护的@Configuration
中。
由于某些用户在设置Spring Cloud应用程序时遇到问题,我们决定添加兼容性验证机制。如果您当前的设置与Spring Cloud要求不兼容,则会断开,并附上一份报告,说明出了什么问题。
目前,我们验证哪个版本的Spring Boot已添加到您的类路径中。
报告范例
*************************** APPLICATION FAILED TO START *************************** Description: Your project setup is incompatible with our requirements due to following reasons: - Spring Boot [2.1.0.RELEASE] is not compatible with this Spring Cloud release train Action: Consider applying the following actions: - Change Spring Boot version to one of the following versions [1.2.x, 1.3.x] . You can find the latest Spring Boot versions here [https://spring.io/projects/spring-boot#learn]. If you want to learn more about the Spring Cloud Release train compatibility, you can visit this page [https://spring.io/projects/spring-cloud#overview] and check the [Release Trains] section.
为了禁用此功能,请将spring.cloud.compatibility-verifier.enabled
设置为false
。如果要覆盖兼容的Spring Boot版本,只需用兼容的Spring Boot版本的逗号分隔列表设置spring.cloud.compatibility-verifier.compatible-boot-versions
属性。
Greenwich SR5
Spring Cloud Config为分布式系统中的外部化配置提供服务器端和客户端支持。使用Config Server,您可以在中心位置管理所有环境中应用程序的外部属性。客户端和服务器上的概念都与Spring Environment
和PropertySource
抽象映射相同,因此它们非常适合Spring应用程序,但可以与以任何语言运行的任何应用程序一起使用。在应用程序从开发人员到测试人员再到生产人员的整个部署过程中,您可以管理这些环境之间的配置,并确保应用程序具有它们迁移时所需的一切。服务器存储后端的默认实现使用git,因此它轻松支持带标签的配置环境版本,并且可以通过各种工具来访问这些内容来管理内容。添加替代实现并将其插入Spring配置很容易。
此快速入门介绍了如何使用Spring Cloud Config服务器的服务器和客户端。
首先,启动服务器,如下所示:
$ cd spring-cloud-config-server $ ../mvnw spring-boot:run
该服务器是Spring Boot应用程序,因此,如果愿意,可以从IDE运行它(主类是ConfigServerApplication
)。
接下来尝试一个客户端,如下所示:
$ curl localhost:8888/foo/development {"name":"foo","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
application
在SpringApplication
中作为spring.config.name
注入(在常规Spring Boot应用中通常是application
),profile
是有效配置文件(或逗号分隔)属性列表),而label
是可选的git标签(默认为master
。)
Spring Cloud Config服务器从各种来源获取远程客户端的配置。以下示例从git存储库(必须提供)中获取配置,如以下示例所示:
spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo
其他来源包括任何与JDBC兼容的数据库,Subversion,Hashicorp Vault,Credhub和本地文件系统。
要在应用程序中使用这些功能,您可以将其构建为依赖于spring-cloud-config-client的Spring Boot应用程序(例如,请参阅config-client或示例应用程序的测试用例)。添加依赖项最方便的方法是使用Spring Boot启动器org.springframework.cloud:spring-cloud-starter-config
。还有一个Maven用户的父pom和BOM(spring-cloud-starter-parent
),以及一个Gradle和Spring CLI用户的Spring IO版本管理属性文件。以下示例显示了典型的Maven配置:
pom.xml。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>{spring-boot-docs-version}</version> <relativePath /> <!-- lookup parent from repository --> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>{spring-cloud-version}</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); } }
当此HTTP服务器运行时,它将从端口8888上的默认本地配置服务器(如果正在运行)中获取外部配置。要修改启动行为,可以使用bootstrap.properties
(类似于application.properties
,但用于应用程序上下文的引导阶段),如以下示例所示:
spring.cloud.config.uri: http://myconfigserver.com
默认情况下,如果未设置应用程序名称,将使用application
。要修改名称,可以将以下属性添加到bootstrap.properties
文件中:
spring.application.name: myapp
注意 | |
---|---|
设置属性 |
引导程序属性在/env
端点中显示为高优先级属性源,如以下示例所示。
$ curl localhost:8080/env { "profiles":[], "configService:https://github.com/spring-cloud-samples/config-repo/bar.properties":{"foo":"bar"}, "servletContextInitParams":{}, "systemProperties":{...}, ... }
名为``configService:<URL of remote repository>/<file name>
的属性源包含值为bar
的foo
属性,并且优先级最高。
注意 | |
---|---|
属性源名称中的URL是git存储库,而不是配置服务器URL。 |
Spring Cloud Config服务器为外部配置(名称/值对或等效的YAML内容)提供了一个基于HTTP资源的API。通过使用@EnableConfigServer
批注,服务器可嵌入到Spring Boot应用程序中。因此,以下应用程序是配置服务器:
ConfigServer.java。
@SpringBootApplication @EnableConfigServer public class ConfigServer { public static void main(String[] args) { SpringApplication.run(ConfigServer.class, args); } }
像所有Spring Boot应用程序一样,它默认在端口8080上运行,但是您可以通过各种方式将其切换到更传统的端口8888。最简单的方法也是设置默认配置存储库,方法是使用spring.config.name=configserver
(在Config Server jar中有configserver.yml
)启动它。另一种方法是使用您自己的application.properties
,如以下示例所示:
application.properties。
server.port: 8888 spring.cloud.config.server.git.uri: file://${user.home}/config-repo
其中${user.home}/config-repo
是包含YAML和属性文件的git存储库。
注意 | |
---|---|
在Windows上,如果文件URL带有驱动器前缀(例如, |
提示 | |
---|---|
以下清单显示了在前面的示例中创建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存储库仅用于测试。您应该使用服务器在生产环境中托管配置存储库。 |
警告 | |
---|---|
如果仅将文本文件保留在其中,则配置存储库的初始克隆可以快速有效。如果存储二进制文件(尤其是大文件),则可能会在首次配置请求时遇到延迟,或者在服务器中遇到内存不足错误。 |
您应该在哪里存储配置服务器的配置数据?控制此行为的策略是服务Environment
对象的EnvironmentRepository
。Environment
是Spring Environment
的域的浅表副本(包括propertySources
作为主要特征)。Environment
资源由三个变量参数化:
{application}
,它映射到客户端的spring.application.name
。{profile}
,它映射到客户端上的spring.profiles.active
(以逗号分隔的列表)。{label}
,这是服务器端功能,标记了一组“版本化”的配置文件。Repository实现通常类似于Spring Boot应用程序,从等于{application}
参数的spring.config.name
和等于{profiles}
参数的spring.profiles.active
加载配置文件。配置文件的优先规则也与常规Spring Boot应用程序中的规则相同:活动配置文件的优先级高于默认设置,并且,如果有多个配置文件,则最后一个优先(与向Map
添加条目类似)。
以下示例客户端应用程序具有此引导程序配置:
bootstrap.yml。
spring: application: name: foo profiles: active: dev,mysql
(与Spring Boot应用程序一样,这些属性也可以由环境变量或命令行参数设置)。
如果存储库基于文件,则服务器从application.yml
(在所有客户端之间共享)和foo.yml
(以foo.yml
优先)创建一个Environment
。如果YAML文件中包含指向Spring配置文件的文档,则将以更高的优先级应用这些文件(按列出的配置文件的顺序)。如果存在特定于配置文件的YAML(或属性)文件,这些文件也将以比默认文件更高的优先级应用。较高的优先级会转换为Environment
中较早列出的PropertySource
。(这些相同的规则适用于独立的Spring Boot应用程序。)
您可以将spring.cloud.config.server.accept-empty设置为false,以便在未找到应用程序的情况下Server返回HTTP 404状态。默认情况下,此标志设置为true。
EnvironmentRepository
的默认实现使用Git后端,这对于管理升级和物理环境以及审核更改非常方便。要更改存储库的位置,可以在Config Server中设置spring.cloud.config.server.git.uri
配置属性(例如,在application.yml
中)。如果您使用file:
前缀进行设置,则它应在本地存储库中运行,以便无需服务器即可快速轻松地开始使用。但是,在那种情况下,服务器直接在本地存储库上运行而无需克隆它(如果它不是裸露的,这并不重要,因为Config Server从不对“远程”存储库进行更改)。要扩展Config Server并使其高度可用,您需要使服务器的所有实例都指向同一存储库,因此仅共享文件系统可以工作。即使在那种情况下,最好对共享文件系统存储库使用ssh:
协议,以便服务器可以克隆它并将本地工作副本用作缓存。
此存储库实现将HTTP资源的{label}
参数映射到git标签(提交ID,分支名称或标记)。如果git分支或标记名称包含斜杠(/
),则应使用特殊字符串(_)
在HTTP URL中指定标签(以避免与其他URL路径产生歧义)。例如,如果标签为foo/bar
,则替换斜杠将产生以下标签:foo(_)bar
。特殊字符串(_)
的包含内容也可以应用于{application}
参数。如果您使用命令行客户端(例如curl),请注意URL中的括号-您应使用单引号('')将其从外壳中移出。
可以通过将git.skipSslValidation
属性设置为true
(默认值为false
)来禁用配置服务器对Git服务器的SSL证书的验证。
spring: cloud: config: server: git: uri: https://example.com/my/repo skipSslValidation: true
您可以配置配置服务器将等待获取HTTP连接的时间(以秒为单位)。使用git.timeout
属性。
spring: cloud: config: server: git: uri: https://example.com/my/repo timeout: 4
Spring Cloud Config服务器支持带有{application}
和{profile}
(如果需要的话还有{label}
)占位符的git存储库URL,但是请记住该标签始终用作git标签。因此,您可以使用类似于以下的结构来支持“ 每个应用程序一个存储库 ”策略:
spring: cloud: config: server: git: uri: https://github.com/myorg/{application}
您也可以使用类似的模式({profile}
)来支持“ 每个配置文件一个存储库 ”策略。
此外,在{application}
参数中使用特殊字符串“(_)”可以启用对多个组织的支持,如以下示例所示:
spring: cloud: config: server: git: uri: https://github.com/{application}
其中在请求时以以下格式提供{application}
:organization(_)application
。
Spring Cloud Config还通过在应用程序和概要文件名称上进行模式匹配来支持更复杂的需求。模式格式是以逗号分隔的{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/*
(在所有配置文件中仅匹配一个名为simple
的应用程序)。在“ 本地 ”库匹配,在所有配置文件(该/*
后缀会自动添加到没有档案资料匹配的任何模式)local
开头的所有应用程序名称。
注意 | |
---|---|
的“ 单行 ”中所使用的短切“ 简单 ”的例子可以只用于如果唯一的属性被设置为URI。如果您需要设置其他任何内容(凭证,模式等),则需要使用完整表格。 |
回购中的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猜测一个包含不以 |
每个存储库还可以选择将配置文件存储在子目录中,用于搜索这些目录的模式可以指定为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: https://git/team-a/config-repo.git team-b: pattern: team-b-* cloneOnStart: false uri: https://git/team-b/config-repo.git team-c: pattern: team-c-* uri: https://git/team-a/config-repo.git
在前面的示例中,服务器在接受任何请求之前会在启动时克隆team-a的config-repo。在请求从存储库进行配置之前,不会克隆所有其他存储库。
注意 | |
---|---|
设置要在Config Server启动时克隆的存储库有助于在Config Server启动时快速识别配置错误的配置源(例如无效的存储库URI)。在未为配置源启用 |
要在远程存储库上使用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位置(例如git@github.com:configuration/cloud-configuration
)时,SSH也应立即可用。重要的是,~/.ssh/known_hosts
文件中应包含Git服务器的条目,并且其格式应为ssh-rsa
。不支持其他格式(例如ecdsa-sha2-nistp256
)。为避免意外,您应确保Git服务器的known_hosts
文件中仅存在一个条目,并且该条目与您提供给配置服务器的URL匹配。如果在URL中使用主机名,则要在known_hosts
文件中完全使用该主机名(而不是IP)。使用JGit访问存储库,因此您找到的任何文档都应该适用。HTTPS代理设置可以在~/.git/config
中设置,也可以(使用与其他JVM进程相同的方式)使用系统属性(-Dhttps.proxyHost
和-Dhttps.proxyPort
)进行设置。
提示 | |
---|---|
如果您不知道 |
Spring Cloud Config服务器还支持AWS CodeCommit身份验证。从命令行使用Git时,AWS CodeCommit使用身份验证帮助程序。该帮助程序未与JGit库一起使用,因此,如果Git URI与AWS CodeCommit模式匹配,则会为AWS CodeCommit创建一个JGit CredentialProvider。AWS CodeCommit URI遵循以下模式://git-codecommit.${AWS_REGION}.amazonaws.com/${repopath}。
如果您提供带有AWS CodeCommit URI的用户名和密码,则它们必须是提供对存储库访问权限的AWS accessKeyId和secretAccessKey。如果您未指定用户名和密码,则使用AWS Default Credential Provider链检索accessKeyId和secretAccessKey 。
如果您的Git URI与CodeCommit URI模式(如前所示)匹配,则必须在用户名和密码或默认凭据提供商链支持的位置之一中提供有效的AWS凭据。AWS EC2实例可以将IAM角色用于EC2实例。
注意 | |
---|---|
|
默认情况下,当使用SSH URI连接到Git存储库时,Spring Cloud Config服务器使用的JGit库使用SSH配置文件,例如~/.ssh/known_hosts
和/etc/ssh/ssh_config
。在Cloud Foundry之类的云环境中,本地文件系统可能是临时的,或者不容易访问。在这种情况下,可以使用Java属性设置SSH配置。为了激活基于属性的SSH配置,必须将spring.cloud.config.server.git.ignoreLocalSshSettings
属性设置为true
,如以下示例所示:
spring: cloud: config: server: git: uri: git@gitserver.com:team/repo1.git ignoreLocalSshSettings: true hostKey: someHostKey hostKeyAlgorithm: ssh-rsa privateKey: | -----BEGIN RSA PRIVATE KEY----- MIIEpgIBAAKCAQEAx4UbaDzY5xjW6hc9jwN0mX33XpTDVW9WqHp5AKaRbtAC3DqX IXFMPgw3K45jxRb93f8tv9vL3rD9CUG1Gv4FM+o7ds7FRES5RTjv2RT/JVNJCoqF ol8+ngLqRZCyBtQN7zYByWMRirPGoDUqdPYrj2yq+ObBBNhg5N+hOwKjjpzdj2Ud 1l7R+wxIqmJo1IYyy16xS8WsjyQuyC0lL456qkd5BDZ0Ag8j2X9H9D5220Ln7s9i oezTipXipS7p7Jekf3Ywx6abJwOmB0rX79dV4qiNcGgzATnG1PkXxqt76VhcGa0W DDVHEEYGbSQ6hIGSh0I7BQun0aLRZojfE3gqHQIDAQABAoIBAQCZmGrk8BK6tXCd fY6yTiKxFzwb38IQP0ojIUWNrq0+9Xt+NsypviLHkXfXXCKKU4zUHeIGVRq5MN9b BO56/RrcQHHOoJdUWuOV2qMqJvPUtC0CpGkD+valhfD75MxoXU7s3FK7yjxy3rsG EmfA6tHV8/4a5umo5TqSd2YTm5B19AhRqiuUVI1wTB41DjULUGiMYrnYrhzQlVvj 5MjnKTlYu3V8PoYDfv1GmxPPh6vlpafXEeEYN8VB97e5x3DGHjZ5UrurAmTLTdO8 +AahyoKsIY612TkkQthJlt7FJAwnCGMgY6podzzvzICLFmmTXYiZ/28I4BX/mOSe pZVnfRixAoGBAO6Uiwt40/PKs53mCEWngslSCsh9oGAaLTf/XdvMns5VmuyyAyKG ti8Ol5wqBMi4GIUzjbgUvSUt+IowIrG3f5tN85wpjQ1UGVcpTnl5Qo9xaS1PFScQ xrtWZ9eNj2TsIAMp/svJsyGG3OibxfnuAIpSXNQiJPwRlW3irzpGgVx/AoGBANYW dnhshUcEHMJi3aXwR12OTDnaLoanVGLwLnkqLSYUZA7ZegpKq90UAuBdcEfgdpyi PhKpeaeIiAaNnFo8m9aoTKr+7I6/uMTlwrVnfrsVTZv3orxjwQV20YIBCVRKD1uX VhE0ozPZxwwKSPAFocpyWpGHGreGF1AIYBE9UBtjAoGBAI8bfPgJpyFyMiGBjO6z FwlJc/xlFqDusrcHL7abW5qq0L4v3R+FrJw3ZYufzLTVcKfdj6GelwJJO+8wBm+R gTKYJItEhT48duLIfTDyIpHGVm9+I1MGhh5zKuCqIhxIYr9jHloBB7kRm0rPvYY4 VAykcNgyDvtAVODP+4m6JvhjAoGBALbtTqErKN47V0+JJpapLnF0KxGrqeGIjIRV cYA6V4WYGr7NeIfesecfOC356PyhgPfpcVyEztwlvwTKb3RzIT1TZN8fH4YBr6Ee KTbTjefRFhVUjQqnucAvfGi29f+9oE3Ei9f7wA+H35ocF6JvTYUsHNMIO/3gZ38N CPjyCMa9AoGBAMhsITNe3QcbsXAbdUR00dDsIFVROzyFJ2m40i4KCRM35bC/BIBs q0TY3we+ERB40U8Z2BvU61QuwaunJ2+uGadHo58VSVdggqAo0BSkH58innKKt96J 69pcVH/4rmLbXdcmNYGm6iu+MlPQk4BUZknHSmVHIFdJ0EPupVaQ8RHT -----END RSA PRIVATE KEY-----
下表描述了SSH配置属性。
表5.1。SSH配置Properties
Property名称 | 备注 |
---|---|
ignoreLocalSshSettings | 如果为 |
私钥 | 有效的SSH私钥。如果 |
hostKey | 有效的SSH主机密钥。如果还设置了 |
hostKeyAlgorithm |
|
strictHostKeyChecking |
|
knownHostsFile | 自定义 |
preferredAuthentications | 覆盖服务器身份验证方法顺序。如果服务器在 |
Spring Cloud Config服务器还支持带有{application}
和{profile}
(如果需要的话还有{label}
)占位符的搜索路径,如以下示例所示:
spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo searchPaths: '{application}'
上面的清单导致在存储库中搜索与目录(以及顶层)同名的文件。通配符在带有占位符的搜索路径中也有效(搜索中包括任何匹配的目录)。
如前所述,Spring Cloud Config服务器会复制远程git存储库,以防本地副本变脏(例如,操作系统进程更改了文件夹内容),使得Spring Cloud Config服务器无法从远程更新本地副本。资料库。
要解决此问题,有一个force-pull
属性,如果本地副本脏了,则可以使Spring Cloud Config服务器从远程存储库强制拉出,如以下示例所示:
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: https://git/team-a/config-repo.git force-pull: true team-b: pattern: team-b-* uri: https://git/team-b/config-repo.git force-pull: true team-c: pattern: team-c-* uri: https://git/team-a/config-repo.git
注意 | |
---|---|
|
由于Spring Cloud Config服务器在检出分支到本地存储库(例如,通过标签获取属性)后具有远程git存储库的克隆,因此它将永久保留该分支,直到下一个服务器重新启动(这将创建新的本地存储库)。因此,可能会删除远程分支,但仍可获取其本地副本。而且,如果Spring Cloud Config服务器客户端服务以--spring.cloud.config.label=deletedRemoteBranch,master
开头,它将从deletedRemoteBranch
本地分支获取属性,而不是从master
获取属性。
为了使本地存储库分支保持整洁并保持远程状态-可以设置deleteUntrackedBranches
属性。这将使Spring Cloud Config服务器从本地存储库中强制删除未跟踪的分支。例:
spring: cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo deleteUntrackedBranches: true
注意 | |
---|---|
|
警告 | |
---|---|
使用基于VCS的后端(git,svn),文件被检出或克隆到本地文件系统。默认情况下,它们以 |
Config Server中还有一个“ 本机 ”配置文件,该配置文件不使用Git,而是从本地类路径或文件系统(您要使用spring.cloud.config.server.native.searchLocations
指向的任何静态URL)加载配置文件。要使用本机配置文件,请使用spring.profiles.active=native
启动Config Server。
注意 | |
---|---|
请记住对文件资源使用 |
警告 | |
---|---|
|
提示 | |
---|---|
文件系统后端非常适合快速入门和测试。要在生产环境中使用它,您需要确保文件系统可靠并且可以在Config Server的所有实例之间共享。 |
搜索位置可以包含{application}
,{profile}
和{label}
的占位符。这样,您可以隔离路径中的目录并选择一种对您有意义的策略(例如,每个应用程序的子目录或每个配置文件的子目录)。
如果在搜索位置中不使用占位符,则此存储库还将HTTP资源的{label}
参数附加到搜索路径上的后缀,因此将从每个搜索位置和与该名称相同的子目录加载属性文件。标签(在Spring环境中,带有标签的属性优先)。因此,没有占位符的默认行为与添加以/{label}/
结尾的搜索位置相同。例如,file:/tmp/config
与file:/tmp/config,file:/tmp/config/{label}
相同。可以通过设置spring.cloud.config.server.native.addLabelLocations=false
来禁用此行为。
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属性:
名称 | 默认值 |
---|---|
host | 127.0.0.1 |
port | 8200 |
scheme | http |
backend | secret |
defaultKey | application |
profileSeparator | , |
kvVersion | 1 |
skipSslValidation | false |
timeout | 5 |
namespace | null |
重要 | |
---|---|
上表中的所有属性必须以 |
所有可配置的属性都可以在org.springframework.cloud.config.server.environment.VaultEnvironmentProperties
中找到。
Vault 0.10.0引入了版本化的键值后端(k / v后端版本2),该后端公开了与早期版本不同的API,现在它需要在安装路径和实际上下文路径之间使用data/
并包装data
对象中的秘密。设置kvVersion=2
将考虑到这一点。
(可选)支持Vault企业版X-Vault-Namespace
标头。要将其发送到Vault,请设置namespace
属性。
在配置服务器运行时,您可以向服务器发出HTTP请求以从Vault后端检索值。为此,您需要Vault服务器的令牌。
首先,将一些数据放入您的Vault中,如以下示例所示:
$ vault kv put secret/application foo=bar baz=bam $ vault kv put 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" } } ] }
使用Vault时,可以为您的应用程序提供多个属性源。例如,假设您已将数据写入Vault中的以下路径:
secret/myApp,dev secret/myApp secret/application,dev secret/application
写入secret/application
的Properties对使用Config Server的所有应用程序均可用。名称为myApp
的应用程序将具有写入secret/myApp
和secret/application
的所有属性。当myApp
启用了dev
配置文件时,写入上述所有路径的属性将可用,列表中第一个路径中的属性优先于其他属性。
配置服务器可以通过HTTP或HTTPS代理访问Git或Vault后端。通过proxy.http
和proxy.https
下的设置,可以为Git或Vault控制此行为。这些设置是针对每个存储库的,因此,如果您使用组合环境存储库,则必须分别为组合中的每个后端配置代理设置。如果使用的网络需要HTTP和HTTPS URL分别使用代理服务器,则可以为单个后端配置HTTP和HTTPS代理设置。
下表描述了HTTP和HTTPS代理的代理配置属性。所有这些属性都必须以proxy.http
或proxy.https
作为前缀。
表5.2。代理配置Properties
Property名称 | 备注 |
---|---|
主办 | 代理的主机。 |
港口 | 用于访问代理的端口。 |
nonProxyHosts | 配置服务器应在代理外部访问的所有主机。如果同时为 |
用户名 | 用来验证代理的用户名。如果同时为 |
密码 | 用来验证代理的密码。如果同时为 |
以下配置使用HTTPS代理访问Git存储库。
spring: profiles: active: git cloud: config: server: git: uri: https://github.com/spring-cloud-samples/config-repo proxy: https: host: my-proxy.host.io password: myproxypassword port: '3128' username: myproxyusername nonProxyHosts: example.com
所有应用程序之间的共享配置根据您采用的方法而异,如以下主题所述:
使用基于文件(git,svn和本机)的存储库,所有客户端应用程序之间共享文件名称为application*
(application.properties
,application.yml
,application-*.properties
等)的资源。您可以使用具有这些文件名的资源来配置全局默认值,并在必要时使它们被应用程序特定的文件覆盖。
#_property_overrides [属性覆盖]功能也可以用于设置全局默认值,允许使用占位符应用程序在本地覆盖它们。
提示 | |
---|---|
使用“ 本机 ”配置文件(本地文件系统后端),您应该使用不属于服务器自身配置的显式搜索位置。否则,默认搜索位置中的 |
将Vault用作后端时,可以通过将配置放在secret/application
中来与所有应用程序共享配置。例如,如果您运行以下Vault命令,则所有使用配置服务器的应用程序都将具有可用的属性foo
和baz
:
$ vault write secret/application foo=bar baz=bam
将CredHub用作后端时,可以通过将配置放在/application/
中或将其放在应用程序的default
配置文件中来与所有应用程序共享配置。例如,如果您运行以下CredHub命令,则使用配置服务器的所有应用程序将具有对它们可用的属性shared.color1
和shared.color2
:
credhub set --name "/application/profile/master/shared" --type=json value: {"shared.color1": "blue", "shared.color": "red"}
credhub set --name "/my-app/default/master/more-shared" --type=json value: {"shared.word1": "hello", "shared.word2": "world"}
Spring Cloud Config服务器支持JDBC(关系数据库)作为配置属性的后端。您可以通过向类路径中添加spring-jdbc
并使用jdbc
配置文件或添加类型为JdbcEnvironmentRepository
的bean来启用此功能。如果您在类路径上包括正确的依赖项(有关更多详细信息,请参见用户指南),Spring Boot将配置数据源。
数据库需要有一个名为PROPERTIES
的表,该表具有名为APPLICATION
,PROFILE
和LABEL
的列(通常具有Environment
的含义),以及KEY
和VALUE
,用于Properties
样式的键和值对。Java中所有字段的类型均为String,因此您可以根据需要将它们设置为VARCHAR
。Property值的行为与来自名为{application}-{profile}.properties
的Spring Boot属性文件的值的行为相同,包括所有加密和解密,它们将用作后处理步骤(也就是说,在存储库中直接执行)。
Spring Cloud Config服务器支持CredHub作为配置属性的后端。您可以通过向Spring CredHub添加依赖项来启用此功能。
pom.xml。
<dependencies> <dependency> <groupId>org.springframework.credhub</groupId> <artifactId>spring-credhub-starter</artifactId> </dependency> </dependencies>
以下配置使用双向TLS访问CredHub:
spring: profiles: active: credhub cloud: config: server: credhub: url: https://credhub:8844
属性应存储为JSON,例如:
credhub set --name "/demo-app/default/master/toggles" --type=json value: {"toggle.button": "blue", "toggle.link": "red"}
credhub set --name "/demo-app/default/master/abs" --type=json value: {"marketing.enabled": true, "external.enabled": false}
名称为spring.cloud.config.name=demo-app
的所有客户端应用程序将具有以下属性:
{ toggle.button: "blue", toggle.link: "red", marketing.enabled: true, external.enabled: false }
注意 | |
---|---|
如果未指定配置文件,将使用 |
您可以使用UAA作为提供程序通过OAuth 2.0进行身份验证。
pom.xml。
<dependencies> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> </dependency> </dependencies>
以下配置使用OAuth 2.0和UAA访问CredHub:
spring: profiles: active: credhub cloud: config: server: credhub: url: https://credhub:8844 oauth2: registration-id: credhub-client security: oauth2: client: registration: credhub-client: provider: uaa client-id: credhub_config_server client-secret: asecret authorization-grant-type: client_credentials provider: uaa: token-uri: https://uaa:8443/oauth/token
注意 | |
---|---|
使用的UAA客户ID的范围应为 |
在某些情况下,您可能希望从多个环境存储库中提取配置数据。为此,您可以在配置服务器的应用程序属性或YAML文件中启用composite
配置文件。例如,如果要从Subversion存储库以及两个Git存储库中提取配置数据,则可以为配置服务器设置以下属性:
spring: profiles: active: composite cloud: config: server: composite: - type: svn uri: file:///path/to/svn/repo - type: git uri: file:///path/to/rex/git/repo - type: git uri: file:///path/to/walter/git/repo
使用此配置,优先级由composite
键下的存储库列出顺序确定。在上面的示例中,首先列出了Subversion存储库,因此在Subversion存储库中找到的值将覆盖在一个Git存储库中为同一属性找到的值。在rex
Git存储库中找到的值将在walter
Git存储库中为相同属性找到的值之前使用。
如果只想从每种不同类型的存储库中提取配置数据,则可以在配置服务器的应用程序属性或YAML文件中启用相应的配置文件,而不启用composite
配置文件。例如,如果要从单个Git存储库和单个HashiCorp Vault服务器中提取配置数据,则可以为配置服务器设置以下属性:
spring: profiles: active: git, vault cloud: config: server: git: uri: file:///path/to/git/repo order: 2 vault: host: 127.0.0.1 port: 8200 order: 1
使用此配置,可以通过order
属性确定优先级。您可以使用order
属性为所有存储库指定优先级顺序。order
属性的数值越低,优先级越高。存储库的优先级顺序有助于解决包含相同属性值的存储库之间的任何潜在冲突。
注意 | |
---|---|
如果您的复合环境包括上一个示例中的Vault服务器,则在对配置服务器的每个请求中都必须包含Vault令牌。请参阅Vault后端。 |
注意 | |
---|---|
从环境存储库中检索值时,任何类型的故障都会导致整个组合环境的故障。 |
注意 | |
---|---|
使用复合环境时,所有存储库都包含相同的标签很重要。如果您的环境与前面的示例中的环境类似,并且您请求带有 |
Config Server具有“ 替代 ”功能,使操作员可以为所有应用程序提供配置属性。应用程序使用常规的Spring Boot钩子不会意外更改重写的属性。要声明覆盖,请将名称/值对的映射添加到spring.cloud.config.server.overrides
,如以下示例所示:
spring: cloud: config: server: overrides: foo: bar
前面的示例使作为配置客户端的所有应用程序读取foo=bar
,而与它们自己的配置无关。
注意 | |
---|---|
配置系统不能强制应用程序以任何特定方式使用配置数据。因此,覆盖无法执行。但是,它们确实为Spring Cloud Config客户端提供了有用的默认行为。 |
提示 | |
---|---|
通常,可以使用反斜杠( |
注意 | |
---|---|
在YAML中,您不需要转义反斜杠本身。但是,在属性文件中,在服务器上配置替代时,确实需要转义反斜杠。 |
您可以通过在远程存储库中设置spring.cloud.config.overrideNone=true
标志(默认为false),使客户端中所有替代的优先级更像默认值,让应用程序在环境变量或系统属性中提供自己的值。
Config Server带有运行状况指示器,用于检查配置的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
禁用运行状况指示器。
您可以用对您有意义的任何方式来保护Config Server(从物理网络安全到OAuth2承载令牌),因为Spring Security和Spring Boot为许多安全措施提供了支持。
要使用默认的Spring Boot配置的HTTP基本安全性,请在类路径上包含Spring Security(例如,通过spring-boot-starter-security
)。缺省值为user
用户名和随机生成的密码。随机密码在实践中没有用,因此我们建议您配置密码(通过设置spring.security.user.password
)并对其进行加密(有关如何操作的说明,请参见下文)。
重要 | |
---|---|
要使用加密和解密功能,您需要在JVM中安装完整功能的JCE(默认情况下不包括)。您可以从Oracle 下载“ Java密码学扩展(JCE)无限强度辖区策略文件 ”并按照安装说明进行操作(本质上,您需要用下载的JRE lib / security目录替换这两个策略文件)。 |
如果远程属性源包含加密的内容(值以{cipher}
开头),则在通过HTTP发送给客户端之前,将对它们进行解密。此设置的主要优点是,当属性值处于“ 静止 ”状态时(例如,在git存储库中),不需要使用纯文本格式。如果无法解密某个值,则将其从属性源中删除,并使用相同的密钥添加一个附加属性,但附加前缀为invalid
和一个表示“ 不适用 ”的值(通常为<n/a>
)。这很大程度上是为了防止将密文用作密码并意外泄漏。
如果为配置客户端应用程序设置了远程配置存储库,则它可能包含与以下内容类似的application.yml
:
application.yml。
spring: datasource: username: dbuser password: '{cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ'
.properties文件中的加密值不能用引号引起来。否则,该值不会解密。以下示例显示了有效的值:
application.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编码的字符,则应对 |
提示 | |
---|---|
确保不要在加密值中包含任何curl命令统计信息。将值输出到文件可以帮助避免此问题。 |
也可以通过/decrypt
使用反向操作(前提是服务器配置了对称密钥或完整密钥对),如以下示例所示:
$ curl localhost:8888/decrypt -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda mysecret
提示 | |
---|---|
如果使用curl进行测试,请使用 |
在将加密的值放入YAML或属性文件之前,以及将其提交并将其推送到远程(可能不安全)存储之前,请获取加密的值并添加{cipher}
前缀。
/encrypt
和/decrypt
端点也都接受/*/{application}/{profiles}
形式的路径,当客户端调用主应用程序时,可用于按应用程序(名称)和配置文件控制密码。环境资源。
注意 | |
---|---|
要以这种精细的方式控制密码,您还必须提供类型为 |
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密钥对)。非对称选择在安全性方面优越,但使用对称密钥通常更方便,因为它是在bootstrap.properties
中配置的单个属性值。
要配置对称密钥,您需要将encrypt.key
设置为秘密字符串(或使用ENCRYPT_KEY
环境变量将其保留在纯文本配置文件之外)。
注意 | |
---|---|
您无法使用 |
要配置非对称密钥,请使用密钥库(例如,由JDK附带的keytool
实用程序创建的密钥库)。密钥库属性为encrypt.keyStore.*
,其中*
等于
Property | 描述 |
---|---|
| Contains a |
| Holds the password that unlocks the keystore |
| Identifies which key in the store to use |
| The type of KeyStore to create. Defaults to |
加密是使用公钥完成的,解密需要私钥。因此,原则上,如果只想加密(并准备用私钥在本地解密值),则只能在服务器中配置公钥。实际上,您可能不希望在本地进行解密,因为它会将密钥管理过程分布在所有客户端上,而不是将其集中在服务器上。另一方面,如果您的配置服务器相对不安全并且只有少数客户端需要加密的属性,那么它可能是一个有用的选项。
要创建用于测试的密钥库,可以使用类似于以下内容的命令:
$ 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
注意 | |
---|---|
使用JDK 11或更高版本时,使用上述命令时可能会收到以下警告。在这种情况下,您可能需要确保 |
Warning: Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -keypass value.
将server.jks
文件放入类路径中(例如),然后在您的bootstrap.yml
中,为Config Server创建以下设置:
encrypt: keyStore: location: classpath:/server.jks password: letmein alias: mytestkey secret: changeme
除了加密属性值中的{cipher}
前缀外,Config Server在(Base64编码的)密文开始之前查找零个或多个{name:value}
前缀。密钥被传递到TextEncryptorLocator
,后者可以执行为密码找到TextEncryptor
所需的任何逻辑。如果已配置密钥库(encrypt.keystore.location
),则默认定位器将查找具有key
前缀提供的别名的密钥,其密文类似于以下内容:
foo: bar: `{cipher}{key:testkey}...`
定位器查找名为“ testkey”的键。也可以通过在前缀中使用{secret:…}
值来提供机密。但是,如果未提供,则默认为使用密钥库密码(这是在构建密钥库且未指定密钥时得到的密码)。如果确实提供了机密,则还应该使用自定义SecretLocator
对机密进行加密。
当密钥仅用于加密几个字节的配置数据时(也就是说,它们未在其他地方使用),从密码的角度讲,几乎不需要旋转密钥。但是,您有时可能需要更改密钥(例如,在发生安全漏洞时)。在这种情况下,所有客户端都需要更改其源配置文件(例如,在git中),并在所有密码中使用新的{key:…}
前缀。请注意,客户端需要首先检查Config Server密钥库中的密钥别名是否可用。
提示 | |
---|---|
如果要让Config Server处理所有加密以及解密,则还可以将 |
来自环境端点的默认JSON格式非常适合Spring应用程序使用,因为它直接映射到Environment
抽象。如果愿意,可以通过在资源路径中添加后缀(“ .yml”,“。yaml”或“ .properties”)来使用与YAML或Java属性相同的数据。对于不关心JSON终结点的结构或它们提供的额外元数据的应用程序来说,这可能很有用(例如,不使用Spring的应用程序可能会受益于此方法的简单性)。
YAML和属性表示形式还有一个附加标志(提供为名为resolvePlaceholders
的布尔查询参数),用于指示应在输出中解析源文档中的占位符(以标准Spring ${…}
格式)在渲染之前,如果可能的话。对于不了解Spring占位符约定的消费者来说,这是一个有用的功能。
注意 | |
---|---|
使用YAML或属性格式存在一些限制,主要是与元数据的丢失有关。例如,JSON被构造为属性源的有序列表,其名称与该源相关。即使值的来源有多个来源,YAML和属性形式也会合并到一个映射中,并且原始来源文件的名称也会丢失。同样,YAML表示也不一定是后备存储库中YAML源的忠实表示。它由一系列平面属性来源构成,并且必须对密钥的形式进行假设。 |
除了使用Environment
抽象(或YAML或属性格式的抽象表示之一)之外,您的应用程序可能需要根据其环境量身定制的通用纯文本配置文件。Config Server通过位于/{application}/{profile}/{label}/{path}
的附加终结点提供了这些终结点,其中application
,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; }
注意 | |
---|---|
与用于环境配置的源文件一样, |
注意 | |
---|---|
如果不想提供 |
Config Server最好作为独立应用程序运行。但是,如果需要,可以将其嵌入另一个应用程序。为此,请使用@EnableConfigServer
批注。在这种情况下,名为spring.cloud.config.server.bootstrap
的可选属性会很有用。它是一个标志,用于指示服务器是否应从其自己的远程存储库中进行配置。默认情况下,该标志为关闭状态,因为它会延迟启动。但是,当嵌入到另一个应用程序中时,以与其他任何应用程序相同的方式进行初始化是有意义的。将spring.cloud.config.server.bootstrap
设置为true
时,还必须使用复合环境存储库配置。例如
spring: application: name: configserver profiles: active: composite cloud: config: server: composite: - type: native search-locations: ${HOME}/Desktop/config bootstrap: true
注意 | |
---|---|
如果使用引导标志,则配置服务器需要在 |
要更改服务器端点的位置,可以(可选)设置spring.cloud.config.server.prefix
(例如,/config
)以在前缀下提供资源。前缀应以/
开头,但不能以/
结尾。它应用于Config Server中的@RequestMappings
(即,在Spring Boot server.servletPath
和server.contextPath
前缀之下)。
如果要直接从后端存储库(而不是从配置服务器)读取应用程序的配置,则基本上需要没有端点的嵌入式配置服务器。您可以不使用@EnableConfigServer
注释(设置为spring.cloud.config.server.bootstrap=true
)来完全关闭端点。
许多源代码存储库提供程序(例如Github,Gitlab,Gitea,Gitee,Gogs或Bitbucket)都通过Webhook通知您存储库中的更改。您可以通过提供者的用户界面将Webhook配置为URL和您感兴趣的一组事件。例如,Github使用POST到Webhook,其JSON主体包含提交列表和设置为push
的标头(X-Github-Event
)。如果在spring-cloud-config-monitor
库上添加依赖项并在Config Server中激活Spring Cloud Bus,则会启用/monitor
端点。
激活Webhook后,配置服务器将发送一个针对它认为可能已更改的应用程序的RefreshRemoteApplicationEvent
。变化检测可以被策略化。但是,默认情况下,它会查找与应用程序名称匹配的文件中的更改(例如,foo.properties
面向foo
应用程序,而application.properties
面向所有应用程序)。当您要覆盖此行为时,使用的策略是PropertyPathNotificationExtractor
,该策略接受请求标头和正文作为参数,并返回已更改文件路径的列表。
默认配置可以与Github,Gitlab,Gitea,Gitee,Gogs或Bitbucket一起使用。除了来自Github,Gitlab,Gitee或Bitbucket的JSON通知之外,您还可以通过使用path={application}
模式的形式编码的正文参数POST到/monitor
来触发更改通知。这样做会向匹配{application}
模式(可以包含通配符)的应用程序广播。
注意 | |
---|---|
仅当在配置服务器和客户端应用程序中都激活了 |
注意 | |
---|---|
默认配置还检测本地git存储库中的文件系统更改。在这种情况下,不使用Webhook。但是,一旦您编辑配置文件,就会广播刷新。 |
Spring Boot应用程序可以立即利用Spring Config Server(或应用程序开发人员提供的其他外部属性源)。它还选择了与Environment
更改事件相关的一些其他有用功能。
在类路径上具有Spring Cloud Config客户端的任何应用程序的默认行为如下:配置客户端启动时,它将绑定到配置服务器(通过spring.cloud.config.uri
引导程序配置属性)并初始化Spring Environment
(带有远程资源来源)。
此行为的最终结果是,所有要使用Config Server的客户端应用程序都需要一个bootstrap.yml
(或环境变量),其服务器地址设置为spring.cloud.config.uri
(默认为“ http:// localhost” :8888“)。
如果使用DiscoveryClient
实现,例如Spring Cloud Netflix和Eureka Service Discovery或Spring Cloud Consul,则可以让Config Server向Discovery Service注册。但是,在默认的“ Config First ”模式下,客户端无法利用注册。
如果您更喜欢使用DiscoveryClient
来查找配置服务器,则可以通过设置spring.cloud.config.discovery.enabled=true
(默认值为false
)来进行。这样做的最终结果是,所有客户端应用程序都需要具有适当发现配置的bootstrap.yml
(或环境变量)。例如,对于Spring Cloud Netflix,您需要定义Eureka服务器地址(例如,在eureka.client.serviceUrl.defaultZone
中)。使用此选项的价格是启动时需要进行额外的网络往返,以查找服务注册。好处是,只要发现服务是固定点,配置服务器就可以更改其坐标。默认服务ID是configserver
,但是您可以通过设置spring.cloud.config.discovery.serviceId
在客户端上(以及在服务器上,以一种通常的服务方式,例如通过设置spring.application.name
)来更改该ID。
发现客户端实现均支持某种元数据映射(例如,对于Eureka,我们有eureka.instance.metadataMap
)。Config Server的某些其他属性可能需要在其服务注册元数据中进行配置,以便客户端可以正确连接。如果Config Server受HTTP Basic保护,则可以将凭据配置为user
和password
。另外,如果Config Server具有上下文路径,则可以设置configPath
。例如,以下YAML文件适用于作为Eureka客户端的Config Server:
bootstrap.yml。
eureka: instance: ... metadataMap: user: osufhalskjrtl password: lviuhlszvaorhvlo5847 configPath: /config
在某些情况下,如果服务无法连接到Config Server,您可能希望启动失败。如果这是期望的行为,请设置引导程序配置属性spring.cloud.config.fail-fast=true
,以使客户端因Exception而停止。
如果您希望配置服务器在您的应用程序启动时偶尔会不可用,则可以使其在失败后继续尝试。首先,您需要设置spring.cloud.config.fail-fast=true
。然后,您需要将spring-retry
和spring-boot-starter-aop
添加到类路径中。默认行为是重试六次,初始回退间隔为1000ms,随后的回退的指数乘数为1.1。您可以通过设置spring.cloud.config.retry.*
配置属性来配置这些属性(和其他属性)。
提示 | |
---|---|
要完全控制重试行为,请添加ID为 |
Config Service提供来自/{application}/{profile}/{label}
的属性源,其中客户端应用程序中的默认绑定如下:
${spring.application.name}
${spring.profiles.active}
(实际上是Environment.getActiveProfiles()
)注意 | |
---|---|
设置属性 |
您可以通过设置spring.cloud.config.*
(其中*
为name
,profile
或label
)来覆盖所有参数。label
对于回滚到以前的配置版本很有用。使用默认的Config Server实现,它可以是git标签,分支名称或提交ID。标签也可以以逗号分隔的列表形式提供。在这种情况下,列表中的项目将一一尝试直到成功为止。在要素分支上工作时,此行为可能很有用。例如,您可能想使配置标签与分支对齐,但使其成为可选(在这种情况下,请使用spring.cloud.config.label=myfeature,develop
)。
为确保在部署了Config Server的多个实例时并希望不时有一个或多个实例不可用时的高可用性,可以指定多个URL(作为spring.cloud.config.uri
属性下的逗号分隔列表)或您的所有实例都在服务注册表中注册,例如Eureka(如果使用Discovery-First Bootstrap模式)。请注意,只有在未运行Config Server时(即,应用程序退出时)或发生连接超时时,这样做才能确保高可用性。例如,如果Config Server返回500(内部服务器错误)响应,或者Config Client从Config Server收到401(由于凭据错误或其他原因),则Config Client不会尝试从其他URL提取属性。此类错误表示用户问题,而不是可用性问题。
如果您在Config Server上使用HTTP基本安全性,则仅当将凭据嵌入在spring.cloud.config.uri
属性下指定的每个URL中时,当前才有可能支持per-Config Server身份验证凭据。如果使用任何其他类型的安全性机制,则(当前)不能支持每台配置服务器的身份验证和授权。
如果要配置超时阈值:
spring.cloud.config.request-read-timeout
配置读取超时。spring.cloud.config.request-connect-timeout
配置连接超时。如果在服务器上使用HTTP基本安全性,则客户端需要知道密码(如果不是默认用户名,则需要用户名)。您可以通过配置服务器URI或通过单独的用户名和密码属性来指定用户名和密码,如以下示例所示:
bootstrap.yml。
spring: cloud: config: uri: https://user:secret@myconfig.mycompany.com
以下示例显示了传递相同信息的另一种方法:
bootstrap.yml。
spring: cloud: config: uri: https://myconfig.mycompany.com username: user password: secret
spring.cloud.config.password
和spring.cloud.config.username
值会覆盖URI中提供的任何内容。
如果您在Cloud Foundry上部署应用程序,则提供密码的最佳方法是通过服务凭据(例如URI中的密码),因为它不需要在配置文件中。以下示例在本地工作,并且适用于名为configserver
的Cloud Foundry上的用户提供的服务:
bootstrap.yml。
spring: cloud: config: uri: ${vcap.services.configserver.credentials.uri:http://user:password@localhost:8888}
如果您使用另一种形式的安全性,则可能需要向ConfigServicePropertySourceLocator
提供一个RestTemplate
(例如,通过在引导上下文中进行抓取并将其注入)。ConfigServicePropertySourceLocator
提供一个{848 /}(例如,通过在引导上下文中进行抓取并将其注入)。
Config Client提供Spring Boot运行状况指示器,该指示器尝试从Config Server加载配置。可以通过设置health.config.enabled=false
禁用运行状况指示器。由于性能原因,响应也被缓存。默认的生存时间为5分钟。要更改该值,请设置health.config.time-to-live
属性(以毫秒为单位)。
在某些情况下,您可能需要自定义来自客户端对配置服务器的请求。通常,这样做涉及传递特殊的Authorization
标头以验证对服务器的请求。提供自定义RestTemplate
:
PropertySourceLocator
,如以下示例所示:CustomConfigServiceBootstrapConfiguration.java。
@Configuration public class CustomConfigServiceBootstrapConfiguration { @Bean public ConfigServicePropertySourceLocator configServicePropertySourceLocator() { ConfigClientProperties clientProperties = configClientProperties(); ConfigServicePropertySourceLocator configServicePropertySourceLocator = new ConfigServicePropertySourceLocator(clientProperties); configServicePropertySourceLocator.setRestTemplate(customRestTemplate(clientProperties)); return configServicePropertySourceLocator; } }
resources/META-INF
中,创建一个名为spring.factories
的文件并指定您的自定义配置,如以下示例所示:spring.factories.
org.springframework.cloud.bootstrap.BootstrapConfiguration = com.my.config.client.CustomConfigServiceBootstrapConfiguration
Greenwich SR5
该项目通过自动配置并绑定到Spring环境和其他Spring编程模型习惯用法,为Spring Boot应用提供了Netflix OSS集成。使用一些简单的批注,您可以快速启用和配置应用程序内部的通用模式,并使用经过测试的Netflix组件构建大型分布式系统。提供的模式包括服务发现(Eureka),断路器(Hystrix),智能路由(Zuul)和客户端负载平衡(Ribbon)。
服务发现是基于微服务的体系结构的主要宗旨之一。尝试手动配置每个客户端或某种形式的约定可能很困难并且很脆弱。Eureka是Netflix Service Discovery服务器和客户端。可以将服务器配置和部署为高可用性,每个服务器将有关已注册服务的状态复制到其他服务器。
要将Eureka客户端包括在您的项目中,请使用启动器,其组ID为org.springframework.cloud
,工件ID为spring-cloud-starter-netflix-eureka-client
。有关使用当前Spring Cloud版本Train设置构建系统的详细信息,请参见Spring Cloud项目页面。
当客户端向Eureka注册时,它会提供有关其自身的元数据-例如主机,端口,运行状况指示器URL,主页和其他详细信息。Eureka从属于服务的每个实例接收心跳消息。如果心跳在可配置的时间表上进行故障转移,则通常会将实例从注册表中删除。
以下示例显示了最小的Eureka客户端应用程序:
@SpringBootApplication @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应用程序。通过在类路径上使用spring-cloud-starter-netflix-eureka-client
,您的应用程序将自动在Eureka服务器中注册。如下例所示,需要进行配置才能找到Eureka服务器:
application.yml。
eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/
在前面的示例中,defaultZone
是一个魔术字符串后备值,它为任何不表达首选项的客户端提供服务URL(换句话说,这是一个有用的默认值)。
警告 | |
---|---|
|
默认应用程序名称(即服务ID),虚拟主机和非安全端口(从Environment
获取)分别为${spring.application.name}
,${spring.application.name}
和${server.port}
。
在类路径上具有spring-cloud-starter-netflix-eureka-client
可使应用程序同时进入Eureka “ 实例 ”(即,它自己注册)和“ 客户端 ”(它可以查询注册表以定位其他服务)。实例行为由eureka.instance.*
配置键驱动,但是如果确保您的应用程序具有spring.application.name
的值(这是Eureka服务ID或VIP的默认值),则默认值很好。
有关可配置选项的更多详细信息,请参见EurekaInstanceConfigBean和EurekaClientConfigBean。
要禁用Eureka Discovery Client,可以将eureka.client.enabled
设置为false
。当spring.cloud.discovery.enabled
设置为false
时,Eureka Discovery Client也将被禁用。
如果其中一个eureka.client.serviceUrl.defaultZone
URL内嵌了凭据,则HTTP基本身份验证会自动添加到您的eureka客户端(卷曲样式,如下:http://user:password@localhost:8761/eureka
)。对于更复杂的需求,您可以创建类型为DiscoveryClientOptionalArgs
的@Bean
并将ClientFilter
实例注入其中,所有这些实例都应用于从客户端到服务器的调用。
注意 | |
---|---|
由于Eureka中的限制,无法支持每服务器的基本身份验证凭据,因此仅使用找到的第一组凭据。 |
Eureka实例的状态页和运行状况指示器分别默认为/info
和/health
,这是Spring Boot Actuator应用程序中有用端点的默认位置。即使您使用非默认上下文路径或Servlet路径(例如server.servletPath=/custom
),也需要更改这些内容,即使对于Actuator应用程序也是如此。下面的示例显示两个设置的默认值:
application.yml。
eureka: instance: statusPageUrlPath: ${server.servletPath}/info healthCheckUrlPath: ${server.servletPath}/health
这些链接显示在客户端使用的元数据中,并在某些情况下用于确定是否将请求发送到您的应用程序,因此,如果请求准确,将很有帮助。
注意 | |
---|---|
在Dalston中,还需要在更改该管理上下文路径时设置状态和运行状况检查URL。从Edgware开始就删除了此要求。 |
如果您希望通过HTTPS与您的应用进行联系,则可以在EurekaInstanceConfig
中设置两个标志:
eureka.instance.[nonSecurePortEnabled]=[false]
eureka.instance.[securePortEnabled]=[true]
这样做会使Eureka发布实例信息,该实例信息显示出对安全通信的明确偏好。对于以这种方式配置的服务,Spring Cloud DiscoveryClient
始终返回以https
开头的URI。同样,以这种方式配置服务时,Eureka(本机)实例信息具有安全的运行状况检查URL。
由于Eureka在内部工作的方式,它仍然会为状态和主页发布非安全URL,除非您也明确地覆盖了它们。您可以使用占位符来配置eureka实例URL,如以下示例所示:
application.yml。
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使用客户端心跳来确定客户端是否启动。除非另有说明,否则发现客户端不会根据Spring Boot Actuator传播应用程序的当前运行状况检查状态。因此,在成功注册之后,Eureka始终宣布该应用程序处于“启动”状态。可以通过启用Eureka运行状况检查来更改此行为,这将导致应用程序状态传播到Eureka。结果,所有其他应用程序都不会将流量发送到处于“ UP”状态以外的其他状态的应用程序。以下示例显示如何为客户端启用运行状况检查:
application.yml。
eureka: client: healthcheck: enabled: true
警告 | |
---|---|
|
如果您需要对运行状况检查进行更多控制,请考虑实施自己的com.netflix.appinfo.HealthCheckHandler
。
值得花费一些时间来了解Eureka元数据的工作方式,因此您可以在平台上使用有意义的方式来使用它。有用于信息的标准元数据,例如主机名,IP地址,端口号,状态页和运行状况检查。这些都发布在服务注册表中,并由客户端用于以直接方式联系服务。可以将其他元数据添加到eureka.instance.metadataMap
中的实例注册中,并且可以在远程客户端中访问此元数据。通常,除非使客户端知道元数据的含义,否则其他元数据不会更改客户端的行为。有几种特殊情况,在本文档的后面部分进行介绍,其中Spring Cloud已经为元数据映射分配了含义。
Cloud Foundry具有全局路由器,因此同一应用程序的所有实例都具有相同的主机名(其他具有类似体系结构的PaaS解决方案具有相同的排列)。这不一定是使用Eureka的障碍。但是,如果您使用路由器(建议或什至是强制性的,具体取决于平台的设置方式),则需要显式设置主机名和端口号(安全或不安全),以便它们使用路由器。您可能还希望使用实例元数据,以便可以区分客户端上的实例(例如,在自定义负载平衡器中)。默认情况下,eureka.instance.instanceId
为vcap.application.instance_id
,如以下示例所示:
application.yml。
eureka: instance: hostname: ${vcap.application.uris[0]} nonSecurePort: 80
根据在Cloud Foundry实例中设置安全规则的方式,您可能可以注册并使用主机VM的IP地址进行直接的服务到服务的调用。Pivotal Web服务(PWS)尚不提供此功能。
如果计划将应用程序部署到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; }
一个普通的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
中提供唯一标识符来覆盖此值,如以下示例所示:
application.yml。
eureka: instance: instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}
通过前面示例中显示的元数据和在本地主机上部署的多个服务实例,在其中插入随机值以使实例唯一。在Cloud Foundry中,vcap.application.instance_id
是在Spring Boot应用程序中自动填充的,因此不需要随机值。
一旦拥有作为发现客户端的应用程序,就可以使用它从Eureka服务器发现服务实例。一种方法是使用本地com.netflix.discovery.EurekaClient
(而不是Spring Cloud DiscoveryClient
),如以下示例所示:
@Autowired private EurekaClient discoveryClient; public String serviceUrl() { InstanceInfo instance = discoveryClient.getNextServerFromEureka("STORES", false); return instance.getHomePageUrl(); }
提示 | |
---|---|
请勿在 |
默认情况下,EurekaClient使用Jersey进行HTTP通信。如果希望避免来自Jersey的依赖关系,可以将其从依赖关系中排除。Spring Cloud基于Spring RestTemplate
自动配置传输客户端。以下示例显示Jersey被排除在外:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> <exclusions> <exclusion> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> </exclusion> <exclusion> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> </exclusion> <exclusion> <groupId>com.sun.jersey.contribs</groupId> <artifactId>jersey-apache-client4</artifactId> </exclusion> </exclusions> </dependency>
您无需使用原始Netflix EurekaClient
。而且,通常在某种包装器后面使用它会更方便。Spring Cloud 通过逻辑Eureka服务标识符(VIP)而非物理URL 支持Feign(REST客户端生成器)和Spring RestTemplate
。要使用固定的物理服务器列表配置Ribbon,可以将<client>.ribbon.listOfServers
设置为以逗号分隔的物理地址(或主机名)列表,其中<client>
是客户端的ID。
您还可以使用org.springframework.cloud.client.discovery.DiscoveryClient
,它为发现客户端提供一个简单的API(非Netflix专用),如以下示例所示:
@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
来更改周期。将其设置为小于30的值可以加快使客户端连接到其他服务的过程。在生产中,最好使用默认值,因为服务器中的内部计算对租约续订期进行了假设。
如果您已将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服务器包含在您的项目中,请使用启动器,其组ID为org.springframework.cloud
,工件ID为spring-cloud-starter-netflix-eureka-server
。有关使用当前Spring Cloud版本Train设置构建系统的详细信息,请参见Spring Cloud项目页面。
注意 | |
---|---|
如果您的项目已经使用Thymeleaf作为模板引擎,则Eureka服务器的Freemarker模板可能无法正确加载。在这种情况下,必须手动配置模板加载器: |
application.yml。
spring: freemarker: template-loader-path: classpath:/templates/ prefer-file-system-access: false
以下示例显示了最小的Eureka服务器:
@SpringBootApplication @EnableEurekaServer public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } }
该服务器具有一个主页,其中包含UI和HTTP API端点,用于/eureka/*
下的常规Eureka功能。
以下链接提供了一些Eureka背景知识:磁通电容器和google小组讨论。
提示 | |
---|---|
由于Gradle的依赖性解析规则以及缺少父bom功能,取决于 build.gradle。 buildscript { dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:{spring-boot-docs-version}") } } apply plugin: "spring-boot" dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:{spring-cloud-version}" } }
|
Eureka服务器没有后端存储,但是注册表中的所有服务实例都必须发送心跳信号以使其注册保持最新(因此可以在内存中完成)。客户端还具有Eureka注册的内存缓存(因此,对于每个对服务的请求,它们都不必转到注册表)。
默认情况下,每个Eureka服务器也是Eureka客户端,并且需要(至少一个)服务URL来定位对等方。如果您不提供该服务,则该服务将运行并运行,但是它将使您的日志充满关于无法向对等方注册的噪音。
只要存在某种监视器或弹性运行时(例如Cloud Foundry),两个缓存(客户端和服务器)和心跳的组合就可以使独立的Eureka服务器对故障具有相当的恢复能力。在独立模式下,您可能希望关闭客户端行为,以使其不会继续尝试并无法到达其对等对象。下面的示例演示如何关闭客户端行为:
application.yml(独立Eureka服务器)。
server: port: 8761 eureka: instance: hostname: localhost client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
请注意,serviceUrl
指向与本地实例相同的主机。
通过运行多个实例并要求它们彼此注册,可以使Eureka更具弹性和可用性。实际上,这是默认行为,因此要使其正常工作,您需要做的就是向对等体添加有效的serviceUrl
,如以下示例所示:
application.yml(两个对等感知Eureka服务器)。
--- 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配置文件中运行,在两个主机(peer1
和peer2
)上运行同一服务器。您可以通过操纵/etc/hosts
解析主机名来使用此配置来测试单个主机上的对等感知(在生产环境中这样做没有太大价值)。实际上,如果您在知道其主机名的计算机上运行,则不需要eureka.instance.hostname
(默认情况下,使用java.net.InetAddress
进行查找)。
您可以将多个对等方添加到系统,并且只要它们都通过至少一个边缘相互连接,它们就可以在彼此之间同步注册。如果对等方在物理上是分开的(在一个数据中心内部或在多个数据中心之间),则该系统原则上可以解决“ 裂脑 ”型故障。您可以将多个对等方添加到系统中,并且只要它们都直接相互连接,它们就可以在彼此之间同步注册。
application.yml(三个对等感知Eureka服务器)。
eureka: client: serviceUrl: defaultZone: http://peer1/eureka/,http://peer2/eureka/,http://peer3/eureka/ --- spring: profiles: peer1 eureka: instance: hostname: peer1 --- spring: profiles: peer2 eureka: instance: hostname: peer2 --- spring: profiles: peer3 eureka: instance: hostname: peer3
在某些情况下,Eureka最好公布服务的IP地址而不是主机名。将eureka.instance.preferIpAddress
设置为true
,并且当应用程序向eureka注册时,它将使用其IP地址而不是其主机名。
提示 | |
---|---|
如果Java无法确定主机名,则IP地址将发送到Eureka。设置主机名的唯一明确方法是设置 |
您只需通过spring-boot-starter-security
将Spring Security添加到服务器的类路径中即可保护Eureka服务器。默认情况下,当Spring Security在类路径上时,它将要求在每次向应用程序发送请求时都发送有效的CSRF令牌。Eureka客户通常不会拥有有效的跨站点请求伪造(CSRF)令牌,您需要为/eureka/**
端点禁用此要求。例如:
@EnableWebSecurity class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().ignoringAntMatchers("/eureka/**"); super.configure(http); } }
有关CSRF的更多信息,请参见Spring Security文档。
可以在Spring Cloud示例存储库中找到Eureka演示服务器。
Netflix创建了一个名为Hystrix的库,该库实现了断路器模式。在微服务架构中,通常有多个服务调用层,如以下示例所示:
较低级别的服务中的服务故障可能会导致级联故障,直至用户。在metrics.rollingStats.timeInMilliseconds
定义的滚动窗口中,当对特定服务的调用超过circuitBreaker.requestVolumeThreshold
(默认:20个请求)并且失败百分比大于circuitBreaker.errorThresholdPercentage
(默认:> 50%)时(默认:10秒) ),则电路断开并且无法进行呼叫。在错误和断路的情况下,开发人员可以提供备用功能。
开路可停止级联故障,并让不堪重负的服务时间得以恢复。后备可以是另一个受Hystrix保护的呼叫,静态数据或合理的空值。可以将回退链接在一起,以便第一个回退进行其他业务调用,然后回退到静态数据。
要将Hystrix包含在您的项目中,请使用起始者,其组ID为org.springframework.cloud
,工件ID为spring-cloud-starter-netflix-hystrix
。有关使用当前Spring Cloud版本Train设置构建系统的详细信息,请参见Spring Cloud项目页面。
以下示例显示了具有Hystrix断路器的最小Eureka服务器:
@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将带有注释的Spring beans自动包装在与Hystrix断路器连接的代理中。断路器计算何时断开和闭合电路,以及在发生故障时应采取的措施。
要配置@HystrixCommand
,可以将commandProperties
属性与@HystrixProperty
批注一起使用。有关
更多详细信息,请参见
此处。有关
可用属性的详细信息,请参见Hystrix Wiki。
如果要将某些线程本地上下文传播到@HystrixCommand
中,则默认声明无效,因为默认声明在线程池中执行命令(如果超时)。通过要求Hystrix使用不同的“ 隔离策略 ”,可以通过配置或直接在批注中切换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的主要好处之一是它收集的有关每个HystrixCommand的一组度量。Hystrix仪表板以有效的方式显示每个断路器的运行状况。
当使用包裹Ribbon客户端的Hystrix命令时,您要确保Hystrix超时配置为比配置的Ribbon超时更长,包括可能进行的任何重试。例如,如果您的Ribbon连接超时是一秒钟,并且Ribbon客户端可能重试了3次请求,则Hystrix超时应该稍微超过3秒。
要将Hystrix仪表板包含在您的项目中,请使用启动器,其组ID为org.springframework.cloud
,工件ID为spring-cloud-starter-netflix-hystrix-dashboard
。有关使用当前Spring Cloud版本Train设置构建系统的详细信息,请参见Spring Cloud项目页面。
要运行Hystrix信息中心,请用@EnableHystrixDashboard
注释Spring Boot主类。然后访问/hystrix
,并将仪表板指向Hystrix客户端应用程序中单个实例的/hystrix.stream
端点。
注意 | |
---|---|
连接到使用HTTPS的 |
从系统的整体运行状况来看,查看单个实例的Hystrix数据不是很有用。Turbine是一个应用程序,它将所有相关的/hystrix.stream
端点聚合到一个组合的/turbine.stream
中,以便在Hystrix仪表板中使用。个别实例通过Eureka定位。运行Turbine需要使用@EnableTurbine
注释对您的主类进行注释(例如,通过使用spring-cloud-starter-netflix-turbine设置类路径)。Turbine 1 Wiki中记录的所有配置属性均适用。唯一的区别是turbine.instanceUrlSuffix
不需要预先添加的端口,因为除非turbine.instanceInsertPort=false
,否则它将自动处理。
注意 | |
---|---|
默认情况下,Turbine通过在Eureka中查找其 |
eureka: instance: metadata-map: management.port: ${management.port:8081}
turbine.appConfig
配置密钥是Eureka serviceId的列表,涡轮使用它们来查找实例。然后,在Hystrix仪表板中使用涡轮流,其URL类似于以下内容:
https://my.turbine.server:8080/turbine.stream?cluster=CLUSTERNAME
如果名称为default
,则可以省略cluster参数。cluster
参数必须与turbine.aggregator.clusterConfig
中的条目匹配。从Eureka返回的值是大写的。因此,如果存在一个向Eureka注册的名为customers
的应用程序,则以下示例可用:
turbine: aggregator: clusterConfig: CUSTOMERS appConfig: customers
如果您需要自定义Turbine应该使用哪些集群名称(因为您不想在turbine.aggregator.clusterConfig
配置中存储集群名称),请提供类型为TurbineClustersProvider
的bean。
clusterName
可以通过turbine.clusterNameExpression
中的SPEL表达式进行自定义,其中根目录为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']
在前面的示例中,来自四个服务的群集名称是从它们的元数据映射中拉出的,并且期望其值包括SYSTEM
和USER
。
要将“ 默认 ”群集用于所有应用程序,您需要一个字符串文字表达式(如果在YAML中,也要使用单引号和双引号进行转义):
turbine: appConfig: customers,stores clusterNameExpression: "'default'"
Spring Cloud提供了spring-cloud-starter-netflix-turbine
,它具有运行Turbine服务器所需的所有依赖关系。要添加Turbine,请创建一个Spring Boot应用程序并使用@EnableTurbine
对其进行注释。
注意 | |
---|---|
默认情况下,Spring Cloud允许Turbine使用主机和端口以允许每个主机,每个集群多个进程。如果你想建立在本地Netflix的行为Turbine,以不使每台主机的多个进程,每簇(关键实例ID是主机名),集合 |
在某些情况下,其他应用程序了解在Turbine中配置了哪些custers可能会很有用。为此,您可以使用/clusters
端点,该端点将返回所有已配置集群的JSON数组。
GET /集群。
[ { "name": "RACES", "link": "http://localhost:8383/turbine.stream?cluster=RACES" }, { "name": "WEB", "link": "http://localhost:8383/turbine.stream?cluster=WEB" } ]
可以通过将turbine.endpoints.clusters.enabled
设置为false
来禁用此端点。
在某些环境中(例如在PaaS设置中),从所有分布式Hystrix命令中提取指标的经典Turbine模型不起作用。在这种情况下,您可能想让Hystrix命令将指标推送到Turbine。Spring Cloud通过消息传递实现了这一点。要在客户端上执行此操作,请向spring-cloud-netflix-hystrix-stream
和您选择的spring-cloud-starter-stream-*
添加一个依赖项。有关代理以及如何配置客户端凭据的详细信息,请参见Spring Cloud Stream文档。对于本地代理,它应该开箱即用。
在服务器端,创建一个Spring Boot应用程序,并用@EnableTurbineStream
对其进行注释。Turbine Stream服务器需要使用Spring Webflux,因此,spring-boot-starter-webflux
必须包含在您的项目中。将spring-cloud-starter-netflix-turbine-stream
添加到您的应用程序时,默认包含spring-boot-starter-webflux
。
然后,您可以将Hystrix仪表板指向Turbine Stream服务器,而不是单独的Hystrix流。如果Turbine Stream在myhost的端口8989上运行,则将http://myhost:8989
放在Hystrix仪表板的流输入字段中。电路以其各自的serviceId
为前缀,后跟一个点(.
),然后是电路名称。
Spring Cloud提供了spring-cloud-starter-netflix-turbine-stream
,其中包含使Turbine Stream服务器运行所需的所有依赖项。然后,您可以添加您选择的流绑定程序,例如spring-cloud-starter-stream-rabbit
。
Turbine Stream服务器还支持cluster
参数。与Turbine服务器不同,Turbine Stream使用eureka serviceIds作为群集名称,并且这些名称不可配置。
如果Turbine Stream服务器在my.turbine.server
的端口8989上运行,并且您的环境中有两个eureka serviceId customers
和products
,则以下URL将在Turbine Stream服务器上可用。default
和空群集名称将提供Turbine Stream服务器接收的所有度量。
https://my.turbine.sever:8989/turbine.stream?cluster=customers https://my.turbine.sever:8989/turbine.stream?cluster=products https://my.turbine.sever:8989/turbine.stream?cluster=default https://my.turbine.sever:8989/turbine.stream
因此,您可以将eureka serviceIds用作Turbine仪表板(或任何兼容的仪表板)的群集名称。您无需为Turbine Stream服务器配置任何属性,例如turbine.appConfig
,turbine.clusterNameExpression
和turbine.aggregator.clusterConfig
。
注意 | |
---|---|
Turbine Stream服务器使用Spring Cloud Stream从配置的输入通道中收集所有度量。这意味着它不会从每个实例中主动收集Hystrix指标。它仅可以提供每个实例已经收集到输入通道中的度量。 |
Ribbon是一种客户端负载平衡器,可让您对HTTP和TCP客户端的行为进行大量控制。Feign已使用Ribbon,因此,如果使用@FeignClient
,则本节也适用。
Ribbon中的中心概念是指定客户的概念。每个负载均衡器都是组件的一部分,这些组件可以一起工作以按需联系远程服务器,并且该组件具有您作为应用程序开发人员提供的名称(例如,使用@FeignClient
批注)。根据需要,Spring Cloud通过使用RibbonClientConfiguration
为每个命名的客户端创建一个新的集合作为ApplicationContext
。其中包含ILoadBalancer
,RestClient
和ServerListFilter
。
要将Ribbon包含在您的项目中,请使用起始者,其组ID为org.springframework.cloud
,工件ID为spring-cloud-starter-netflix-ribbon
。有关使用当前Spring Cloud版本Train设置构建系统的详细信息,请参见Spring Cloud项目页面。
您可以使用<client>.ribbon.*
中的外部属性来配置Ribbon客户端的某些位,这与本地使用Netflix API相似,不同之处在于可以使用Spring Boot配置文件。可以将本机选项检查为CommonClientConfigKey
(功能区核心的一部分)中的静态字段。
Spring Cloud还允许您通过使用@RibbonClient
声明其他配置(在RibbonClientConfiguration
之上)来完全控制客户端,如以下示例所示:
@Configuration @RibbonClient(name = "custom", configuration = CustomConfiguration.class) public class TestConfiguration { }
在这种情况下,客户端由RibbonClientConfiguration
中已有的组件以及CustomConfiguration
中的任何组件组成(其中后者通常会覆盖前者)。
警告 | |
---|---|
|
下表显示了Spring Cloud Netflix默认为Ribbon提供的beans:
Bean类型 | Bean名称 | 班级名称 |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
创建其中一种类型的bean并将其放置在@RibbonClient
配置中(例如上述FooConfiguration
),您可以覆盖所描述的每个beans,如以下示例所示:
@Configuration protected static class FooConfiguration { @Bean public ZonePreferenceServerListFilter serverListFilter() { ZonePreferenceServerListFilter filter = new ZonePreferenceServerListFilter(); filter.setZone("myTestZone"); return filter; } @Bean public IPing ribbonPing() { return new PingUrl(); } }
上一示例中的include语句将NoOpPing
替换为PingUrl
,并提供了自定义serverListFilter
。
通过使用@RibbonClients
批注并注册默认配置,可以为所有Ribbon客户端提供默认配置,如以下示例所示:
@RibbonClients(defaultConfiguration = DefaultRibbonConfig.class) public class RibbonClientDefaultConfigurationTestsConfig { public static class BazServiceList extends ConfigurationBasedServerList { public BazServiceList(IClientConfig config) { super.initWithNiwsConfig(config); } } } @Configuration class DefaultRibbonConfig { @Bean public IRule ribbonRule() { return new BestAvailableRule(); } @Bean public IPing ribbonPing() { return new PingUrl(); } @Bean public ServerList<Server> ribbonServerList(IClientConfig config) { return new RibbonClientDefaultConfigurationTestsConfig.BazServiceList(config); } @Bean public ServerListSubsetFilter serverListFilter() { ServerListSubsetFilter filter = new ServerListSubsetFilter(); return filter; } }
从版本1.2.0开始,Spring Cloud Netflix现在支持通过将属性设置为与Ribbon文档兼容来自定义Ribbon客户端。
这使您可以在启动时在不同环境中更改行为。
以下列表显示了受支持的属性>:
<clientName>.ribbon.NFLoadBalancerClassName
:应实施ILoadBalancer
<clientName>.ribbon.NFLoadBalancerRuleClassName
:应实施IRule
<clientName>.ribbon.NFLoadBalancerPingClassName
:应实施IPing
<clientName>.ribbon.NIWSServerListClassName
:应实施ServerList
<clientName>.ribbon.NIWSServerListFilterClassName
:应实施ServerListFilter
注意 | |
---|---|
这些属性中定义的类优先于使用 |
要为名为users
的服务名称设置IRule
,可以设置以下属性:
application.yml。
users: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
当Eureka与Ribbon结合使用时(也就是说,两者都在类路径上),ribbonServerList
被扩展名DiscoveryEnabledNIWSServerList
覆盖,这将填充{71中的服务器列表/}。它还用NIWSDiscoveryPing
替换了IPing
接口,该接口委托给Eureka确定服务器是否启动。默认安装的ServerList
是DomainExtractingServerList
。其目的是不使用AWS AMI元数据(这就是Netflix所依赖的)使元数据可用于负载均衡器。默认情况下,服务器列表是使用实例元数据中提供的“ zone ”信息构建的(因此,在远程客户端上,设置为eureka.instance.metadataMap.zone
)。如果缺少该字段,并且设置了approximateZoneFromHostname
标志,则它可以使用服务器主机名中的域名作为该区域的代理。一旦区域信息可用,就可以在ServerListFilter
中使用它。默认情况下,它用于在与客户端相同的区域中定位服务器,因为默认值为ZonePreferenceServerListFilter
。默认情况下,以与远程实例相同的方式(即通过eureka.instance.metadataMap.zone
)确定客户端的区域。
注意 | |
---|---|
设置客户端区域的传统“ archaius ”方法是通过名为“ @zone”的配置属性。如果可用,Spring Cloud优先于所有其他设置使用该设置(请注意,该键必须在YAML配置中用引号引起来)。 |
注意 | |
---|---|
如果没有其他区域数据源,则根据客户端配置(而不是实例配置)进行猜测。我们取 |
Eureka是一种抽象发现远程服务器的便捷方法,因此您不必在客户端中对它们的URL进行硬编码。但是,如果您不想使用Eureka,则Ribbon和Feign也可以使用。假设您为“商店”声明了@RibbonClient
,并且Eureka未被使用(甚至不在类路径上)。Ribbon客户端默认为配置的服务器列表。您可以提供以下配置:
application.yml。
stores: ribbon: listOfServers: example.com,google.com
将ribbon.eureka.enabled
属性设置为false
会显式禁用Ribbon中的Eureka,如以下示例所示:
application.yml。
ribbon: eureka: enabled: false
您也可以直接使用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命名的客户端都有一个相应的子应用程序上下文,Spring Cloud维护该上下文。该应用程序上下文在对命名客户端的第一个请求上延迟加载。通过指定Ribbon客户端的名称,可以更改此延迟加载行为,以代替在启动时急于加载这些子应用程序上下文,如以下示例所示:
application.yml。
ribbon: eager-load: enabled: true clients: client1, client2, client3
如果将zuul.ribbonIsolationStrategy
更改为THREAD
,则Hystrix的线程隔离策略将用于所有路由。在这种情况下,HystrixThreadPoolKey
默认设置为RibbonCommand
。这意味着所有路由的HystrixCommands在相同的Hystrix线程池中执行。可以使用以下配置更改此行为:
application.yml。
zuul: threadPool: useSeparateThreadPools: true
前面的示例导致在每个路由的Hystrix线程池中执行HystrixCommands。
在这种情况下,默认HystrixThreadPoolKey
与每个路由的服务ID相同。要将前缀添加到HystrixThreadPoolKey
,请将zuul.threadPool.threadPoolKeyPrefix
设置为要添加的值,如以下示例所示:
application.yml。
zuul: threadPool: useSeparateThreadPools: true threadPoolKeyPrefix: zuulgw
如果您需要提供自己的IRule
实现来处理诸如“ canary ”测试之类的特殊路由要求,请将一些信息传递给IRule
的choose
方法。
com.netflix.loadbalancer.IRule.java。
public interface IRule{ public Server choose(Object key); :
您可以提供一些信息,供您的IRule
实现用来选择目标服务器,如以下示例所示:
RequestContext.getCurrentContext() .set(FilterConstants.LOAD_BALANCER_KEY, "canary-test");
如果您使用密钥FilterConstants.LOAD_BALANCER_KEY
将任何对象放入RequestContext
中,则该对象将传递到IRule
实现的choose
方法中。上例中显示的代码必须在执行RibbonRoutingFilter
之前执行。Zuul的前置过滤器是执行此操作的最佳位置。您可以通过预过滤器中的RequestContext
访问HTTP标头和查询参数,因此可以用来确定传递到Ribbon的LOAD_BALANCER_KEY
。如果没有在RequestContext
中用LOAD_BALANCER_KEY
放置任何值,则将空值作为choose
方法的参数传递。
Archaius是Netflix客户端配置库。它是所有Netflix OSS组件用于配置的库。Archaius是Apache Commons Configuration项目的扩展。它允许通过轮询源以进行更改或通过将源将更改推送到客户端来更新配置。Archaius使用Dynamic <Type> Property类作为属性的句柄,如以下示例所示:
Archaius示例。
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工具(大部分情况下)。
路由是微服务架构不可或缺的一部分。例如,/
可能被映射到您的web应用程序,/api/users
被映射到用户服务,/api/shop
被映射到商店服务。
Zuul是Netflix的基于JVM的路由器和服务器端负载平衡器。
Netflix将Zuul用于以下用途:
Zuul的规则引擎可使用几乎所有JVM语言编写规则和过滤器,并内置对Java和Groovy的支持。
注意 | |
---|---|
配置属性 |
注意 | |
---|---|
所有路由的默认Hystrix隔离模式( |
要将Zuul包含在您的项目中,请使用组ID为org.springframework.cloud
和工件ID为spring-cloud-starter-netflix-zuul
的启动程序。有关使用当前Spring Cloud版本Train设置构建系统的详细信息,请参见Spring Cloud项目页面。
Spring Cloud已创建嵌入式Zuul代理,以简化UI应用程序要对一个或多个后端服务进行代理调用的常见用例的开发。此功能对于用户界面代理所需的后端服务很有用,从而避免了为所有后端独立管理CORS和身份验证问题的需求。
要启用它,请用@EnableZuulProxy
注释Spring Boot主类。这样做会导致将本地呼叫转发到适当的服务。按照约定,ID为users
的服务从位于/users
的代理接收请求(前缀被去除)。代理使用Ribbon来定位要通过发现转发到的实例。所有请求均在hystrix命令中执行,因此失败以Hystrix指标显示。一旦电路断开,代理就不会尝试与服务联系。
注意 | |
---|---|
Zuul入门程序不包含发现客户端,因此,对于基于服务ID的路由,您还需要在类路径上提供其中之一(Eureka是一种选择)。 |
要跳过自动添加服务的过程,请将zuul.ignored-services
设置为服务ID模式的列表。如果服务与被忽略但已包含在显式配置的路由映射中的模式匹配,则将其忽略,如以下示例所示:
application.yml。
zuul: ignoredServices: '*' routes: users: /myusers/**
在前面的示例中,除 users
外,所有服务均被忽略。
要增加或更改代理路由,可以添加外部配置,如下所示:
application.yml。
zuul: routes: users: /myusers/**
前面的示例意味着对/myusers
的HTTP调用被转发到users
服务(例如,/myusers/101
被转发到/101
)。
要对路由进行更细粒度的控制,可以分别指定路径和serviceId,如下所示:
application.yml。
zuul: routes: users: path: /myusers/** serviceId: users_service
前面的示例意味着对/myusers
的HTTP调用将转发到users_service
服务。路由必须具有可以指定为蚂蚁样式模式的path
,因此/myusers/*
仅匹配一个级别,而/myusers/**
则分层匹配。
后端的位置可以指定为serviceId
(用于发现服务)或url
(用于物理位置),如以下示例所示:
application.yml。
zuul: routes: users: path: /myusers/** url: https://example.com/users_service
这些简单的url路由不会作为HystrixCommand
来执行,也不会使用Ribbon对多个URL进行负载均衡。为了实现这些目标,可以使用静态服务器列表指定一个serviceId
,如下所示:
application.yml。
zuul: routes: echo: path: /myusers/** serviceId: myusers-service stripPrefix: true hystrix: command: myusers-service: execution: isolation: thread: timeoutInMilliseconds: ... myusers-service: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList listOfServers: https://example1.com,http://example2.com ConnectTimeout: 1000 ReadTimeout: 3000 MaxTotalHttpConnections: 500 MaxConnectionsPerHost: 100
另一种方法是指定服务路由并为serviceId
配置Ribbon客户端(这样做需要在Ribbon中禁用Eureka支持- 有关更多信息,请参见上文),如下所示例:
application.yml。
zuul: routes: users: path: /myusers/** serviceId: users ribbon: eureka: enabled: false users: ribbon: listOfServers: example.com,google.com
您可以使用regexmapper
在serviceId
和路由之间提供约定。它使用正则表达式命名组从serviceId
中提取变量,并将其注入到路由模式中,如以下示例所示:
ApplicationConfiguration.java。
@Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }
前面的示例意味着myusers-v1
中的serviceId
被映射到路由/v1/myusers/**
。可以接受任何正则表达式,但是所有命名组必须同时存在于servicePattern
和routePattern
中。如果servicePattern
与serviceId
不匹配,则使用默认行为。在前面的示例中,myusers
中的serviceId
被映射到“ / myusers / **”路由(未检测到版本)。默认情况下,此功能是禁用的,仅适用于发现的服务。
要为所有映射添加前缀,请将zuul.prefix
设置为一个值,例如/api
。默认情况下,在转发请求之前,将从请求中删除代理前缀(您可以使用zuul.stripPrefix=false
将此行为关闭)。您还可以关闭从单个路由中剥离特定于服务的前缀,如以下示例所示:
application.yml。
zuul: routes: users: path: /myusers/** stripPrefix: false
注意 | |
---|---|
|
在前面的示例中,对/myusers/101
的请求被转发到users
服务上的/myusers/101
。
zuul.routes
条目实际上绑定到类型为ZuulProperties
的对象。如果查看该对象的属性,则可以看到它也有一个retryable
标志。将该标志设置为true
,以使Ribbon客户端自动重试失败的请求。当您需要修改使用Ribbon客户端配置的重试操作的参数时,也可以将该标志设置为true
。
默认情况下,X-Forwarded-Host
标头被添加到转发的请求中。要关闭它,请设置zuul.addProxyHeaders = false
。默认情况下,前缀路径被剥离,并且后端请求使用X-Forwarded-Prefix
标头(在前面显示的示例中为/myusers
)。
如果设置默认路由(/
),则带有@EnableZuulProxy
的应用程序可以充当独立服务器。例如,zuul.route.home: /
会将所有流量(“ / **”)路由到“ home”服务。
如果需要更细粒度的忽略,则可以指定要忽略的特定模式。这些模式在路线定位过程开始时进行评估,这意味着模式中应包含前缀以保证匹配。被忽略的模式跨越所有服务,并取代任何其他路由规范。以下示例显示了如何创建忽略的模式:
application.yml。
zuul: ignoredPatterns: /**/admin/** routes: users: /myusers/**
前面的示例意味着所有呼叫(例如/myusers/101
)都被转发到users
服务上的/101
。但是,包括/admin/
在内的呼叫无法解决。
警告 | |
---|---|
如果您需要保留路由的顺序,则需要使用YAML文件,因为使用属性文件时顺序会丢失。以下示例显示了这样的YAML文件: |
application.yml。
zuul: routes: users: path: /myusers/** legacy: path: /**
如果要使用属性文件,则legacy
路径可能最终位于users
路径的前面,从而导致users
路径不可访问。
Zuul使用的默认HTTP客户端现在由Apache HTTP客户端支持,而不是已弃用的Ribbon RestClient
。要使用RestClient
或okhttp3.OkHttpClient
,请分别设置ribbon.restclient.enabled=true
或ribbon.okhttp.enabled=true
。如果要自定义Apache HTTP客户端或OK HTTP客户端,请提供类型为ClosableHttpClient
或OkHttpClient
的bean。
您可以在同一系统中的服务之间共享标头,但您可能不希望敏感标头泄漏到下游到外部服务器中。您可以在路由配置中指定忽略的标头列表。Cookies发挥着特殊的作用,因为它们在浏览器中具有定义明确的语义,并且始终将它们视为敏感内容。如果代理的使用者是浏览器,那么下游服务的cookie也会给用户带来麻烦,因为它们都混杂在一起(所有下游服务看起来都来自同一位置)。
如果您对服务的设计很谨慎(例如,如果只有一个下游服务设置cookie),则可以让它们从后端一直流到调用者。另外,如果您的代理设置了cookie,并且您的所有后端服务都在同一系统中,则很自然地简单地共享它们(例如,使用Spring Session将它们链接到某些共享状态)。除此之外,由下游服务设置的任何cookie可能对调用者都无用,因此建议您将(至少)Set-Cookie
和Cookie
设置为敏感的标头,用于那些没有使用的路由您网域的一部分。即使对于属于您网域的路由,在让Cookie在它们和代理之间流动之前,也应仔细考虑其含义。
可以将敏感头配置为每个路由的逗号分隔列表,如以下示例所示:
application.yml。
zuul: routes: users: path: /myusers/** sensitiveHeaders: Cookie,Set-Cookie,Authorization url: https://downstream
注意 | |
---|---|
这是 |
sensitiveHeaders
是黑名单,默认值不为空。因此,要使Zuul发送所有标头(ignored
除外),必须将其显式设置为空列表。如果要将Cookie或授权标头传递到后端,则必须这样做。以下示例显示如何使用sensitiveHeaders
:
application.yml。
zuul: routes: users: path: /myusers/** sensitiveHeaders: url: https://downstream
您还可以通过设置zuul.sensitiveHeaders
来设置敏感标题。如果在路由上设置了sensitiveHeaders
,它将覆盖全局sensitiveHeaders
设置。
除了路由敏感的标头之外,您还可以为与下游服务交互期间应丢弃的值(请求和响应)设置一个名为zuul.ignoredHeaders
的全局值。默认情况下,如果Spring Security不在类路径中,则它们为空。否则,它们将初始化为Spring Security指定的一组众所周知的“ 安全性 ”标头(例如,涉及缓存)。在这种情况下的假设是,下游服务也可以添加这些标头,但是我们需要来自代理的值。要在类路径上有Spring Security时不丢弃这些众所周知的安全标头,可以将zuul.ignoreSecurityHeaders
设置为false
。如果您在Spring Security中禁用了HTTP安全响应标头,并希望由下游服务提供值,则这样做很有用。
默认情况下,如果将@EnableZuulProxy
与Spring Boot Actuator结合使用,则将启用两个附加端点:
在/routes
处的路由端点的GET返回已映射路由的列表:
GET /路线。
{ /stores/**: "http://localhost:8081" }
可以通过将?format=details
查询字符串添加到/routes
来请求其他路由详细信息。这样做会产生以下输出:
获取/ routes / details。
{ "/stores/**": { "id": "stores", "fullPath": "/stores/**", "location": "http://localhost:8081", "path": "/**", "prefix": "/stores", "retryable": false, "customSensitiveHeaders": false, "prefixStripped": true } }
POST
至/routes
强制刷新现有路由(例如,当服务目录中发生更改时)。您可以通过将endpoints.routes.enabled
设置为false
来禁用此端点。
注意 | |
---|---|
路由应该自动响应服务目录中的更改,但是从 |
迁移现有应用程序或API时,常见的模式是“ 勒死 ”旧的端点,并用不同的实现方式慢慢替换它们。Zuul代理是一个有用的工具,因为您可以使用它来处理来自旧端点的客户端的所有流量,但可以将一些请求重定向到新请求。
以下示例显示“ 扼杀 ”方案的配置详细信息:
application.yml。
zuul: routes: first: path: /first/** url: https://first.example.com second: path: /second/** url: forward:/second third: path: /third/** url: forward:/3rd legacy: path: /** url: https://legacy.example.com
在前面的示例中,我们扼杀了“ legacy ”应用程序,该应用程序映射到与其他模式之一不匹配的所有请求。/first/**
中的路径已使用外部URL提取到新服务中。/second/**
中的路径被转发,以便可以在本地处理(例如,使用普通Spring @RequestMapping
)。/third/**
中的路径也被转发,但是前缀不同(/third/foo
被转发到/3rd/foo
)。
注意 | |
---|---|
被忽略的模式不会被完全忽略,它们不会由代理处理(因此它们也可以在本地有效转发)。 |
如果使用@EnableZuulProxy
,则可以使用代理路径上载文件,只要文件很小,它就可以正常工作。对于大文件,有一个替代路径可以绕过“ / zuul / *”中的Spring DispatcherServlet
(以避免进行多部分处理)。换句话说,如果您拥有zuul.routes.customers=/customers/**
,则可以将POST
大文件复制到/zuul/customers/*
。Servlet路径通过zuul.servletPath
外部化。如果代理路由将您带到Ribbon负载均衡器,则超大文件也需要提高超时设置,如以下示例所示:
application.yml。
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
方法按原样使用查询字符串,如以下示例所示:
application.yml。
zuul: forceOriginalQueryStringEncoding: true
注意 | |
---|---|
该特殊标志仅适用于 |
在处理传入请求时,在将请求URI与路由匹配之前,先对其进行解码。然后在路由过滤器中重建后端请求时,将对请求URI进行重新编码。如果您的URI包含编码的“ /”字符,则可能导致某些意外行为。
要使用原始请求URI,可以将特殊标志传递给'ZuulProperties',以便使用HttpServletRequest::getRequestURI
方法按原样使用URI,如以下示例所示:
application.yml。
zuul: decodeUrl: false
注意 | |
---|---|
如果使用 |
如果使用@EnableZuulServer
(而不是@EnableZuulProxy
),则也可以运行Zuul服务器而不进行代理或有选择地打开代理平台的某些部分。您添加到类型为ZuulFilter
的应用程序中的所有beans都会自动安装(与@EnableZuulProxy
一样),但是不会自动添加任何代理过滤器。
在这种情况下,仍然可以通过配置“ zuul.routes。*”来指定进入Zuul服务器的路由,但是没有服务发现也没有代理。因此,“ serviceId”和“ url”设置将被忽略。以下示例将“ / api / **”中的所有路径映射到Zuul过滤器链:
application.yml。
zuul: routes: api: /api/**
Spring Cloud的Zuul带有多个ZuulFilter
beans,默认情况下在代理和服务器模式下都启用。有关可以启用的过滤器列表,请参见Zuul过滤器包。如果要禁用一个,请设置zuul.<SimpleClassName>.<filterType>.disable=true
。按照惯例,filters
之后的软件包是Zuul过滤器类型。例如,要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter
,请设置zuul.SendResponseFilter.post.disable=true
。
当Zuul中给定路由的电路跳闸时,可以通过创建类型为FallbackProvider
的bean提供回退响应。在此bean中,您需要指定回退的路由ID,并提供一个ClientHttpResponse
作为回退的路由。以下示例显示了一个相对简单的FallbackProvider
实现:
class MyFallbackProvider implements FallbackProvider { @Override public String getRoute() { return "customers"; } @Override public ClientHttpResponse fallbackResponse(String route, final Throwable cause) { if (cause instanceof HystrixTimeoutException) { return response(HttpStatus.GATEWAY_TIMEOUT); } else { return response(HttpStatus.INTERNAL_SERVER_ERROR); } } private ClientHttpResponse response(final HttpStatus status) { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return status; } @Override public int getRawStatusCode() throws IOException { return status.value(); } @Override public String getStatusText() throws IOException { return status.getReasonPhrase(); } @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/**
如果您想为所有路由提供默认后备,则可以创建类型为FallbackProvider
的bean,并让getRoute
方法返回*
或null
,如以下示例:
class MyFallbackProvider implements FallbackProvider { @Override public String getRoute() { return "*"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable throwable) { 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代理的请求配置套接字超时和读取超时,则根据您的配置,有两个选项:
ribbon.ReadTimeout
和ribbon.SocketTimeout
Ribbon属性配置这些超时。如果通过指定URL配置了Zuul路由,则需要使用zuul.host.connect-timeout-millis
和zuul.host.socket-timeout-millis
。
如果Zuul在web应用程序的前面,则当web应用程序通过HTTP状态代码3XX
重定向时,您可能需要重新编写Location
标头。否则,浏览器将重定向到web应用程序的URL,而不是Zuul URL。您可以配置LocationRewriteFilter
Zuul过滤器,将Location
标头重写为Zuul的URL。它还添加回去的全局前缀和特定于路由的前缀。以下示例通过使用Spring配置文件添加过滤器:
import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter; ... @Configuration @EnableZuulProxy public class ZuulConfig { @Bean public LocationRewriteFilter locationRewriteFilter() { return new LocationRewriteFilter(); } }
警告 | |
---|---|
小心使用此过滤器。筛选器作用于所有 |
默认情况下,Zuul将所有跨源请求(CORS)路由到服务。如果您希望Zuul处理这些请求,可以通过提供自定义WebMvcConfigurer
bean来完成:
@Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/path-1/**") .allowedOrigins("https://allowed-origin.com") .allowedMethods("GET", "POST"); } }; }
在上面的示例中,我们允许https://allowed-origin.com
中的GET
和POST
方法将跨域请求发送到以path-1
开头的端点。您可以使用/**
映射将CORS配置应用于特定的路径模式或整个应用程序的全局路径。您可以通过此配置来自定义属性:allowedOrigins
,allowedMethods
,allowedHeaders
,exposedHeaders
,allowCredentials
和maxAge
。
Zuul将在执行器指标终结点下提供指标,以解决路由请求时可能发生的任何故障。可以通过点击/actuator/metrics
来查看这些指标。指标的名称格式为ZUUL::EXCEPTION:errorCause:statusCode
。
有关Zuul的工作原理的一般概述,请参见Zuul Wiki。
Zuul被实现为Servlet。对于一般情况,Zuul已嵌入Spring调度机制中。这使Spring MVC可以控制路由。在这种情况下,Zuul缓冲请求。如果需要通过Zuul而不缓冲请求(例如,用于大文件上传),则Servlet也将安装在Spring Dispatcher之外。缺省情况下,该servlet的地址为/zuul
。可以使用zuul.servlet-path
属性更改此路径。
要在过滤器之间传递信息,Zuul使用RequestContext
。其数据保存在每个请求专用的ThreadLocal
中。有关在何处路由请求,错误以及实际的HttpServletRequest
和HttpServletResponse
的信息存储在此处。RequestContext
扩展了ConcurrentHashMap
,因此任何内容都可以存储在上下文中。FilterConstants
包含Spring Cloud Netflix安装的过滤器使用的密钥(稍后会详细介绍)。
Spring Cloud Netflix安装了许多过滤器,具体取决于启用了Zuul的注释。@EnableZuulProxy
是@EnableZuulServer
的超集。换句话说,@EnableZuulProxy
包含@EnableZuulServer
安装的所有筛选器。“ 代理 ”中的其他过滤器启用路由功能。如果您想使用“ 空白 ” Zuul,则应使用@EnableZuulServer
。
@EnableZuulServer
创建一个SimpleRouteLocator
,该文件从Spring Boot配置文件中加载路由定义。
已安装以下过滤器(按常规方式Spring Beans):
前置过滤器:
ServletDetectionFilter
:检测请求是否通过Spring分派器进行。设置键为FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY
的布尔值。FormBodyWrapperFilter
:解析表单数据并为下游请求重新编码。DebugFilter
:如果设置了debug
请求参数,则将RequestContext.setDebugRouting()
和RequestContext.setDebugRequest()
设置为true
。*路由过滤器:SendForwardFilter
:使用Servlet RequestDispatcher
的Forwards请求。转发位置存储在RequestContext
属性FilterConstants.FORWARD_TO_KEY
中。这对于转发到当前应用程序中的端点很有用。帖子过滤器:
SendResponseFilter
:将代理请求的响应写入当前响应。错误过滤器:
SendErrorFilter
:如果RequestContext.getThrowable()
不为空,则Forwards至/error
(默认)。您可以通过设置error.path
属性来更改默认转发路径(/error
)。创建一个DiscoveryClientRouteLocator
,它从DiscoveryClient
(例如Eureka)以及属性中加载路由定义。从DiscoveryClient
为每个serviceId
创建一条路由。添加新服务后,将刷新路由。
除了前面描述的过滤器之外,还安装了以下过滤器(常规Spring Beans):
前置过滤器:
PreDecorationFilter
:根据提供的RouteLocator
确定路线和路线。它还为下游请求设置了各种与代理相关的标头。路线过滤器:
RibbonRoutingFilter
:使用Ribbon,Hystrix和可插拔的HTTP客户端发送请求。在RequestContext
属性FilterConstants.SERVICE_ID_KEY
中可以找到服务ID。此过滤器可以使用不同的HTTP客户端:
HttpClient
:默认客户端。OkHttpClient
v3:通过在类路径上放置com.squareup.okhttp3:okhttp
库并设置ribbon.okhttp.enabled=true
来启用。ribbon.restclient.enabled=true
启用。该客户端具有局限性,包括不支持PATCH方法,但是还具有内置的重试功能。SimpleHostRoutingFilter
:通过Apache HttpClient将请求发送到预定的URL。可在RequestContext.getRouteHost()
中找到URL。下面的大多数“如何编写”示例都包含在示例Zuul过滤器项目中。在该存储库中也有一些处理请求或响应正文的示例。
本节包括以下示例:
前置过滤器可在RequestContext
中设置数据,以便在下游的过滤器中使用。主要用例是设置路由过滤器所需的信息。以下示例显示了Zuul前置过滤器:
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("sample") != null) { // put the serviceId in `RequestContext` ctx.put(SERVICE_ID_KEY, request.getParameter("foo")); } return null; } }
前面的过滤器从sample
请求参数中填充SERVICE_ID_KEY
。实际上,您不应该执行这种直接映射。而是应从sample
的值中查找服务ID。
现在已填充SERVICE_ID_KEY
,PreDecorationFilter
将不运行,而RibbonRoutingFilter
将运行。
提示 | |
---|---|
如果要路由到完整URL,请致电 |
要修改路由过滤器转发到的路径,请设置REQUEST_URI_KEY
。
路由过滤器在预过滤器之后运行,并向其他服务发出请求。这里的许多工作是将请求和响应数据与客户端所需的模型相互转换。以下示例显示了Zuul路由过滤器:
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-Sample
标头:
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-Sample", UUID.randomUUID().toString()); return null; } }
注意 | |
---|---|
其他操作,例如转换响应主体,则更加复杂且计算量大。 |
如果在Zuul过滤器生命周期的任何部分抛出异常,则将执行错误过滤器。仅当RequestContext.getThrowable()
不是null
时才运行SendErrorFilter
。然后,它在请求中设置特定的javax.servlet.error.*
属性,并将请求转发到Spring Boot错误页面。
您是否要使用非JVM语言来利用Eureka,Ribbon和Config Server?Spring Cloud Netflix Sidecar的灵感来自Netflix Prana。它包括一个HTTP API,用于获取给定服务的所有实例(按主机和端口)。您也可以通过嵌入式Zuul代理来代理服务调用,该代理从Eureka获取其路由条目。可以直接通过主机查找或通过Zuul代理访问Spring Cloud Config服务器。非JVM应用程序应实施运行状况检查,以便Sidecar可以向Eureka报告应用程序是启动还是关闭。
要在项目中包含Sidecar,请使用组ID为org.springframework.cloud
且工件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.secure-port-enabled
选项提供了一种启用流量安全端口的方法。sidecar.health-uri
是在非JVM应用程序上可访问的URI,它模仿Spring Boot运行状况指示器。它应该返回类似于以下内容的JSON文档:
health-uri-document。
{ "status":"UP" }
以下application.yml示例显示了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
的示例响应在不同的主机上返回两个实例:
/ hosts / customers。
[ { "host": "myhost", "port": 9000, "uri": "http://myhost:9000", "serviceId": "CUSTOMERS", "secure": false }, { "host": "myhost2", "port": 9000, "uri": "http://myhost2:9000", "serviceId": "CUSTOMERS", "secure": false } ]
非JVM应用程序(如果Sidecar位于端口5678上)可通过http://localhost:5678/hosts/{serviceId}
访问此API。
Zuul代理会自动将Eureka中已知的每个服务的路由添加到/<serviceId>
,因此可以在/customers
中使用客户服务。非JVM应用程序可以在http://localhost:5678/customers
上访问客户服务(假设Sidecar正在侦听5678端口)。
如果Config Server已向Eureka注册,则非JVM应用程序可以通过Zuul代理对其进行访问。如果ConfigServer的serviceId
为configserver
并且Sidecar在端口5678上,则可以在http:// localhost:5678 / configserver上对其进行访问。
非JVM应用程序可以利用Config Server返回YAML文档的功能。例如,调用https://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
要在使用HTTP时使运行状况检查请求接受所有证书,请将sidecar.accept-all-ssl-certificates
设置为`true。
Spring Cloud Netflix提供了多种发出HTTP请求的方式。您可以使用负载均衡的RestTemplate
,Ribbon或Feign。无论您如何选择创建HTTP请求,始终都有一个请求失败的机会。当请求失败时,您可能希望自动重试该请求。为此,在使用Sping Cloud Netflix时,您需要在应用程序的类路径中包含Spring重试。如果存在Spring重试,则负载平衡的RestTemplates
,Feign和Zuul会自动重试任何失败的请求(假设您的配置允许这样做)。
默认情况下,重试请求时不使用任何退避策略。如果要配置退避策略,则需要创建类型为LoadBalancedRetryFactory
的bean并为给定服务覆盖createBackOffPolicy
方法,如以下示例所示:
@Configuration public class MyConfiguration { @Bean LoadBalancedRetryFactory retryFactory() { return new LoadBalancedRetryFactory() { @Override public BackOffPolicy createBackOffPolicy(String service) { return new ExponentialBackOffPolicy(); } }; } }
将Ribbon与Spring重试一起使用时,可以通过配置某些Ribbon属性来控制重试功能。为此,请设置client.ribbon.MaxAutoRetries
,client.ribbon.MaxAutoRetriesNextServer
和client.ribbon.OkToRetryOnAllOperations
属性。有关这些属性的作用的说明,请参见Ribbon文档。
警告 | |
---|---|
启用 |
此外,当响应中返回某些状态代码时,您可能想重试请求。您可以通过设置clientName.ribbon.retryableStatusCodes
属性来列出希望Ribbon客户端重试的响应代码,如以下示例所示:
clientName: ribbon: retryableStatusCodes: 404,502
您也可以创建类型为LoadBalancedRetryPolicy
的bean,并实现retryableStatusCode
方法以根据状态码重试请求。
Spring Cloud Netflix会自动为您创建Ribbon,Feign和Zuul使用的HTTP客户端。但是,您也可以根据需要提供自定义的HTTP客户端。为此,如果使用的是Apache Http Cient,则可以创建类型为ClosableHttpClient
的bean,如果使用的是OK HTTP,则可以创建类型为OkHttpClient
的bean。
注意 | |
---|---|
创建自己的HTTP客户端时,您还负责为这些客户端实施正确的连接管理策略。这样做不当会导致资源管理问题。 |
将模块置于维护模式意味着Spring Cloud团队将不再向模块添加新功能。我们将修复阻止程序错误和安全性问题,还将考虑并审查社区的一些小请求。
自Greenwich 发布列车全面上市以来,我们打算继续为这些模块提供至少一年的支持。
以下Spring Cloud Netflix模块和相应的启动器将进入维护模式:
注意 | |
---|---|
这不包括Eureka或并发限制模块。 |
Greenwich SR5
该项目通过自动配置并绑定到Spring环境和其他Spring编程模型习惯用法,为Spring Boot应用提供了OpenFeign集成。
Feign是声明性的web服务客户端。它使编写web服务客户端更加容易。要使用Feign,请创建一个接口并对其进行注释。它具有可插入的注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并支持使用Spring Web中默认使用的同一HttpMessageConverters
。Spring Cloud集成了Ribbon和Eureka以在使用Feign时提供负载平衡的http客户端。
要将Feign包含在您的项目中,请将启动器与组org.springframework.cloud
和工件ID spring-cloud-starter-openfeign
一起使用。有关
使用当前Spring Cloud版本Train设置构建系统的详细信息,请参见Spring Cloud项目页面。
示例spring boot应用
@SpringBootApplication @EnableFeignClients public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
StoreClient.java。
@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
批注中,字符串值(上面的“ stores”)是一个任意的客户端名称,用于创建Ribbon负载均衡器(请参见下面的Ribbon support的详细信息)。您还可以使用url
属性(绝对值或仅是主机名)来指定URL。在应用程序上下文中,bean的名称是接口的标准名称。要指定自己的别名值,可以使用@FeignClient
批注的qualifier
值。
上面的Ribbon客户端将希望发现“商店”服务的物理地址。如果您的应用程序是Eureka客户端,则它将在Eureka服务注册表中解析该服务。如果您不想使用Eureka,则可以简单地在外部配置中配置服务器列表(例如,参见 上文)。
Spring Cloud的Feign支持中的中心概念是指定客户的概念。每个虚拟客户端都是组件的一部分,这些组件可以一起工作以按需联系远程服务器,并且该组件的名称是您使用@FeignClient
批注将其指定为应用程序开发人员的。Spring Cloud根据需要使用FeignClientsConfiguration
为每个命名客户端创建一个新的合奏作为ApplicationContext
。其中包含feign.Decoder
,feign.Encoder
和feign.Contract
。通过使用@FeignClient
批注的contextId
属性,可以覆盖该集合的名称。
Spring Cloud使您可以通过使用@FeignClient
声明其他配置(在FeignClientsConfiguration
之上)来完全控制假客户端。例:
@FeignClient(name = "stores", configuration = FooConfiguration.class) public interface StoreClient { //.. }
在这种情况下,客户端由FeignClientsConfiguration
中已有的组件以及FooConfiguration
中的任何组件组成(其中后者将覆盖前者)。
注意 | |
---|---|
|
注意 | |
---|---|
现在不推荐使用 |
注意 | |
---|---|
除了更改 |
警告 | |
---|---|
以前,使用 |
name
和url
属性中支持占位符。
@FeignClient(name = "${feign.name}", url = "${feign.url}") public interface StoreClient { //.. }
Spring Cloud Netflix默认提供以下beans伪装(BeanType
beanName:ClassName
):
Decoder
feignDecoder:ResponseEntityDecoder
(包装SpringDecoder
)Encoder
feignEncoder:SpringEncoder
Logger
feignLogger:Slf4jLogger
Contract
feignContract:SpringMvcContract
Feign.Builder
feignBuilder:HystrixFeign.Builder
Client
feignClient:如果启用了Ribbon,则它是LoadBalancerFeignClient
,否则使用默认的伪装客户端。可以通过分别将feign.okhttp.enabled
或feign.httpclient.enabled
设置为true
并将其放在类路径中来使用OkHttpClient和ApacheHttpClient虚拟客户端。您可以自定义HTTP客户端,方法是在使用Apache时提供ClosableHttpClient
的bean,在使用OK HTTP时提供OkHttpClient
。
Spring Cloud Netflix 默认情况下不会为伪装提供以下beans,但仍会从应用程序上下文中查找以下类型的beans以创建伪装客户端:
Logger.Level
Retryer
ErrorDecoder
Request.Options
Collection<RequestInterceptor>
SetterFactory
创建其中一种类型的bean并将其放置在@FeignClient
配置中(例如上述FooConfiguration
),您可以覆盖上述的每个beans。例:
@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
的集合中。
@FeignClient
也可以使用配置属性进行配置。
application.yml
feign: client: config: feignName: connectTimeout: 5000 readTimeout: 5000 loggerLevel: full errorDecoder: com.example.SimpleErrorDecoder retryer: com.example.SimpleRetryer requestInterceptors: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor decode404: false encoder: com.example.SimpleEncoder decoder: com.example.SimpleDecoder contract: com.example.SimpleContract
可以按照与上述类似的方式在@EnableFeignClients
属性defaultConfiguration
中指定默认配置。不同之处在于此配置将适用于所有伪客户端。
如果您希望使用配置属性来配置所有@FeignClient
,则可以使用default
虚拟名称创建配置属性。
application.yml
feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic
如果我们同时创建@Configuration
bean和配置属性,则配置属性将获胜。它将覆盖@Configuration
值。但是,如果要将优先级更改为@Configuration
,可以将feign.client.default-to-properties
更改为false
。
注意 | |
---|---|
如果您需要在 |
application.yml
# To disable Hystrix in Feign feign: hystrix: enabled: false # To set thread isolation to SEMAPHORE hystrix: command: default: execution: isolation: strategy: SEMAPHORE
如果我们要创建多个具有相同名称或URL的伪装客户端,以便它们指向同一台服务器,但每个客户端使用不同的自定义配置,则必须使用@FeignClient
的contextId
属性,以避免这些配置beans的名称冲突。
@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class) public interface FooClient { //.. }
@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class) public interface BarClient { //.. }
在某些情况下,可能有必要使用上述方法无法实现的方式自定义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, Contract contract) { this.fooClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor("user", "user")) .target(FooClient.class, "http://PROD-SVC"); this.adminClient = Feign.builder().client(client) .encoder(encoder) .decoder(decoder) .contract(contract) .requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin")) .target(FooClient.class, "http://PROD-SVC"); } }
注意 | |
---|---|
在上面的示例中, |
注意 | |
---|---|
|
注意 | |
---|---|
Feign |
如果Hystrix在类路径上并且在feign.hystrix.enabled=true
上,则Feign将使用断路器包装所有方法。还可以返回com.netflix.hystrix.HystrixCommand
。这使您可以使用反应性模式(通过调用.toObservable()
或.observe()
或异步使用(通过调用.queue()
)。
要基于每个客户端禁用Hystrix支持,请创建具有{prototype“范围的普通Feign.Builder
,例如:
@Configuration public class FooConfiguration { @Bean @Scope("prototype") public Feign.Builder feignBuilder() { return Feign.builder(); } }
警告 | |
---|---|
在Spring Cloud Dalston发行版之前,如果Hystrix在类路径Feign上,则默认情况下会将所有方法包装在断路器中。Spring Cloud Dalston中对此默认行为进行了更改,以支持选择加入方法。 |
Hystrix支持回退的概念:当它们的电路断开或出现错误时执行的默认代码路径。要为给定的@FeignClient
启用回退,请将fallback
属性设置为实现回退的类名称。您还需要将实现声明为Spring bean。
@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 HystrixClient() { @Override public Hello iFailSometimes() { return new Hello("fallback; reason was: " + cause.getMessage()); } }; } }
警告 | |
---|---|
Feign中的后备实现以及Hystrix后备如何工作存在局限性。返回 |
当将Feign与后退Hystrix一起使用时,ApplicationContext
中有多个相同类型的beans。这将导致@Autowired
无法正常工作,因为没有一个bean或标记为主要的一个。要解决此问题,Spring Cloud Netflix将所有Feign实例标记为@Primary
,因此Spring Framework将知道要插入哪个bean。在某些情况下,这可能不是理想的。要关闭此行为,请将@FeignClient
的primary
属性设置为false。
@FeignClient(name = "hello", primary = false) public interface HelloClient { // methods here }
Feign通过单继承接口支持样板API。这允许将常用操作分组为方便的基本接口。
UserService.java。
public interface UserService { @RequestMapping(method = RequestMethod.GET, value ="/users/{id}") User getUser(@PathVariable("id") long id); }
UserResource.java。
@RestController public class UserResource implements UserService { }
UserClient.java。
package project.user; @FeignClient("users") public interface UserClient extends UserService { }
注意 | |
---|---|
通常不建议在服务器和客户端之间共享接口。它引入了紧密耦合,并且实际上也不能与当前形式的Spring MVC一起使用(方法参数映射不被继承)。 |
您可以考虑为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日志记录仅响应DEBUG
级别。
application.yml。
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; } }
OpenFeign @QueryMap
批注支持将POJO用作GET参数映射。不幸的是,默认的OpenFeign QueryMap注释与Spring不兼容,因为它缺少value
属性。
Spring Cloud OpenFeign提供等效的@SpringQueryMap
批注,该批注用于将POJO或Map参数注释为查询参数映射。
例如,Params
类定义参数param1
和param2
:
// Params.java public class Params { private String param1; private String param2; // [Getters and setters omitted for brevity] }
以下伪装客户端通过使用@SpringQueryMap
批注来使用Params
类:
@FeignClient("demo") public class DemoTemplate { @GetMapping(path = "/demo") String demoEndpoint(@SpringQueryMap Params params); }
Spring的数据集成之旅始于Spring Integration。通过其编程模型,它为开发人员提供了一致的开发经验,以构建可以包含企业集成模式以与外部系统(例如数据库,消息代理等)连接的应用程序。
快进到云时代,微服务已在企业环境中变得突出。Spring Boot改变了开发人员构建应用程序的方式。借助Spring的编程模型和Spring Boot处理的运行时职责,无缝开发了基于生产,生产级Spring的独立微服务。
为了将其扩展到数据集成工作负载,Spring Integration和Spring Boot被放到一个新项目中。Spring Cloud Stream出生了。
使用Spring Cloud Stream,开发人员可以:*隔离地构建,测试,迭代和部署以数据为中心的应用程序。*应用现代微服务架构模式,包括通过消息传递进行组合。*以事件为中心的思维将应用程序职责分离。事件可以表示及时发生的事件,下游消费者应用程序可以在不知道事件起源或生产者身份的情况下做出反应。*将业务逻辑移植到消息代理(例如RabbitMQ,Apache Kafka,Amazon Kinesis)上。*通过使用项目Reactor的Flux和Kafka Streams API,可以在基于通道的应用程序和基于非通道的应用程序绑定方案之间进行互操作,以支持无状态和有状态的计算。*依靠框架对常见用例的自动内容类型支持。可以扩展到不同的数据转换类型。
您可以按照以下三步指南在不到5分钟的时间内尝试Spring Cloud Stream。
我们向您展示如何创建一个Spring Cloud Stream应用程序,该应用程序接收来自您选择的消息传递中间件的消息(稍后会详细介绍),并将接收到的消息记录到控制台。我们称之为LoggingConsumer
。尽管不是很实用,但是它很好地介绍了一些主要概念和抽象,使您更容易理解本用户指南的其余部分。
三个步骤如下:
要开始使用,请访问Spring Initializr。从那里,您可以生成我们的LoggingConsumer
应用程序。为此:
stream
。当“ 云流 ”选项出现时,选择它。选择“ Kafka ”或“ RabbitMQ ”。
基本上,您选择应用程序绑定到的消息传递中间件。我们建议您使用已经安装的那种,或者对安装和运行感到更自在。另外,从“启动程序”屏幕上可以看到,还有一些其他选项可以选择。例如,您可以选择Gradle作为构建工具,而不是Maven(默认设置)。
在工件字段中,输入“ logging-consumer”。
Artifact字段的值成为应用程序名称。如果您选择RabbitMQ作为中间件,则Spring Initializr现在应该如下所示:
单击生成项目按钮。
这样做会将生成的项目的压缩版本下载到硬盘上。
提示 | |
---|---|
我们鼓励您探索Spring Initializr中可用的许多可能性。它使您可以创建许多不同种类的Spring应用程序。 |
现在,您可以将项目导入到IDE中。请记住,取决于IDE,您可能需要遵循特定的导入过程。例如,根据项目的生成方式(Maven或Gradle),您可能需要遵循特定的导入过程(例如,在Eclipse或STS中,您需要使用File→Import→Maven→现有的Maven项目)。
导入后,该项目必须没有任何错误。另外,src/main/java
应该包含com.example.loggingconsumer.LoggingConsumerApplication
。
从技术上讲,此时,您可以运行应用程序的主类。它已经是有效的Spring Boot应用程序。但是,它没有任何作用,因此我们想添加一些代码。
修改com.example.loggingconsumer.LoggingConsumerApplication
类,如下所示:
@SpringBootApplication @EnableBinding(Sink.class) public class LoggingConsumerApplication { public static void main(String[] args) { SpringApplication.run(LoggingConsumerApplication.class, args); } @StreamListener(Sink.INPUT) public void handle(Person person) { System.out.println("Received: " + person); } public static class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return this.name; } } }
从前面的清单中可以看到:
@EnableBinding(Sink.class)
启用了Sink
绑定(输入无输出)。这样做会向框架发出信号,以启动对消息传递中间件的绑定,在该消息传递中间件自动创建绑定到Sink.INPUT
通道的目的地(即队列,主题和其他)。handler
方法来接收类型为Person
的传入消息。这样做可以使您看到框架的核心功能之一:它尝试自动将传入的消息有效负载转换为类型Person
。您现在有了一个功能齐全的Spring Cloud Stream应用程序,该应用程序确实侦听消息。为了简单起见,我们从这里开始,假设您在第一步中选择了RabbitMQ 。假设已经安装并运行了RabbitMQ,则可以通过在IDE中运行其main
方法来启动应用程序。
您应该看到以下输出:
--- [ main] c.s.b.r.p.RabbitExchangeQueueProvisioner : declaring queue for inbound: input.anonymous.CbMIwdkJSBO1ZoPDOtHtCg, bound to: input --- [ main] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672] --- [ main] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory#2a3a299:0/SimpleConnection@66c83fc8. . . . . . --- [ main] o.s.i.a.i.AmqpInboundChannelAdapter : started inbound.input.anonymous.CbMIwdkJSBO1ZoPDOtHtCg . . . --- [ main] c.e.l.LoggingConsumerApplication : Started LoggingConsumerApplication in 2.531 seconds (JVM running for 2.897)
转到RabbitMQ管理控制台或任何其他RabbitMQ客户端,然后向input.anonymous.CbMIwdkJSBO1ZoPDOtHtCg
发送消息。anonymous.CbMIwdkJSBO1ZoPDOtHtCg
部分代表组名并已生成,因此在您的环境中它一定是不同的。对于更可预测的内容,可以通过设置spring.cloud.stream.bindings.input.group=hello
(或您喜欢的任何名称)来使用显式组名。
消息的内容应为Person
类的JSON表示形式,如下所示:
{"name":"Sam Spade"}
然后,在控制台中,您应该看到:
Received: Sam Spade
您还可以将应用程序生成并打包到引导jar中(使用./mvnw clean install
),并使用java -jar
命令运行生成的JAR。
现在,您有了一个正在运行的(尽管非常基础的)Spring Cloud Stream应用程序。
Spring Cloud Stream引入了许多新功能,增强功能和更改。以下各节概述了最值得注意的部分:
MeterRegistry
也以bean的形式提供,以便自定义应用程序可以将其自动连线以捕获自定义指标。有关更多详细信息,请参见 “ 第37章,度量标准发射器 ”。RetryTemplate
的属性外,我们现在还允许您提供自己的模板,有效地覆盖了框架提供的模板。要使用它,请在您的应用程序中将其配置为@Bean
。此版本包括以下显着增强:
如果既不需要执行器也不需要web依赖项,那么此更改将减少已部署应用程序的占用空间。通过手动添加以下依赖项之一,它还使您可以在反应式和常规web范式之间切换。
以下清单显示了如何添加常规的web框架:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
以下清单显示了如何添加反应式web框架:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
下表显示了如何添加执行器依赖性:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Verion 2.0的核心主题之一是围绕内容类型协商和消息转换的改进(在一致性和性能方面)。以下摘要概述了该领域的显着变化和改进。有关更多详细信息,请参见“ 第32章,内容类型协商 ”部分。此外,此博客文章还包含更多详细信息。
MessageConverter
对象处理。@StreamMessageConverter
批注以提供自定义MessageConverter
对象。Content Type
作为application/json
,在迁移1.3应用程序或以混合模式(即1.3生产者→2.0消费者)进行操作时,需要考虑该默认值。MessageHandler
的参数类型的情况下,带有文本有效载荷且contentType
为text/…
或…/json
的消息不再转换为Message<String>
。 public void handle(Message<?> message)
或public void handle(Object payload)
)。此外,强参数类型可能不足以正确地转换消息,因此contentType
标头可能被某些MessageConverters
用作补充。从2.0版开始,不推荐使用以下项目:
JavaSerializationMessageConverter
和KryoMessageConverter
暂时保留。但是,我们计划将来将它们移出核心软件包和支持。弃用此文件的主要原因是要标记基于类型,特定于语言的序列化可能在分布式环境中引起的问题,在该环境中,生产者和使用者可能依赖于不同的JVM版本或具有不同版本的支持库(即Kryo)。我们还想提请注意这样一个事实,即消费者和生产者甚至可能都不是基于Java的,因此多语言风格的序列化(即JSON)更适合。
以下是显着弃用的快速摘要。有关更多详细信息,请参见相应的{spring-cloud-stream-javadoc-current} [javadoc]。
SharedChannelRegistry
.使用SharedBindingTargetRegistry
。Bindings
.符合条件的Beans已通过其类型唯一标识,例如,提供了Source
,Processor
或自定义绑定:public interface Sample { String OUTPUT = "sampleOutput"; @Output(Sample.OUTPUT) MessageChannel output(); }
HeaderMode.raw
.使用none
,headers
或embeddedHeaders
ProducerProperties.partitionKeyExtractorClass
赞成partitionKeyExtractorName
,而ProducerProperties.partitionSelectorClass
赞成partitionSelectorName
。此更改可确保Spring配置和管理两个组件,并以Spring友好的方式对其进行引用。BinderAwareRouterBeanPostProcessor
.在保留该组件的同时,它不再是BeanPostProcessor
,并且将来会重命名。BinderProperties.setEnvironment(Properties environment)
.使用BinderProperties.setEnvironment(Map<String, Object> environment)
。本节将详细介绍如何使用Spring Cloud Stream。它涵盖了诸如创建和运行流应用程序之类的主题。
Spring Cloud Stream是用于构建消息驱动的微服务应用程序的框架。Spring Cloud Stream在Spring Boot的基础上创建了独立的生产级Spring应用程序,并使用Spring Integration提供了到消息代理的连接。它提供了来自多家供应商的中间件的合理配置,并介绍了持久性发布-订阅语义,使用者组和分区的概念。
您可以在应用程序中添加@EnableBinding
批注,以立即连接到消息代理,还可以在方法中添加@StreamListener
,以使其接收流处理的事件。以下示例显示了接收外部消息的接收器应用程序:
@SpringBootApplication @EnableBinding(Sink.class) public class VoteRecordingSinkApplication { public static void main(String[] args) { SpringApplication.run(VoteRecordingSinkApplication.class, args); } @StreamListener(Sink.INPUT) public void processVote(Vote vote) { votingService.recordVote(vote); } }
@EnableBinding
批注将一个或多个接口作为参数(在这种情况下,该参数是单个Sink
接口)。接口声明输入和输出通道。Spring Cloud Stream提供了Source
,Sink
和Processor
接口。您也可以定义自己的接口。
以下清单显示了Sink
接口的定义:
public interface Sink { String INPUT = "input"; @Input(Sink.INPUT) SubscribableChannel input(); }
@Input
注释标识一个输入通道,接收到的消息通过该输入通道进入应用程序。@Output
注释标识一个输出通道,已发布的消息通过该输出通道离开应用程序。@Input
和@Output
批注可以使用频道名称作为参数。如果未提供名称,则使用带注释的方法的名称。
Spring Cloud Stream为您创建接口的实现。您可以通过自动装配在应用程序中使用它,如以下示例所示(来自测试用例):
@RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = VoteRecordingSinkApplication.class) @WebAppConfiguration @DirtiesContext public class StreamApplicationTests { @Autowired private Sink sink; @Test public void contextLoads() { assertNotNull(this.sink.input()); } }
Spring Cloud Stream提供了许多抽象和原语,简化了消息驱动的微服务应用程序的编写。本节概述以下内容:
Spring Cloud Stream应用程序由与中间件无关的内核组成。该应用程序通过Spring Cloud Stream注入到其中的输入和输出通道与外界进行通信。通道通过特定于中间件的Binder实现与外部代理连接。
可以从IDE以独立模式运行Spring Cloud Stream应用程序以进行测试。要在生产环境中运行Spring Cloud Stream应用程序,您可以使用为Maven或Gradle提供的标准Spring Boot工具来创建可执行(或“ fat ”)JAR。有关更多详细信息,请参见Spring Boot参考指南。
Spring Cloud Stream为Kafka和Rabbit MQ提供了Binder实现。Spring Cloud Stream还包括一个TestSupportBinder,它使通道保持不变,因此测试可以与通道直接交互并可靠地断言所接收的内容。您也可以使用可扩展的API编写自己的Binder。
Spring Cloud Stream使用Spring Boot进行配置,而Binder抽象使Spring Cloud Stream应用程序可以灵活地连接中间件。例如,部署者可以在运行时动态选择通道连接到的目的地(例如Kafka主题或RabbitMQ交换)。可以通过外部配置属性以及Spring Boot支持的任何形式(包括应用程序参数,环境变量以及application.yml
或application.properties
文件)提供这种配置。在第27章“ 介绍Spring Cloud Stream”的接收器示例中,将spring.cloud.stream.bindings.input.destination
应用程序属性设置为raw-sensor-data
会使其从raw-sensor-data
Kafka主题或绑定到该队列的队列中读取raw-sensor-data
RabbitMQ交换。
Spring Cloud Stream自动检测并使用在类路径上找到的活页夹。您可以使用具有相同代码的不同类型的中间件。为此,在构建时包括一个不同的活页夹。对于更复杂的用例,您还可以在应用程序中打包多个活页夹,并在运行时选择活页夹(甚至为不同的通道使用不同的活页夹)。
应用程序之间的通信遵循发布-订阅模型,其中数据通过共享主题进行广播。在下图中可以看到,该图显示了一组交互的Spring Cloud Stream应用程序的典型部署。
传感器报告给HTTP端点的数据将发送到名为raw-sensor-data
的公共目标。从目的地开始,它由计算时间窗平均值的微服务应用程序和另一个将原始数据提取到HDFS(Hadoop分布式文件系统)的微服务应用程序独立处理。为了处理数据,两个应用程序都在运行时将主题声明为其输入。
发布-订阅通信模型降低了生产者和使用者的复杂性,并允许在不中断现有流程的情况下将新应用添加到拓扑中。例如,在平均计算应用程序的下游,您可以添加一个应用程序,该应用程序计算用于显示和监视的最高温度值。然后,您可以添加另一个解释相同平均值流以进行故障检测的应用程序。通过共享主题而不是点对点队列进行所有通信可以减少微服务之间的耦合。
尽管发布-订阅消息传递的概念并不是新概念,但是Spring Cloud Stream采取了额外的步骤,使其成为其应用程序模型的明智选择。通过使用本机中间件支持,Spring Cloud Stream还简化了跨不同平台的发布-订阅模型的使用。
尽管发布-订阅模型使通过共享主题轻松连接应用程序变得很重要,但是通过创建给定应用程序的多个实例进行扩展的能力同样重要。这样做时,会将应用程序的不同实例置于竞争的消费者关系中,在该消费者关系中,仅其中一个实例可以处理给定消息。
Spring Cloud Stream通过消费者群体的概念对这种行为进行建模。(Spring Cloud Stream消费者组类似于Kafka消费者组并受其启发。)每个消费者绑定都可以使用spring.cloud.stream.bindings.<channelName>.group
属性来指定组名。对于下图所示的消费者,此属性将设置为spring.cloud.stream.bindings.<channelName>.group=hdfsWrite
或spring.cloud.stream.bindings.<channelName>.group=average
。
订阅给定目标的所有组都将收到已发布数据的副本,但是每个组中只有一个成员从该目标接收给定消息。默认情况下,未指定组时,Spring Cloud Stream会将应用程序分配给与所有其他使用者组具有发布-订阅关系的匿名且独立的单成员使用者组。
支持两种类型的使用者:
在2.0版之前,仅支持异步使用者。消息一旦可用,就会被传递,并且有线程可以处理它。
当您希望控制消息的处理速率时,可能需要使用同步使用者。
Spring Cloud Stream支持在给定应用程序的多个实例之间分区数据。在分区方案中,物理通信介质(例如代理主题)被视为结构化为多个分区。一个或多个生产者应用程序实例将数据发送到多个消费者应用程序实例,并确保由共同特征标识的数据由同一消费者实例处理。
Spring Cloud Stream提供了用于以统一方式实现分区处理用例的通用抽象。因此,无论代理本身是否自然地被分区(例如,Kafka)(例如,RabbitMQ),都可以使用分区。
分区是有状态处理中的关键概念,对于确保所有相关数据都一起处理,分区是至关重要的(出于性能或一致性方面的考虑)。例如,在带时间窗的平均计算示例中,重要的是,来自任何给定传感器的所有测量都应由同一应用实例处理。
注意 | |
---|---|
要设置分区处理方案,必须同时配置数据产生端和数据消耗端。 |
要了解编程模型,您应该熟悉以下核心概念:
目标Binders是Spring Cloud Stream的扩展组件,负责提供必要的配置和实现以促进与外部消息传递系统的集成。这种集成负责连接,委派和与生产者和消费者之间的消息路由,数据类型转换,用户代码调用等等。
Binders承担了许多样板工作,否则这些工作就落在了您的肩上。但是,要实现这一点,活页夹仍然需要用户提供的一些简单但需要的指令集形式的帮助,通常以某种类型的配置形式出现。
尽管讨论所有可用的绑定器和绑定配置选项(本手册的其余部分都涉及它们)不在本节的讨论范围之内,但 目标绑定确实需要特别注意。下一节将详细讨论。
如前所述,目标绑定提供了外部消息传递系统与应用程序提供的生产者和消费者之间的桥梁。
将@EnableBinding批注应用于应用程序的配置类之一可定义目标绑定。@EnableBinding
注释本身使用@Configuration
进行元注释,并触发Spring Cloud Stream基础结构的配置。
下面的示例显示了一个功能完整且运行正常的Spring Cloud Stream应用程序,该应用程序从INPUT
目标接收的消息净荷为String
类型(请参见第32章,内容类型协商部分),并将其记录到控制台,并将其转换为大写字母后将其发送到OUTPUT
目标。
@SpringBootApplication @EnableBinding(Processor.class) public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public String handle(String value) { System.out.println("Received: " + value); return value.toUpperCase(); } }
如您所见,@EnableBinding
批注可以将一个或多个接口类作为参数。这些参数称为绑定,它们包含表示可绑定组件的方法。这些组件通常是基于通道的活页夹(例如Rabbit,Kafka等)的消息通道(请参见Spring消息传递)。但是,其他类型的绑定可以为相应技术的本机功能提供支持。例如,Kafka Streams绑定器(以前称为KStream)允许直接绑定到Kafka Streams(有关更多详细信息,请参见Kafka Streams)。
Spring Cloud Stream已经为典型的消息交换合同提供了绑定接口,其中包括:
public interface Sink { String INPUT = "input"; @Input(Sink.INPUT) SubscribableChannel input(); }
public interface Source { String OUTPUT = "output"; @Output(Source.OUTPUT) MessageChannel output(); }
public interface Processor extends Source, Sink {}
尽管前面的示例满足了大多数情况,但是您也可以通过定义自己的绑定接口并使用@Input
和@Output
批注来标识实际的可绑定组件,从而定义自己的合同。
例如:
public interface Barista { @Input SubscribableChannel orders(); @Output MessageChannel hotDrinks(); @Output MessageChannel coldDrinks(); }
将上一个示例中显示的接口用作@EnableBinding
的参数将分别触发三个绑定通道的创建,分别命名为orders
,hotDrinks
和coldDrinks
。
您可以根据需要提供任意数量的绑定接口,作为@EnableBinding
批注的参数,如以下示例所示:
@EnableBinding(value = { Orders.class, Payment.class })
在Spring Cloud Stream中,可绑定的MessageChannel
组件是Spring消息传递MessageChannel
(用于出站)及其扩展名SubscribableChannel
(用于入站)。
可轮询的目标绑定
尽管前面描述的绑定支持基于事件的消息使用,但是有时您需要更多控制,例如使用率。
从2.0版开始,您现在可以绑定可轮询的使用者:
以下示例显示了如何绑定可轮询的使用者:
public interface PolledBarista { @Input PollableMessageSource orders(); . . . }
在这种情况下,PollableMessageSource
的实现绑定到orders
“通道”。有关更多详细信息,请参见第29.3.5节“使用轮询的使用者”。
自定义频道名称
通过使用@Input
和@Output
批注,可以为该通道指定自定义的通道名称,如以下示例所示:
public interface Barista { @Input("inboundOrders") SubscribableChannel orders(); }
在前面的示例中,创建的绑定通道被命名为inboundOrders
。
通常,您不需要直接访问各个通道或绑定(除非通过@EnableBinding
注释对其进行配置)。但是,您有时可能会遇到诸如测试或其他极端情况的情况。
除了为每个绑定生成通道并将其注册为Spring beans外,对于每个绑定接口,Spring Cloud Stream还会生成一个实现该接口的bean。这意味着您可以通过在应用程序中自动接线来访问表示绑定或各个通道的接口,如以下两个示例所示:
自动接线绑定界面
@Autowire private Source source public void sayHello(String name) { source.output().send(MessageBuilder.withPayload(name).build()); }
自动连线个别频道
@Autowire private MessageChannel output; public void sayHello(String name) { output.send(MessageBuilder.withPayload(name).build()); }
对于自定义通道名称或在需要特别命名通道的多通道方案中,您也可以使用标准Spring的@Qualifier
批注。
下面的示例演示如何以这种方式使用@Qualifier批注:
@Autowire @Qualifier("myChannel") private MessageChannel output;
您可以使用Spring Integration注释或Spring Cloud Stream本机注释编写Spring Cloud Stream应用程序。
Spring Cloud Stream建立在Enterprise Integration Patterns定义的概念和模式的基础之上,并依靠其内部实现依赖于Spring项目组合Spring Integration框架中已经建立且流行的Enterprise Integration Patterns实现 。
因此,它支持Spring Integration已经建立的基础,语义和配置选项是很自然的。
例如,您可以将Source
的输出通道附加到MessageSource
并使用熟悉的@InboundChannelAdapter
注释,如下所示:
@EnableBinding(Source.class) public class TimerSource { @Bean @InboundChannelAdapter(value = Source.OUTPUT, poller = @Poller(fixedDelay = "10", maxMessagesPerPoll = "1")) public MessageSource<String> timerMessageSource() { return () -> new GenericMessage<>("Hello Spring Cloud Stream"); } }
同样,可以在提供处理器绑定合同的消息处理程序方法的实现时使用@Transformer或@ServiceActivator ,如以下示例所示:
@EnableBinding(Processor.class) public class TransformProcessor { @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) public Object transform(String message) { return message.toUpperCase(); } }
注意 | |
---|---|
尽管这可能会略过一些,但重要的是要了解,当您使用 |
作为对Spring Integration支持的补充,Spring Cloud Stream提供了自己的@StreamListener
注释,其模仿其他Spring消息注释(@MessageMapping
,@JmsListener
,@RabbitListener
等)并提供便利,例如基于内容的路由等。
@EnableBinding(Sink.class) public class VoteHandler { @Autowired VotingService votingService; @StreamListener(Sink.INPUT) public void handle(Vote vote) { votingService.record(vote); } }
与其他Spring消息传递方法一样,方法参数可以用@Payload
,@Headers
和@Header
进行注释。
对于返回数据的方法,必须使用@SendTo
批注为该方法返回的数据指定输出绑定目标,如以下示例所示:
@EnableBinding(Processor.class) public class TransformProcessor { @Autowired VotingService votingService; @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public VoteResult handle(Vote vote) { return votingService.record(vote); } }
Spring Cloud Stream支持根据条件将消息调度到用@StreamListener
注释的多个处理程序方法。
为了有资格支持条件分派,一种方法必须满足以下条件:
该条件由注释的condition
参数中的SpEL表达式指定,并针对每条消息进行评估。所有与条件匹配的处理程序都在同一线程中调用,并且不必假设调用的顺序。
在具有分配条件的@StreamListener
的以下示例中,所有带有标头type
且具有值bogey
的消息都被分配到receiveBogey
方法,所有带有标头{11的消息值bacall
的/}发送到receiveBacall
方法。
@EnableBinding(Sink.class) @EnableAutoConfiguration public static class TestPojoWithAnnotatedArguments { @StreamListener(target = Sink.INPUT, condition = "headers['type']=='bogey'") public void receiveBogey(@Payload BogeyPojo bogeyPojo) { // handle the message } @StreamListener(target = Sink.INPUT, condition = "headers['type']=='bacall'") public void receiveBacall(@Payload BacallPojo bacallPojo) { // handle the message } }
condition
上下文中的内容类型协商
了解使用@StreamListener
参数condition
的基于内容的路由背后的一些机制很重要,尤其是在整个消息类型的上下文中。如果在继续之前熟悉第32章,内容类型协商,这也可能会有所帮助。
请考虑以下情形:
@EnableBinding(Sink.class) @EnableAutoConfiguration public static class CatsAndDogs { @StreamListener(target = Sink.INPUT, condition = "payload.class.simpleName=='Dog'") public void bark(Dog dog) { // handle the message } @StreamListener(target = Sink.INPUT, condition = "payload.class.simpleName=='Cat'") public void purr(Cat cat) { // handle the message } }
前面的代码是完全有效的。它可以毫无问题地进行编译和部署,但是永远不会产生您期望的结果。
这是因为您正在测试的东西在您期望的状态下尚不存在。这是因为消息的有效负载尚未从有线格式(byte[]
)转换为所需的类型。换句话说,它尚未经过第32章,内容类型协商中描述的类型转换过程。
因此,除非使用SPeL表达式评估原始数据(例如,字节数组中第一个字节的值),否则请使用基于消息标头的表达式(例如condition = "headers['type']=='dog'"
)。
注意 | |
---|---|
目前,仅基于通道的绑定程序(不支持响应编程)支持通过 |
从Spring Cloud Stream v2.1开始,定义流处理程序和源的另一种方法是使用对Spring Cloud函数的内置支持,其中可以将它们表示为java.util.function.[Supplier/Function/Consumer]
类型的beans。
若要指定要绑定到绑定公开的外部目标的功能bean,必须提供spring.cloud.stream.function.definition
属性。
这是Processor应用程序将消息处理程序公开为java.util.function.Function
的示例
@SpringBootApplication @EnableBinding(Processor.class) public class MyFunctionBootApp { public static void main(String[] args) { SpringApplication.run(MyFunctionBootApp.class, "--spring.cloud.stream.function.definition=toUpperCase"); } @Bean public Function<String, String> toUpperCase() { return s -> s.toUpperCase(); } }
在上面的代码中,我们仅定义了类型为java.util.function.Function
的bean(称为toUpperCase)并将其标识为bean,用作消息处理程序,其“输入”和“输出”必须绑定到外部目标由处理器绑定公开。
以下是支持源,处理器和接收器的简单功能应用程序的示例。
这是定义为java.util.function.Supplier
的Source应用程序的示例
@SpringBootApplication @EnableBinding(Source.class) public static class SourceFromSupplier { public static void main(String[] args) { SpringApplication.run(SourceFromSupplier.class, "--spring.cloud.stream.function.definition=date"); } @Bean public Supplier<Date> date() { return () -> new Date(12345L); } }
这是定义为java.util.function.Function
的Processor应用程序的示例
@SpringBootApplication @EnableBinding(Processor.class) public static class ProcessorFromFunction { public static void main(String[] args) { SpringApplication.run(ProcessorFromFunction.class, "--spring.cloud.stream.function.definition=toUpperCase"); } @Bean public Function<String, String> toUpperCase() { return s -> s.toUpperCase(); } }
这是一个定义为java.util.function.Consumer
的接收器应用程序的示例
@EnableAutoConfiguration @EnableBinding(Sink.class) public static class SinkFromConsumer { public static void main(String[] args) { SpringApplication.run(SinkFromConsumer.class, "--spring.cloud.stream.function.definition=sink"); } @Bean public Consumer<String> sink() { return System.out::println; } }
使用此编程模型,您还可以从功能组合中受益,在该功能组合中,您可以从一组简单的函数中动态组成复杂的处理程序。作为示例,我们将以下函数bean添加到上面定义的应用程序中
@Bean public Function<String, String> wrapInQuotes() { return s -> "\"" + s + "\""; }
并修改spring.cloud.stream.function.definition
属性以反映您打算从'toUpperCase'和'wrapInQuotes'编写新函数的意图。为此,可以使用Spring Cloud函数使用|
(管道)符号。因此,完成我们的示例,我们的属性现在将如下所示:
—spring.cloud.stream.function.definition=toUpperCase|wrapInQuotes
使用轮询的使用者时,您可以按需轮询PollableMessageSource
。考虑以下受调查消费者的示例:
public interface PolledConsumer { @Input PollableMessageSource destIn(); @Output MessageChannel destOut(); }
给定上一个示例中的受调查消费者,您可以按以下方式使用它:
@Bean public ApplicationRunner poller(PollableMessageSource destIn, MessageChannel destOut) { return args -> { while (someCondition()) { try { if (!destIn.poll(m -> { String newPayload = ((String) m.getPayload()).toUpperCase(); destOut.send(new GenericMessage<>(newPayload)); })) { Thread.sleep(1000); } } catch (Exception e) { // handle failure } } }; }
PollableMessageSource.poll()
方法采用一个MessageHandler
参数(通常为lambda表达式,如此处所示)。如果收到并成功处理了消息,它将返回true
。
与消息驱动的使用者一样,如果MessageHandler
引发异常,消息将发布到错误通道,如“ ???”中所述。”。
通常,poll()
方法会在MessageHandler
退出时确认该消息。如果该方法异常退出,则该消息将被拒绝(不重新排队),但请参阅“处理错误”一节。您可以通过对确认负责来覆盖该行为,如以下示例所示:
@Bean public ApplicationRunner poller(PollableMessageSource dest1In, MessageChannel dest2Out) { return args -> { while (someCondition()) { if (!dest1In.poll(m -> { StaticMessageHeaderAccessor.getAcknowledgmentCallback(m).noAutoAck(); // e.g. hand off to another thread which can perform the ack // or acknowledge(Status.REQUEUE) })) { Thread.sleep(1000); } } }; }
重要 | |
---|---|
您必须在某一时刻 |
重要 | |
---|---|
某些消息传递系统(例如Apache Kafka)在日志中维护简单的偏移量。如果传递失败,并用 |
还有一个重载的poll
方法,其定义如下:
poll(MessageHandler handler, ParameterizedTypeReference<?> type)
type
是一个转换提示,它允许转换传入的消息有效负载,如以下示例所示:
boolean result = pollableSource.poll(received -> { Map<String, Foo> payload = (Map<String, Foo>) received.getPayload(); ... }, new ParameterizedTypeReference<Map<String, Foo>>() {});
默认情况下,为可轮询源配置了一个错误通道。如果回调引发异常,则将ErrorMessage
发送到错误通道(<destination>.<group>.errors
);此错误通道也桥接到全局Spring Integration errorChannel
。
您可以使用@ServiceActivator
订阅任何一个错误通道来处理错误。如果没有订阅,则将仅记录错误并确认消息成功。如果错误通道服务激活器引发异常,则该消息将被拒绝(默认情况下),并且不会重新发送。如果服务激活器抛出RequeueCurrentMessageException
,则该消息将在代理处重新排队,并在随后的轮询中再次检索。
如果侦听器直接抛出RequeueCurrentMessageException
,则如上所述,该消息将重新排队,并且不会发送到错误通道。
错误会发生,Spring Cloud Stream提供了几种灵活的机制来处理它们。错误处理有两种形式:
Spring Cloud Stream使用Spring重试库来促进成功的消息处理。有关更多详细信息,请参见第29.4.3节“重试模板”。但是,当所有方法均失败时,消息处理程序引发的异常将传播回绑定程序。那时,活页夹调用自定义错误处理程序或将错误传达回消息传递系统(重新排队,DLQ等)。
有两种类型的应用程序级错误处理。可以在每个绑定订阅中处理错误,或者全局处理程序可以处理所有绑定订阅错误。让我们查看详细信息。
对于每个输入绑定,Spring Cloud Stream创建具有以下语义<destinationName>.errors
的专用错误通道。
注意 | |
---|---|
|
考虑以下:
spring.cloud.stream.bindings.input.group=myGroup
@StreamListener(Sink.INPUT) // destination name 'input.myGroup' public void handle(Person value) { throw new RuntimeException("BOOM!"); } @ServiceActivator(inputChannel = Processor.INPUT + ".myGroup.errors") //channel name 'input.myGroup.errors' public void error(Message<?> message) { System.out.println("Handling ERROR: " + message); }
在前面的示例中,目标名称为input.myGroup
,专用错误通道名称为input.myGroup.errors
。
注意 | |
---|---|
@StreamListener批注的使用专门用于定义桥接内部通道和外部目标的绑定。假设目标特定错误通道没有关联的外部目标,则该通道是Spring Integration(SI)的特权。这意味着必须使用SI处理程序注释之一(即@ ServiceActivator,@ Transformer等)定义用于此类目标的处理程序。 |
注意 | |
---|---|
如果未指定 |
另外,如果您绑定到现有目的地,例如:
spring.cloud.stream.bindings.input.destination=myFooDestination spring.cloud.stream.bindings.input.group=myGroup
完整的目标名称为myFooDestination.myGroup
,然后专用错误通道名称为myFooDestination.myGroup.errors
。
回到例子...
预订名为input
的通道的handle(..)
方法会引发异常。给定错误通道input.myGroup.errors
的订阅者,所有错误消息均由该订阅者处理。
如果您有多个绑定,则可能需要一个错误处理程序。Spring Cloud Stream 通过将每个单独的错误通道桥接到名为errorChannel
的通道来自动提供对全局错误通道的支持,从而允许单个订阅者处理所有错误,如以下示例所示:
@StreamListener("errorChannel") public void error(Message<?> message) { System.out.println("Handling ERROR: " + message); }
如果错误处理逻辑相同,则与哪个处理程序产生错误无关,这可能是一个方便的选择。
系统级错误处理意味着将错误传递回消息传递系统,并且鉴于并非每个消息传递系统都相同,因此各个粘合剂的功能可能有所不同。
也就是说,在本节中,我们解释了系统级错误处理背后的一般思想,并以Rabbit活页夹为例。注意:Kafka活页夹提供了类似的支持,尽管某些配置属性确实有所不同。另外,有关更多详细信息和配置选项,请参见各个活页夹的文档。
如果未配置内部错误处理程序,则错误将传播到绑定程序,而绑定程序随后会将这些错误传播回消息传递系统。根据消息传递系统的功能,此类系统可能会丢弃该消息,重新排队该消息以进行重新处理或将失败的消息发送给DLQ。Rabbit和Kafka都支持这些概念。但是,其他联编程序可能没有,因此请参阅您单独的联编程序的文档,以获取有关受支持的系统级错误处理选项的详细信息。
DLQ允许将失败的消息发送到特殊目标:-Dead Letter Queue。
配置后,失败的消息将发送到此目标,以进行后续的重新处理或审核与对帐。
例如,继续前面的示例,并使用Rabbit活页夹设置DLQ,您需要设置以下属性:
spring.cloud.stream.rabbit.bindings.input.consumer.auto-bind-dlq=true
请记住,在以上属性中,input
对应于输入目标绑定的名称。consumer
指示它是消费者属性,auto-bind-dlq
指示绑定程序为input
目标配置DLQ,这将导致名为input.myGroup.dlq
的附加Rabbit队列。
配置完成后,所有失败的消息都会通过错误消息路由到此队列,类似于以下内容:
delivery_mode: 1 headers: x-death: count: 1 reason: rejected queue: input.hello time: 1522328151 exchange: routing-keys: input.myGroup Payload {"name”:"Bob"}
从上面可以看到,原始消息会保留下来以供进一步操作。
但是,您可能已经注意到的一件事是,有关消息处理的原始问题的信息有限。例如,您看不到与原始错误相对应的堆栈跟踪。要获取有关原始错误的更多相关信息,您必须设置一个附加属性:
spring.cloud.stream.rabbit.bindings.input.consumer.republish-to-dlq=true
这样做会强制内部错误处理程序在将错误消息发布到DLQ之前拦截该错误消息并向其添加其他信息。配置完成后,您会看到错误消息包含与原始错误有关的更多信息,如下所示:
delivery_mode: 2 headers: x-original-exchange: x-exception-message: has an error x-original-routingKey: input.myGroup x-exception-stacktrace: org.springframework.messaging.MessageHandlingException: nested exception is org.springframework.messaging.MessagingException: has an error, failedMessage=GenericMessage [payload=byte[15], headers={amqp_receivedDeliveryMode=NON_PERSISTENT, amqp_receivedRoutingKey=input.hello, amqp_deliveryTag=1, deliveryAttempt=3, amqp_consumerQueue=input.hello, amqp_redelivered=false, id=a15231e6-3f80-677b-5ad7-d4b1e61e486e, amqp_consumerTag=amq.ctag-skBFapilvtZhDsn0k3ZmQg, contentType=application/json, timestamp=1522327846136}] at org.spring...integ...han...MethodInvokingMessageProcessor.processMessage(MethodInvokingMessageProcessor.java:107) at. . . . . Payload {"name”:"Bob"}
这有效地结合了应用程序级和系统级的错误处理,以进一步协助下游故障排除机制。
如前所述,当前支持的活页夹(Rabbit和Kafka)依靠RetryTemplate
来促进成功的消息处理。有关详细信息,请参见第29.4.3节“重试模板”。但是,对于max-attempts
属性设置为1的情况,将禁用消息的内部重新处理。此时,您可以通过指示消息传递系统重新排队失败的消息来促进消息的重新处理(重试)。重新排队后,失败的消息将被发送回原始处理程序,从而创建一个重试循环。
如果错误的性质与某些资源的偶发性但短期不可用有关,则此选项可能是可行的。
为此,必须设置以下属性:
spring.cloud.stream.bindings.input.consumer.max-attempts=1 spring.cloud.stream.rabbit.bindings.input.consumer.requeue-rejected=true
在前面的示例中,max-attempts
设置为1,实际上禁用了内部重试,而requeue-rejected
(重新排队拒绝消息的缩写)被设置为true
。设置后,失败的消息将重新提交给同一处理程序并连续循环,直到处理程序抛出AmqpRejectAndDontRequeueException
为止,从本质上讲,您可以在处理程序本身内构建自己的重试逻辑。
RetryTemplate
是Spring重试库的一部分。尽管涵盖RetryTemplate
的所有功能超出了本文档的范围,但我们将提及以下与RetryTemplate
特别相关的使用者属性:
处理消息的尝试次数。
默认值:3。
重试时的退避初始间隔。
默认值1000毫秒。
最大退避间隔。
默认值10000毫秒。
退避乘数。
默认为2.0。
retryableExceptions
中未列出的由侦听器引发的异常是否可以重试。
默认值:true
。
键中Throwable类名称的映射,值中布尔值的映射。指定将要重试的那些异常(和子类)。另请参见defaultRetriable
。示例:spring.cloud.stream.bindings.input.consumer.retryable-exceptions.java.lang.IllegalStateException=false
。
默认值:空。
尽管上述设置足以满足大多数自定义要求,但它们可能无法满足某些复杂的要求,此时,您可能希望提供自己的RetryTemplate
实例。为此,在应用程序配置中将其配置为bean。应用程序提供的实例将覆盖框架提供的实例。另外,为避免冲突,必须将绑定程序要使用的RetryTemplate
实例限定为@StreamRetryTemplate
。例如,
@StreamRetryTemplate public RetryTemplate myRetryTemplate() { return new RetryTemplate(); }
从上面的示例中可以看到,由于@StreamRetryTemplate
是合格的@Bean
,因此无需使用@Bean
对其进行注释。
Spring Cloud Stream还支持使用反应式API,将传入和传出的数据作为连续的数据流进行处理。可通过spring-cloud-stream-reactive
获得对反应式API的支持,需要将其显式添加到您的项目中。
具有响应式API的编程模型是声明性的。您可以使用描述从入站数据流到出站数据流的功能转换的运算符,而不是指定每个消息的处理方式。
目前Spring Cloud Stream仅支持Reactor API。将来,我们打算支持基于反应式流的更通用的模型。
反应式编程模型还使用@StreamListener
注释来设置反应式处理程序。区别在于:
@StreamListener
批注不能指定输入或输出,因为它们作为参数提供并从方法返回值。@Input
和@Output
注释,分别指示传入和传出数据流连接到哪个输入或输出。@Output
注释,指示应该将数据发送到的输入。注意 | |
---|---|
响应式编程支持需要Java 1.8。 |
注意 | |
---|---|
从Spring Cloud Stream 1.1.1起(从发行版Brooklyn.SR2开始),反应式编程支持要求使用Reactor 3.0.4.RELEASE及更高版本。不支持更早的Reactor版本(包括3.0.1.RELEASE,3.0.2.RELEASE和3.0.3.RELEASE)。 |
注意 | |
---|---|
当前,术语“ 反应式 ”的使用是指正在使用的反应式API,而不是指执行模型是反应式的(也就是说,绑定的端点仍然使用“推”式而非“拉式”模型)。尽管通过使用Reactor提供了一些反压支持,但在将来的发行版中,我们确实打算通过对连接的中间件使用本机反应性客户端来完全支持反应性管道。 |
基于Reactor的处理程序可以具有以下参数类型:
@Input
注释的参数,它支持Reactor Flux
类型。入站Flux的参数化遵循与处理单个消息时相同的规则:可以是整个Message
,可以是Message
有效负载的POJO或由于以下原因而产生的POJO:基于Message
内容类型标头的转换。提供了多个输入。Output
注释的参数,它支持FluxSender
类型,该类型将方法生成的Flux
与输出连接起来。一般而言,仅在该方法可以具有多个输出时才建议将输出指定为参数。基于Reactor的处理程序支持Flux
的返回类型。在这种情况下,必须用@Output
进行注释。当单个输出Flux
可用时,建议使用该方法的返回值。
以下示例显示了基于Reactor的Processor
:
@EnableBinding(Processor.class) @EnableAutoConfiguration public static class UppercaseTransformer { @StreamListener @Output(Processor.OUTPUT) public Flux<String> receive(@Input(Processor.INPUT) Flux<String> input) { return input.map(s -> s.toUpperCase()); } }
使用输出参数的同一处理器看起来像以下示例:
@EnableBinding(Processor.class) @EnableAutoConfiguration public static class UppercaseTransformer { @StreamListener public void receive(@Input(Processor.INPUT) Flux<String> input, @Output(Processor.OUTPUT) FluxSender output) { output.send(input.map(s -> s.toUpperCase())); } }
Spring Cloud Stream反应性支持还提供了通过@StreamEmitter
注释创建反应性源的功能。通过使用@StreamEmitter
批注,可以将常规源转换为被动源。@StreamEmitter
是方法级别的注释,用于将方法标记为用@EnableBinding
声明的输出的发射器。您不能将@Input
批注与@StreamEmitter
一起使用,因为标有该批注的方法不会监听任何输入。而是用标记为@StreamEmitter
的方法生成输出。遵循@StreamListener
中使用的相同编程模型,@StreamEmitter
还允许灵活地使用@Output
批注,具体取决于方法是否具有任何参数,返回类型和其他考虑因素。
本节的其余部分包含使用各种样式的@StreamEmitter
批注的示例。
以下示例每毫秒发出一次Hello, World
消息,并发布到Reactor Flux
中:
@EnableBinding(Source.class) @EnableAutoConfiguration public static class HelloWorldEmitter { @StreamEmitter @Output(Source.OUTPUT) public Flux<String> emit() { return Flux.intervalMillis(1) .map(l -> "Hello World"); } }
在前面的示例中,Flux
中的结果消息被发送到Source
的输出通道。
下一个示例是@StreamEmmitter
的另一种形式,它发送Reactor Flux
。以下方法代替返回Flux
,而是使用FluxSender
从源代码中以编程方式发送Flux
:
@EnableBinding(Source.class) @EnableAutoConfiguration public static class HelloWorldEmitter { @StreamEmitter @Output(Source.OUTPUT) public void emit(FluxSender output) { output.send(Flux.intervalMillis(1) .map(l -> "Hello World")); } }
下一个示例在功能和样式上与上述代码段完全相同。但是,它没有在方法上使用显式的@Output
注释,而是在方法参数上使用了注释。
@EnableBinding(Source.class) @EnableAutoConfiguration public static class HelloWorldEmitter { @StreamEmitter public void emit(@Output(Source.OUTPUT) FluxSender output) { output.send(Flux.intervalMillis(1) .map(l -> "Hello World")); } }
本节的最后一个示例是使用Reactive Streams Publisher API并利用Spring Integration Java DSL中对它的支持来编写反应源的另一种方式。以下示例中的Publisher
仍在幕后使用Reactor Flux
,但是,从应用程序角度看,这对用户是透明的,并且对于Spring Integration仅需要响应流和Java DSL:
@EnableBinding(Source.class) @EnableAutoConfiguration public static class HelloWorldEmitter { @StreamEmitter @Output(Source.OUTPUT) @Bean public Publisher<Message<String>> emit() { return IntegrationFlows.from(() -> new GenericMessage<>("Hello World"), e -> e.poller(p -> p.fixedDelay(1))) .toReactivePublisher(); } }
Spring Cloud Stream提供了Binder抽象,用于连接到外部中间件上的物理目标。本节提供有关Binder SPI背后的主要概念,其主要组件以及特定于实现的详细信息。
下图显示了生产者和消费者的一般关系:
生产者是将消息发送到通道的任何组件。可以将该通道绑定到具有该代理的Binder
实现的外部消息代理。调用bindProducer()
方法时,第一个参数是代理内目标的名称,第二个参数是生产者向其发送消息的本地通道实例,第三个参数包含属性(例如分区键表达式) ),以在为该通道创建的适配器中使用。
使用者是从通道接收消息的任何组件。与生产者一样,消费者的渠道可以绑定到外部消息代理。调用bindConsumer()
方法时,第一个参数是目标名称,第二个参数提供逻辑消费者组的名称。由给定目标的使用者绑定表示的每个组都接收生产者发送到该目标的每个消息的副本(也就是说,它遵循常规的发布-订阅语义)。如果有多个使用相同组名绑定的使用者实例,那么消息将在这些使用者实例之间进行负载平衡,以便由生产者发送的每条消息仅在每个组内的单个使用者实例中被使用(也就是说,它遵循常规排队语义)。
Binder SPI由许多接口,现成的实用程序类和发现策略组成,这些策略提供了用于连接到外部中间件的可插拔机制。
SPI的关键是Binder
接口,这是将输入和输出连接到外部中间件的策略。以下清单显示了Binder
接口的定义:
public interface Binder<T, C extends ConsumerProperties, P extends ProducerProperties> { Binding<T> bindConsumer(String name, String group, T inboundBindTarget, C consumerProperties); Binding<T> bindProducer(String name, T outboundBindTarget, P producerProperties); }
该接口已参数化,提供了许多扩展点:
MessageChannel
,但将来打算将其用作扩展点。典型的活页夹实现包括以下内容:
Binder
接口的类;@Configuration
类,它创建类型为Binder
的bean和中间件连接基础结构。在类路径上找到一个META-INF/spring.binders
文件,其中包含一个或多个绑定程序定义,如以下示例所示:
kafka:\ org.springframework.cloud.stream.binder.kafka.config.KafkaBinderConfiguration
Spring Cloud Stream依赖于Binder SPI的实现来执行将通道连接到消息代理的任务。每个Binder实现通常都连接到一种消息传递系统。
默认情况下,Spring Cloud Stream依靠Spring Boot的自动配置来配置绑定过程。如果在类路径上找到单个Binder实现,则Spring Cloud Stream将自动使用它。例如,旨在仅绑定到RabbitMQ的Spring Cloud Stream项目可以添加以下依赖项:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-rabbit</artifactId> </dependency>
有关其他绑定程序依赖项的特定Maven坐标,请参阅该绑定程序实现的文档。
当类路径上存在多个绑定程序时,应用程序必须指示将哪个绑定程序用于每个通道绑定。每个活页夹配置都包含一个META-INF/spring.binders
文件,它是一个简单的属性文件,如以下示例所示:
rabbit:\ org.springframework.cloud.stream.binder.rabbit.config.RabbitServiceAutoConfiguration
其他提供的活页夹实现(例如Kafka)也存在类似的文件,并且期望自定义活页夹实现也将提供它们。关键字表示绑定程序实现的标识名,而该值是逗号分隔的配置类列表,每个配置类都包含一个且仅一个bean类型为org.springframework.cloud.stream.binder.Binder
的定义。
可以使用spring.cloud.stream.defaultBinder
属性(例如,spring.cloud.stream.defaultBinder=rabbit
)在全局上执行Binder选择,也可以通过在每个通道绑定上配置活页夹来分别进行Binder选择。例如,从Kafka读取并写入RabbitMQ的处理器应用程序(具有分别名为input
和output
的通道用于读取和写入)可以指定以下配置:
spring.cloud.stream.bindings.input.binder=kafka spring.cloud.stream.bindings.output.binder=rabbit
默认情况下,活页夹共享应用程序的Spring Boot自动配置,以便创建在类路径上找到的每个活页夹的一个实例。如果您的应用程序应连接到多个相同类型的代理,则可以指定多个绑定程序配置,每个配置具有不同的环境设置。
注意 | |
---|---|
启用显式绑定程序配置将完全禁用默认的绑定程序配置过程。如果这样做,则配置中必须包括所有正在使用的活页夹。打算透明使用Spring Cloud Stream的框架可以创建可以按名称引用的活页夹配置,但它们不会影响默认的活页夹配置。为此,活页夹配置可以将其 |
以下示例显示了连接到两个RabbitMQ代理实例的处理器应用程序的典型配置:
spring: cloud: stream: bindings: input: destination: thing1 binder: rabbit1 output: destination: thing2 binder: rabbit2 binders: rabbit1: type: rabbit environment: spring: rabbitmq: host: <host1> rabbit2: type: rabbit environment: spring: rabbitmq: host: <host2>
从2.0版开始,Spring Cloud Stream支持通过Actuator端点进行绑定的可视化和控制。
从2.0版执行器开始,并且web是可选的,您必须首先添加web依赖项之一,然后手动添加执行器依赖项。以下示例说明如何为Web框架添加依赖项:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
以下示例显示如何为WebFlux框架添加依赖项:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
您可以添加执行器依赖项,如下所示:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
注意 | |
---|---|
要在Cloud Foundry中运行Spring Cloud Stream 2.0应用程序,必须将 |
您还必须通过设置以下属性来启用bindings
执行器端点:--management.endpoints.web.exposure.include=bindings
。
一旦满足这些先决条件。应用程序启动时,您应该在日志中看到以下内容:
: Mapped "{[/actuator/bindings/{name}],methods=[POST]. . . : Mapped "{[/actuator/bindings],methods=[GET]. . . : Mapped "{[/actuator/bindings/{name}],methods=[GET]. . .
要显示当前绑定,请访问以下URL:http://<host>:<port>/actuator/bindings
或者,要查看单个绑定,请访问类似于以下内容的URL之一:http://<host>:<port>/actuator/bindings/myBindingName
您还可以通过发布到相同的URL来停止,开始,暂停和恢复单个绑定,同时提供一个state
作为JSON的参数,如以下示例所示:
curl -d'{“ state”:“ STOPPED”}'-H“内容类型:应用程序/ json” -X POST http:// <主机>:<port> / actuator / bindings / myBindingName curl -d'{ “ state”:“ STARTED”}'-H“内容类型:application / json” -X POST http:// <host>:<port> / actuator / bindings / myBindingName curl -d'{“ state”:“ PAUSED“}'-H”内容类型:application / json“ -X POST http:// <host>:<port> / actuator / bindings / myBindingName curl -d'{” state“:” RESUMED“}'- H“内容类型:应用程序/ json” -X POST http:// <主机>:<端口> / actuator / bindings / myBindingName
注意 | |
---|---|
|
定制活页夹配置时,以下属性可用。这些属性通过org.springframework.cloud.stream.config.BinderProperties
公开
它们必须以spring.cloud.stream.binders.<configurationName>
为前缀。
资料夹类型。它通常引用在类路径上找到的绑定器之一-特别是META-INF/spring.binders
文件中的键。
默认情况下,它具有与配置名称相同的值。
配置是否继承应用程序本身的环境。
默认值:true
。
根可用于定制活页夹环境的一组属性。设置此属性后,在其中创建活页夹的上下文不是应用程序上下文的子级。该设置允许在粘合剂组分和应用组分之间完全分离。
默认值:empty
。
活页夹配置是被视为默认活页夹的候选者还是仅在明确引用时才可以使用。此设置允许添加活页夹配置,而不会干扰默认处理。
默认值:true
。
Spring Cloud Stream支持常规配置选项以及绑定和活页夹的配置。一些活页夹使附加的绑定属性支持特定于中间件的功能。
可以通过Spring Boot支持的任何机制向Spring Cloud Stream应用程序提供配置选项。这包括应用程序参数,环境变量以及YAML或.properties文件。
这些属性通过org.springframework.cloud.stream.config.BindingServiceProperties
公开
应用程序已部署实例的数量。必须在生产者端进行分区设置。使用RabbitMQ时必须在用户端设置,如果使用autoRebalanceEnabled=false
,则必须在Kafka时设置。
默认值:1
。
0
到instanceCount - 1
的数字。用于通过RabbitMQ和Kafka(如果是autoRebalanceEnabled=false
)进行分区。在Cloud Foundry中自动设置以匹配应用程序的实例索引。可以动态绑定的目的地列表(例如,在动态路由方案中)。如果设置,则只能绑定列出的目的地。
默认值:空(将任何目的地绑定)。
如果配置了多个联编程序,则使用的默认联编程序。请参见Classpath上的多个Binders。
默认值:空。
仅当cloud
配置文件处于活动状态并且应用程序提供了Spring Cloud Connectors时,此属性才适用。如果属性为false
(默认值),则绑定器检测到合适的绑定服务(例如,RabbitMQ绑定器在Cloud Foundry中绑定的RabbitMQ服务)并将其用于创建连接(通常通过Spring Cloud Connectors)。设置为true
时,此属性指示绑定程序完全忽略绑定的服务,并依赖Spring Boot属性(例如,依赖于环境中为RabbitMQ绑定程序提供的spring.rabbitmq.*
属性) 。连接到多个系统时,此属性的典型用法是嵌套在自定义环境中。
默认值:false
。
例如,活页夹不支持后期绑定和代理(例如Apache Kafka)关闭时,重试绑定创建之间的间隔(以秒为单位)。将该值设置为零可将此类情况视为致命情况,从而阻止应用程序启动。
默认值:30
绑定属性是使用spring.cloud.stream.bindings.<channelName>.<property>=<value>
格式提供的。<channelName>
代表正在配置的通道的名称(例如,对于Source
,为output
)。
为避免重复,Spring Cloud Stream支持所有通道的设置值,格式为spring.cloud.stream.default.<property>=<value>
。
在避免重复使用扩展绑定属性时,应使用此格式-spring.cloud.stream.<binder-type>.default.<producer|consumer>.<property>=<value>
。
在下面的内容中,我们指出了省略了spring.cloud.stream.bindings.<channelName>.
前缀的位置,仅着眼于属性名称,但要了解前缀是在运行时包含的。
这些属性通过org.springframework.cloud.stream.config.BindingProperties
公开
以下绑定属性可用于输入和输出绑定,并且必须以spring.cloud.stream.bindings.<channelName>.
为前缀(例如,spring.cloud.stream.bindings.input.destination=ticktock
)。
可以使用前缀spring.cloud.stream.default
设置默认值(例如“ spring.cloud.stream.default.contentType = application / json”)。
String
值。如果未设置,则使用通道名称。此属性的默认值不能被覆盖。渠道的消费群体。仅适用于入站绑定。请参阅消费者组。
默认值:null
(指示匿名使用者)。
频道的内容类型。请参见“ 第32章,内容类型协商 ”。
默认值:application/json
。
此绑定使用的粘合剂。有关详细信息,请参见“ 第30.4节“类路径上的多个Binders ”)。
默认值:null
(如果存在默认活页夹,则使用默认活页夹)。
这些属性通过org.springframework.cloud.stream.binder.ConsumerProperties
公开
以下绑定属性仅可用于输入绑定,并且必须以spring.cloud.stream.bindings.<channelName>.consumer.
为前缀(例如,spring.cloud.stream.bindings.input.consumer.concurrency=3
)。
可以使用spring.cloud.stream.default.consumer
前缀(例如,spring.cloud.stream.default.consumer.headerMode=none
)设置默认值。
入站使用者的并发。
默认值:1
。
消费者是否从分区生产者那里接收数据。
默认值:false
。
设置为none
时,禁用输入的标头解析。仅对本身不支持消息头并且需要消息头嵌入的消息中间件有效。当不支持本机头时,使用来自非Spring Cloud Stream应用程序的数据时,此选项很有用。设置为headers
时,它使用中间件的本机头机制。设置为embeddedHeaders
时,它将标头嵌入到消息有效负载中。
默认值:取决于活页夹的实现。
如果处理失败,则尝试处理消息的次数(包括第一次)。设置为1
以禁用重试。
默认值:3
。
重试时的退避初始间隔。
默认值:1000
。
最大退避间隔。
默认值:10000
。
退避乘数。
默认值:2.0
。
retryableExceptions
中未列出的由侦听器引发的异常是否可以重试。
默认值:true
。
设置为大于零的值时,它允许自定义此使用者的实例索引(如果与spring.cloud.stream.instanceIndex
不同)。设置为负值时,默认为spring.cloud.stream.instanceIndex
。有关更多信息,请参见“ 第34.2节“实例索引和实例计数” ”。
默认值:-1
。
设置为大于零的值时,它允许自定义此使用者的实例计数(如果与spring.cloud.stream.instanceCount
不同)。设置为负值时,默认为spring.cloud.stream.instanceCount
。有关更多信息,请参见“ 第34.2节“实例索引和实例计数” ”。
默认值:-1
。
键中Throwable类名称的映射,值中布尔值的映射。指定将要重试的那些异常(和子类)。另请参见defaultRetriable
。示例:spring.cloud.stream.bindings.input.consumer.retryable-exceptions.java.lang.IllegalStateException=false
。
默认值:空。
设置为true
时,入站消息将直接由客户端库反序列化,该库必须进行相应配置(例如,设置适当的Kafka生产者值反序列化器)。使用此配置时,入站消息解组不是基于绑定的contentType
。使用本机解码时,生产者负责使用适当的编码器(例如,Kafka生产者值序列化程序)对出站消息进行序列化。同样,当使用本机编码和解码时,headerMode=embeddedHeaders
属性将被忽略,并且标头不会嵌入消息中。请参见生产者属性useNativeEncoding
。
默认值:false
。
这些属性通过org.springframework.cloud.stream.binder.ProducerProperties
公开
以下绑定属性仅可用于输出绑定,并且必须以spring.cloud.stream.bindings.<channelName>.producer.
为前缀(例如,spring.cloud.stream.bindings.input.producer.partitionKeyExpression=payload.id
)。
可以使用前缀spring.cloud.stream.default.producer
(例如,spring.cloud.stream.default.producer.partitionKeyExpression=payload.id
)设置默认值。
一个SpEL表达式,用于确定如何对出站数据进行分区。如果已设置或设置了partitionKeyExtractorClass
,则会对该通道上的出站数据进行分区。partitionCount
必须设置为大于1的值才能生效。与partitionKeyExtractorClass
互斥。请参见“ 第28.6节“分区支持” ”。
默认值:null。
PartitionKeyExtractorStrategy
实现。如果已设置,或者已设置partitionKeyExpression
,则会对该通道上的出站数据进行分区。partitionCount
必须设置为大于1的值才能生效。与partitionKeyExpression
互斥。请参见“ 第28.6节“分区支持” ”。
默认值:null
。
PartitionSelectorStrategy
实现。与partitionSelectorExpression
互斥。如果两者均未设置,则将该分区选择为hashCode(key) % partitionCount
,其中key
通过partitionKeyExpression
或partitionKeyExtractorClass
计算。
默认值:null
。
用于自定义分区选择的SpEL表达式。与partitionSelectorClass
互斥。如果两者均未设置,则将分区选择为hashCode(key) % partitionCount
,其中key
通过partitionKeyExpression
或partitionKeyExtractorClass
计算。
默认值:null
。
数据的目标分区数(如果启用了分区)。如果生产者已分区,则必须将其设置为大于1的值。在Kafka上,它被解释为提示。取其较大者,并使用目标主题的分区数。
默认值:1
。
设置为none
时,它将禁用在输出中嵌入标头。它仅对本身不支持消息头并且需要消息头嵌入的消息中间件有效。当不支持本机头时,为非Spring Cloud Stream应用程序生成数据时,此选项很有用。设置为headers
时,它使用中间件的本机头机制。设置为embeddedHeaders
时,它将标头嵌入到消息有效负载中。
默认值:取决于活页夹的实现。
设置为true
时,出站消息将直接由客户端库进行序列化,该库必须进行相应配置(例如,设置适当的Kafka生产者值序列化程序)。使用此配置时,出站消息编组不是基于绑定的contentType
。使用本机编码时,使用方负责使用适当的解码器(例如,Kafka使用方值反序列化器)对入站消息进行反序列化。此外,当使用本机编码和解码时,headerMode=embeddedHeaders
属性将被忽略,并且标头不会嵌入消息中。请参阅消费者属性useNativeDecoding
。
默认值:false
。
设置为true
时,如果活页夹支持异步发送结果,则发送失败将发送到目标的错误通道。参见“ ??? ”以获取更多信息。
默认值:false
。
除了使用@EnableBinding
定义的通道外,Spring Cloud Stream还允许应用程序将消息发送到动态绑定的目的地。例如,当需要在运行时确定目标目的地时,这很有用。应用程序可以通过使用@EnableBinding
注释自动注册的BinderAwareChannelResolver
bean来实现。
“ spring.cloud.stream.dynamicDestinations”属性可用于将动态目标名称限制为已知集合(白名单)。如果未设置此属性,则可以动态绑定任何目标。
BinderAwareChannelResolver
可以直接使用,如以下使用路径变量来确定目标通道的REST控制器示例所示:
@EnableBinding @Controller public class SourceWithDynamicDestination { @Autowired private BinderAwareChannelResolver resolver; @RequestMapping(path = "/{target}", method = POST, consumes = "*/*") @ResponseStatus(HttpStatus.ACCEPTED) public void handleRequest(@RequestBody String body, @PathVariable("target") target, @RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) { sendMessage(body, target, contentType); } private void sendMessage(String body, String target, Object contentType) { resolver.resolveDestination(target).send(MessageBuilder.createMessage(body, new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType)))); } }
现在考虑当我们在默认端口(8080)上启动应用程序并使用CURL发出以下请求时会发生什么:
curl -H "Content-Type: application/json" -X POST -d "customer-1" http://localhost:8080/customers curl -H "Content-Type: application/json" -X POST -d "order-1" http://localhost:8080/orders
在经纪人中创建目的地“客户”和“订单”(交换为Rabbit或在主题为“ Kafka”中),名称为“客户”和“订单”,数据为发布到适当的目的地。
BinderAwareChannelResolver
是通用的Spring Integration DestinationResolver
,并且可以注入到其他组件中,例如,在路由器中使用基于传入的target
字段的SpEL表达式的路由器JSON消息。以下示例包含一个读取SpEL表达式的路由器:
@EnableBinding @Controller public class SourceWithDynamicDestination { @Autowired private BinderAwareChannelResolver resolver; @RequestMapping(path = "/", method = POST, consumes = "application/json") @ResponseStatus(HttpStatus.ACCEPTED) public void handleRequest(@RequestBody String body, @RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) { sendMessage(body, contentType); } private void sendMessage(Object body, Object contentType) { routerChannel().send(MessageBuilder.createMessage(body, new MessageHeaders(Collections.singletonMap(MessageHeaders.CONTENT_TYPE, contentType)))); } @Bean(name = "routerChannel") public MessageChannel routerChannel() { return new DirectChannel(); } @Bean @ServiceActivator(inputChannel = "routerChannel") public ExpressionEvaluatingRouter router() { ExpressionEvaluatingRouter router = new ExpressionEvaluatingRouter(new SpelExpressionParser().parseExpression("payload.target")); router.setDefaultOutputChannelName("default-output"); router.setChannelResolver(resolver); return router; } }
该路由器接收器应用程序使用此技术的按需创建的目的地。
如果预先知道通道名称,则可以像其他任何目的地一样配置生产者属性。或者,如果您注册NewBindingCallback<>
bean,则会在创建绑定之前调用它。回调采用绑定程序使用的扩展生产者属性的通用类型。它有一种方法:
void configure(String channelName, MessageChannel channel, ProducerProperties producerProperties,
T extendedProducerProperties);
下面的示例显示如何使用RabbitMQ活页夹:
@Bean public NewBindingCallback<RabbitProducerProperties> dynamicConfigurer() { return (name, channel, props, extended) -> { props.setRequiredGroups("bindThisQueue"); extended.setQueueNameGroupOnly(true); extended.setAutoBindDlq(true); extended.setDeadLetterQueueName("myDLQ"); }; }
注意 | |
---|---|
如果需要支持具有多个活页夹类型的动态目标,请对通用类型使用 |
数据转换是任何消息驱动的微服务体系结构的核心功能之一。假设在Spring Cloud Stream中,此类数据表示为Spring Message
,则在到达消息之前,可能必须将消息转换为所需的形状或大小。这是必需的,原因有两个:
有线格式通常为byte[]
(对于Kafka和Rabbit活页夹而言是正确的),但是它由活页夹实现方式控制。
在Spring Cloud Stream中,消息转换是通过org.springframework.messaging.converter.MessageConverter
完成的。
注意 | |
---|---|
作为后续细节的补充,您可能还需要阅读以下博客文章。 |
为了更好地理解内容类型协商的机制和必要性,我们以下面的消息处理程序为例,看一个非常简单的用例:
@StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public String handle(Person person) {..}
注意 | |
---|---|
为简单起见,我们假设这是应用程序中唯一的处理程序(我们假设没有内部管道)。 |
前面示例中所示的处理程序期望将Person
对象作为参数,并产生String
类型作为输出。为了使框架成功将传入的Message
作为参数传递给此处理程序,它必须以某种方式将Message
类型的有效负载从有线格式转换为Person
类型。换句话说,框架必须找到并应用适当的MessageConverter
。为此,该框架需要用户提供一些指导。处理程序方法本身的签名(Person
类型)已经提供了这些指令之一。因此,从理论上讲,这应该是(并且在某些情况下是足够的)。但是,对于大多数用例而言,为了选择适当的MessageConverter
,框架需要额外的信息。缺少的部分是contentType
。
Spring Cloud Stream提供了三种机制来定义contentType
(按优先顺序):
contentType
可以通过消息本身进行通信。通过提供contentType
标头,您可以声明用于查找和应用适当的MessageConverter
的内容类型。绑定:可以通过设置spring.cloud.stream.bindings.input.content-type
属性为每个目标绑定设置contentType
。
注意 | |
---|---|
属性名称中的 |
Message
标头或绑定中不存在contentType
,则使用默认的application/json
内容类型来查找和应用适当的MessageConverter
。如前所述,前面的列表还演示了平局时的优先顺序。例如,标头提供的内容类型优先于任何其他内容类型。对于按绑定设置的内容类型也是如此,这实际上使您可以覆盖默认内容类型。但是,它也提供了明智的默认值(由社区反馈确定)。
将application/json
设置为默认值的另一个原因是由分布式微服务体系结构驱动的互操作性要求,在该体系结构中,生产者和使用者不仅可以在不同的JVM中运行,而且还可以在不同的非JVM平台上运行。
当非无效处理程序方法返回时,如果返回值已经是Message
,则该Message
成为有效负载。但是,当返回值不是Message
时,将使用返回值作为有效负载构造新的Message
,同时从输入Message
继承标头减去由SpringIntegrationProperties.messageHandlerNotPropagatedHeaders
定义或过滤的标头。默认情况下,仅设置一个标头:contentType
。这意味着新的Message
没有设置contentType
头,从而确保contentType
可以演进。您始终可以选择不从处理程序方法返回Message
的位置,在该方法中可以注入所需的任何标头。
如果存在内部管道,则通过相同的转换过程将Message
发送到下一个处理程序。但是,如果没有内部管道或您已经到达内部管道的末尾,则Message
将发送回输出目的地。
如前所述,为了使框架选择适当的MessageConverter
,它需要参数类型以及(可选)内容类型信息。选择适当的MessageConverter
的逻辑驻留在参数解析器(HandlerMethodArgumentResolvers
)中,该解析器在调用用户定义的处理程序方法之前(即当框架知道实际的参数类型时)触发。如果参数类型与当前有效负载的类型不匹配,则框架将委派给预先配置的MessageConverters
的堆栈,以查看其中是否有一个可以转换有效负载。如您所见,MessageConverter的Object fromMessage(Message<?> message, Class<?> targetClass);
操作将targetClass
作为其参数之一。该框架还确保提供的Message
始终包含一个contentType
头。当没有contentType标头时,它会插入按绑定的contentType
标头或默认的contentType
标头。contentType
参数类型的组合是框架确定消息是否可以转换为目标类型的机制。如果找不到合适的MessageConverter
,则会引发异常,您可以通过添加自定义MessageConverter
来处理该异常(请参见“ 第32.3节,“用户定义的消息转换器” ”)。
但是,如果有效载荷类型与处理程序方法声明的目标类型匹配,该怎么办?在这种情况下,没有任何要转换的内容,并且有效载荷未经修改地传递。尽管这听起来很简单且合乎逻辑,但请记住以Message<?>
或Object
作为参数的处理程序方法。通过将目标类型声明为Object
(在Java中为instanceof
,是所有内容),实际上就放弃了转换过程。
注意 | |
---|---|
不要期望仅根据 |
MessageConverters
定义两种方法:
Object fromMessage(Message<?> message, Class<?> targetClass);
Message<?> toMessage(Object payload, @Nullable MessageHeaders headers);
重要的是要了解这些方法的约定及其用法,尤其是在Spring Cloud Stream的上下文中。
fromMessage
方法将传入的Message
转换为参数类型。Message
的有效载荷可以是任何类型,而MessageConverter
的实际实现要支持多种类型。例如,某些JSON转换器可能支持有效负载类型为byte[]
,String
等。当应用程序包含内部管道(即输入→handler1→handler2→....→输出)并且上游处理程序的输出结果为Message
时,这可能不是初始连接格式,这一点很重要。
但是,toMessage
方法的合同更为严格,必须始终将Message
转换为有线格式:byte[]
。
因此,出于所有意图和目的(尤其是在实现自己的转换器时),您将这两种方法视为具有以下签名:
Object fromMessage(Message<?> message, Class<?> targetClass); Message<byte[]> toMessage(Object payload, @Nullable MessageHeaders headers);
如前所述,该框架已经提供了MessageConverters
堆栈来处理最常见的用例。以下列表按优先级描述了提供的MessageConverters
(使用了第一个有效的MessageConverter
):
ApplicationJsonMessageMarshallingConverter
:org.springframework.messaging.converter.MappingJackson2MessageConverter
的变体。对于contentType
为application/json
(默认)的情况,支持将Message
的有效负载转换为POJO或从POJO转换为PO195。TupleJsonMessageConverter
:已弃用支持将Message
的有效负载转换为org.springframework.tuple.Tuple
或从org.springframework.tuple.Tuple
转换。ByteArrayMessageConverter
:在contentType
为application/octet-stream
的情况下,支持将Message
的有效载荷从byte[]
转换为byte[]
。它本质上是一个传递,主要是为了向后兼容而存在。ObjectStringMessageConverter
:当contentType
为text/plain
时,支持将任何类型转换为String
。它调用Object的toString()
方法,或者,如果有效载荷为byte[]
,则调用新的String(byte[])
。JavaSerializationMessageConverter
:已弃用当contentType
为application/x-java-serialized-object
时,支持基于Java序列化的转换。KryoMessageConverter
:已弃用当contentType
为application/x-java-object
时,支持基于Kryo序列化的转换。JsonUnmarshallingConverter
:类似于ApplicationJsonMessageMarshallingConverter
。当contentType
为application/x-java-object
时,它支持任何类型的转换。它期望将实际类型信息作为属性嵌入在contentType
中(例如,application/x-java-object;type=foo.bar.Cat
)。当找不到合适的转换器时,框架将引发异常。发生这种情况时,应检查代码和配置,并确保您没有错过任何内容(即,确保使用绑定或标头提供了contentType
)。但是,很可能您发现了一些不常见的情况(例如自定义contentType
),并且提供的MessageConverters
的当前堆栈不知道如何进行转换。在这种情况下,您可以添加自定义MessageConverter
。请参见第32.3节“用户定义的消息转换器”。
Spring Cloud Stream公开了定义和注册其他MessageConverters
的机制。要使用它,请实现org.springframework.messaging.converter.MessageConverter
,将其配置为@Bean
,并用@StreamMessageConverter
进行注释。然后将其附加到MessageConverter的现有堆栈中。
注意 | |
---|---|
了解自定义 |
下面的示例说明如何创建消息转换器bean以支持称为application/bar
的新内容类型:
@EnableBinding(Sink.class) @SpringBootApplication public static class SinkApplication { ... @Bean @StreamMessageConverter public MessageConverter customMessageConverter() { return new MyCustomMessageConverter(); } } public class MyCustomMessageConverter extends AbstractMessageConverter { public MyCustomMessageConverter() { super(new MimeType("application", "bar")); } @Override protected boolean supports(Class<?> clazz) { return (Bar.class.equals(clazz)); } @Override protected Object convertFromInternal(Message<?> message, Class<?> targetClass, Object conversionHint) { Object payload = message.getPayload(); return (payload instanceof Bar ? payload : new Bar((byte[]) payload)); } }
Spring Cloud Stream还为基于Avro的转换器和模式演变提供支持。有关详细信息,请参见“ 第33章,Schema Evolution支持 ”。
Spring Cloud Stream为模式演化提供了支持,因此数据可以随着时间的推移而演化,并且仍然可以与较新的生产者和消费者以及反之亦然。大多数序列化模型,尤其是旨在跨不同平台和语言进行移植的模型,都依赖于一种描述如何在二进制有效负载中序列化数据的模式。为了序列化数据然后解释它,发送方和接收方都必须有权访问描述二进制格式的模式。在某些情况下,可以从序列化时的有效负载类型或反序列化时的目标类型推断模式。但是,许多应用程序可以从访问描述二进制数据格式的显式架构中受益。通过模式注册表,您可以以文本格式(通常为JSON)存储模式信息,并使该信息可用于需要它以二进制格式接收和发送数据的各种应用程序。模式可引用为一个元组,该元组包括:
以下各节详细介绍了架构演变过程中涉及的各种组件。
与模式注册表服务器进行交互的客户端抽象是SchemaRegistryClient
接口,该接口具有以下结构:
public interface SchemaRegistryClient { SchemaRegistrationResponse register(String subject, String format, String schema); String fetch(SchemaReference schemaReference); String fetch(Integer id); }
Spring Cloud Stream提供了开箱即用的实现,可以与其自己的模式服务器进行交互,也可以与Confluent Schema注册中心进行交互。
可以使用@EnableSchemaRegistryClient
来配置Spring Cloud Stream模式注册表的客户端,如下所示:
@EnableBinding(Sink.class) @SpringBootApplication @EnableSchemaRegistryClient public static class AvroSinkApplication { ... }
注意 | |
---|---|
默认转换器经过优化,不仅可以缓存来自远程服务器的模式,还可以缓存 |
Schema Registry Client支持以下属性:
spring.cloud.stream.schemaRegistryClient.endpoint
http
或https
),端口和上下文路径。http://localhost:8990/
spring.cloud.stream.schemaRegistryClient.cached
false
。使用架构注册表客户端的客户端应将此设置为true
。true
对于已在应用程序上下文中注册了SchemaRegistryClient bean的应用程序,Spring Cloud Stream自动为架构管理配置Apache Avro消息转换器。由于接收消息的应用程序可以轻松访问可以与自己的读取器模式进行协调的写入器模式,因此这简化了模式的演变。
对于出站消息,如果通道的内容类型设置为application/*+avro
,则激活MessageConverter
,如下例所示:
spring.cloud.stream.bindings.output.contentType=application/*+avro
在出站转换期间,消息转换器尝试使用SchemaRegistryClient
推断每个出站消息的模式(基于其类型)并将其注册到主题(基于有效负载类型)。如果已经找到相同的模式,则将检索对其的引用。如果不是,则注册架构,并提供新的版本号。通过使用以下方案,将消息与contentType
头一起发送:application/[prefix].[subject].v[version]+avro
,其中prefix
是可配置的,而subject
是从有效负载类型推导出来的。
例如,类型为User
的消息可能作为二进制有效载荷发送,其内容类型为application/vnd.user.v2+avro
,其中user
是主题,2
是版本号。
接收消息时,转换器从传入消息的标头中推断模式引用,并尝试检索它。该模式在反序列化过程中用作编写器模式。
如果通过设置spring.cloud.stream.bindings.output.contentType=application/*+avro
启用了基于Avro的架构注册表客户端,则可以通过设置以下属性来自定义注册行为。
如果您希望转换器使用反射来从POJO推断Schema,请启用。
默认值:false
null
在Schema服务器中注册此属性中列出的所有.avsc
文件。
默认值:empty
Content-Type标头上要使用的前缀。
默认值:vnd
Spring Cloud Stream通过其spring-cloud-stream-schema
模块为基于模式的消息转换器提供支持。当前,基于模式的消息转换器开箱即用的唯一序列化格式是Apache Avro,将来的版本中将添加更多格式。
spring-cloud-stream-schema
模块包含可用于Apache Avro序列化的两种消息转换器:
AvroSchemaMessageConverter
支持通过使用预定义的架构或通过使用类中可用的架构信息(反射地或包含在SpecificRecord
中)来对消息进行序列化和反序列化。如果提供自定义转换器,则不会创建默认的AvroSchemaMessageConverter bean。以下示例显示了一个自定义转换器:
要使用自定义转换器,您只需将其添加到应用程序上下文中,就可以选择指定一个或多个与其关联的MimeTypes
。默认MimeType
为application/avro
。
如果转换的目标类型是GenericRecord
,则必须设置架构。
以下示例显示如何通过在没有预定义架构的情况下注册Apache Avro MessageConverter
在接收器应用程序中配置转换器。在此示例中,请注意,哑剧类型值为avro/bytes
,而不是默认的application/avro
。
@EnableBinding(Sink.class) @SpringBootApplication public static class SinkApplication { ... @Bean public MessageConverter userMessageConverter() { return new AvroSchemaMessageConverter(MimeType.valueOf("avro/bytes")); } }
相反,以下应用程序使用预定义的架构(在类路径上找到)注册一个转换器:
@EnableBinding(Sink.class) @SpringBootApplication public static class SinkApplication { ... @Bean public MessageConverter userMessageConverter() { AvroSchemaMessageConverter converter = new AvroSchemaMessageConverter(MimeType.valueOf("avro/bytes")); converter.setSchemaLocation(new ClassPathResource("schemas/User.avro")); return converter; } }
Spring Cloud Stream提供了模式注册表服务器实现。要使用它,可以将spring-cloud-stream-schema-server
工件添加到项目中,并使用@EnableSchemaRegistryServer
批注,该批注将架构注册表服务器REST控制器添加到您的应用程序。该注释旨在与Spring Boot web应用程序一起使用,并且服务器的侦听端口由server.port
属性控制。spring.cloud.stream.schema.server.path
属性可用于控制模式服务器的根路径(尤其是当它嵌入在其他应用程序中时)。spring.cloud.stream.schema.server.allowSchemaDeletion
布尔属性可以删除模式。默认情况下,这是禁用的。
架构注册表服务器使用关系数据库来存储架构。默认情况下,它使用嵌入式数据库。您可以使用Spring Boot SQL数据库和JDBC配置选项来自定义模式存储。
以下示例显示了一个启用架构注册表的Spring Boot应用程序:
@SpringBootApplication @EnableSchemaRegistryServer public class SchemaRegistryServerApplication { public static void main(String[] args) { SpringApplication.run(SchemaRegistryServerApplication.class, args); } }
Schema Registry Server API包含以下操作:
POST /
—参见“ 称为“注册新的Schema”部分 ”GET /{subject}/{format}
—参见“ 称为“按主题和格式检索现有Schema的部分” ”GET /schemas/{id}
-参见“ 称为“通过ID检索现有Schema ” 的部分” ”DELETE /{subject}/{format}/{version}
—请参阅“ 称为“按主题,格式和版本删除Schema的部分” ”DELETE /schemas/{id}
—参见“ 称为“通过ID删除Schema ” 的部分” ”DELETE /{subject}
-参见“ 称为“按主题删除Schema的部分” ”要注册新模式,请向/
端点发送一个POST
请求。
/
接受具有以下字段的JSON有效负载:
subject
:架构主题format
:架构格式definition
:架构定义它的响应是JSON中的架构对象,具有以下字段:
id
:架构IDsubject
:架构主题format
:架构格式version
:架构版本definition
:模式定义要按主题,格式和版本检索现有架构,请向/{subject}/{format}/{version}
端点发送GET
请求。
它的响应是JSON中的架构对象,具有以下字段:
id
:架构IDsubject
:架构主题format
:架构格式version
:架构版本definition
:模式定义要按主题和格式检索现有架构,请向/subject/format
端点发送一个GET
请求。
它的响应是JSON中每个模式对象的模式列表,其中包含以下字段:
id
:模式IDsubject
:架构主题format
:架构格式version
:架构版本definition
:架构定义要通过其ID检索架构,请向/schemas/{id}
端点发送一个GET
请求。
它的响应是JSON中的架构对象,具有以下字段:
id
:架构IDsubject
:架构主题format
:架构格式version
:架构版本definition
:模式定义默认配置将创建DefaultSchemaRegistryClient
bean。如果要使用Confluent模式注册表,则需要创建类型为ConfluentSchemaRegistryClient
的bean,该类型将替代框架默认配置的类型。下面的示例说明如何创建这样的bean:
@Bean public SchemaRegistryClient schemaRegistryClient(@Value("${spring.cloud.stream.schemaRegistryClient.endpoint}") String endpoint){ ConfluentSchemaRegistryClient client = new ConfluentSchemaRegistryClient(); client.setEndpoint(endpoint); return client; }
注意 | |
---|---|
ConfluentSchemaRegistryClient已针对Confluent平台4.0.0版进行了测试。 |
为了更好地了解Spring Cloud Stream如何注册和解析新模式及其对Avro模式比较功能的使用,我们提供了两个单独的小节:
注册过程的第一部分是从通过通道发送的有效负载中提取模式。诸如SpecificRecord
或GenericRecord
之类的Avro类型已经包含一个架构,可以从实例中立即检索该架构。对于POJO,如果将spring.cloud.stream.schema.avro.dynamicSchemaGenerationEnabled
属性设置为true
(默认值),则将推断模式。
获得一个模式,转换器从远程服务器加载其元数据(版本)。首先,它查询本地缓存。如果未找到结果,它将把数据提交给服务器,服务器将提供版本信息。转换器始终缓存结果,以避免为每个需要序列化的新消息查询Schema服务器的开销。
使用架构版本信息,转换器将消息的contentType
标头设置为携带版本信息,例如:application/vnd.user.v1+avro
。
当读取包含版本信息的消息(即contentType
标头,其格式类似于“ 第33.6.1节“ Schema注册过程(序列化)” ”)所述)时,转换器将查询Schema服务器以获取消息的编写器架构。一旦找到了传入消息的正确架构,它将检索阅读器架构,并使用Avro的架构解析支持将其读入阅读器定义(设置默认值和所有缺少的属性)。
Spring Cloud Stream启用应用程序之间的通信。应用程序间通信是一个涉及多个问题的复杂问题,如以下主题所述:
尽管Spring Cloud Stream使单个Spring Boot应用程序易于连接到消息传递系统,但Spring Cloud Stream的典型方案是创建多应用程序管道,微服务应用程序在该管道中相互发送数据。您可以通过关联“ 相邻 ”应用程序的输入和输出目标来实现此方案。
假设设计要求Time Source应用程序将数据发送到Log Sink应用程序。您可以将名为ticktock
的公共目标用于两个应用程序中的绑定。
时间源(通道名称为output
)将设置以下属性:
spring.cloud.stream.bindings.output.destination=ticktock
日志接收器(通道名称为input
)将设置以下属性:
spring.cloud.stream.bindings.input.destination=ticktock
在扩展Spring Cloud Stream应用程序时,每个实例可以接收有关同一应用程序还存在多少其他实例以及它自己的实例索引是什么的信息。Spring Cloud Stream通过spring.cloud.stream.instanceCount
和spring.cloud.stream.instanceIndex
属性进行此操作。例如,如果存在HDFS接收器应用程序的三个实例,则所有三个实例的spring.cloud.stream.instanceCount
都设置为3
,并且各个应用程序的spring.cloud.stream.instanceIndex
都设置为0
,1
,和2
。
通过Spring Cloud Data Flow部署Spring Cloud Stream应用程序时,将自动配置这些属性;否则,将自动配置这些属性。分别启动Spring Cloud Stream应用程序时,必须正确设置这些属性。默认情况下,spring.cloud.stream.instanceCount
是1
,而spring.cloud.stream.instanceIndex
是0
。
在按比例放大的方案中,这两个属性的正确配置通常对于解决分区行为很重要(请参见下文),并且某些绑定程序(例如,Kafka绑定程序)始终需要这两个属性,以便确保在多个使用者实例之间正确分割数据。
Spring Cloud Stream中的分区包括两个任务:
您可以通过设置其partitionKeyExpression
或partitionKeyExtractorName
属性及其partitionCount
属性中的一个或仅一个,来配置输出绑定以发送分区数据。
例如,以下是有效的典型配置:
spring.cloud.stream.bindings.output.producer.partitionKeyExpression=payload.id spring.cloud.stream.bindings.output.producer.partitionCount=5
基于该示例配置,通过使用以下逻辑将数据发送到目标分区。
根据partitionKeyExpression
为发送到分区输出通道的每条消息计算分区键的值。partitionKeyExpression
是一个SpEL表达式,该表达式根据出站消息进行评估以提取分区键。
如果SpEL表达式不足以满足您的需要,则可以通过提供org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy
的实现并将其配置为bean(通过使用@Bean
注释)来计算分区键值。 。如果在应用程序上下文中有多个org.springframework.cloud.stream.binder.PartitionKeyExtractorStrategy
类型的bean,则可以通过使用partitionKeyExtractorName
属性指定其名称来进一步过滤它,如以下示例所示:
--spring.cloud.stream.bindings.output.producer.partitionKeyExtractorName=customPartitionKeyExtractor --spring.cloud.stream.bindings.output.producer.partitionCount=5 . . . @Bean public CustomPartitionKeyExtractorClass customPartitionKeyExtractor() { return new CustomPartitionKeyExtractorClass(); }
注意 | |
---|---|
在Spring Cloud Stream的早期版本中,您可以通过设置 |
一旦计算出消息密钥,分区选择过程就会将目标分区确定为0
与partitionCount - 1
之间的值。适用于大多数情况的默认计算基于以下公式:key.hashCode() % partitionCount
。这可以在绑定上进行自定义,方法是将SpEL表达式设置为针对'key'进行评估(通过partitionSelectorExpression
属性),也可以将org.springframework.cloud.stream.binder.PartitionSelectorStrategy
的实现配置为bean (通过使用@ Bean批注)。与PartitionKeyExtractorStrategy
类似,当应用程序上下文中有多个这种类型的bean可用时,您可以使用spring.cloud.stream.bindings.output.producer.partitionSelectorName
属性进一步过滤它,如以下示例所示:
--spring.cloud.stream.bindings.output.producer.partitionSelectorName=customPartitionSelector . . . @Bean public CustomPartitionSelectorClass customPartitionSelector() { return new CustomPartitionSelectorClass(); }
注意 | |
---|---|
在Spring Cloud Stream的早期版本中,您可以通过设置 |
通过设置应用程序本身的partitioned
属性以及应用程序本身的instanceIndex
和instanceCount
属性,将输入绑定(通道名称为input
)配置为接收分区数据。下面的例子:
spring.cloud.stream.bindings.input.consumer.partitioned=true spring.cloud.stream.instanceIndex=3 spring.cloud.stream.instanceCount=5
instanceCount
值表示应在其之间分区数据的应用程序实例的总数。instanceIndex
在多个实例中必须是唯一的值,其值应介于0
和instanceCount - 1
之间。实例索引可帮助每个应用程序实例识别从中接收数据的唯一分区。活页夹要求使用不支持本地分区的技术。例如,对于RabbitMQ,每个分区都有一个队列,该队列名称包含实例索引。对于Kafka,如果autoRebalanceEnabled
为true
(默认值),则Kafka负责在实例之间分配分区,并且不需要这些属性。如果autoRebalanceEnabled
设置为false,则绑定器将使用instanceCount
和instanceIndex
来确定实例所预订的分区(您必须拥有与实例数量一样多的分区) 。活页夹分配分区而不是Kafka。如果您希望特定分区的消息始终发送到同一实例,这可能很有用。当活页夹配置需要它们时,重要的是正确设置两个值,以确保使用所有数据,并且应用程序实例接收互斥的数据集。
尽管在单独情况下使用多个实例进行分区数据处理可能会很复杂,但Spring Cloud Dataflow可以通过正确填充输入和输出值并让您依赖运行时来显着简化流程基础架构,以提供有关实例索引和实例计数的信息。
Spring Cloud Stream提供了在不连接消息传递系统的情况下测试您的微服务应用程序的支持。您可以使用spring-cloud-stream-test-support
库提供的TestSupportBinder
库来完成此操作,该库可以作为测试依赖项添加到应用程序中,如以下示例所示:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-test-support</artifactId> <scope>test</scope> </dependency>
注意 | |
---|---|
|
TestSupportBinder
使您可以与绑定的频道进行交互,并检查应用程序发送和接收的所有消息。
对于出站消息通道,TestSupportBinder
注册一个订户,并将应用程序发出的消息保留在MessageCollector
中。在测试期间可以检索它们,并针对它们进行断言。
您还可以将消息发送到入站消息通道,以便使用者应用程序可以使用消息。以下示例显示了如何在处理器上测试输入和输出通道:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) public class ExampleTest { @Autowired private Processor processor; @Autowired private MessageCollector messageCollector; @Test @SuppressWarnings("unchecked") public void testWiring() { Message<String> message = new GenericMessage<>("hello"); processor.input().send(message); Message<String> received = (Message<String>) messageCollector.forChannel(processor.output()).poll(); assertThat(received.getPayload(), equalTo("hello world")); } @SpringBootApplication @EnableBinding(Processor.class) public static class MyProcessor { @Autowired private Processor channels; @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) public String transform(String in) { return in + " world"; } } }
在前面的示例中,我们创建了一个具有输入通道和输出通道的应用程序,两者均通过Processor
接口绑定。绑定的接口被注入到测试中,以便我们可以访问两个通道。我们在输入通道上发送一条消息,然后使用Spring Cloud Stream的测试支持提供的MessageCollector
捕获消息已作为结果发送到输出通道。收到消息后,我们可以验证组件是否正常运行。
测试绑定程序背后的目的是取代类路径上的所有其他绑定程序,以使其易于测试您的应用程序而无需更改生产依赖性。在某些情况下(例如,集成测试),使用实际的生产绑定程序是有用的,并且这需要禁用测试绑定程序自动配置。为此,可以使用Spring Boot自动配置排除机制之一来排除org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration
类,如以下示例所示:
@SpringBootApplication(exclude = TestSupportBinderAutoConfiguration.class) @EnableBinding(Processor.class) public static class MyProcessor { @Transformer(inputChannel = Processor.INPUT, outputChannel = Processor.OUTPUT) public String transform(String in) { return in + " world"; } }
禁用自动配置后,测试绑定程序将在类路径上可用,并且其defaultCandidate
属性设置为false
,以使其不会干扰常规用户配置。可以使用名称test
来引用它,如以下示例所示:
spring.cloud.stream.defaultBinder=test
Spring Cloud Stream为活页夹提供了健康指标。它以名称binders
注册,可以通过设置management.health.binders.enabled
属性来启用或禁用。
默认情况下,management.health.binders.enabled
设置为false
。将management.health.binders.enabled
设置为true
会启用运行状况指示器,使您可以访问/health
端点以检索绑定程序运行状况指示器。
健康指标是特定于活页夹的,某些活页夹实现不一定提供健康指标。
Spring Boot Actuator为Micrometer提供依赖项管理和自动配置,Micrometer是一种支持众多监视系统的应用程序度量外观。
Spring Cloud Stream提供了将任何可用的基于千分尺的度量标准发送到绑定目标的支持,从而允许从流应用程序定期收集度量标准数据,而无需依赖于轮询各个端点。
通过定义spring.cloud.stream.bindings.applicationMetrics.destination
属性来激活“度量标准发射器”,该属性指定当前绑定程序用于发布度量标准消息的绑定目标的名称。
例如:
spring.cloud.stream.bindings.applicationMetrics.destination=myMetricDestination
前面的示例指示绑定程序绑定到myMetricDestination
(即,Rabbit交换,Kafka主题等)。
以下属性可用于自定义指标的发射:
发出的度量标准的名称。每个应用程序的唯一值。
默认值:${spring.application.name:${vcap.application.name:${spring.config.name:application}}}
允许白名单应用程序属性添加到度量有效负载
默认值:null。
控制要捕获的“仪表”的模式。例如,指定spring.integration.*
将捕获名称以spring.integration.
开头的仪表的度量标准信息
默认值:捕获所有“仪表”。
控制发布度量标准数据的速率的时间间隔。
默认值:1分钟
考虑以下:
java -jar time-source.jar \ --spring.cloud.stream.bindings.applicationMetrics.destination=someMetrics \ --spring.cloud.stream.metrics.properties=spring.application** \ --spring.cloud.stream.metrics.meter-filter=spring.integration.*
下面的示例显示由于上述命令而发布到绑定目标的数据的有效负载:
{ "name": "application", "createdTime": "2018-03-23T14:48:12.700Z", "properties": { }, "metrics": [ { "id": { "name": "spring.integration.send", "tags": [ { "key": "exception", "value": "none" }, { "key": "name", "value": "input" }, { "key": "result", "value": "success" }, { "key": "type", "value": "channel" } ], "type": "TIMER", "description": "Send processing time", "baseUnit": "milliseconds" }, "timestamp": "2018-03-23T14:48:12.697Z", "sum": 130.340546, "count": 6, "mean": 21.72342433333333, "upper": 116.176299, "total": 130.340546 } ] }
注意 | |
---|---|
鉴于在迁移到Micrometer后Metric消息的格式略有变化,发布的消息还将 |
有关Spring Cloud Stream示例,请参见GitHub上的spring-cloud-stream-samples存储库。
在CloudFoundry上,通常通过称为VCAP_SERVICES的特殊环境变量来公开服务。
配置资料夹连接时,可以使用环境变量中的值,如数据流Cloud Foundry服务器文档中所述。
要使用Apache Kafka活页夹,您需要将spring-cloud-stream-binder-kafka
作为依赖项添加到Spring Cloud Stream应用程序中,如以下Maven的示例所示:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-kafka</artifactId> </dependency>
另外,您也可以使用Spring Cloud Stream Kafka入门程序,如以下Maven示例所示:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-kafka</artifactId> </dependency>
下图显示了Apache Kafka活页夹的工作方式的简化图:
Apache Kafka Binder实现将每个目的地映射到Apache Kafka主题。消费者组直接映射到相同的Apache Kafka概念。分区也直接映射到Apache Kafka分区。
活页夹当前使用Apache Kafka kafka-clients
1.0.0 jar,并且设计为与至少该版本的代理一起使用。该客户端可以与较早的代理进行通信(请参见Kafka文档),但是某些功能可能不可用。例如,对于低于0.11.xx的版本,不支持本机头。另外,0.11.xx不支持autoAddPartitions
属性。
本节包含Apache Kafka活页夹使用的配置选项。
有关与活页夹有关的常见配置选项和属性,请参阅核心文档。
Kafka活页夹所连接的代理列表。
默认值:localhost
。
brokers
允许指定带有或不带有端口信息的主机(例如,host1,host2:port2
)。当代理列表中未配置任何端口时,这将设置默认端口。
默认值:9092
。
客户端属性(生产者和消费者)的键/值映射传递给绑定程序创建的所有客户端。由于生产者和消费者都使用了这些属性,因此应将使用限制为通用属性,例如安全性设置。Properties在这里取代引导中设置的所有属性。
默认值:空地图。
任意Kafka客户端使用者属性的键/值映射。这里的Properties取代了启动时和上面的configuration
属性中设置的所有属性。
默认值:空地图。
活页夹传输的自定义标头列表。仅当与kafka-clients
版本<0.11.0.0的旧版应用程序(⇐1.3.x)通信时才需要。较新的版本本机支持标头。
默认值:空。
等待获取分区信息的时间,以秒为单位。如果此计时器到期,运行状况将报告为已关闭。
默认值:10
代理程序上所需的确认数。有关生产者acks
属性的信息,请参见Kafka文档。
默认值:1
。
仅在设置了autoCreateTopics
或autoAddPartitions
时有效。活页夹在生成或使用数据的主题上配置的全局最小分区数。可以通过生产者的partitionCount
设置或生产者的instanceCount * concurrency
设置的值(如果任一个较大)来代替它。
默认值:1
。
任意Kafka客户端生产者属性的键/值映射。这里的Properties取代了启动时和上面的configuration
属性中设置的所有属性。
默认值:空地图。
如果autoCreateTopics
有效,则自动创建的主题的复制因子。可以在每个绑定上覆盖。
默认值:1
。
如果设置为true
,则活页夹将自动创建新主题。如果设置为false
,则活页夹依赖于已配置的主题。在后一种情况下,如果主题不存在,则活页夹无法启动。
注意 | |
---|---|
此设置与代理的 |
默认值:true
。
如果设置为true
,则活页夹将根据需要创建新分区。如果设置为false
,则活页夹依赖于已配置的主题的分区大小。如果目标主题的分区数小于预期值,则活页夹无法启动。
默认值:false
。
在活页夹中启用事务。请参阅Kafka文档中的transaction.id
和spring-kafka
文档中的Transactions。启用事务后,将忽略各个producer
属性,并且所有生产者都将使用spring.cloud.stream.kafka.binder.transaction.producer.*
属性。
默认值null
(无交易)
交易绑定中生产者的全球生产者属性。请参见spring.cloud.stream.kafka.binder.transaction.transactionIdPrefix
和第39.3.3节“ Kafka生产者Properties”以及所有活页夹支持的常规生产者属性。
默认值:请参见各个生产者属性。
KafkaHeaderMapper
的bean名称,用于将spring-messaging
标头映射到Kafka标头和从Kafka标头映射。例如,如果您希望自定义在标头中使用JSON反序列化的DefaultKafkaHeaderMapper
中的受信任软件包,请使用此方法。
默认值:无。
以下属性仅适用于Kafka使用者,并且必须以spring.cloud.stream.kafka.bindings.<channelName>.consumer.
为前缀。
供应主题时使用的Kafka主题属性中的Map
(例如,spring.cloud.stream.kafka.bindings.input.consumer.admin.configuration.message.format.version=0.9.0.0
默认值:无。
副本分配的Map <Integer,List <Integer >>,键为分区,值为分配。在配置新主题时使用。请参阅kafka-clients
jar中的NewTopic
Javadocs。
默认值:无。
设置主题时要使用的复制因子。覆盖活页夹范围的设置。忽略是否存在replicas-assignments
。
默认值:无(使用资料夹范围的默认值1)。
当为true
时,主题分区将在使用者组的成员之间自动重新平衡。当false
时,将为每个使用者分配基于spring.cloud.stream.instanceCount
和spring.cloud.stream.instanceIndex
的固定分区集合。这要求在每个启动的实例上同时设置spring.cloud.stream.instanceCount
和spring.cloud.stream.instanceIndex
属性。在这种情况下,spring.cloud.stream.instanceCount
属性的值通常必须大于1。
默认值:true
。
当autoCommitOffset
为true
时,此设置指示在处理每条记录后是否提交偏移量。默认情况下,在处理consumer.poll()
返回的记录批次中的所有记录之后,将提交偏移量。可以使用max.poll.records
Kafka属性控制轮询返回的记录数,该属性是通过使用者configuration
属性设置的。将此设置为true
可能会导致性能下降,但是这样做会减少发生故障时重新传送记录的可能性。另外,请参见活页夹requiredAcks
属性,该属性还影响落实偏移量的性能。
默认值:false
。
处理消息后是否自动提交偏移量。如果设置为false
,则入站消息中将出现带有类型为org.springframework.kafka.support.Acknowledgment
头的键kafka_acknowledgment
的头。应用程序可以使用此标头来确认消息。有关详细信息,请参见示例部分。当此属性设置为false
时,Kafka活页夹将ack模式设置为org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMode.MANUAL
,应用程序负责确认记录。另请参阅ackEachRecord
。
默认值:true
。
仅在autoCommitOffset
设置为true
时有效。如果设置为false
,它将抑制导致错误的消息的自动提交,仅对成功的消息进行提交。如果持续出现故障,它允许流从上次成功处理的消息自动重播。如果设置为true
,它将始终自动提交(如果启用了自动提交)。如果未设置(默认值),则其有效值与enableDlq
相同,如果将错误消息发送到DLQ,则自动提交错误消息,否则不提交。
默认值:未设置。
是否将使用者的偏移量重置为startOffset提供的值。
默认值:false
。
新组的起始偏移量。允许的值:earliest
和latest
。如果为消费者“绑定”显式设置了消费者组(通过spring.cloud.stream.bindings.<channelName>.group
),则“ startOffset”设置为earliest
。否则,将anonymous
消费者组的值设置为latest
。另请参见resetOffsets
(在此列表的前面)。
默认值:null(等于earliest
)。
设置为true时,它将为使用者启用DLQ行为。默认情况下,导致错误的消息将转发到名为error.<destination>.<group>
的主题。可以通过设置dlqName
属性来配置DLQ主题名称。对于错误数量相对较小并且重放整个原始主题可能太麻烦的情况,这为更常见的Kafka重播方案提供了一个替代选项。有关更多信息,请参见第39.6节“ Dead-Letter主题处理”处理。从2.0版开始,发送到DLQ主题的消息将通过以下标头得到增强:x-original-topic
,x-exception-message
和x-exception-stacktrace
为byte[]
。
当destinationIsPattern
为true
时不允许使用。
默认值:false
。
使用包含通用Kafka使用者属性的键/值对进行映射。
默认值:空地图。
接收错误消息的DLQ主题的名称。
默认值:null(如果未指定,则导致错误的消息将转发到名为error.<destination>.<group>
的主题)。
使用此功能,可以设置特定于DLQ的生产者属性。通过kafka生产者属性可用的所有属性都可以通过此属性设置。
默认:默认Kafka生产者属性。
指示入站通道适配器填充哪些标准头。允许的值:none
,id
,timestamp
或both
。如果使用本机反序列化并且第一个组件接收消息需要id
(例如配置为使用JDBC消息存储的聚合器),则很有用。
默认值:none
实现RecordMessageConverter
的bean的名称。在入站通道适配器中用于替换默认的MessagingMessageConverter
。
默认值:null
事件之间的间隔(以毫秒为单位),指示最近未接收到任何消息。使用ApplicationListener<ListenerContainerIdleEvent>
接收这些事件。有关用法示例,请参见“示例:暂停和恢复使用者”一节。
默认值:30000
如果为true,则将目的地视为正则表达式Pattern
,用于由代理匹配主题名称。设置为true时,不设置主题,并且不允许enableDlq
,因为绑定者在设置阶段不知道主题名称。请注意,检测与模式匹配的新主题所花费的时间由消费者属性metadata.max.age.ms
控制,该属性(在撰写本文时)默认为300,000ms(5分钟)。可以使用上面的configuration
属性进行配置。
默认值:false
以下属性仅适用于Kafka生产者,并且必须以spring.cloud.stream.kafka.bindings.<channelName>.producer.
为前缀。
预置新主题时使用的Kafka主题属性中的Map
(例如,spring.cloud.stream.kafka.bindings.input.consumer.admin.configuration.message.format.version=0.9.0.0
默认值:无。
副本分配的Map <Integer,List <Integer >>,键为分区,值为分配。在配置新主题时使用。请参见kafka-clients
jar中的NewTopic
javadocs。
默认值:无。
设置新主题时要使用的复制因子。覆盖活页夹范围的设置。如果存在replicas-assignments
,则忽略。
默认值:无(使用资料夹范围的默认值1)。
Kafka生产者在发送之前尝试分批处理的数据量的上限(以字节为单位)。
默认值:16384
。
生产者是否同步。
默认值:false
。
生产者在发送消息之前等待允许更多消息在同一批中累积的时间。(通常,生产者根本不等待,仅发送在上一次发送过程中累积的所有消息。)非零值可能会增加吞吐量,但会增加延迟。
默认值:0
。
根据用于填充产生的Kafka消息的密钥的传出消息(例如,headers['myKey']
)评估的SpEL表达式。有效负载无法使用,因为在评估此表达式时,有效负载已经采用byte[]
的形式。
默认值:none
。
以逗号分隔的简单模式列表,以匹配要映射到ProducerRecord
中的Kafka Headers
的Spring消息头。模式可以以通配符(星号)开头或结尾。可以使用前缀!
来否定模式。比赛在第一个比赛(正数或负数)之后停止。例如,!ask,as*
将传递ash
,但不会传递ask
。id
和timestamp
从未映射。
默认值:*
(所有标头-id
和timestamp
除外)
使用包含通用Kafka生产者属性的键/值对进行映射。
默认值:空地图。
注意 | |
---|---|
Kafka活页夹使用生产者的 |
在本节中,我们将说明针对特定方案使用前面的属性。
此示例说明了如何在用户应用程序中手动确认偏移。
本示例要求将spring.cloud.stream.kafka.bindings.input.consumer.autoCommitOffset
设置为false
。在您的示例中使用相应的输入通道名称。
@SpringBootApplication @EnableBinding(Sink.class) public class ManuallyAcknowdledgingConsumer { public static void main(String[] args) { SpringApplication.run(ManuallyAcknowdledgingConsumer.class, args); } @StreamListener(Sink.INPUT) public void process(Message<?> message) { Acknowledgment acknowledgment = message.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class); if (acknowledgment != null) { System.out.println("Acknowledgment provided"); acknowledgment.acknowledge(); } } }
Apache Kafka 0.9支持客户端和代理之间的安全连接。要利用此功能,请遵循Apache Kafka文档中的准则以及Confluent文档中的Kafka 0.9 安全准则。使用spring.cloud.stream.kafka.binder.configuration
选项为活页夹创建的所有客户端设置安全性属性。
例如,要将security.protocol
设置为SASL_SSL
,请设置以下属性:
spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_SSL
可以以类似方式设置所有其他安全属性。
使用Kerberos时,请遵循参考文档中的说明来创建和引用JAAS配置。
Spring Cloud Stream支持通过使用JAAS配置文件并使用Spring Boot属性将JAAS配置信息传递到应用程序。
可以使用系统属性为Spring Cloud Stream应用程序设置JAAS和(可选)krb5文件位置。以下示例显示如何通过使用JAAS配置文件使用SASL和Kerberos启动Spring Cloud Stream应用程序:
java -Djava.security.auth.login.config=/path.to/kafka_client_jaas.conf -jar log.jar \
--spring.cloud.stream.kafka.binder.brokers=secure.server:9092 \
--spring.cloud.stream.bindings.input.destination=stream.ticktock \
--spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_PLAINTEXT
作为使用JAAS配置文件的替代方法,Spring Cloud Stream提供了一种通过使用Spring Boot属性为Spring Cloud Stream应用程序设置JAAS配置的机制。
以下属性可用于配置Kafka客户端的登录上下文:
登录模块名称。正常情况下无需设置。
默认值:com.sun.security.auth.module.Krb5LoginModule
。
登录模块的控制标志。
默认值:required
。
使用包含登录模块选项的键/值对进行映射。
默认值:空地图。
以下示例显示如何使用Spring Boot配置属性使用SASL和Kerberos启动Spring Cloud Stream应用程序:
java --spring.cloud.stream.kafka.binder.brokers=secure.server:9092 \ --spring.cloud.stream.bindings.input.destination=stream.ticktock \ --spring.cloud.stream.kafka.binder.autoCreateTopics=false \ --spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_PLAINTEXT \ --spring.cloud.stream.kafka.binder.jaas.options.useKeyTab=true \ --spring.cloud.stream.kafka.binder.jaas.options.storeKey=true \ --spring.cloud.stream.kafka.binder.jaas.options.keyTab=/etc/security/keytabs/kafka_client.keytab \ --spring.cloud.stream.kafka.binder.jaas.options.principal=kafka-client-1@EXAMPLE.COM
前面的示例表示以下JAAS文件的等效项:
KafkaClient { com.sun.security.auth.module.Krb5LoginModule required useKeyTab=true storeKey=true keyTab="/etc/security/keytabs/kafka_client.keytab" principal="kafka-client-1@EXAMPLE.COM"; };
如果所需的主题已经存在于代理上或将由管理员创建,则可以关闭自动创建,仅需要发送客户端JAAS属性。
注意 | |
---|---|
请勿在同一应用程序中混合使用JAAS配置文件和Spring Boot属性。如果 |
注意 | |
---|---|
将 |
如果希望暂停使用但不引起分区重新平衡,则可以暂停并恢复使用方。通过将Consumer
作为参数添加到@StreamListener
中,可以简化此操作。要恢复,需要为ListenerContainerIdleEvent
实例使用ApplicationListener
。事件的发布频率由idleEventInterval
属性控制。由于使用者不是线程安全的,因此必须在调用线程上调用这些方法。
以下简单的应用程序显示了如何暂停和恢复:
@SpringBootApplication @EnableBinding(Sink.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @StreamListener(Sink.INPUT) public void in(String in, @Header(KafkaHeaders.CONSUMER) Consumer<?, ?> consumer) { System.out.println(in); consumer.pause(Collections.singleton(new TopicPartition("myTopic", 0))); } @Bean public ApplicationListener<ListenerContainerIdleEvent> idleListener() { return event -> { System.out.println(event); if (event.getConsumer().paused().size() > 0) { event.getConsumer().resume(event.getConsumer().paused()); } }; } }
从版本1.3开始,绑定程序无条件地将异常发送到每个使用者目标的错误通道,并且还可以配置为将异步生产者发送失败消息发送到错误通道。有关更多信息,请参见第29.4节“错误处理”。
发送失败的ErrorMessage
的有效载荷是具有以下属性的KafkaSendFailureException
:
failedMessage
:发送失败的Spring消息Message<?>
。record
:从failedMessage
创建的原始ProducerRecord
没有对生产者异常的自动处理(例如发送到Dead-Letter队列)。您可以使用自己的Spring Integration流使用这些异常。
Kafka活页夹模块公开以下指标:
spring.cloud.stream.binder.kafka.offset
:此度量标准指示给定的消费者组尚未从给定的活页夹主题中消费多少消息。提供的指标基于Mircometer指标库。度量标准包含消费者组信息,主题以及与主题上的最新偏移量有关的承诺偏移量的实际滞后时间。该指标对于向PaaS平台提供自动缩放反馈特别有用。
因为您无法预期用户将如何处置死信,所以该框架没有提供任何标准机制来处理它们。如果死信的原因是暂时的,则您可能希望将消息路由回原始主题。但是,如果问题是永久性问题,则可能导致无限循环。本主题中的示例Spring Boot应用程序是如何将这些消息路由回原始主题的示例,但是在尝试了三遍之后,将其移至“ 停车场 ”主题。该应用程序是另一个从死信主题读取的spring-cloud-stream应用程序。5秒钟未收到任何消息时,它将终止。
这些示例假定原始目的地为so8400out
,而使用者组为so8400
。
有两种策略可供考虑:
以下代码清单显示了示例应用程序:
application.properties。
spring.cloud.stream.bindings.input.group=so8400replay spring.cloud.stream.bindings.input.destination=error.so8400out.so8400 spring.cloud.stream.bindings.output.destination=so8400out spring.cloud.stream.bindings.output.producer.partitioned=true spring.cloud.stream.bindings.parkingLot.destination=so8400in.parkingLot spring.cloud.stream.bindings.parkingLot.producer.partitioned=true spring.cloud.stream.kafka.binder.configuration.auto.offset.reset=earliest spring.cloud.stream.kafka.binder.headers=x-retries
应用。
@SpringBootApplication @EnableBinding(TwoOutputProcessor.class) public class ReRouteDlqKApplication implements CommandLineRunner { private static final String X_RETRIES_HEADER = "x-retries"; public static void main(String[] args) { SpringApplication.run(ReRouteDlqKApplication.class, args).close(); } private final AtomicInteger processed = new AtomicInteger(); @Autowired private MessageChannel parkingLot; @StreamListener(Processor.INPUT) @SendTo(Processor.OUTPUT) public Message<?> reRoute(Message<?> failed) { processed.incrementAndGet(); Integer retries = failed.getHeaders().get(X_RETRIES_HEADER, Integer.class); if (retries == null) { System.out.println("First retry for " + failed); return MessageBuilder.fromMessage(failed) .setHeader(X_RETRIES_HEADER, new Integer(1)) .setHeader(BinderHeaders.PARTITION_OVERRIDE, failed.getHeaders().get(KafkaHeaders.RECEIVED_PARTITION_ID)) .build(); } else if (retries.intValue() < 3) { System.out.println("Another retry for " + failed); return MessageBuilder.fromMessage(failed) .setHeader(X_RETRIES_HEADER, new Integer(retries.intValue() + 1)) .setHeader(BinderHeaders.PARTITION_OVERRIDE, failed.getHeaders().get(KafkaHeaders.RECEIVED_PARTITION_ID)) .build(); } else { System.out.println("Retries exhausted for " + failed); parkingLot.send(MessageBuilder.fromMessage(failed) .setHeader(BinderHeaders.PARTITION_OVERRIDE, failed.getHeaders().get(KafkaHeaders.RECEIVED_PARTITION_ID)) .build()); } return null; } @Override public void run(String... args) throws Exception { while (true) { int count = this.processed.get(); Thread.sleep(5000); if (count == this.processed.get()) { System.out.println("Idle, terminating"); return; } } } public interface TwoOutputProcessor extends Processor { @Output("parkingLot") MessageChannel parkingLot(); } }
Apache Kafka本机支持主题分区。
有时,将数据发送到特定的分区是有好处的-例如,当您要严格订购消息处理时(特定客户的所有消息应转到同一分区)。
以下示例显示了如何配置生产方和消费者方:
@SpringBootApplication @EnableBinding(Source.class) public class KafkaPartitionProducerApplication { private static final Random RANDOM = new Random(System.currentTimeMillis()); private static final String[] data = new String[] { "foo1", "bar1", "qux1", "foo2", "bar2", "qux2", "foo3", "bar3", "qux3", "foo4", "bar4", "qux4", }; public static void main(String[] args) { new SpringApplicationBuilder(KafkaPartitionProducerApplication.class) .web(false) .run(args); } @InboundChannelAdapter(channel = Source.OUTPUT, poller = @Poller(fixedRate = "5000")) public Message<?> generate() { String value = data[RANDOM.nextInt(data.length)]; System.out.println("Sending: " + value); return MessageBuilder.withPayload(value) .setHeader("partitionKey", value) .build(); } }
application.yml。
spring: cloud: stream: bindings: output: destination: partitioned.topic producer: partitioned: true partition-key-expression: headers['partitionKey'] partition-count: 12
重要 | |
---|---|
必须为该主题提供足够的分区,以实现所有消费者组所需的并发性。上面的配置最多支持12个使用者实例(如果 |
注意 | |
---|---|
前面的配置使用默认分区( |
由于分区是由Kafka本地处理的,因此在使用者端不需要特殊配置。Kafka在实例之间分配分区。
以下Spring Boot应用程序侦听Kafka流,并打印(到控制台)每条消息去往的分区ID:
@SpringBootApplication @EnableBinding(Sink.class) public class KafkaPartitionConsumerApplication { public static void main(String[] args) { new SpringApplicationBuilder(KafkaPartitionConsumerApplication.class) .web(false) .run(args); } @StreamListener(Sink.INPUT) public void listen(@Payload String in, @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition) { System.out.println(in + " received from partition " + partition); } }
application.yml。
spring: cloud: stream: bindings: input: destination: partitioned.topic group: myGroup
您可以根据需要添加实例。Kafka重新平衡分区分配。如果实例计数(或instance count * concurrency
)超过了分区数,则某些使用者处于空闲状态。
要使用Kafka Streams活页夹,只需使用以下Maven坐标将其添加到Spring Cloud Stream应用程序中:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-stream-binder-kafka-streams</artifactId> </dependency>
Spring Cloud Stream的Apache Kafka支持还包括为Apache Kafka流绑定显式设计的绑定器实现。通过这种本机集成,Spring Cloud Stream“处理器”应用程序可以直接在核心业务逻辑中使用 Apache Kafka Streams API。
Kafka Streams活页夹实现基于Spring Kafka 项目中的Kafka Streams提供的基础。
Kafka Streams绑定器为Kafka Streams中的三种主要类型(KStream,KTable和GlobalKTable)提供了绑定功能。
作为本机集成的一部分, Kafka Streams API提供的高级Streams DSL可用于业务逻辑。
还提供处理器API支持的早期版本。
如早期所述,Spring Cloud Stream中的Kafka流支持仅在处理器模型中严格可用。可以应用一种模型,在该模型中,可以从入站主题读取消息,进行业务处理,然后可以将转换后的消息写入出站主题。它也可以用于无出站目的地的处理器应用程序中。
此应用程序消耗来自Kafka主题(例如words
)的数据,在5秒的时间窗口内为每个唯一单词计算单词计数,并将计算结果发送到下游主题(例如counts
)进行进一步处理。
@SpringBootApplication @EnableBinding(KStreamProcessor.class) public class WordCountProcessorApplication { @StreamListener("input") @SendTo("output") public KStream<?, WordCount> process(KStream<?, String> input) { return input .flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+"))) .groupBy((key, value) -> value) .windowedBy(TimeWindows.of(5000)) .count(Materialized.as("WordCounts-multi")) .toStream() .map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value, new Date(key.window().start()), new Date(key.window().end())))); } public static void main(String[] args) { SpringApplication.run(WordCountProcessorApplication.class, args); }
一旦构建为uber-jar(例如wordcount-processor.jar
),您就可以像下面一样运行上面的示例。
java -jar wordcount-processor.jar --spring.cloud.stream.bindings.input.destination=words --spring.cloud.stream.bindings.output.destination=counts
此应用程序将使用来自Kafka主题words
的消息,并将计算的结果发布到输出主题counts
。
Spring Cloud Stream将确保来自传入和传出主题的消息都自动绑定为KStream对象。作为开发人员,您可以专注于代码的业务方面,即编写处理器中所需的逻辑。框架自动处理Kafka Streams基础结构所需的Streams DSL特定配置的设置。
本节包含Kafka Streams绑定程序使用的配置选项。
有关与活页夹有关的常见配置选项和属性,请参阅核心文档。
以下属性在活页夹级别可用,并且必须以spring.cloud.stream.kafka.streams.binder.
文字作为前缀。
spring.cloud.stream.kafka.streams.binder.
为前缀。以下是使用此属性的一些示例。spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde=org.apache.kafka.common.serialization.Serdes$StringSerde spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde=org.apache.kafka.common.serialization.Serdes$StringSerde spring.cloud.stream.kafka.streams.binder.configuration.commit.interval.ms=1000
有关可能用于流配置的所有属性的更多信息,请参见Apache Kafka Streams文档中的StreamsConfig JavaDocs。
经纪人网址
默认值:localhost
Zookeeper网址
默认值:localhost
反序列化错误处理程序类型。可能的值为-logAndContinue
,logAndFail
或sendToDlq
默认值:logAndFail
在绑定程序级别全局设置Kafka Streams应用程序的application.id的简便方法。如果应用程序包含多个StreamListener
方法,则应在每个输入绑定的绑定级别上设置application.id。
默认值:none
以下属性仅适用于Kafka Streams生产者,并且必须以spring.cloud.stream.kafka.streams.bindings.<binding name>.producer.
字面量为前缀。为方便起见,如果存在多个输出绑定并且它们都需要一个公共值,则可以使用前缀spring.cloud.stream.kafka.streams.default.producer.
进行配置。
要使用的密钥序列
默认值:none
。
使用价值服务
默认值:none
。
标志以启用本机编码
默认值:false
。
以下属性仅适用于Kafka Streams使用者,并且必须以spring.cloud.stream.kafka.streams.bindings.<binding name>.consumer.`literal.
For convenience, if there multiple input bindings and they all require a common value, that can be configured by using the prefix `spring.cloud.stream.kafka.streams.default.consumer.
为前缀。
设置每个输入绑定的application.id。
默认值:none
要使用的密钥序列
默认值:none
。
使用价值服务
默认值:none
。
使用传入的KTable类型实现状态存储
默认值:none
。
标志以启用本机解码
默认值:false
。
DLQ主题名称。
默认值:none
。
对于需要多个传入KStream对象或KStream和KTable对象的组合的用例,Kafka Streams绑定程序提供了多个绑定支持。
让我们来看看它的作用。
@EnableBinding(KStreamKTableBinding.class) ..... ..... @StreamListener public void process(@Input("inputStream") KStream<String, PlayEvent> playEvents, @Input("inputTable") KTable<Long, Song> songTable) { .... .... } interface KStreamKTableBinding { @Input("inputStream") KStream<?, ?> inputStream(); @Input("inputTable") KTable<?, ?> inputTable(); }
在上面的示例中,应用程序被编写为接收器,即没有输出绑定,并且应用程序必须决定有关下游处理的内容。当您以这种方式编写应用程序时,您可能希望向下游发送信息或将其存储在状态存储中(有关可查询状态存储,请参见下文)。
对于传入的KTable,如果要将计算具体化为状态存储,则必须通过以下属性将其表示。
spring.cloud.stream.kafka.streams.bindings.inputTable.consumer.materializedAs: all-songs
上面的示例显示了将KTable用作输入绑定。绑定器还支持GlobalKTable的输入绑定。当您必须确保应用程序的所有实例都可以访问主题中的数据更新时,GlobalKTable绑定非常有用。KTable和GlobalKTable绑定仅在输入上可用。Binder支持KStream的输入和输出绑定。
@EnableBinding(KStreamKTableBinding.class) .... .... @StreamListener @SendTo("output") public KStream<String, Long> process(@Input("input") KStream<String, Long> userClicksStream, @Input("inputTable") KTable<String, String> userRegionsTable) { .... .... } interface KStreamKTableBinding extends KafkaStreamsProcessor { @Input("inputX") KTable<?, ?> inputTable(); }
Kafka流允许根据某些谓词将出站数据分为多个主题。Kafka Streams绑定程序提供对此功能的支持,而不会损害最终用户应用程序中通过StreamListener
公开的编程模型。
您可以按照上面在字数示例中展示的常用方法编写应用程序。但是,使用分支功能时,您需要做一些事情。首先,您需要确保您的返回类型为KStream[]
,而不是常规的KStream
。其次,您需要使用SendTo
批注,该批注按顺序包含输出绑定(请参见下面的示例)。对于这些输出绑定中的每一个,您都需要配置目标,内容类型等,并符合标准Spring Cloud Stream的要求。
这是一个例子:
@EnableBinding(KStreamProcessorWithBranches.class) @EnableAutoConfiguration public static class WordCountProcessorApplication { @Autowired private TimeWindows timeWindows; @StreamListener("input") @SendTo({"output1","output2","output3}) public KStream<?, WordCount>[] process(KStream<Object, String> input) { Predicate<Object, WordCount> isEnglish = (k, v) -> v.word.equals("english"); Predicate<Object, WordCount> isFrench = (k, v) -> v.word.equals("french"); Predicate<Object, WordCount> isSpanish = (k, v) -> v.word.equals("spanish"); return input .flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+"))) .groupBy((key, value) -> value) .windowedBy(timeWindows) .count(Materialized.as("WordCounts-1")) .toStream() .map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value, new Date(key.window().start()), new Date(key.window().end())))) .branch(isEnglish, isFrench, isSpanish); } interface KStreamProcessorWithBranches { @Input("input") KStream<?, ?> input(); @Output("output1") KStream<?, ?> output1(); @Output("output2") KStream<?, ?> output2(); @Output("output3") KStream<?, ?> output3(); } }
Properties:
spring.cloud.stream.bindings.output1.contentType: application/json spring.cloud.stream.bindings.output2.contentType: application/json spring.cloud.stream.bindings.output3.contentType: application/json spring.cloud.stream.kafka.streams.binder.configuration.commit.interval.ms: 1000 spring.cloud.stream.kafka.streams.binder.configuration: default.key.serde: org.apache.kafka.common.serialization.Serdes$StringSerde default.value.serde: org.apache.kafka.common.serialization.Serdes$StringSerde spring.cloud.stream.bindings.output1: destination: foo producer: headerMode: raw spring.cloud.stream.bindings.output2: destination: bar producer: headerMode: raw spring.cloud.stream.bindings.output3: destination: fox producer: headerMode: raw spring.cloud.stream.bindings.input: destination: words consu