微服务架构模式的核心在于如何识别服务的边界,设计出合理的微服务。但如果要将微服务架构运用到生产项目上,并且能够发挥该架构模式的重要作用,则需要微服务框架的支持。
在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的依赖:
org.springframework.cloud
spring-cloud-starter-eureka-server
创建Spring Boot Application:
@EnableEurekaServer
@SpringBootApplication
publicclassApplication{
publicstaticvoidmain(String[] args){
newSpringApplicationBuilder(Application.class).web(true).run(args);
}
}
注册服务提供者
要让自己编写的微服务能够注册到Eureka服务器中,需要在服务的Spring Boot Application中添加@EnableDiscoveryClient注解,如此才能让Eureka服务器发现该服务。当然,pom.xml文件中也需要添加相关依赖:
org.springframework.cloud
spring-cloud-starter-eureka
同时,我们还需要为服务命名,并指定地址。这些信息都可以在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的依赖:
org.springframework.cloud
spring-cloud-starter-eureka
org.springframework.cloud
spring-cloud-starter-ribbon
然后在主应用类ConosumerApplication中注入RestTemplate,并引入@LoadBalanced注解开启客户端负载均衡:
@EnableDiscoveryClient
@SpringBootApplication
publicclassConsumerApplication{
@Bean
@LoadBalanced
RestTemplaterestTemplate(){
returnnewRestTemplate();
}
publicstaticvoidmain(String[] args){
SpringApplication.run(ConsumerApplication.class, args)
}
}
假设消费demo-service的客户端代码写在demo-consumer服务的其中一个Controller中:
@RestController
publicclassConsumerController{
@Autowired
RestTemplate restTemplate;
@RequestMapping(value ="/demo-consumer", method = RequestMethod.Get)
publicStringhelloConsumer(){
returnrestTemplate.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文件中添加如下依赖:
org.springframework.cloud
spring-cloud-starter-feign
同时,还需要在被消费的微服务Application中添加@EnableFeignClients注解。例如在Statistics服务的应用程序类中:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
publicclassStatisticsApplication{
publicstaticvoidmain(String[] args){
SpringApplication.run(StatisticsApplication.class, args);
}
}
由于Account服务需要调用Statistics服务,因此需要在Account服务项目中增加对应的client接口:
@FeignClient(name ="statistics-service")
publicinterfaceStatisticsServiceClient{
@RequestMapping(method = RequestMethod.PUT, value ="/statistics/{accountName}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
voidupdateStatistics(@PathVariable("accountName")String accountName, Account account);
}
StatisticsServiceClient接口的updateStatistics()方法会调用URI为/statistics/{accountName}的REST服务,且HTTP动词为put。这个服务其实对应就是Statistics Service中StatisticsController类中的saveStatistics()方法:
@RestController
publicclassStatisticsController{
@Autowired
privateStatisticsService statisticsService;
@RequestMapping(value ="/{accountName}", method = RequestMethod.PUT)
publicvoidsaveStatistics(@PathVariable String accountName, @Valid @RequestBody Account account){
statisticsService.save(accountName, account);
}
}
在Account服务中,如果要调用Statistics服务,都应该通过StatisticsServiceClient接口进行调用。例如,Account服务中的AccountServiceImpl要调用updateStatistics()方法,就可以在该类的实现中通过@autowired注入StatisticsServiceClient接口:
@Service
publicclassAccountServiceImplimplementsAccountService{
@Autowired
privateStatisticsServiceClient statisticsClient;
@Autowired
privateAccountRepository repository;
@Override
publicvoidsaveChanges(String name, Account update){
//...
statisticsClient.updateStatistics(name, account);
}
}
Notification服务对Account服务的调用如法炮制。
服务容错保护
在微服务架构中,微服务之间可能存在依赖关系,例如Notification Service会调用Account Service,Account Service调用Statistics Service。真实产品中,微服务之间的调用会更加寻常。倘若上游服务出现了故障,就可能会因为依赖关系而导致故障的蔓延,最终导致整个系统的瘫痪。
Spring Cloud Hystrix通过实现断路器(Circuit Breaker)模式以及线程隔离等功能,实现服务的容错保护。
仍然参考前面的例子。现在系统的微服务包括:
上游服务:demo-service
下游服务:demo-consumer
Eureka服务器:eureka-server
假设上游服务可能会出现故障,为保证系统的健壮性,需要在下游服务中加入容错包含功能。首先需要在demo-consumer服务中添加对hystrix的依赖:
org.springframework.cloud
spring-cloud-starter-hystrix
然后在demo-consumer的应用程序类中加入@EnableCircuitBreaker开启断路器功能:
@EnableCircuitBreaker
@EnableDiscoveryClient
@SpringBootApplication
publicclassConsumerApplication{
@Bean
@LoadBalanced
RestTemplaterestTemplate(){
returnnewRestTemplate();
}
publicstaticvoidmain(String[] args){
SpringApplication.run(ConsumerApplication.class, args)
}
}
作者:java菜
链接:https://www.jianshu.com/p/97c9111c8b0a