# Spring Cloud 总线

Spring Cloud总线将分布式系统的节点与轻量级消息代理连接起来。然后可以使用此代理来广播状态更改（例如配置更改）或其他管理指令。一个关键的想法是，总线就像是 Spring 启动应用程序的分布式执行器，该应用程序是按比例扩展的。然而，它也可以用作应用程序之间的沟通渠道。该项目为 AMQP 代理或 Kafka 提供了作为传输的启动器。

|   |Spring Cloud是在非限制性的 Apache2.0 许可下发布的。如果你想对文档的这一部分做出贡献，或者你发现了一个错误，请在[github](https://github.com/spring-cloud/spring-cloud-bus)的项目中找到源代码和问题追踪器。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## 1.快速启动

Spring Cloud总线的工作原理是，如果在 Classpath 上检测到自身，则添加 Spring 引导自动配置。要启用总线，请将`spring-cloud-starter-bus-amqp`或`spring-cloud-starter-bus-kafka`添加到依赖项管理中。 Spring 剩下的事由云来解决。确保代理（RabbitMQ 或 Kafka）可用并进行了配置。在 LocalHost 上运行时，你不需要做任何事情。如果远程运行，请使用 Spring Cloud Connectors 或 Spring Boot 约定来定义代理凭据，如下面的 Rabbit 示例所示：

应用程序.yml

```
spring:
  rabbitmq:
    host: mybroker.com
    port: 5672
    username: user
    password: secret
```

总线目前支持将消息发送到监听的所有节点或特定服务的所有节点（由 Eureka 定义）。执行器名称空间`/bus/*`具有一些 HTTP 端点。目前，有两个项目已经实施。第一种是`/bus/env`，它发送键/值对来更新每个节点的 Spring 环境。第二种是`/bus/refresh`，重新加载每个应用程序的配置，就好像它们都在其`/refresh`端点上被 pinged 了一样。

|   |Spring Cloud总线启动器覆盖 Rabbit 和 Kafka，因为这是两个最<br/>的常见实现。然而， Spring Cloud流是相当灵活的，并且活页夹<br/>与`spring-cloud-bus`一起工作。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## 2.总线端点

Spring Cloud Bus 提供了两个端点，`/actuator/busrefresh`和`/actuator/busenv`，它们分别对应于 Spring Cloud Commons 中的单个执行器端点，`/actuator/refresh`和`/actuator/env`。

### 2.1.总线刷新端点

`/actuator/busrefresh`端点清除`RefreshScope`缓存并重新绑定`@ConfigurationProperties`。有关更多信息，请参见[刷新范围](#refresh-scope)文档。

要公开`/actuator/busrefresh`端点，需要向应用程序添加以下配置：

```
management.endpoints.web.exposure.include=busrefresh
```

### 2.2.总线 ENV 端点

`/actuator/busenv`端点使用跨多个实例的指定键/值对更新每个实例环境。

要公开`/actuator/busenv`端点，需要向应用程序添加以下配置：

```
management.endpoints.web.exposure.include=busenv
```

`/actuator/busenv`端点接受具有以下形状的`POST`请求：

```
{
    "name": "key1",
    "value": "value1"
}
```

## 3.寻址实例

应用程序的每个实例都有一个服务 ID，其值可以用`spring.cloud.bus.id`设置，其值应该是一个以冒号分隔的标识符列表，顺序从最不特定到最特定。默认值是作为`spring.application.name`和`server.port`（或`spring.application.index`，如果设置）的组合从环境构造的。ID 的默认值以`app:index:id`的形式构造，其中：

* `app`是`vcap.application.name`，如果它存在，或者`spring.application.name`

* `index`是`vcap.application.instance_index`，如果存在，`spring.application.index`，`local.server.port`，`server.port`，或`0`（按此顺序排列）。

* `id`是`vcap.application.instance_id`，如果它存在，或者是一个随机值。

HTTP 端点接受一个“destination”路径参数，例如`/busrefresh/customers:9000`，其中`destination`是一个服务 ID。如果 ID 由总线上的一个实例拥有，那么它将处理消息，而所有其他实例将忽略它。

## 4.处理服务的所有实例

在 Spring `PathMatcher`（路径分隔符为冒号—`:`）中使用“destination”参数来确定实例是否处理消息。使用前面的示例，`/busenv/customers:**`以“客户”服务的所有实例为目标，而不考虑服务 ID 的其余部分。

## 5.服务 ID 必须是唯一的

总线尝试两次消除处理一个事件——一次来自原始`ApplicationEvent`，一次来自队列。为此，它会根据当前的服务 ID 检查发送服务 ID。如果一个服务的多个实例具有相同的 ID，则不会对事件进行处理。当在本地机器上运行时，每个服务都位于不同的端口上，而该端口是 ID 的一部分。Cloud Foundry 提供了一个用于区分的索引。要确保 ID 在 Cloud Foundry 之外是唯一的，请将`spring.application.index`设置为服务的每个实例都是唯一的。

## 6.自定义消息代理

Spring Cloud总线使用[Spring Cloud Stream](https://cloud.spring.io/spring-cloud-stream)来广播消息。因此，要使消息流起来，你只需要在 Classpath 中包含你选择的绑定器实现。对于具有 AMQP 和 Kafka（`spring-cloud-starter-bus-[amqp|kafka]`）的总线，有方便的启动器。一般来说， Spring Cloud Stream 依赖于 Spring Boot AutoConfiguration 约定来配置中间件。例如，可以使用`spring.rabbitmq.*`配置属性更改 AMQP 代理地址。 Spring Cloud Bus 在`spring.cloud.bus.*`中具有少量的本机配置属性（例如，`spring.cloud.bus.destination`是用作外部中间件的主题的名称）。通常情况下，默认值就足够了。

要了解有关如何自定义消息代理设置的更多信息，请参阅 Spring Cloud Stream 文档。

## 7.追踪总线事件

总线事件（`RemoteApplicationEvent`的子类）可以通过设置`spring.cloud.bus.trace.enabled=true`来跟踪。如果这样做， Spring boot`TraceRepository`（如果存在）将显示发送的每个事件和来自每个服务实例的所有 ACK。以下示例来自`/trace`端点：

```
{
  "timestamp": "2015-11-26T10:24:44.411+0000",
  "info": {
    "signal": "spring.cloud.bus.ack",
    "type": "RefreshRemoteApplicationEvent",
    "id": "c4d374b7-58ea-4928-a312-31984def293b",
    "origin": "stores:8081",
    "destination": "*:**"
  }
  },
  {
  "timestamp": "2015-11-26T10:24:41.864+0000",
  "info": {
    "signal": "spring.cloud.bus.sent",
    "type": "RefreshRemoteApplicationEvent",
    "id": "c4d374b7-58ea-4928-a312-31984def293b",
    "origin": "customers:9000",
    "destination": "*:**"
  }
  },
  {
  "timestamp": "2015-11-26T10:24:41.862+0000",
  "info": {
    "signal": "spring.cloud.bus.ack",
    "type": "RefreshRemoteApplicationEvent",
    "id": "c4d374b7-58ea-4928-a312-31984def293b",
    "origin": "customers:9000",
    "destination": "*:**"
  }
}
```

前面的跟踪显示，一个`RefreshRemoteApplicationEvent`从`customers:9000`发送，广播到所有服务，并由`customers:9000`和`stores:8081`接收。

要自己处理 ACK 信号，可以在应用程序中添加`@EventListener`和`SentApplicationEvent`类型的`@EventListener`（并启用跟踪）。或者，你可以利用`TraceRepository`并从那里挖掘数据。

|   |任何总线应用程序都可以跟踪 ACK。然而，有时，在可以对数据执行更复杂的<br/>查询或将其转发给专门的跟踪服务的中心服务中执行此操作是很有用的。|
|---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## 8.播放自己的活动

总线可以承载类型`RemoteApplicationEvent`的任何事件。默认传输是 JSON，反序列化器需要提前知道要使用哪些类型。要注册一个新类型，必须将其放入`org.springframework.cloud.bus.event`的子包中。

要自定义事件名，你可以在自定义类上使用`@JsonTypeName`，也可以使用默认策略，即使用类的简单名称。

|   |生产者和消费者都需要访问类定义。|
|---|-----------------------------------------------------------------------|

### 8.1.在自定义包中注册事件

如果你不能或不想使用`org.springframework.cloud.bus.event`的子包来表示你的自定义事件，那么你必须指定要使用`RemoteApplicationEvent`注释来扫描类型为`@RemoteApplicationEventScan`的事件的包。用`@RemoteApplicationEventScan`指定的包包括子包。

例如，考虑以下自定义事件，称为`MyEvent`:

```
package com.acme;

public class MyEvent extends RemoteApplicationEvent {
    ...
}
```

你可以通过以下方式向反序列化器注册该事件：

```
package com.acme;

@Configuration
@RemoteApplicationEventScan
public class BusConfiguration {
    ...
}
```

在不指定值的情况下，注册了使用`@RemoteApplicationEventScan`的类的包。在本例中，`com.acme`通过使用`BusConfiguration`的包进行注册。

还可以在`@RemoteApplicationEventScan`上使用`value`、`basePackages`或`basePackageClasses`属性显式地指定要扫描的包，如以下示例所示：

```
package com.acme;

@Configuration
//@RemoteApplicationEventScan({"com.acme", "foo.bar"})
//@RemoteApplicationEventScan(basePackages = {"com.acme", "foo.bar", "fizz.buzz"})
@RemoteApplicationEventScan(basePackageClasses = BusConfiguration.class)
public class BusConfiguration {
    ...
}
```

前面的`@RemoteApplicationEventScan`的所有示例都是等效的，因为`com.acme`包是通过在`@RemoteApplicationEventScan`上显式指定包来注册的。

|   |你可以指定要扫描的多个基包。|
|---|-----------------------------------------------|

## 9.配置属性

要查看所有与总线相关的配置属性的列表，请检查[附录页](appendix.html)。
