快人一步
VMware 提供培训和认证,助您快速进步。
了解更多微服务架构风格与其说是构建独立的各个服务,不如说是让服务之间的交互变得可靠和容错。虽然对这些交互的关注是新的,但对其的需求并不是。我们早就知道服务不是在真空中运行的。即使在云经济之前,我们就知道——在实际世界中——客户端应该设计成能够抵御服务中断。云使得将容量视为短暂、流动的变得容易。管理这种内在复杂性的负担落在了客户端身上。
在这篇文章中,我们将探讨 Spring Cloud 如何通过像 Eureka 和 Consul 这样的服务注册中心以及客户端负载均衡来帮助您管理这种复杂性。
服务注册中心就像您微服务的电话簿。每个服务都向服务注册中心注册自己,并告诉注册中心它在哪里(主机、端口、节点名称),或许还有其他服务特定的元数据——其他服务可以使用这些信息来做出明智的决策。客户端可以询问服务拓扑结构的问题(“是否有任何可用的 '履行服务',如果有,它们在哪里?”)和服务能力的问题(“你能处理 X、Y 和 Z 吗?”)。您可能已经在使用某种具有集群概念的技术(Cassandra、Memcached 等),并且这些信息最好存储在服务注册中心。
有几种流行的服务注册中心选项。Netflix 构建并开源了他们自己的服务注册中心 Eureka。另一个新的、但越来越受欢迎的选项是 Consul。我们将主要关注 Spring Cloud 和 Netflix Eureka 服务注册中心之间的一些集成。
引自 Spring Cloud 项目页面:“Spring Cloud 为开发人员提供了工具,用于快速构建分布式系统中常见的一些模式(例如,配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导者选举、分布式会话、集群状态)。协调分布式系统会导致大量样板代码,而使用 Spring Cloud,开发人员可以快速搭建实现这些模式的服务和应用程序。它们将在任何分布式环境中运行良好,包括开发人员自己的笔记本电脑、裸金属数据中心以及 Cloud Foundry 等托管平台。”
Spring Cloud 已经支持 Eureka 和 Consul,尽管在这篇文章中我将重点介绍 Eureka,因为它可以在 Spring Cloud 的一个自动配置中自动启动。Eureka 是用 JVM 实现的,而 Consul 是用 Go 实现的。
如果您的类路径中有 org.springframework.boot:spring-cloud-starter-eureka-server
,那么启动一个 Eureka 服务注册中心实例非常容易。
package registry;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
我的名义上的 src/main/resources/application.yml
现在看起来像这样。
server:
port: ${PORT:8761}
eureka:
client:
registerWithEureka: false
fetchRegistry: false
server:
waitTimeInMsWhenSyncEmpty: 0
如果 Cloud Foundry 的 VCAP_APPLICATION_PORT
环境变量不可用,服务的端口默认设置为周知端口 8761。其余配置只是告诉这个实例不要向它找到的 Eureka 实例注册自己,因为那个实例就是... 它自己。如果您在本地运行它,可以将浏览器指向 http://localhost:8761
并从那里监控注册中心。
Spring Cloud 将通过其 Spring Boot 自动配置启动一个 Eureka 实例。在部署 Eureka 时需要考虑几个问题。首先,在生产环境中应该始终使用高可用配置。Spring Cloud Eureka 示例展示了如何以高可用配置进行部署。
客户端需要知道在哪里找到 Eureka 实例。如果您有 DNS,这可能是一个选项,前提是您没有污染一个太大的全局命名空间。如果您在平台即服务(PaaS)上运行并采用 12 Factor App 风格的应用,那么后端服务凭据是配置,并且存在于应用程序外部,通常暴露为环境变量。不过,您现在可以通过使用 Cloud Foundry 的 cf
CLI 创建一个用户提供的服务来获得一个 Eureka 服务的效果。
cf cups eureka-service -p '{"uri":"http://host-of-your-eureka-setup"}'
将 host-of-your-eureka-setup
指向您的 Eureka 高可用设置的一个周知主机。我猜我们很快就会看到一种将 Eureka 创建为后端服务的方式,就像您在 Pivotal Cloud Foundry 上创建 PostgreSQL 或 ElasticSearch 实例一样。
现在 Eureka 已经启动并运行,让我们用它来连接一些服务吧!
基于 Spring Cloud 的服务有一个 spring.application.name
属性。它用于从配置服务器拉取配置,用于向 Eureka 识别服务,并且在构建基于 Spring Cloud 的应用程序时可以在许多其他上下文中使用。这个值通常位于 src/main/resources/bootstrap.(yml,properties)
中,它比普通的 src/main/resources/application.(yml,properties)
更早地在初始化过程中被加载。类路径中包含 org.springframework.cloud:spring-cloud-starter-eureka
的服务将使用其 spring.application.name
向 Eureka 注册中心注册。
我的每个服务的 src/main/resources/boostrap.yml
文件看起来像这样,其中 my-service
是随服务而变化的服务名称
spring:
application:
name: my-service
Spring Cloud 在服务启动时使用 bootstrap.yml
中的信息来发现 Eureka 服务注册中心并注册服务及其 spring.application.name
、主机、端口等。您可能会对第一部分感到疑惑。Spring Cloud 尝试在一个周知地址 (http://127.0.0.1:
) 上查找它,但是您可以更改这个地址。这里是我名义上的 Spring Cloud 微服务的 src/main/resources/application.yml
,尽管这没有理由不能存在于 Spring Cloud 配置服务器中。可能有许多实例将自己标识为 my-service
;Eureka 会将该进程的信息添加到具有相同 ID 的注册列表。
eureka:
client:
serviceUrl:
defaultZone: ${vcap.services.eureka-service.credentials.uri:http://127.0.0.1:8761}/eureka/
---
spring:
profiles: cloud
eureka:
instance:
hostname: ${APPLICATION_DOMAIN}
nonSecurePort: 80
在此配置中,Spring Cloud Eureka 客户端知道如果 Cloud Foundry 的 VCAP_SERVICES
环境变量不存在或不包含有效凭据,则连接到在 localhost 上运行的 Eureka 实例。
---
分隔符下的配置部分是当应用程序在 cloud
Spring Profile 下运行时使用的。使用 SPRING_PROFILES_ACTIVE
环境变量可以轻松设置 Profile。您可以在 manifest.yml
中配置 Cloud Foundry 环境变量,或者在 Cloud Foundry Lattice 上,配置您的 Docker 文件。
cloud
profile 特定的配置明确告诉 Eureka 客户端如何在发现的 Eureka 注册中心注册服务。我这样做是因为我的服务不使用固定的 DNS。APPLICATION_DOMAIN
是我在部署脚本中设置的环境变量,它告诉服务其外部可引用的 URI 是什么。
在 30 秒后(截至本文撰写时),点击 Eureka web UI 上的刷新按钮,您就会看到您的 web 服务已注册。
Spring Cloud 通过服务的 spring.application.name
值引用其他服务。在构建基于 Spring Cloud 的服务时,了解这个值在很多情境下都很有用。
您会记得,目标是让客户端根据上下文信息(这些信息可能因客户端而异)来决定它将连接到哪个服务实例。Netflix 有一个感知 Eureka 的客户端负载均衡客户端,名为 Ribbon,Spring Cloud 对其进行了深度集成。Ribbon 是一个内置软件负载均衡器的客户端库。让我们看一个直接使用 Eureka,然后通过 Ribbon 和 Spring Cloud 集成使用它的例子。
package passport;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.web(false)
.run(args);
}
}
@Component
class DiscoveryClientExample implements CommandLineRunner {
@Autowired
private DiscoveryClient discoveryClient;
@Override
public void run(String... strings) throws Exception {
discoveryClient.getInstances("photo-service").forEach((ServiceInstance s) -> {
System.out.println(ToStringBuilder.reflectionToString(s));
});
discoveryClient.getInstances("bookmark-service").forEach((ServiceInstance s) -> {
System.out.println(ToStringBuilder.reflectionToString(s));
});
}
}
@Component
class RestTemplateExample implements CommandLineRunner {
@Autowired
private RestTemplate restTemplate;
@Override
public void run(String... strings) throws Exception {
// use the "smart" Eureka-aware RestTemplate
ResponseEntity<List<Bookmark>> exchange =
this.restTemplate.exchange(
"http://bookmark-service/{userId}/bookmarks",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Bookmark>>() {
},
(Object) "mstine");
exchange.getBody().forEach(System.out::println);
}
}
@Component
class FeignExample implements CommandLineRunner {
@Autowired
private BookmarkClient bookmarkClient;
@Override
public void run(String... strings) throws Exception {
this.bookmarkClient.getBookmarks("jlong").forEach(System.out::println);
}
}
@FeignClient("bookmark-service")
interface BookmarkClient {
@RequestMapping(method = RequestMethod.GET, value = "/{userId}/bookmarks")
List<Bookmark> getBookmarks(@PathVariable("userId") String userId);
}
class Bookmark {
private Long id;
private String href, label, description, userId;
@Override
public String toString() {
return "Bookmark{" +
"id=" + id +
", href='" + href + '\'' +
", label='" + label + '\'' +
", description='" + description + '\'' +
", userId='" + userId + '\'' +
'}';
}
public Bookmark() {
}
public Long getId() {
return id;
}
public String getHref() {
return href;
}
public String getLabel() {
return label;
}
public String getDescription() {
return description;
}
public String getUserId() {
return userId;
}
}
DiscoveryClientExample
bean 演示了如何使用 Spring Cloud 通用 DiscoveryClient
来查询服务。结果包含每个服务的主机名和端口等信息。
RestTemplateExample
bean 演示了自动配置的感知 Ribbon 的 RestTemplate
实例。请注意,URI 使用的是服务 ID,而不是实际的主机名。URI 中的服务 ID 被提取并传递给 Ribbon,然后 Ribbon 使用负载均衡器从 Eureka 中已注册的实例中选择一个,最后向一个真实的服务实例发起 HTTP 调用。
FeignExample
bean 演示了使用 Spring Cloud Feign 集成。 Feign 是 Netflix 的一个方便的项目,它允许您通过接口上的注解以声明方式描述 REST API 客户端。在这种情况下,我们希望将对 bookmark-service
的调用产生的 HTTP 结果映射到 BookmarkClient
Java 接口。这个映射在代码页顶部的 Application
类中配置。
@Bean
BookmarkClient bookmarkClient() {
return loadBalance(BookmarkClient.class, "http://bookmark-service");
}
URI 是服务引用,而不是实际的主机名。它经过与上一个示例中提供给 RestTemplate
的 URI 相同的处理过程。
相当酷吧?您可以使用更基本的 DiscoveryClient
API 发起调用,或者使用感知 Ribbon 和 Eureka 的 RestTemplate
或 Feign 集成客户端。
DiscoveryClient
API 根据服务 ID 交互式查询 Eureka。RestTemplate
可以在 URI 中用服务 ID 替代主机名,并可以委托 Ribbon 选择服务。我们只看了 Eureka 的服务发现和解析。我们在这里讨论的大多数内容也适用于 Consul,而且 Consul 确实有一些 Netflix 没有的功能。
轮询负载均衡只是一个选项。您可能需要某种领导节点的概念,以及领导者选举。Spring Cloud 也旨在提供对这类协调的支持。
服务注册和客户端负载均衡只是 Spring Cloud 为促进更具弹性的服务间调用所做的其中一件事情。我们还没有讨论它对单点登录和安全、分布式锁和领导者选举、像断路器这样的可靠性模式以及更多方面的支持。
示例代码都可以在线获取,所以不要犹豫,在您的本地机器上查看示例,或者使用提供的 cf.sh
脚本和各种 manifest.yml
文件将其推送到 Cloud Foundry。