最初由 Paul Chapman 为 Spring 博客撰写
微服务
允许从多个协作组件构建大型系统。它在进程级别上所做的事情正是 Spring 在组件级别上所做的:松散耦合的进程而不是松散耦合的组件。
例如,想象一个在线商店为用户帐户、产品目录订单处理和购物车提供单独的微服务:
不可避免地,您必须设置和配置许多移动部件才能构建这样的系统。如何让它们协同工作并不明显——您需要非常熟悉 Spring Boot,因为 Spring Cloud 大量利用它,需要多个 Netflix 或其他 OSS 项目,当然,还有一些 Spring 配置“魔法”!
在本文中,我旨在通过逐步构建尽可能简单的系统来阐明事物的工作原理。因此,我将只实现大系统的一小部分——用户账户服务。
Web 应用程序 将使用 RESTful API 向 帐户服务 微服务发出请求。我们还需要添加一个 发现 服务——这样其他进程就可以找到彼此。
此应用程序的代码位于:https: //github.com/paulc4/microservices-demo 。
后续1:其他资源
本文只讨论一个最小的系统。有关更多信息,您可能想阅读 Josh Long 的博客文章 使用 Spring Cloud 和 Netflix 的 Eureka 进行微服务注册和发现 ,其中展示了在 Cloud Foundry 上运行完整的微服务系统。
Spring Cloud 项目在 这里 。
跟进 2:SpringOne 2GX 2015
尽快在华盛顿特区的 SpringOne2GX 预订您的位置——这是了解第一手所有活动并提供直接反馈的最佳机会。在 Spring Cloud 和 Cloud Native 应用程序上会有一个完整的轨道。
好的,让我们开始吧……
服务注册
当您有多个进程一起工作时,它们需要找到彼此。如果您曾经使用过 Java 的 RMI 机制,您可能还记得它依赖于中央注册表,以便 RMI 进程可以找到彼此。微服务有相同的要求。
Netflix 的开发人员在构建系统时遇到了这个问题,并创建了一个名为 Eureka 的注册服务器(希腊语“我找到了”)。对我们来说幸运的是,他们将他们的发现服务器开源,并且 Spring 已合并到 Spring Cloud 中,这使得运行 Eureka 服务器变得更加容易。这是 完整的 应用程序:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
真的就是这么简单!
Spring Cloud 构建于 Spring Boot 之上,并利用父 POM 和启动 POM。 POM 的重要部分是:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
注意: Angel.SR3 是当前的“发布序列”——一组协调的发布——请参阅 Spring Cloud 主页 上的注释(向下滚动到第二部分)。
默认情况下,Spring Boot 应用程序会查找
application.properties
或
application.yml
文件进行配置。通过设置
spring.config.name
属性,我们可以告诉 Spring Boot 寻找不同的文件——如果你在同一个项目中有多个 Spring Boot 应用程序,这很有用——我很快就会这样做。
此应用程序查找
registration-server.properties
或
registration-server.yml
。这是
registration-server.yml
的相关配置:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
默认情况下,Eureka 在端口 8761 上运行,但在这里我们将使用端口
1111
。此外,通过在我的流程中包含注册码,我可能是服务器或客户端。配置指定我不是客户端并停止尝试向自身注册的服务器进程。
使用领事
Spring Cloud 还支持 Consul 作为 Eureka 的替代品。您使用脚本启动 Consul Agent(其注册服务器),然后客户端使用它来查找他们的微服务。详情见这篇博 文 或项目 主页 。
现在尝试运行 RegistrationServer (有关运行应用程序的帮助,请参见 参考资料)。您可以在此处打开 Eureka 仪表板: http://localhost:1111 ,显示应用程序的部分将为空。
从现在开始,我们将提及 发现服务器 ,因为它可能是 Eureka 或 Consul(请参阅侧面板)。
创建微服务: 账户服务
微服务是处理定义明确的需求的独立流程。
当使用 Spring 配置应用程序时,我们强调松散耦合和紧密内聚,这些不是新概念(Larry Constantine 在 1960 年代后期首先定义了这些 - 参考资料 )但现在我们正在应用它们,而不是交互组件(Spring Beans),但对于交互过程。
在此示例中,我有一个简单的帐户管理微服务,它使用 Spring Data 实现 JPA
AccountRepository
和 Spring REST 以提供帐户信息的 RESTful 接口。在大多数方面,这是一个简单的 Spring Boot 应用程序。
它的特别之处在于它会在启动时向 发现服务器 注册自己。下面是Spring Boot的启动类:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
注释完成以下工作:
-
@EnableAutoConfiguration
- 将其定义为 Spring Boot 应用程序。 -
@EnableDiscoveryClient
- 这将启用服务注册和发现。在这种情况下,此进程使用其应用程序名称(见下文)向 发现服务器 服务注册自己。 -
@Import(AccountsWebApplication.class)
- 这个 Java Configuration 类设置了其他所有内容(有关更多详细信息,请参见 )。
使它成为微服务的原因是通过
@EnableDiscoveryClient
向
发现服务器
注册,其 YML 配置完成设置:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
注意这个文件
-
将应用程序名称设置为
accounts-service
。该服务以该名称注册,也可以通过该名称访问 - 见下文。 - 指定要侦听的自定义端口 (2222)。我所有的进程都在使用 Tomcat,它们不能全部监听 8080 端口。
- Eureka 服务进程的 URL - 来自上一节。
现在运行 AccountsService 应用程序并让它完成初始化。刷新仪表板 http://localhost:1111 ,您应该会看到 ACCOUNTS-SERVICE 列在应用程序下。有时注册可能需要 10-20 秒,所以请耐心等待 - 检查 RegistrationService 的日志输出
警告: 不要尝试使用 Eclipse/STS 的内部 Web 查看器显示 XML 输出,因为它不能这样做。请改用您最喜欢的网络浏览器。
有关更多详细信息,请转到此处: http://localhost:1111/eureka/apps/ ,您应该会看到如下内容:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
或者转到 http://localhost:1111/eureka/apps/ACCOUNTS-SERVICE 并查看 AccountsService 的详细信息 - 如果它未注册,您将获得 404。
访问微服务: Web 服务
为了使用 RESTful 服务,Spring 提供了
RestTemplate
类。这允许您将 HTTP 请求发送到 RESTful 服务器并获取多种格式的数据 - 例如 JSON 和 XML。
注意: Accounts 微服务通过 HTTP 提供 RESTful 接口,但可以使用任何合适的协议。使用 AMQP 或 JMS 的消息传递是一个明显的选择。
可以使用哪些格式取决于类路径上是否存在封送处理类 - 例如 JAXB 始终会被检测到,因为它是 Java 的标准部分。如果类路径中存在 Jackson jars,则支持 JSON。
微服务(发现)客户端可以使用
RestTemplate
,Spring 会自动将其配置为微服务感知(稍后会详细介绍)。
封装微服务访问
这是我的
客户端
应用程序的
WebAccountService
的一部分:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
请注意,我的
WebAccountService
只是 RestTemplate 从微服务获取数据的包装器。有趣的部分是
serviceUrl
和
RestTemplate
。
访问微服务
serviceUrl
由主程序提供给
WebAccountController
,后者又将其传递给
WebAccountService
(如上所示):
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
需要注意的几点:
-
WebController
是典型的 Spring MVC 基于视图的控制器,返回 HTML。该应用程序使用 Thymeleaf 作为视图技术(用于生成动态 HTML) -
WebServer
也是一个@EnableDiscoveryClient
,但在这种情况下,除了向 发现服务器 注册自己(这不是必需的,因为它不提供自己的服务),它还使用 Eureka 来定位帐户服务。 -
从 Spring Boot 继承的默认组件扫描器设置查找
@Component
类,在这种情况下,找到我的WebAccountController
并尝试创建它。但是,我想自己创建它,所以我像这样禁用扫描器@ComponentScan(useDefaultFilters=false)
。 -
我传递给
WebAccountController
的 service-url 是服务用于向 发现服务器 注册自身的名称——默认情况下,这与spring.application.name
相同,该进程是account-service
- 请参阅account-service.yml
上面的account-service.yml
。不需要使用大写字母,但它确实有助于强调 ACCOUNTS-SERVICE 是逻辑主机(将通过发现获得)而不是实际主机。
负载均衡 RestTemplate
RestTemplate 已由 Spring Cloud 自动配置为使用自定义
HttpRequestClient
,该客户端使用 Netflix
Ribbon
进行微服务查找。 Ribbon 也是负载均衡器,因此如果您有多个可用服务实例,它会为您选择一个。 (Eureka 和 Consul 都没有自己执行负载均衡,因此我们使用 Ribbon 来代替)。
如果查看 RibbonClientHttpRequestFactory ,您将看到以下代码:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
loadBalancer
采用逻辑服务名称(在
发现服务器
中注册)并将其转换为所选微服务的实际主机名。
RestTemplate
实例是线程安全的,可用于访问应用程序不同部分中的任意数量的服务(例如,我可能有一个
CustomerService
包装同一个
RestTemplate
实例来访问客户数据微服务)。
配置
下面是来自
web-server.yml
的相关配置。它用于:
- 设置应用名称
- 定义访问发现服务器的 URL
- 将 Tomcat 端口设置为 3333
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
如何运行演示
该系统的一个小演示位于 http://github.com/paulc4/microservices-demo 。克隆它并加载到您最喜欢的 IDE 或直接使用 maven。有关如何运行演示的建议包含在项目主页的 自述文件 中。
附加说明
有关这些应用程序使用 Spring Boot 的一些说明。如果你不熟悉 Spring Boot,这解释了一些“魔法”!
查看模板引擎
Eureka 仪表板(在
RegistrationServer
内部)是使用 FreeMarker 模板实现的,但其他两个应用程序使用 Thymeleaf。为了确保每个都使用正确的视图引擎,每个 YML 文件中都有额外的配置。
这是在
registration-server.yml
的末尾禁用 Thymeleaf。
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
由于
AccountService
和
WebService
都使用 thymeleaf,我们还需要将每个指向它们自己的模板。这是
account-server.yml
的一部分:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
web-server.yml
类似,但其模板由
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
请注意每个
spring.thymeleaf.prefix
类路径末尾的 / - 这是
至关重要的
。
命令行执行
当从命令行调用时,jar 被编译为自动运行
io.pivotal.microservices.services.Main
- 请参阅
Main.java
。
可以在
POM
中看到用于设置
start-class
Spring Boot 选项:
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
AccountsWeb应用程序配置
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
这是 AccountService 的主要配置类,是使用 Spring Data 的经典 Spring Boot 应用程序。注释完成了大部分工作:
-
@SpringBootApplication
- 将其定义为 Spring Boot 应用程序。这个方便的注释结合了@EnableAutoConfiguration
、@Configuration
和@ComponentScan
(默认情况下,这会导致 Spring 在包含此类的包及其子包中搜索组件 - 潜在的 Spring Beans:AccountController
和AccountRepository
)。 -
@EntityScan("io.pivotal.microservices.accounts")
- 因为我使用的是 JPA,所以我需要指定@Entity
类的位置。通常这是您在 JPA 的persistence.xml
中或在创建LocalContainerEntityManagerFactoryBean
时指定的选项。 Spring Boot 将为我创建这个工厂 bean,因为spring-boot-starter-data-jpa
依赖项位于类路径上。因此,另一种指定在哪里可以找到@Entity
类的方法是使用@EntityScan
。这将找到Account
。 -
@EnableJpaRepositories("io.pivotal.microservices.accounts")
- 查找扩展 Spring Data 的Repository
标记接口的类并使用 JPA 自动实现它们 - 请参阅 Spring Data JPA 。 -
@PropertySource("classpath:db-config.properties")
- 用于配置我的DataSource
属性 – 请参阅 db-config.properties 。
请注意, AccountsWebApplication 本身可以作为独立应用程序运行,我发现这对测试很有用。它监听默认的 Tomcat 端口:8080,所以主页是 http://localhost:8080 。
配置属性
如上所述,Spring Boot 应用程序寻找
application.properties
或
application.yml
来配置自己。由于此应用程序中使用的所有三个服务器都在同一个项目中,因此它们会自动使用相同的配置。
为避免这种情况,每个人都通过设置
spring.config.name
属性指定一个替代文件。
例如这里是
WebServer.java
的一部分。
@SpringBootApplication
@EnableEurekaServer
public class ServiceRegistrationServer {
public static void main(String[] args) {
// Tell Boot to look for registration-server.yml
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(ServiceRegistrationServer.class, args);
}
}
在运行时,应用程序将在
src/main/resources
中查找并使用
web-server.yml
。
记录
Spring Boot 默认为 Spring 设置 INFO 级别的日志记录。由于我们需要检查日志以获取微服务正常运行的证据,因此我将级别提高到 WARN 以减少日志记录量。
为此,需要在每个
xxxx-server.yml
配置文件中指定日志记录级别。这通常是定义它们的最佳位置,因为
不能
在属性文件中指定日志记录属性(日志记录在处理 @PropertySource 指令之前已经初始化)。 Spring Boot 手册中对此有注释,但很容易遗漏。
我没有在每个 YAML 文件中复制日志记录配置,而是选择将其放在 logback 配置文件中,因为 Spring Boot 使用 logback - 请参阅
src/main/resources/logback.xml
。所有三个服务将共享相同的
logback.xml
。