1.3.0.RELEASE

该项目通过自动配置和绑定到Spring环境和其他Spring编程模型的习惯方式来为Spring Boot应用程序提供Netflix OSS集成。通过几个简单的注释,您可以快速启用和配置应用程序中的常见模式,并通过经过测试的Netflix组件构建大型分布式系统。提供的模式包括服务发现(Eureka),断路器(Hystrix),智能路由(Zuul)和客户端负载平衡(Ribbon)。

服务发现:Eureka客户端

服务发现是基于微服务架构的关键原则之一。尝试配置每个客户端或某种形式的约定可能非常困难,可以非常脆弱。Netflix服务发现服务器和客户端是Eureka。可以将服务器配置和部署为高可用性,每个服务器将注册服务的状态复制到其他服务器。

如何包含Eureka客户端

要在您的项目中包含Eureka客户端,使用组org.springframework.cloud和工件id spring-cloud-starter-eureka的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面

注册Eureka

当客户端注册Eureka时,它提供有关自身的元数据,例如主机和端口,运行状况指示符URL,主页等。Eureka从属于服务的每个实例接收心跳消息。如果心跳失败超过可配置的时间表,则通常将该实例从注册表中删除。

示例eureka客户端:

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableEurekaClient
@RestController
public class Application {

    @RequestMapping("/")
    public String home() {
        return "Hello world";
    }

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

}

(即完全正常的Spring Boot应用程序)。在这个例子中,我们明确地使用@EnableEurekaClient,但只有Eureka可用,你也可以使用@EnableDiscoveryClient需要配置才能找到Eureka服务器。例:

application.yml
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

其中“defaultZone”是一个魔术字符串后备值,为任何不表示首选项的客户端提供服务URL(即它是有用的默认值)。

Environment获取的默认应用程序名称(服务ID),虚拟主机和非安全端口分别为${spring.application.name}${spring.application.name}${server.port}

@EnableEurekaClient使应用程序成为Eureka“实例”(即注册自身)和“客户端”(即它可以查询注册表以查找其他服务)。实例行为由eureka.instance.*配置键驱动,但如果您确保您的应用程序具有spring.application.name(这是Eureka服务标识或VIP的默认值),那么默认值将会很好。

有关可配置选项的更多详细信息,请参阅EurekaInstanceConfigBeanEurekaClientConfigBean

使用Eureka服务器进行身份验证

如果其中一个eureka.client.serviceUrl.defaultZone URL中包含一个凭据(如http://user:password@localhost:8761/eureka)),HTTP基本身份验证将自动添加到您的eureka客户端。对于更复杂的需求,您可以创建@Bean类型DiscoveryClientOptionalArgs并注入ClientFilter实例,所有这些都将应用于从客户端到服务器的调用。

注意
由于Eureka中的限制,不可能支持每个服务器的基本身份验证凭据,因此只能使用第一个找到的集合。

状态页和健康指标

Eureka实例的状态页面和运行状况指示器分别默认为“/ info”和“/ health”,它们是Spring Boot Actuator应用程序中有用端点的默认位置。如果您使用非默认上下文路径或servlet路径(例如server.servletPath=/foo)或管理端点路径(例如management.contextPath=/admin)),则需要更改这些,即使是执行器应用程序。例:

application.yml
eureka:
  instance:
    statusPageUrlPath: ${management.context-path}/info
    healthCheckUrlPath: ${management.context-path}/health

这些链接显示在客户端使用的元数据中,并在某些情况下用于决定是否将请求发送到应用程序,因此如果它们是准确的,这是有帮助的。

注册安全应用程序

如果您的应用程序想通过HTTPS联系,则可以分别在EurekaInstanceConfig eureka.instance.[nonSecurePortEnabled,securePortEnabled]=[false,true] 中设置两个标志这将使Eureka发布实例信息显示安全通信的显式偏好。Spring Cloud DiscoveryClient将始终为这样配置的服务返回https://…​; URI,并且Eureka(本机)实例信息将具有安全的健康检查URL。

由于Eureka在内部工作,它仍然会发布状态和主页的非安全网址,除非您也明确地覆盖。您可以使用占位符来配置eureka实例URL,例如

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的健康检查

默认情况下,Eureka使用客户端心跳来确定客户端是否已启动。除非另有规定,否则Discovery Client将不会根据Spring Boot Actuator传播应用程序的当前运行状况检查状态。这意味着成功注册后Eureka将永远宣布申请处于“UP”状态。可以通过启用Eureka运行状况检查来改变此行为,从而将应用程序状态传播到Eureka。因此,每个其他应用程序将不会在“UP”之外的状态下将流量发送到应用程序。

application.yml
eureka:
  client:
    healthcheck:
      enabled: true
警告
eureka.client.healthcheck.enabled=true只能在application.yml中设置。设置bootstrap.yml中的值将导致不良副作用,例如在eureka中注册UNKNOWN状态。

如果您需要更多的控制健康检查,您可以考虑实施自己的com.netflix.appinfo.HealthCheckHandler

Eureka实例和客户端的元数据

值得花点时间了解Eureka元数据的工作原理,以便您可以在平台上使用它。有主机名,IP地址,端口号,状态页和运行状况检查等标准元数据。这些发布在服务注册表中,由客户使用,以直接的方式联系服务。额外的元数据可以添加到eureka.instance.metadataMap中的实例注册中,并且这可以在远程客户端中访问,但一般不会改变客户端的行为,除非知道元数据的含义。下面描述了几个特殊情况,其中Spring Cloud已经为元数据映射指定了含义。

在Cloudfoundry上使用Eureka

Cloudfoundry有一个全局路由器,所以同一个应用程序的所有实例都具有相同的主机名(在具有相似架构的其他PaaS解决方案中也是如此)。这不一定是使用Eureka的障碍,但是如果您使用路由器(建议,甚至是强制性的,具体取决于您的平台的设置方式),则需要明确设置主机名和端口号(安全或非安全),以便他们使用路由器。您可能还需要使用实例元数据,以便您可以区分客户端上的实例(例如,在自定义负载平衡器中)。默认情况下,eureka.instance.instanceIdvcap.application.instance_id例如:

application.yml
eureka:
  instance:
    hostname: ${vcap.application.uris[0]}
    nonSecurePort: 80

根据Cloudfoundry实例中安全规则的设置方式,您可以注册并使用主机VM的IP地址进行直接的服务到服务调用。此功能尚未在Pivotal Web Services(PWS)上提供。

在AWS上使用Eureka

如果应用程序计划将部署到AWS云,那么Eureka实例必须被配置为AWS意识到,这可以通过定制来完成EurekaInstanceConfigBean方式如下:

@Bean
@Profile("!default")
public EurekaInstanceConfigBean eurekaInstanceConfig(InetUtils inetUtils) {
  EurekaInstanceConfigBean b = new EurekaInstanceConfigBean(inetUtils);
  AmazonInfo info = AmazonInfo.Builder.newBuilder().autoBuild("eureka");
  b.setDataCenterInfo(info);
  return b;
}

更改Eureka实例ID

香草Netflix Eureka实例注册了与其主机名相同的ID(即每个主机只有一个服务)。Spring Cloud Eureka提供了一个明智的默认,如下所示:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}}例如myhost:myappname:8080

使用Spring Cloud,您可以通过在eureka.instance.instanceId中提供唯一的标识来覆盖此。例如:

application.yml
eureka:
  instance:
    instanceId: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${random.value}}}

使用这个元数据和在localhost上部署的多个服务实例,随机值将在那里进行,以使实例是唯一的。在Cloudfoundry中,vcap.application.instance_id将在Spring Boot应用程序中自动填充,因此不需要随机值。

使用EurekaClient

一旦您有一个@EnableDiscoveryClient(或@EnableEurekaClient)的应用程序,您可以使用它来从Eureka服务器发现服务实例一种方法是使用本机com.netflix.discovery.EurekaClient(而不是Spring Cloud DiscoveryClient),例如

@Autowired
private EurekaClient discoveryClient;

public String serviceUrl() {
    InstanceInfo instance = discoveryClient.getNextServerFromEureka("STORES", false);
    return instance.getHomePageUrl();
}
提示

不要使用@PostConstruct方法或@Scheduled方法(或ApplicationContext可能尚未启动的任何地方)EurekaClient它被初始化为SmartLifecycle(使用phase=0),所以您可以依赖的最早可用的是另一个具有较高阶段的SmartLifecycle

本机Netflix EurekaClient的替代方案

您不必使用原始的Netflix EurekaClient,通常在某种包装器后面使用它更为方便。Spring Cloud支持Feign(REST客户端构建器),还支持使用逻辑Eureka服务标识符(VIP)而不是物理URL的Spring RestTemplate要使用固定的物理服务器列表配置Ribbon,您可以将<client>.ribbon.listOfServers设置为以逗号分隔的物理地址(或主机名)列表,其中<client>是客户端的ID。

您还可以使用org.springframework.cloud.client.discovery.DiscoveryClient,它为Netflix不具体的发现客户端提供简单的API,例如

@Autowired
private DiscoveryClient discoveryClient;

public String serviceUrl() {
    List<ServiceInstance> list = discoveryClient.getInstances("STORES");
    if (list != null && list.size() > 0 ) {
        return list.get(0).getUri();
    }
    return null;
}

为什么注册服务这么慢?

作为一个实例也包括定期心跳到注册表(通过客户端的serviceUrl),默认持续时间为30秒。在实例,服务器和客户端在其本地缓存中都具有相同的元数据(因此可能需要3个心跳)之前,客户端才能发现服务。您可以使用eureka.instance.leaseRenewalIntervalInSeconds更改期限,这将加快客户端连接到其他服务的过程。在生产中,最好坚持使用默认值,因为服务器内部有一些计算可以对租赁更新期进行假设。

如果您已将Eureka客户端部署到多个区域,您可能希望这些客户端在使用另一个区域中的服务之前,在同一区域内利用服务。为此,您需要正确配置您的Eureka客户端。

首先,您需要确保将Eureka服务器部署到每个区域,并且它们是彼此的对等体。有关详细信息,请参阅区域和区域部分

接下来,您需要告知Eureka您的服务所在的区域。您可以使用metadataMap属性来执行此操作。例如,如果将service 1部署到zone 1zone 2,则需要在service 1中设置以下Eureka属性

1区服务1

eureka.instance.metadataMap.zone = zone1
eureka.client.preferSameZoneEureka = true

第2区的服务1

eureka.instance.metadataMap.zone = zone2
eureka.client.preferSameZoneEureka = true

服务发现:Eureka服务器

如何包括Eureka服务器

要在项目中包含Eureka服务器,请使用组org.springframework.cloud和工件id spring-cloud-starter-eureka-server的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面

如何运行Eureka服务器

示例eureka服务器;

@SpringBootApplication
@EnableEurekaServer
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

}

服务器具有一个带有UI的主页,根据/eureka/*下的正常Eureka功能的HTTP API端点。

Eureka背景阅读:看助焊剂谷歌小组讨论

提示

由于Gradle的依赖关系解决规则和父母的bom功能缺乏,只要依靠spring-cloud-starter-eureka-server就可能导致应用程序启动失败。要解决这个问题,必须添加Spring Boot Gradle插件,并且必须像Spring那样导入Spring cloud starter parent bom:

的build.gradle
buildscript {
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.5.RELEASE")
  }
}

apply plugin: "spring-boot"

dependencyManagement {
  imports {
    mavenBom "org.springframework.cloud:spring-cloud-dependencies:Brixton.RELEASE"
  }
}

高可用性,区域和地区

Eureka服务器没有后端存储,但注册表中的服务实例都必须发送心跳以保持其注册更新(因此可以在内存中完成)。客户端还具有eureka注册的内存缓存(因此,他们不必为注册表提供每个服务请求)。

默认情况下,每个Eureka服务器也是Eureka客户端,并且需要(至少一个)服务URL来定位对等体。如果您不提供该服务将运行和工作,但它将淋浴您的日志与大量的噪音无法注册对等体。

有关区域和区域的客户端Ribbon支持的详细信息,请参见下文

独立模式

只要存在某种监视器或弹性运行时间(例如Cloud Foundry),两个缓存(客户机和服务器)和心跳的组合使独立的Eureka服务器对故障具有相当的弹性。在独立模式下,您可能更喜欢关闭客户端行为,因此不会继续尝试并且无法访问其对等体。例:

application.yml(Standalone Eureka Server)
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配置文件中运行它来运行2台主机(peer1和peer2)上的相同服务器。您可以使用此配置来测试单个主机上的对等体感知(通过操作/etc/hosts来解析主机名,在生产中没有太多价值)。事实上,如果您在一台知道自己的主机名的机器上运行(默认情况下使用java.net.InetAddress查找),则不需要eureka.instance.hostname

您可以向系统添加多个对等体,只要它们至少一个边缘彼此连接,则它们将在它们之间同步注册。如果对等体在物理上分离(在数据中心内或多个数据中心之间),则系统原则上可以分裂脑型故障。

喜欢IP地址

在某些情况下,Eureka最好是通告服务的IP地址而不是主机名。eureka.instance.preferIpAddress设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名。

断路器:Hystrix客户端

Netflix的创造了一个调用的库Hystrix实现了断路器图案在微服务架构中,通常有多层服务调用。

HystrixGraph
图1.微服务图

较低级别的服务中的服务故障可能导致用户级联故障。当对特定服务的呼叫达到一定阈值时(Hystrix中的默认值为5秒,20次故障),电路打开,不进行通话。在错误和开路的情况下,开发人员可以提供后备。

HystrixFallback
图2. Hystrix回退防止级联故障

开放式电路会停止级联故障,并允许不必要的或失败的服务时间来愈合。回退可以是另一个Hystrix受保护的调用,静态数据或一个正常的空值。回退可能被链接,所以第一个回退使得一些其他业务电话又回到静态数据。

如何加入Hystrix

在您的项目中包含Hystrix使用组org.springframework.cloud和artifact id spring-cloud-starter-hystrix的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面

示例启动应用程序:

@SpringBootApplication
@EnableCircuitBreaker
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class).web(true).run(args);
    }

}

@Component
public class StoreIntegration {

    @HystrixCommand(fallbackMethod = "defaultStores")
    public Object getStores(Map<String, Object> parameters) {
        //do stuff that might fail
    }

    public Object defaultStores(Map<String, Object> parameters) {
        return /* something useful */;
    }
}

@HystrixCommand由名为“javanica”的Netflix contrib库提供 Spring Cloud使用该注释在连接到Hystrix断路器的代理中自动包装Spring bean。断路器计算何时打开和关闭电路,以及在发生故障时应该做什么。

要配置@HystrixCommand,您可以使用commandProperties属性列出@HystrixProperty注释。请参阅 这里 了解更多详情。有关 可用属性的详细信息,请参阅Hystrix维基

传播安全上下文或使用Spring Scopes

如果您希望某些线程本地上下文传播到@HystrixCommand,默认声明将不起作用,因为它在线程池中执行命令(超时)。您可以使用某些配置或直接在注释中使用与使用相同的线程来调用Hystrix,方法是要求使用不同的“隔离策略”。例如:

@HystrixCommand(fallbackMethod = "stubMyService",
    commandProperties = {
      @HystrixProperty(name="execution.isolation.strategy", value="SEMAPHORE")
    }
)
...

如果您使用@SessionScope@RequestScope,同样的事情也适用。您将知道何时需要执行此操作,因为运行时异常说它找不到范围的上下文。

您还可以选择将hystrix.shareSecurityContext属性设置为true这样做会自动配置一个Hystrix并发策略插件钩子,他将SecurityContext从主线程传输到Hystrix命令使用的钩子。Hystrix不允许多个hystrix并发策略被注册,因此通过将自己的HystrixConcurrencyStrategy声明为Spring bean可以使用扩展机制。Spring Cloud将在Spring上下文中查找您的实现,并将其包装在自己的插件中。

健康指标

连接的断路器的状态也暴露在呼叫应用程序的/health端点中。

{
    "hystrix": {
        "openCircuitBreakers": [
            "StoreIntegration::getStoresByLocationLink"
        ],
        "status": "CIRCUIT_OPEN"
    },
    "status": "UP"
}

Hystrix指标流

要使Hystrix度量流包含对spring-boot-starter-actuator的依赖。这将使/hystrix.stream作为管理端点。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

断路器:Hystrix仪表板

Hystrix的主要优点之一是它收集了关于每个HystrixCommand的一套指标。Hystrix仪表板以有效的方式显示每个断路器的运行状况。

豪猪
图3. Hystrix仪表板

Hystrix超时和Ribbon客户

当使用包含Ribbon客户端的Hystrix命令时,您需要确保您的Hystrix超时配置为长于配置的Ribbon超时,包括任何可能的重试。例如,如果您的Ribbon连接超时为一秒钟,并且Ribbon客户端可能会重试该请求三次,那么您的Hystrix超时时间应该略超过三秒钟。

如何加入Hystrix仪表板

要在项目中包含Hystrix仪表板,请使用组org.springframework.cloud和工件ID spring-cloud-starter-hystrix-dashboard的起始器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面

运行Hystrix仪表板使用@EnableHystrixDashboard注释您的Spring Boot主类。然后访问/hystrix并将仪表板指向Hystrix客户端应用程序中的单个实例/hystrix.stream端点。

Turbine

查看个别实例Hystrix数据在系统的总体健康状况方面不是很有用。Turbine是将所有相关/hystrix.stream端点聚合到Hystrix仪表板中使用的组合/turbine.stream的应用程序。单个实例通过Eureka定位。运行Turbine就像使用@EnableTurbine注释(例如使用spring-cloud-starter-turbine设置类路径)注释主类一样简单。来自Turbine 1维基的所有文档配置属性都适用。唯一的区别是,turbine.instanceUrlSuffix不需要预先添加的端口,因为除非turbine.instanceInsertPort=false自动处理。

注意
默认情况下,Turbine通过在Eureka中查找其homePageUrl条目,然后将/hystrix.stream附加到注册的实例上查找/hystrix.stream端点。这意味着如果spring-boot-actuator在自己的端口上运行(这是默认值),则对/hystrix.stream的调用将失败。要使涡轮机找到正确端口的Hystrix流,需要向实例的元数据中添加management.port
eureka:
  instance:
    metadata-map:
      management.port: ${management.port:8081}

配置密钥turbine.appConfig是涡轮机将用于查找实例的尤里卡服务列表。涡轮流然后在Hystrix仪表板中使用如下URL:http://my.turbine.sever:8080/turbine.stream?cluster=<CLUSTERNAME>;(如果名称为“默认”,则可以省略群集参数)。cluster参数必须与turbine.aggregator.clusterConfig中的条目匹配。从eureka返回的值是大写字母,因此如果有一个名为“customers”的Eureka注册了一个应用程序,我们希望该示例可以工作:

turbine:
  aggregator:
    clusterConfig: CUSTOMERS
  appConfig: customers

clusterName可以通过turbine.clusterNameExpression中的SPEL表达式使用root实例来定制InstanceInfo默认值为appName,这意味着Eureka serviceId最终作为集群密钥(即客户的InstanceInfo具有appName“CUSTOMERS”)。一个不同的例子是turbine.clusterNameExpression=aSGName,它将从AWS ASG名称获取集群名称。另一个例子:

turbine:
  aggregator:
    clusterConfig: SYSTEM,USER
  appConfig: customers,stores,ui,admin
  clusterNameExpression: metadata['cluster']

在这种情况下,来自4个服务的集群名称从其元数据映射中提取,并且预期具有包含“SYSTEM”和“USER”的值。

要为所有应用程序使用“默认”集群,您需要一个字符串文字表达式(带单引号,并且如果它在YAML中也使用双引号进行转义):

turbine:
  appConfig: customers,stores
  clusterNameExpression: "'default'"

Spring Cloud提供了一个spring-cloud-starter-turbine,它具有运行Turbine服务器所需的所有依赖关系。只需创建一个Spring Boot应用程序并用@EnableTurbine进行注释。

注意
默认情况下,Spring Cloud允许Turbine使用主机和端口,以允许每个主机在每个群集中进行多个进程。如果你想建成Turbine,它的原生Netflix的行为允许每个主机上的多个过程,每簇(关键实例ID是主机名),然后将该属性设置turbine.combineHostPort=false

Turbine Stream

在某些环境中(例如,在PaaS设置中),从所有分布式Hystrix命令中提取度量的经典Turbine模型不起作用。在这种情况下,您可能希望使Hystrix命令将度量标准推送到Turbine,并且Spring Cloud可以使用消息传递。您需要在客户端上执行的所有操作都为您选择的spring-cloud-netflix-hystrix-streamspring-cloud-starter-stream-*添加依赖关系(有关代理的详细信息,请参阅Spring Cloud Stream文档,以及如何配置客户端凭据,但是应该为当地经纪人开箱即用)。

在服务器端只需创建一个Spring Boot应用程序并使用@EnableTurbineStream进行注释,默认情况下将会出现在8989端口(将您的Hystrix仪表板指向该端口,任何路径)。您可以使用server.portturbine.stream.port自定义端口。如果类路径中还有spring-boot-starter-webspring-boot-starter-actuator,那么您可以通过提供不同的management.port在单独的端口(默认情况下使用Tomcat)打开Actuator端点。

然后,您可以将Hystrix仪表板指向Turbine Stream服务器,而不是单个Hystrix流。如果Turbine Stream在myhost上的端口8989上运行,则将http://myhost:8989放在Hystrix仪表板中的流输入字段中。电路将以各自的serviceId为前缀,后跟一个点,然后是电路名称。

Spring Cloud提供了一个spring-cloud-starter-turbine-stream,它具有您需要的Turbine Stream服务器运行所需的所有依赖项,只需添加您选择的Stream binder,例如spring-cloud-starter-stream-rabbit您需要Java 8来运行应用程序,因为它是基于Netty的。

客户侧负载均衡器:Ribbon

Ribbon是一个客户端负载平衡器,它可以很好地控制HTTP和TCP客户端的行为。Feign已经使用Ribbon,所以如果你使用@FeignClient,那么这一节也适用。

Ribbon中的中心概念是命名客户端的概念。每个负载平衡器是组合的组合的一部分,它们一起工作以根据需要联系远程服务器,并且集合具有您将其作为应用程序开发人员(例如使用@FeignClient注释)的名称。Spring Cloud使用RibbonClientConfiguration为每个命名的客户端根据需要创建一个新的集合,作为ApplicationContext这包含(除其他外)ILoadBalancerRestClientServerListFilter

如何加入Ribbon

要在项目中包含Ribbon,请使用组org.springframework.cloud和artifact id spring-cloud-starter-ribbon的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面

自定义Ribbon客户端

您可以使用<client>.ribbon.*中的外部属性来配置Ribbon客户端的某些位,这与使用Netflix API本身没有什么不同,只能使用Spring Boot配置文件。本机选项可以在CommonClientConfigKey(功能区内核心部分)中作为静态字段进行检查。

Spring Cloud还允许您通过使用@RibbonClient声明其他配置(位于RibbonClientConfiguration之上)来完全控制客户端。例:

@Configuration
@RibbonClient(name = "foo", configuration = FooConfiguration.class)
public class TestConfiguration {
}

在这种情况下,客户端由RibbonClientConfiguration中已经存在的组件与FooConfiguration中的任何组件组成(后者通常会覆盖前者)。

警告
FooConfiguration必须是@Configuration,但请注意,主应用程序上下文不属于@ComponentScan,否则将由@RibbonClients共享。如果您使用@ComponentScan(或@SpringBootApplication),则需要采取措施避免包含(例如将其放在一个单独的,不重叠的包中,或者指定要在@ComponentScan)。

Spring Cloud Netflix默认提供以下功能区(BeanType beanName:ClassName)的bean:

  • IClientConfig ribbonClientConfig:DefaultClientConfigImpl

  • IRule ribbonRule:ZoneAvoidanceRule

  • IPing ribbonPing:NoOpPing

  • ServerList<Server> ribbonServerList:ConfigurationBasedServerList

  • ServerListFilter<Server> ribbonServerListFilter:ZonePreferenceServerListFilter

  • ILoadBalancer ribbonLoadBalancer:ZoneAwareLoadBalancer

  • ServerListUpdater ribbonServerListUpdater:PollingServerListUpdater

创建一个类型的bean并将其放置在@RibbonClient配置(例如上面的FooConfiguration)中)允许您覆盖所描述的每个bean。例:

@Configuration
public class FooConfiguration {
    @Bean
    public IPing ribbonPing(IClientConfig config) {
        return new PingUrl();
    }
}

这将PingUrl替换为PingUrl

使用属性自定义Ribbon客户端

从版本1.2.0开始,Spring Cloud Netflix现在支持使用属性与Ribbon文档兼容来自定义Ribbon客户端

这允许您在不同环境中更改启动时的行为。

支持的属性如下所示,应以<clientName>.ribbon.为前缀:

  • NFLoadBalancerClassName:应实施ILoadBalancer

  • NFLoadBalancerRuleClassName:应实施IRule

  • NFLoadBalancerPingClassName:应实施IPing

  • NIWSServerListClassName:应实施ServerList

  • NIWSServerListFilterClassName应实施ServerListFilter

注意
在这些属性中定义的类优先于使用@RibbonClient(configuration=MyRibbonConfig.class)定义的bean和由Spring Cloud Netflix提供的默认值。

要设置服务名称usersIRule,您可以设置以下内容:

application.yml
users:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule

有关Ribbon提供的实现,请参阅Ribbon文档

使用Ribbon与Eureka

当Eureka与Ribbon结合使用(即两者都在类路径上)时,ribbonServerList将被扩展为DiscoveryEnabledNIWSServerList,扩展名为Eureka的服务器列表。它还用NIWSDiscoveryPing替换IPing接口,代理到Eureka以确定服务器是否启动。默认情况下安装的ServerList是一个DomainExtractingServerList,其目的是使物理元数据可用于负载平衡器,而不使用AWS AMI元数据(Netflix依赖的是)。默认情况下,服务器列表将使用实例元数据(如远程客户机集eureka.instance.metadataMap.zone)中提供的“区域”信息构建,如果缺少,则可以使用服务器主机名中的域名作为代理对于区域(如果设置了标志approximateZoneFromHostname)。一旦区域信息可用,它可以在ServerListFilter中使用。默认情况下,它将用于定位与客户端相同区域的服务器,因为默认值为ZonePreferenceServerListFilter默认情况下,即通过eureka.instance.metadataMap.zone,客户端的区域与远程实例的方式相同。

注意
设置客户端区域的正统“archaius”方式是通过一个名为“@zone”的配置属性,如果可用,Spring Cloud将优先使用所有其他设置(请注意,该键必须被引用)在YAML配置中)。
注意
如果没有其他的区域数据源,则基于客户端配置(与实例配置相反)进行猜测。我们将eureka.client.availabilityZones,它是从区域名称到区域列表的地图,并拉出实例本身区域的第一个区域(即eureka.client.region,默认为“us-east-1”,为与本机Netflix的兼容性)。

示例:如何使用Ribbon不使用Eureka

Eureka是抽象远程服务器发现的一种方便的方式,因此您不必在客户端中对其URL进行硬编码,但如果您不想使用它,Ribbon和Feign仍然很适用的。假设你已经为“store”声明了一个@RibbonClient,并且Eureka没有被使用(甚至不在类路径上)。Ribbon客户端默认为已配置的服务器列表,您可以提供这样的配置

application.yml
stores:
  ribbon:
    listOfServers: example.com,google.com

示例:在Ribbon中禁用Eureka使用

设置属性ribbon.eureka.enabled = false将明确禁用在Ribbon中使用Eureka。

application.yml
ribbon:
  eureka:
   enabled: false

直接使用Ribbon API

您也可以直接使用LoadBalancerClient例:

public class MyClass {
    @Autowired
    private LoadBalancerClient loadBalancer;

    public void doStuff() {
        ServiceInstance instance = loadBalancer.choose("stores");
        URI storesUri = URI.create(String.format("http://%s:%s", instance.getHost(), instance.getPort()));
        // ... do something with the URI
    }
}

缓存Ribbon配置

每个Ribbon命名的客户机都有一个对应的子应用程序上下文,Spring Cloud维护,这个应用程序上下文在第一个请求中被延迟加载到命名的客户端。可以通过指定Ribbon客户端的名称,在启动时,可以更改这种惰性加载行为,从而热切加载这些子应用程序上下文。

application.yml
ribbon:
  eager-load:
    enabled: true
    clients: client1, client2, client3

声明性REST客户端:Feign

Feign是一个声明式的Web服务客户端。这使得Web服务客户端的写入更加方便 要使用Feign创建一个界面并对其进行注释。它具有可插拔注释支持,包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud添加了对Spring MVC注释的支持,并在Spring Web中使用默认使用的HttpMessageConvertersSpring Cloud集成Ribbon和Eureka以在使用Feign时提供负载均衡的http客户端。

如何包含Feign

要在您的项目中包含Feign,请使用组org.springframework.cloud和工件ID spring-cloud-starter-feign的启动器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面

示例Spring Boot应用程序

@Configuration
@ComponentScan
@EnableAutoConfiguration
@EnableEurekaClient
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
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注释中,String值(以上“存储”)是一个任意的客户端名称,用于创建Ribbon负载平衡器(有关Ribbon支持的详细信息,请参阅下文))。您还可以使用url属性(绝对值或只是主机名)指定URL。应用程序上下文中的bean的名称是该接口的完全限定名称。要指定自己的别名值,您可以使用@FeignClient注释的qualifier值。

以上的Ribbon客户端将要发现“商店”服务的物理地址。如果您的应用程序是Eureka客户端,那么它将解决Eureka服务注册表中的服务。如果您不想使用Eureka,您可以简单地配置外部配置中的服务器列表(例如,参见 上文)。

覆盖Feign默认值

Spring Cloud的Feign支持中的一个中心概念就是命名的客户端。每个假装客户端是组合的组合的一部分,它们一起工作以按需联系远程服务器,并且该集合具有您将其作为应用程序开发人员使用@FeignClient注释的名称。Spring Cloud使用FeignClientsConfiguration为每个命名的客户端根据需要创建一个新的集合ApplicationContext这包含(其中包括)feign.Decoderfeign.Encoderfeign.Contract

Spring Cloud可以通过使用@FeignClient声明其他配置(位于FeignClientsConfiguration之上)来完全控制假客户端。例:

@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
    //..
}

在这种情况下,客户端由FeignClientsConfiguration中的组件与FooConfiguration中的任何组件组成(后者将覆盖前者)。

注意
FooConfiguration不需要使用@Configuration注释。但是,如果是,则请注意将其从任何@ComponentScan中排除,否则将包含此配置,因为它将成为feign.Decoderfeign.Encoderfeign.Contract等的默认来源,指定时。这可以通过将其放置在任何@ComponentScan@SpringBootApplication的单独的不重叠的包中,或者可以在@ComponentScan中明确排除。
注意
serviceId属性现在已被弃用,有利于name属性。
警告
以前,使用url属性,不需要name属性。现在需要使用name

nameurl属性支持占位符。

@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
    //..
}

Spring Cloud Netflix默认为feign(BeanType beanName:ClassName)提供以下bean:

  • Decoder feignDecoder:ResponseEntityDecoder(其中包含SpringDecoder

  • Encoder feignEncoder:SpringEncoder

  • Logger feignLogger:Slf4jLogger

  • Contract feignContract:SpringMvcContract

  • Feign.Builder feignBuilder:HystrixFeign.Builder

  • Client feignClient:如果Ribbon被启用,它是一个LoadBalancerFeignClient,否则使用默认的feign客户端。

可以通过将feign.okhttp.enabledfeign.httpclient.enabled设置为true,并将它们放在类路径上来使用OkHttpClient和ApacheHttpClient feign客户端。

Spring Cloud Netflix 默认情况下提供以下bean,但是仍然从应用程序上下文中查找这些类型的bean以创建假客户端:

  • Logger.Level

  • Retryer

  • ErrorDecoder

  • Request.Options

  • Collection<RequestInterceptor>

  • SetterFactory

创建其中一个类型的bean并将其放置在@FeignClient配置(例如上面的FooConfiguration)中)允许您覆盖所描述的每个bean。例:

@Configuration
public class FooConfiguration {
    @Bean
    public Contract feignContract() {
        return new feign.Contract.Default();
    }

    @Bean
    public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
        return new BasicAuthRequestInterceptor("user", "password");
    }
}

这将SpringMvcContract替换为feign.Contract.Default,并将RequestInterceptor添加到RequestInterceptor的集合中。

可以在@EnableFeignClients属性defaultConfiguration中以与上述相似的方式指定默认配置。不同之处在于,此配置将适用于所有假客户端。

注意
如果您需要在RequestInterceptor`s you will need to either set the thread isolation strategy for Hystrix to `SEMAPHORE中使用ThreadLocal绑定变量或在Feign中禁用Hystrix。

application.yml

# To disable Hystrix in Feign
feign:
  hystrix:
    enabled: false

# To set thread isolation to SEMAPHORE
hystrix:
  command:
    default:
      execution:
        isolation:
          strategy: SEMAPHORE

手动创建Feign客户端

在某些情况下,可能需要以上述方法不可能自定义您的Feign客户端。在这种情况下,您可以使用Feign Builder API创建客户端 下面是一个创建两个具有相同接口的Feign客户端的示例,但是使用单独的请求拦截器配置每个客户端。

@Import(FeignClientsConfiguration.class)
class FooController {

	private FooClient fooClient;

	private FooClient adminClient;

    @Autowired
	public FooController(
			Decoder decoder, Encoder encoder, Client client) {
		this.fooClient = Feign.builder().client(client)
				.encoder(encoder)
				.decoder(decoder)
				.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
				.target(FooClient.class, "http://PROD-SVC");
		this.adminClient = Feign.builder().client(client)
				.encoder(encoder)
				.decoder(decoder)
				.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
				.target(FooClient.class, "http://PROD-SVC");
    }
}
注意
在上面的例子中,FeignClientsConfiguration.class是由Spring Cloud Netflix提供的默认配置。
注意
PROD-SVC是客户端将要求的服务的名称。

Feign Hystrix支持

如果Hystrix在类路径和feign.hystrix.enabled=true上,则Feign将使用断路器包装所有方法。还可以返回com.netflix.hystrix.HystrixCommand这样可以使用无效模式(调用.toObservable().observe()或异步使用(调用.queue()))。

要在每个客户端基础上禁用Hystrix支持,使用“prototype”范围创建一个vanilla Feign.Builder,例如:

@Configuration
public class FooConfiguration {
    @Bean
	@Scope("prototype")
	public Feign.Builder feignBuilder() {
		return Feign.builder();
	}
}
警告
在Spring Cloud达尔斯顿发布之前,如果Hystrix在类路径Feign中,默认情况下将所有方法都封装在断路器中。这种默认行为在Spring Cloud达尔斯顿更改了赞成选择加入的方式。

Feign Hystrix回退

Hystrix支持回退的概念:当电路打开或出现错误时执行的默认代码路径。要为给定的@FeignClient启用回退,请将fallback属性设置为实现回退的类名。

@FeignClient(name = "hello", fallback = HystrixClientFallback.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

static class HystrixClientFallback implements HystrixClient {
    @Override
    public Hello iFailSometimes() {
        return new Hello("fallback");
    }
}

如果需要访问导致回退触发的原因,可以使用@FeignClient内的fallbackFactory属性。

@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
	@RequestMapping(method = RequestMethod.GET, value = "/hello")
	Hello iFailSometimes();
}

@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
	@Override
	public HystrixClient create(Throwable cause) {
		return new HystrixClientWithFallBackFactory() {
			@Override
			public Hello iFailSometimes() {
				return new Hello("fallback; reason was: " + cause.getMessage());
			}
		};
	}
}
警告
在Feign中执行回退以及如何执行Hystrix回退是有限制的。当前返回com.netflix.hystrix.HystrixCommandrx.Observable的方法目前不支持回退。

Feign和@Primary

当使用Feign与Hystrix回退时,在同一类型的ApplicationContext中有多个bean。这将导致@Autowired不起作用,因为没有一个bean,或者标记为主。要解决这个问题,Spring Cloud Netflix将所有Feign实例标记为@Primary,因此Spring Framework将知道要注入的bean。在某些情况下,这可能是不可取的。要关闭此行为,将@FeignClientprimary属性设置为false。

@FeignClient(name = "hello", primary = false)
public interface HelloClient {
	// methods here
}

Feign继承支持

Feign通过单继承接口支持样板apis。这样就可以将常用操作分成方便的基本界面。

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请求/响应压缩

您可以考虑启用针对您的Feign请求的请求或响应GZIP压缩。您可以通过启用其中一个属性来执行此操作:

feign.compression.request.enabled=true
feign.compression.response.enabled=true

Feign请求压缩为您提供与您为Web服务器设置的设置类似的设置:

feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

这些属性可以让您对压缩介质类型和最小请求阈值长度有选择性。

Feign日志记录

为每个创建的Feign客户端创建一个记录器。默认情况下,记录器的名称是用于创建Feign客户端的接口的完整类名。Feign日志记录仅响应DEBUG级别。

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;
    }
}

外部配置:Archaius

Netflix客户端配置库是Archaius它是所有Netflix OSS组件用于配置的库。Archaius是Apache Commons配置项目的扩展它允许通过轮询源进行更改或将源更改推送到客户端来进行配置更新。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工具。

路由器和过滤器:Zuul

路由在微服务体系结构的一个组成部分。例如,/可以映射到您的Web应用程序,/api/users映射到用户服务,/api/shop映射到商店服务。Zuul是Netflix的基于JVM的路由器和服务器端负载均衡器。

Netflix使用Zuul进行以下操作:

  • 认证

  • 洞察

  • 压力测试

  • 金丝雀测试

  • 动态路由

  • 服务迁移

  • 负载脱落

  • 安全

  • 静态响应处理

  • 主动/主动流量管理

Zuul的规则引擎允许基本上以任何JVM语言编写规则和过滤器,内置Java和Groovy。

注意
配置属性zuul.max.host.connections已被两个新属性zuul.host.maxTotalConnectionszuul.host.maxPerRouteConnections替换,分别默认为200和20。
注意
所有路由的默认Hystrix隔离模式(ExecutionIsolationStrategy)是SEMAPHORE。如果此隔离模式是首选,则zuul.ribbonIsolationStrategy可以更改为THREAD。

如何加入Zuul

要在项目中包含Zuul,请使用组org.springframework.cloud和工件id spring-cloud-starter-zuul的起始器。有关 使用当前的Spring Cloud发布列表设置构建系统的详细信息,请参阅Spring Cloud项目页面

嵌入式Zuul反向代理

Spring Cloud已经创建了一个嵌入式Zuul代理,以简化UI应用程序想要代理对一个或多个后端服务的呼叫的非常常见的用例的开发。此功能对于用户界面对其所需的后端服务进行代理是有用的,避免了对所有后端独立管理CORS和验证问题的需求。

要启用它,使用@EnableZuulProxy注释Spring Boot主类,并将本地调用转发到相应的服务。按照惯例,具有ID“用户”的服务将接收来自位于/users的代理(具有前缀stripped)的请求。代理使用Ribbon来定位要通过发现转发的实例,并且所有请求都以 hystrix命令执行,所以故障将显示在Hystrix指标中,一旦电路打开,代理将不会尝试联系服务。

注意
Zuul启动器不包括发现客户端,因此对于基于服务ID的路由,您还需要在类路径中提供其中一个路由(例如Eureka)。

要跳过自动添加的服务,请将zuul.ignored-services设置为服务标识模式列表。如果一个服务匹配一个被忽略的模式,而且包含在明确配置的路由映射中,那么它将被无符号。例:

application.yml
 zuul:
  ignoredServices: '*'
  routes:
    users: /myusers/**

在此示例中, “用户” 之外,所有服务都被忽略

要扩充或更改代理路由,可以添加如下所示的外部配置:

application.yml
 zuul:
  routes:
    users: /myusers/**

这意味着对“/ myusers”的http呼叫转发到“用户”服务(例如“/ myusers / 101”转发到“/ 101”)。

要获得对路由的更细粒度的控制,您可以独立地指定路径和serviceId:

application.yml
 zuul:
  routes:
    users:
      path: /myusers/**
      serviceId: users_service

这意味着对“/ myusers”的http呼叫转发到“users_service”服务。路由必须有一个“路径”,可以指定为蚂蚁样式模式,所以“/ myusers / *”只匹配一个级别,但“/ myusers / **”分层匹配。

后端的位置可以被指定为“serviceId”(用于发现的服务)或“url”(对于物理位置),例如

application.yml
 zuul:
  routes:
    users:
      path: /myusers/**
      url: http://example.com/users_service

这些简单的URL路由不会被执行为HystrixCommand,也不能使用Ribbon对多个URL进行负载平衡。为此,请指定服务路由并为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和路由之间提供约定。它使用名为group的正则表达式从serviceId中提取变量并将它们注入到路由模式中。

ApplicationConfiguration.java
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
    return new PatternServiceRouteMapper(
        "(?<name>^.+)-(?<version>v.+$)",
        "${version}/${name}");
}

这意味着serviceId“myusers-v1”将被映射到路由“/ v1 / myusers / **”。任何正则表达式都被接受,但所有命名组都必须存在于servicePattern和routePattern中。如果servicePattern与serviceId不匹配,则使用默认行为。在上面的示例中,serviceId“myusers”将被映射到路由“/ myusers / **”(检测不到版本)此功能默认禁用,仅适用于已发现的服务。

要为所有映射添加前缀,请将zuul.prefix设置为一个值,例如/api默认情况下,请求被转发之前,代理前缀被删除(使用zuul.stripPrefix=false切换此行为)。您还可以关闭从各路线剥离服务特定的前缀,例如

application.yml
 zuul:
  routes:
    users:
      path: /myusers/**
      stripPrefix: false
注意
zuul.stripPrefix仅适用于zuul.prefix中设置的前缀。它对给定路由path中定义的前缀有影响。

在本示例中,对“/ myusers / 101”的请求将转发到“/ myusers / 101”上的“users”服务。

zuul.routes条目实际上绑定到类型为ZuulProperties的对象。如果您查看该对象的属性,您将看到它还具有“可重试”标志。将该标志设置为“true”以使Ribbon客户端自动重试失败的请求(如果需要,可以使用Ribbon客户端配置修改重试操作的参数)。

默认情况下,将X-Forwarded-Host标头添加到转发的请求中。要关闭set zuul.addProxyHeaders = false默认情况下,前缀路径被删除,对后端的请求会拾取一个标题“X-Forwarded-Prefix”(上述示例中的“/ myusers”)。

如果您设置默认路由(“/”),例如zuul.route.home: /将路由所有流量(即“/ **”)到“home”服务,则具有@EnableZuulProxy的应用程序可以充当独立服务器。

如果需要更细粒度的忽略,可以指定要忽略的特定模式。在路由位置处理开始时评估这些模式,这意味着前缀应包含在模式中以保证匹配。忽略的模式跨越所有服务,并取代任何其他路由规范。

application.yml
 zuul:
  ignoredPatterns: /**/admin/**
  routes:
    users: /myusers/**

这意味着诸如“/ myusers / 101”的所有呼叫将被转发到“用户”服务上的“/ 101”。但是包含“/ admin /”的呼叫将无法解决。

警告
如果您需要您的路由保留订单,则需要使用YAML文件,因为使用属性文件将会丢失订购。例如:
application.yml
 zuul:
  routes:
    users:
      path: /myusers/**
    legacy:
      path: /**

如果要使用属性文件,legacy路径可能会在users路径前面展开,从而使users路径不可达。

Zuul Http客户端

zuul使用的默认HTTP客户端现在由Apache HTTP Client支持,而不是不推荐使用的Ribbon RestClient分别使用RestClient或使用okhttp3.OkHttpClient集合ribbon.restclient.enabled=trueribbon.okhttp.enabled=true

Cookie和敏感标题

在同一个系统中的服务之间共享标题是可行的,但是您可能不希望敏感标头泄漏到外部服务器的下游。您可以在路由配置中指定被忽略头文件列表。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的默认值,因此您不需要设置它,除非您希望它不同。注意这是Spring Cloud Netflix 1.1中的新功能(在1.0中,用户无法控制标题,所有Cookie都在两个方向上流动)。

sensitiveHeaders是一个黑名单,默认值不为空,因此要使Zuul发送所有标头(“被忽略”除外),您必须将其显式设置为空列表。如果您要将Cookie或授权标头传递到后端,这是必要的。例:

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安全响应头,并希望下载服务提供的值,这将非常有用

路线端点

如果您在Spring Boot Actuator中使用@EnableZuulProxy,则将启用(默认情况下)另一个端点,通过HTTP可以使用/routes到此端点的GET将返回映射路由的列表。POST将强制刷新现有路由(例如,如果服务目录中有更改)。您可以通过将endpoints.routes.enabled设置为false来禁用此端点。

注意
路由应自动响应服务目录中的更改,但POST到/路由是强制更改立即发生的一种方式。

扼杀模式和本地前进

迁移现有应用程序或API时的常见模式是“扼杀”旧端点,用不同的实现慢慢替换它们。Zuul代理是一个有用的工具,因为您可以使用它来处理来自旧端点的客户端的所有流量,但将一些请求重定向到新端点。

示例配置:

application.yml
 zuul:
  routes:
    first:
      path: /first/**
      url: http://first.example.com
    second:
      path: /second/**
      url: forward:/second
    third:
      path: /third/**
      url: forward:/3rd
    legacy:
      path: /**
      url: http://legacy.example.com

在这个例子中,我们扼杀了“遗留”应用程序,该应用程序映射到所有与其他模式不匹配的请求。/first/**中的路径已被提取到具有外部URL的新服务中。并且/second/**中的路径被转发,以便它们可以在本地处理,例如使用正常的Spring @RequestMapping/third/**中的路径也被转发,但使用不同的前缀(即/third/foo转发到/3rd/foo)。

注意
被忽略的模式并不完全被忽略,它们只是不被代理处理(因此它们也被有效地转发到本地)。

通过Zuul上传文件

如果您@EnableZuulProxy您可以使用代理路径上传文件,只要文件很小,它就应该工作。对于大文件,有一个替代路径绕过“/ zuul / *”中的Spring DispatcherServlet(以避免多部分处理)。那么如果zuul.routes.customers=/customers/**然后你可以将大文件发送到“/ zuul / customers / *”。servlet路径通过zuul.servletPath进行外部化。如果代理路由引导您通过Ribbon负载均衡器,例如,超大文件也将需要提升超时设置

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

注意:此特殊标志仅适用于SimpleHostRoutingFilter,您可以使用RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)轻松覆盖查询参数,因为查询字符串现在直接在原始HttpServletRequest上获取。

普通嵌入式Zuul

如果您使用@EnableZuulServer(而不是@EnableZuulProxy),您也可以运行不带代理的Zuul服务器,或选择性地切换代理平台的部分。您添加到ZuulFilter类型的应用程序的任何bean将自动安装,与@EnableZuulProxy一样,但不会自动添加任何代理过滤器。

在这种情况下,仍然通过配置“zuul.routes。*”来指定进入Zuul服务器的路由,但没有服务发现,没有代理,所以“serviceId”和“url”设置将被忽略。例如:

application.yml
 zuul:
  routes:
    api: /api/**

将“/ api / **”中的所有路径映射到Zuul过滤器链。

禁用Zuul过滤器

在Spring Cloud的Zuul中,在代理和服务器模式下默认启用了多个ZuulFilter bean。有关启用的可能过滤器,请参阅zuul过滤器包如果要禁用它,只需设置zuul.<SimpleClassName>.<filterType>.disable=true按照惯例,filters之后的包是Zuul过滤器类型。例如,禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter设置zuul.SendResponseFilter.post.disable=true

提供Hystrix路由回退

当Zuul中给定路由的电路跳闸时,您可以通过创建类型为ZuulFallbackProvider的bean来提供回退响应。在这个bean中,您需要指定回退的路由ID,并提供ClientHttpResponse作为回退返回。这是一个非常简单的ZuulFallbackProvider实现。

class MyFallbackProvider implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        return "customers";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

这里是路由配置的样子。

zuul:
  routes:
    customers: /customers/**

如果要为所有路由提供缺省回退,您可以创建一个类型为ZuulFallbackProvider的bean,并且getRoute方法返回*null

class MyFallbackProvider implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
                return 200;
            }

            @Override
            public String getStatusText() throws IOException {
                return "OK";
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("fallback".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                return headers;
            }
        };
    }
}

Zuul开发人员指南

有关Zuul如何工作的一般概述,请参阅Zuul维基

Zuul Servlet

Zuul实现为Servlet。对于一般情况,Zuul嵌入到Spring调度机制中。这允许Spring MVC控制路由。在这种情况下,Zuul配置为缓冲请求。如果需要通过Zuul缓存请求(例如大文件上传),则Servlet也将安装在Spring Dispatcher之外。默认情况下,它位于/zuul可以使用zuul.servlet-path属性更改此路径。

Zuul RequestContext

要在过滤器之间传递信息,Zuul使用a RequestContext其数据按每个请求特定的ThreadLocal保存。有关在哪里路由请求,错误和实际HttpServletRequestHttpServletResponse的信息存储在那里。RequestContext扩展ConcurrentHashMap,所以任何东西都可以存储在上下文中。FilterConstants包含由Spring Cloud Netflix安装的过滤器使用的密钥(稍后再说明)。

@EnableZuulProxy@EnableZuulServer

Spring Cloud Netflix会根据使用哪个注释来启用Zuul安装多个过滤器。@EnableZuulProxy@EnableZuulServer的超集。换句话说,@EnableZuulProxy包含@EnableZuulServer安装的所有过滤器。“代理”中的其他过滤器启用路由功能。如果你想要一个“空白”Zuul,你应该使用@EnableZuulServer

@EnableZuulServer过滤器

创建从Spring Boot配置文件加载路由定义的SimpleRouteLocator

安装了以下过滤器(像普通的Spring Bean):

前置过滤器

  • ServletDetectionFilter:检测请求是否通过Spring调度程序。使用键FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY设置布尔值。

  • FormBodyWrapperFilter:解析表单数据,并为下游请求重新编码它。

  • DebugFilter:如果设置debug请求参数,则此过滤器将RequestContext.setDebugRouting()RequestContext.setDebugRequest()设置为true。

路由过滤器

  • SendForwardFilter:此过滤器使用Servlet RequestDispatcher转发请求。转发位置存储在RequestContext属性FilterConstants.FORWARD_TO_KEY中。这对于转发到当前应用程序中的端点很有用。

过滤器:

  • SendResponseFilter:将代理请求的响应写入当前响应。

错误过滤器:

  • SendErrorFilter:如果RequestContext.getThrowable()不为null,则转发到/错误(默认情况下)。可以通过设置error.path属性来更改默认转发路径(/error)。

@EnableZuulProxy过滤器

创建从DiscoveryClient(如Eureka)以及属性加载路由定义的DiscoveryClientRouteLocatorDiscoveryClient的每个serviceId创建路由。随着新服务的添加,路由将被刷新。

除了上述过滤器之外,还安装了以下过滤器(如通常的Spring Bean):

前置过滤器

  • PreDecorationFilter:此过滤器根据提供的RouteLocator确定在哪里和如何路由。它还为下游请求设置各种与代理相关的头。

路由过滤器

  • RibbonRoutingFilter:此过滤器使用Ribbon,Hystrix和可插拔HTTP客户端发送请求。服务ID位于RequestContext属性FilterConstants.SERVICE_ID_KEY中。此过滤器可以使用不同的HTTP客户端。他们是:

    • Apache HttpClient这是默认的客户端。

    • Squareup OkHttpClient v3。这通过在类路径上设置com.squareup.okhttp3:okhttp库并设置ribbon.okhttp.enabled=true来启用。

    • Netflix Ribbon HTTP客户端。这可以通过设置ribbon.restclient.enabled=true来启用。这个客户端有限制,比如它不支持PATCH方法,还有内置的重试。

  • SimpleHostRoutingFilter:此过滤器通过Apache HttpClient发送请求到预定的URL。URL位于RequestContext.getRouteHost()

自定义Zuul过滤示例

以下大部分以下“如何撰写”示例都包含示例Zuul过滤器项目。还有一些操作该存储库中的请求或响应正文的例子。

如何编写预过滤器

前置过滤器用于设置RequestContext中的数据,用于下游的过滤器。主要用例是设置路由过滤器所需的信息。

public class QueryParamPreFilter extends ZuulFilter {
	@Override
	public int filterOrder() {
		return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration
	}

	@Override
	public String filterType() {
		return PRE_TYPE;
	}

	@Override
	public boolean shouldFilter() {
		RequestContext ctx = RequestContext.getCurrentContext();
		return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded
				&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId
	}
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
		HttpServletRequest request = ctx.getRequest();
		if (request.getParameter("foo") != null) {
		    // put the serviceId in `RequestContext`
    		ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));
    	}
        return null;
    }
}

上面的过滤器从foo请求参数填充SERVICE_ID_KEY实际上,做这种直接映射并不是一个好主意,而是应该从foo的值中查找服务ID。

现在SERVICE_ID_KEY已经填满,PreDecorationFilter将不会运行,RibbonRoutingFilter将会运行。如果您想要路由到完整的网址,请调用ctx.setRouteHost(url)

要修改路由过滤器将转发的路径,请设置REQUEST_URI_KEY

如何编写路由过滤器

路由过滤器在预过滤器之后运行,并用于向其他服务发出请求。这里的大部分工作是将请求和响应数据转换到客户端所需的模型。

public class OkHttpRoutingFilter extends ZuulFilter {
	@Autowired
	private ProxyRequestHelper helper;

	@Override
	public String filterType() {
		return ROUTE_TYPE;
	}

	@Override
	public int filterOrder() {
		return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;
	}

	@Override
	public boolean shouldFilter() {
		return RequestContext.getCurrentContext().getRouteHost() != null
				&& RequestContext.getCurrentContext().sendZuulResponse();
	}

    @Override
    public Object run() {
		OkHttpClient httpClient = new OkHttpClient.Builder()
				// customize
				.build();

		RequestContext context = RequestContext.getCurrentContext();
		HttpServletRequest request = context.getRequest();

		String method = request.getMethod();

		String uri = this.helper.buildZuulRequestURI(request);

		Headers.Builder headers = new Headers.Builder();
		Enumeration<String> headerNames = request.getHeaderNames();
		while (headerNames.hasMoreElements()) {
			String name = headerNames.nextElement();
			Enumeration<String> values = request.getHeaders(name);

			while (values.hasMoreElements()) {
				String value = values.nextElement();
				headers.add(name, value);
			}
		}

		InputStream inputStream = request.getInputStream();

		RequestBody requestBody = null;
		if (inputStream != null && HttpMethod.permitsRequestBody(method)) {
			MediaType mediaType = null;
			if (headers.get("Content-Type") != null) {
				mediaType = MediaType.parse(headers.get("Content-Type"));
			}
			requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));
		}

		Request.Builder builder = new Request.Builder()
				.headers(headers.build())
				.url(uri)
				.method(method, requestBody);

		Response response = httpClient.newCall(builder.build()).execute();

		LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>();

		for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
			responseHeaders.put(entry.getKey(), entry.getValue());
		}

		this.helper.setResponse(response.code(), response.body().byteStream(),
				responseHeaders);
		context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running
		return null;
    }
}

上述过滤器将Servlet请求信息转换为OkHttp3请求信息,执行HTTP请求,然后将OkHttp3响应信息转换为Servlet响应。警告:此过滤器可能有错误,但功能不正确。

如何编写过滤器

后置过滤器通常操纵响应。在下面的过滤器中,我们添加一个随机UUID作为X-Foo头。其他操作,如转换响应体,要复杂得多,计算密集。

public class AddResponseHeaderFilter extends ZuulFilter {
	@Override
	public String filterType() {
		return POST_TYPE;
	}

	@Override
	public int filterOrder() {
		return SEND_RESPONSE_FILTER_ORDER - 1;
	}

	@Override
	public boolean shouldFilter() {
		return true;
	}

	@Override
	public Object run() {
		RequestContext context = RequestContext.getCurrentContext();
    	HttpServletResponse servletResponse = context.getResponse();
		servletResponse.addHeader("X-Foo", UUID.randomUUID().toString());
		return null;
	}
}

Zuul错误如何工作

如果在Zuul过滤器生命周期的任何部分抛出异常,则会执行错误过滤器。SendErrorFilter只有RequestContext.getThrowable()不是null才会运行。然后在请求中设置特定的javax.servlet.error.*属性,并将请求转发到Spring Boot错误页面。

Zuul渴望应用程序上下文加载

Zuul内部使用Ribbon调用远程URL,并且Ribbon客户端默认在第一次调用时由Spring Cloud加载。可以使用以下配置更改Zuul的此行为,并将导致在应用程序启动时,子Ribbon相关的应用程序上下文正在加载。

application.yml
zuul:
  ribbon:
    eager-load:
      enabled: true

Polyglot支持Sidecar

你有非jvm语言你想要利用Eureka,Ribbon和Config Server?Netflix Prana启发了Spring Cloud Netflix Sidecar 它包含一个简单的http api来获取给定服务的所有实例(即主机和端口)。您还可以通过从Eureka获取其路由条目的嵌入式Zuul代理来代理服务调用。可以通过主机查找或通过Zuul代理访问Spring Cloud配置服务器。非jvm应用程序应该执行健康检查,以便Sidecar可以向应用程序启动或关闭时向尤里卡报告。

要在项目中包含Sidecar,请使用组合org.springframework.cloud和artifact id spring-cloud-netflix-sidecar的依赖关系。

要启用Sidecar,请使用@EnableSidecar创建Spring Boot应用程序。此注释包括@EnableCircuitBreaker@EnableDiscoveryClient@EnableZuulProxy在与非jvm应用程序相同的主机上运行生成的应用程序。

要配置侧车,将sidecar.portsidecar.health-uri添加到application.ymlsidecar.port属性是非jvm应用程序正在侦听的端口。所以Sidecar可以使用Eureka正确注册该应用。sidecar.health-uri是一个可以在非jvm应用程序上访问的uri,它模仿Spring Boot健康指标。它应该返回一个json文档,如下所示:

健康-URI文档
{
  "status":"UP"
}

以下是Sidecar应用程序的application.yml示例:

application.yml
server:
  port: 5678
spring:
  application:
    name: sidecar

sidecar:
  port: 8000
  health-uri: http://localhost:8000/health.json

DiscoveryClient.getInstances()方法的api是/hosts/{serviceId}以下是对不同主机返回两个实例的/hosts/customers的示例响应。这个api可以通过http://localhost:5678/hosts/{serviceId}访问非jvm应用程序(如果sidecar在端口5678)。

/主机/客户
[
    {
        "host": "myhost",
        "port": 9000,
        "uri": "http://myhost:9000",
        "serviceId": "CUSTOMERS",
        "secure": false
    },
    {
        "host": "myhost2",
        "port": 9000,
        "uri": "http://myhost2:9000",
        "serviceId": "CUSTOMERS",
        "secure": false
    }
]

Zuul代理自动将eureka中已知的每个服务的路由添加到/<serviceId>,因此客户服务可在/customers获得。非jvm应用程序可以通过http://localhost:5678/customers访问客户服务(假设侧面正在侦听端口5678)。

如果配置服务器注册了Eureka,则非jvm应用程序可以通过Zuul代理访问它。如果ConfigServer的serviceId为configserver且端口5678为Sidecar,则可以在 http:// localhost:5678 / configserver

非jvm应用程序可以利用Config Server返回YAML文档的功能。例如,调用http://sidecar.local.spring.io:5678/configserver/default-master.yml 可能会导致一个YAML文档,如下所示

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  password: password
info:
  description: Spring Cloud Samples
  url: https://github.com/spring-cloud-samples

RxJava与Spring MVC

Spring Cloud Netflix包括RxJava

RxJava是Reactive Extensions的Java VM实现:用于通过使用observable序列来组合异步和基于事件的程序的库。

Spring Cloud Netflix支持从Spring MVC控制器返回rx.Single对象。它还支持对服务器发送事件(SSE)使用rx.Observable对象如果您的内部API已经使用RxJava构建(参见Feign Hystrix支持示例),这可能非常方便

以下是使用rx.Single的一些示例:

@RequestMapping(method = RequestMethod.GET, value = "/single")
public Single<String> single() {
	return Single.just("single value");
}

@RequestMapping(method = RequestMethod.GET, value = "/singleWithResponse")
public ResponseEntity<Single<String>> singleWithResponse() {
	return new ResponseEntity<>(Single.just("single value"),
			HttpStatus.NOT_FOUND);
}

@RequestMapping(method = RequestMethod.GET, value = "/singleCreatedWithResponse")
public Single<ResponseEntity<String>> singleOuterWithResponse() {
	return Single.just(new ResponseEntity<>("single value", HttpStatus.CREATED));
}

@RequestMapping(method = RequestMethod.GET, value = "/throw")
public Single<Object> error() {
	return Single.error(new RuntimeException("Unexpected"));
}

如果您有Observable而不是单个,则可以使用.toSingle().toList().toSingle()这里有些例子:

@RequestMapping(method = RequestMethod.GET, value = "/single")
public Single<String> single() {
    return Observable.just("single value").toSingle();
}

@RequestMapping(method = RequestMethod.GET, value = "/multiple")
public Single<List<String>> multiple() {
    return Observable.just("multiple", "values").toList().toSingle();
}

@RequestMapping(method = RequestMethod.GET, value = "/responseWithObservable")
public ResponseEntity<Single<String>> responseWithObservable() {

    Observable<String> observable = Observable.just("single value");
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(APPLICATION_JSON_UTF8);
    return new ResponseEntity<>(observable.toSingle(), headers, HttpStatus.CREATED);
}

@RequestMapping(method = RequestMethod.GET, value = "/timeout")
public Observable<String> timeout() {
    return Observable.timer(1, TimeUnit.MINUTES).map(new Func1<Long, String>() {
        @Override
        public String call(Long aLong) {
            return "single value";
        }
    });
}

如果您有流式端点和客户端,SSE可以是一个选项。要将rx.Observable转换为Spring SseEmitter,请使用RxResponse.sse()这里有些例子:

@RequestMapping(method = RequestMethod.GET, value = "/sse")
public SseEmitter single() {
	return RxResponse.sse(Observable.just("single value"));
}

@RequestMapping(method = RequestMethod.GET, value = "/messages")
public SseEmitter messages() {
	return RxResponse.sse(Observable.just("message 1", "message 2", "message 3"));
}

@RequestMapping(method = RequestMethod.GET, value = "/events")
public SseEmitter event() {
	return RxResponse.sse(APPLICATION_JSON_UTF8,
			Observable.just(new EventDto("Spring io", getDate(2016, 5, 19)),
					new EventDto("SpringOnePlatform", getDate(2016, 8, 1))));
}

指标:Spectator,Servo和Atlas

当一起使用时,Spectator / Servo和Atlas提供了近实时的操作洞察平台。

Spectator和Servo是Netflix的指标收集库。Atlas是用于管理维度时间序列数据的Netflix指标后端。

Servo为Netflix服务了几年,并且仍然可以使用,但是逐渐被淘汰出来,赞成Spectator,这仅适用于Java 8. Spring Cloud Netflix为两者提供了支持,但Java鼓励使用基于8的应用程序来使用Spectator。

维度与层次度量

Spring Boot Actuator指标是层次结构,指标只能由名称分隔。这些名称通常遵循将密钥/值属性对(维)嵌入到以句点分隔的名称中的命名约定。考虑以下两个端点(root和star-star)的指标:

{
    "counter.status.200.root": 20,
    "counter.status.400.root": 3,
    "counter.status.200.star-star": 5,
}

第一个指标给出了每单位时间内针对根端点的成功请求的归一化计数。但是如果系统有20个端点,并且想要获得针对所有端点的成功请求计数呢?一些分级度量后端将允许您指定一个通配符,例如counter.status.200. ,它将读取所有20个指标并聚合结果。或者,您可以提供HandlerInterceptorAdapter拦截并记录所有成功请求的counter.status.200.all等指标,而不考虑端点,但现在您必须写入20 + 1个不同的指标。同样,如果您想知道服务中所有端点的成功请求总数,您可以指定一个通配符,例如counter.status.2 .*

即使在分级度量后端的通配符支持的情况下,命名一致性也是困难的。具体来说,这些标签在名称字符串中的位置可能会随着时间而滑落,从而导致查询错 例如,假设我们为上述HTTP方法添加了一个额外的维度。那么counter.status.200.root成为counter.status.200.method.get.root等等。我们的counter.status.200.*突然不再具有相同的语义。此外,如果新的维度在整个代码库中不均匀地应用,某些查询可能会变得不可能。这可以很快失控。

Netflix指标被标记(又称维度)。每个指标都有一个名称,但是这个单一的命名度量可以包含多个统计信息和“标签”键/值对,这允许更多的查询灵活性。实际上统计本身就是记录在一个特殊的标签上。

使用Netflix Servo或Spectator记录,上述根端点的计时器包含每个状态码的4个统计信息,其中计数统计信息与Spring Boot Actuator的计数器相同。如果到目前为止,我们遇到了HTTP 200和400,将有8个可用数据点:

{
    "root(status=200,stastic=count)": 20,
    "root(status=200,stastic=max)": 0.7265630630000001,
    "root(status=200,stastic=totalOfSquares)": 0.04759702862580789,
    "root(status=200,stastic=totalTime)": 0.2093076914666667,
    "root(status=400,stastic=count)": 1,
    "root(status=400,stastic=max)": 0,
    "root(status=400,stastic=totalOfSquares)": 0,
    "root(status=400,stastic=totalTime)": 0,
}

默认度量集合

没有任何附加依赖或配置,基于Spring Cloud的服务将自动配置Servo MonitorRegistry,并开始收集每个Spring MVC请求的指标。默认情况下,将为每个MVC请求记录名称为rest的Servo定时器,标记为:

  1. HTTP方法

  2. HTTP状态(例如200,400,500)

  3. URI(如果URI为空,则为“root”),为Atlas

  4. 异常类名称,如果请求处理程序抛出异常

  5. 如果在请求上设置了匹配netflix.metrics.rest.callerHeader的密钥的请求头,则呼叫者。netflix.metrics.rest.callerHeader没有默认键。如果您希望收集来电者信息,则必须将其添加到应用程序属性中。

设置netflix.metrics.rest.metricName属性将度量标准的名称从rest更改为您提供的名称。

如果启用了Spring AOP,并且您的运行时类路径上存在org.aspectj:aspectjweaver,则Spring Cloud还将收集每个使用RestTemplate进行的客户端调用的指标。将为每个MVC请求记录名称为restclient的Servo定时器,其标记为:

  1. HTTP方法

  2. HTTP状态(例如200,400,500),如果响应返回为空,则为“CLIENT_ERROR”,否则在执行RestTemplate方法时发生IOException时为“IO_ERROR”

  3. URI,为Atlas

  4. 客户名称

警告
避免在RestTemplate内使用硬编码的url参数。定位动态端点时使用URL变量。这将避免潜在的“GC覆盖上限限制”问题,其中ServoMonitorCache将每个网址视为唯一密钥。
// recommended
String orderid = "1";
restTemplate.getForObject("http://testeurekabrixtonclient/orders/{orderid}", String.class, orderid)

// avoid
restTemplate.getForObject("http://testeurekabrixtonclient/orders/1", String.class)

指标集:Spectator

要启用Spectator指标,请在spring-boot-starter-spectator上添加依赖关系:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-spectator</artifactId>
    </dependency>

在Spectator说明中,仪表是一个命名,键入和标记的配置,而指标表示给定仪表在某个时间点的值。Spectator米由注册表创建和控制,注册表目前有几个不同的实现。Spectator提供4米类型:计数器,定时器,量规和分配摘要。

Spring Cloud Spectator集成为您配置可注入的com.netflix.spectator.api.Registry实例。具体来说,它配置一个ServoRegistry实例,以统一REST度量标准的集合,并在Servo API之前将指标导出到Atlas后端。实际上,这意味着您的代码可能会使用Servo显示器和Spectator米的混合,两者都将被Spring Boot Actuator MetricReader实例舀起,并将两者都发送到Atlas后端。

Spectator计数器

计数器用于测量某些事件发生的速率。

// create a counter with a name and a set of tags
Counter counter = registry.counter("counterName", "tagKey1", "tagValue1", ...);
counter.increment(); // increment when an event occurs
counter.increment(10); // increment by a discrete amount

计数器记录单个时间归一化统计量。

Spectator计时器

一个计时器用于测量一些事件需要多长时间。Spring Cloud自动记录Spring MVC请求和有条件RestTemplate请求的计时器,稍后可用于创建与请求相关的指标(如延迟)的仪表板:

RequestLatency
图4.请求延迟
// create a timer with a name and a set of tags
Timer timer = registry.timer("timerName", "tagKey1", "tagValue1", ...);

// execute an operation and time it at the same time
T result = timer.record(() -> fooReturnsT());

// alternatively, if you must manually record the time
Long start = System.nanoTime();
T result = fooReturnsT();
timer.record(System.nanoTime() - start, TimeUnit.NANOSECONDS);

计时器同时记录4个统计信息:count,max,totalOfSquares和totalTime。如果您在每次记录时间时在计数器上调用了increment()一次,计数统计量将始终与计数器提供的单个归一化值相匹配,因此,对于单个操作,不需要单独计数和计时。

对于长时间运行的操作,Spectator提供了一个特殊的LongTaskTimer

Spectator量规

量规用于确定一些当前值,如队列的大小或处于运行状态的线程数。由于仪表被采样,它们不提供关于这些值在样品之间如何波动的信息。

仪器的正常使用包括在初始化中使用标识符注册仪表,对要采样的对象的引用,以及基于对象获取或计算数值的功能。对对象的引用被单独传递,并且Spectator注册表将保持对该对象的弱引用。如果对象被垃圾回收,则Spectator将自动删除注册。注释 Spectator是关于潜在的内存泄漏的文件中,如果这个API被滥用。

// the registry will automatically sample this gauge periodically
registry.gauge("gaugeName", pool, Pool::numberOfRunningThreads);

// manually sample a value in code at periodic intervals -- last resort!
registry.gauge("gaugeName", Arrays.asList("tagKey1", "tagValue1", ...), 1000);

Spectator分发摘要

分发摘要用于跟踪事件的分布情况。它类似于一个计时器,但更普遍的是,大小不一定是一段时间。例如,分发摘要可用于测量服务器的请求的有效载荷大小。

// the registry will automatically sample this gauge periodically
DistributionSummary ds = registry.distributionSummary("dsName", "tagKey1", "tagValue1", ...);
ds.record(request.sizeInBytes());

指标集:Servo

警告
如果您的代码在Java 8上编译,请使用Spectator而不是Servo,因为Spectator注定要从长远来完全替换Servo。

在Servo语言中,监视器是一个命名,键入和标记的配置,而指标表示给定监视器在某个时间点的值。Servo监视器在逻辑上等同于Spectator米。Servo监视器由MonitorRegistry创建和控制。尽管有上述警告,Servo确实具有比Spectator有米更多的监视器选项。

Spring Cloud集成为您配置可注入的com.netflix.servo.MonitorRegistry实例。一旦您在Servo中创建了相应的Monitor类型,记录数据的过程完全类似于Spectator。

创建Servo显示器

如果您正在使用由Spring Cloud提供的Servo MonitorRegistry实例(具体来说是DefaultMonitorRegistry的实例),则Servo为检索计数器计时器提供便利类这些便利类确保每个唯一的名称和标签组合只注册一个Monitor

要在Servo中手动创建监视器类型,特别是对于不提供方便方法的异域监视器类型,通过提供MonitorConfig实例来实例化适当的类型:

MonitorConfig config = MonitorConfig.builder("timerName").withTag("tagKey1", "tagValue1").build();

// somewhere we should cache this Monitor by MonitorConfig
Timer timer = new BasicTimer(config);
monitorRegistry.register(timer);

指标后端:Atlas

Netflix开发了Atlas,用于管理维度时间序列数据,实现近实时操作洞察。Atlas具有内存数据存储功能,可以非常快速地收集和报告大量的指标。

Atlas捕获操作情报。而商业智能是收集的数据,用于分析一段时间内的趋势,操作情报提供了系统中目前发生的情况。

Spring Cloud提供了一个spring-cloud-starter-atlas,它具有您需要的所有依赖关系。然后使用@EnableAtlas注释您的Spring Boot应用程序,并为运行中的Atlas服务器提供netflix.atlas.uri属性的位置。

全球标签

Spring Cloud可让您为发送到Atlas后端的每个指标添加标签。全局标签可用于按应用程序名称,环境,区域等分隔度量。

实现AtlasTagProvider的每个bean将贡献全局标签列表:

@Bean
AtlasTagProvider atlasCommonTags(
    @Value("${spring.application.name}") String appName) {
  return () -> Collections.singletonMap("app", appName);
}

使用Atlas

要引导内存独立的Atlas实例:

$ curl -LO https://github.com/Netflix/atlas/releases/download/v1.4.2/atlas-1.4.2-standalone.jar
$ java -jar atlas-1.4.2-standalone.jar
提示
在r3.2xlarge(61GB RAM)上运行的Atlas独立节点可以在给定的6小时窗口中处理每分钟大约200万个度量。

一旦运行,您收集了少量指标,请通过在Atlas服务器上列出代码来验证您的设置是否正确:

$ curl http://ATLAS/api/v1/tags
提示
在针对您的服务执行多个请求后,您可以通过在浏览器中粘贴以下URL来收集关于每个请求的请求延迟的一些非常基本的信息:http://ATLAS/api/v1/graph?q=name,rest,:eq,:avg

Atlas wiki包含各种场景样本查询的汇编。

确保使用双指数平滑来查看警报原理和文档,以生成动态警报阈值。

重试失败的请求

Spring Cloud Netflix提供了多种方式来进行HTTP请求。您可以使用负载平衡RestTemplate,Ribbon或Feign。无论您如何选择HTTP请求,始终有可能失败的请求。当请求失败时,您可能希望自动重试该请求。要在使用Sping Cloud Netflix时完成此操作,您需要在应用程序的类路径中包含 Spring Retry当Spring Retry存在负载均衡RestTemplates时,Feign和Zuul将自动重试任何失败的请求(假设配置允许)。

组态

随着Spring Retry使用Ribbon,您可以通过配置某些Ribbon属性来控制重试功能。您可以使用的属性是client.ribbon.MaxAutoRetriesclient.ribbon.MaxAutoRetriesNextServerclient.ribbon.OkToRetryOnAllOperations请参阅Ribbon文档 ,了解属性的具体内容。

此外,您可能希望在响应中返回某些状态代码时重试请求。您可以列出您希望Ribbon客户端使用属性clientName.ribbon.retryableStatusCodes重试的响应代码。例如

clientName:
  ribbon:
    retryableStatusCodes: 404,502

您还可以创建一个类型为LoadBalancedRetryPolicy的bean,并实现retryableStatusCode方法来确定是否要重试发出状态代码的请求。

Zuul

您可以通过将zuul.retryable设置为false来关闭Zuul的重试功能。您还可以通过将zuul.routes.routename.retryable设置为false,以路由方式禁用重试功能。