微服务架构模式的核心在于如何识别服务的边界,设计出合理的微服务。但如果要将微服务架构运用到生产项目上,并且能够发挥该架构模式的重要作用,则需要微服务框架的支持。
在Java生态圈,目前使用较多的微服务框架就是集成了包括Netfilix OSS以及Spring的Spring Cloud。它包括:
- Spring Cloud Config:配置管理工具,支持使用Git存储配置内容,可以实现应用配置的外部化存储,支持客户端配置信息刷新、加密/解密配置内容等。
- Spring Cloud Netflix:对Netflix OSS进行了整合。其中又包括:
- Eureka:服务治理组件,包含服务注册中心、服务注册与发现。
- Hystrix:容器管理组件,实现断路器模式,倘若依赖的服务出现延迟或故障,则提供强大的容错功能。
- Ribbon:客户端负载均衡的服务调用组件。
- Feign:基于Ribbon和Hystrix的声明式服务调用组件。
- Zuul:网关组件,提供智能路由、访问过滤等功能。
- Archaius:外部化配置组件。
- Spring Cloud Bus:事件、消息总线。
- Spring Cloud Cluster:针对ZooKeeper、Redis、Hazelcast、Consul的选举算法和通用状态模式的实现。
- Spring Cloud Cloudfoundry:与Pivotal Cloudfoundry的整合支持。
- Spring Cloud Consul:服务发现与配置管理工具。
- Spring Cloud Stream:通过Redis、Rabbit或者Kafka实现的消息驱动的微服务。
- Spirng Cloud AWS:简化和整合Amazon Web Service。
- Spring Cloud Security:安全工具包,提供Zuul代理中对OAuth2客户端请求的中继器。
- Spring Cloud Sleuth:Spring Cloud应用的分布式跟踪实现,可以整合Zipkin。
- Spring Cloud ZooKeeper:基于ZooKeeper的服务发现与配置管理组件。
- Spring Cloud Starters:Spring Cloud的基础组件,是基于Spring Boot风格项目的基础依赖模块。
- Spring Cloud CLI:用于在Groovy中快速创建Spring Cloud应用的Spring Boot CLI插件。
服务治理
当一个系统的微服务数量越来越多的时候,我们就需要对服务进行治理,提供统一的服务注册中心,然后在其框架下提供发现服务的功能。这样就避免了对多个微服务的配置,以及微服务之间以及与客户端之间的耦合。
Spring Cloud Eureka是对Netflix Eureka的包装,用以实现服务注册与发现。Eureka服务端即服务注册中心,支持高可用配置。它依托于强一致性提供良好的服务实例可用性,并支持集群模式部署。Eureka客户端则负责处理服务的注册与发现。客户端服务通过annotation与参数配置的方式,嵌入在客户端应用程序代码中。在运行应用程序时,Eureka客户端向注册中心注册自身提供的服务,并周期性地发送心跳更新它的服务租约。
搭建服务注册中心
服务注册中心是一个独立部署的服务(你可以认为它也是一个微服务),所以需要单独为它创建一个项目,并在pom.xml中添加Eureka的依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency>
创建Spring Boot Application:
@EnableEurekaServer @SpringBootApplication public class Application { public static void main(String[] args) { new SpringApplicationBuilder(Application.class).web(true).run(args); } }
注册服务提供者
要让自己编写的微服务能够注册到Eureka服务器中,需要在服务的Spring Boot Application中添加@EnableDiscoveryClient
注解,如此才能让Eureka服务器发现该服务。当然,pom.xml文件中也需要添加相关依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
同时,我们还需要为服务命名,并指定地址。这些信息都可以在application.properties配置文件中配置:
spring.application.name=demo-service eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
说明:Spring更推荐使用yml文件来维护系统的配置,yml文件可以体现出配置节的层次关系,表现力比单纯的key-value形式更好。如果结合使用后面讲到的Spring Cloud Config,则客户端的配置文件必须命名为bootstrap.properties或者bootstrap.yml。与上述配置相同的yml文件配置为:
spring: application: name: demo-service eureka: client: serviceUrl: defaultZone: http://localhost:1111/eureka/
服务发现与消费
在微服务架构下,许多微服务可能会扮演双重身份。一方面它是服务的提供者,另一方面它又可能是服务的消费者。注册在Eureka Server中的微服务可能会被别的服务消费。此时,就相当于在服务中创建另一个服务的客户端,并通过RestTemplate发起对服务的调用。为了更好地提高性能,可以在服务的客户端引入Ribbon,作为客户端负载均衡。
现在假定我们要为demo-service创建一个服务消费者demo-consumer。该消费者自身也是一个Spring Boot微服务,同时也能够被Eureka服务器注册。这时,就需要在该服务的pom.xml中添加eureka与ribbon的依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> </dependency>
然后在主应用类ConosumerApplication
中注入RestTemplate
,并引入@LoadBalanced
注解开启客户端负载均衡:
@EnableDiscoveryClient @SpringBootApplication public class ConsumerApplication { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args) } }
假设消费demo-service的客户端代码写在demo-consumer服务的其中一个Controller中:
@RestController public class ConsumerController { @Autowired RestTemplate restTemplate; @RequestMapping(value = "/demo-consumer", method = RequestMethod.Get) public String helloConsumer() { return restTemplate.getForEntity("http://demo-service/demo", String.class).getBody(); } }
通过RestTemplate
就可以发起对demo-service的消费调用。
声明式服务调用
通过Ribbon和Hystrix可以实现对微服务的调用以及容错保护,但Spring Cloud还提供了另一种更简单的声明式服务调用方式,即Spring Cloud Feign。Feign实际上就是对Ribbon与Hystrix的进一步封装。通过Feign,我们只需创建一个接口并用annotation的方式配置,就可以完成对服务供应方的接口(REST API)绑定。
假设我们有三个服务:
- Notification Service
- Account Service
- Statistics Service
服务之间的依赖关系如下图所示:
要使用Feign来完成声明式的服务调用,需要在作为调用者的服务中创建Client。Client通过Eureka Server调用注册的对应服务,这样可以解除服务之间的耦合。结构如下图所示:
为了使用Feign,需要对应微服务的pom.xml文件中添加如下依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency>
同时,还需要在被消费的微服务Application中添加@EnableFeignClients
注解。例如在Statistics服务的应用程序类中:
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class StatisticsApplication { public static void main(String[] args) { SpringApplication.run(StatisticsApplication.class, args); } }
由于Account服务需要调用Statistics服务,因此需要在Account服务项目中增加对应的client接口:
@FeignClient(name = "statistics-service") public interface StatisticsServiceClient { @RequestMapping(method = RequestMethod.PUT, value = "/statistics/{accountName}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE) void updateStatistics(@PathVariable("accountName") String accountName, Account account); }
StatisticsServiceClient接口的updateStatistics()
方法会调用URI为/statistics/{accountName}
的REST服务,且HTTP动词为put。这个服务其实对应就是Statistics Service中StatisticsController类中的saveStatistics()
方法:
@RestController public class StatisticsController { @Autowired private StatisticsService statisticsService; @RequestMapping(value = "/{accountName}", method = RequestMethod.PUT) public void saveStatistics(@PathVariable String accountName, @Valid @RequestBody Account account) { statisticsService.save(accountName, account); } }
在Account服务中,如果要调用Statistics服务,都应该通过StatisticsServiceClient接口进行调用。例如,Account服务中的AccountServiceImpl要调用updateStatistics()
方法,就可以在该类的实现中通过@autowired
注入StatisticsServiceClient接口:
@Service public class AccountServiceImpl implements AccountService { @Autowired private StatisticsServiceClient statisticsClient; @Autowired private AccountRepository repository; @Override public void saveChanges(String name, Account update) { //... statisticsClient.updateStatistics(name, account); } }
Notification服务对Account服务的调用如法炮制。