微服务架构设计模式相对于整体设计模式,具有很多优点。 它不去创建一个大型的单个应用程序,而是想着要把单个的应用程序细分成一堆互相连接起来的子应用程序。每一个微服务都有类似于整体应用程序的分层架构。
通过使用微服务架构搭配一些设计模式,有几个优势可以很容易的得到实现。
可扩展性。应用程序通常会有三种类型的扩展。X维度上的扩展就是对应用程序进行平面的克隆, Y维度上的扩展就是将不同的应用程序功能进行分割, 而Z维度上的扩展就是对数据进行分区和分片。当Y维度上的扩展被应用到整体应用程序时,应用程序就会被分成许多更小的单元,各自被分配到具有微服务架构风格的业务功能。
模式: 每一个微服务都拥有自己的被隔离的实体和容器。多个实体中的相同服务可以实现服务层面的负载均衡。
可利用性:微服务被部署在不同的实例,比如相同的微服务运行在多实例中,保证整个系统的高可用。
模式:分级服务的负载平衡通过优化实现高可用,电路破坏者模式可以通过优化实现容错,而服务配置与探测可以使新服务建立通信。
可持续部署性。每个微服务之间相互独立。这就保证任何 相互独立 服务的部署速度更快,实现可持续部署。
循环处理性。微服务提供不同方式来获取循环处理。每个微服务在分级服务有自己的结构层次,在数据库作为一个持续层来运行于自我隔离的环境。
技术多样性。考虑到微服务具有一个隔离特征,一个混合多样技术在整个应用程序中,可以使用来安装不同微服务。
低成本。服务实例可以基于应用程序使用习惯进行优化。低利用率的实例可以用于最少使用的服务,而高利用率的实例可用于商业级别的服务。
性能。考虑到微服务的技术上多个优势,将会直接影响到性能。比如说,高阻塞调用服务运行在单线程的技术栈中,而高CPU使用率的服务运行于多线程的技术栈中。
相比庞大结构,微服务也拥有一些缺陷。第一,部署和管理分布式应用相比一个独立程序时非常困难的。微服务需要一个进程间通信(IPC)机制来与不同微服务之间进行通信,这样会影响到性能(取决于网络带宽)。
高效的微服务需要实现如下特性:
-
要实现IPC,则微服务间需要通过 rest请求或者RPC 相互 调用 。
-
一个微服务可能会调用另一个或多个微服务。一个服务由于负载过高或者其他服务异常可能会不可用,或者没有及时响应。所以需要有响应的 局部故障或者回退机制 。
-
基于微服务的应用相对与单一结构应用,其部署的复杂度会更高,因为其包含了不同的服务,而这些服务又可能是运行在不同的容器或者实例中。需要有响应的 服务注册和发现机制 将新服务注册进去同时能发现需要交互的服务。
-
一个微服务客户端在一个功能中可能会调用多个远程微服务。这可能会导致大量的基于网络的rest或者RPC请求。应该有一个 服务网关 用于接受微服务的一个请求然后 生成多个本地请求(服务网关的本地),最后将这些请求结果聚合起来返回到客户端微服务。
-
当同一个微服务被部署为同一个实例的不同容器时,需要有服务层的 负载均衡 用于分发负载和实现故障恢复机制。
-
分布式应用同时也需要某种 集中日志 框架用于将所有数据集中处理以生成日志数据。
从头完全实现上述特性将会是十分复杂并且耗时的,而且开发人员也需要消耗大量时间开发和测试基础框架。但是,如果这些都是用现成的,就可以只关注于业务逻辑了。
Spring Cloud 框架提供了工具来快速构建那些在分布式服务中的通用模式,比如配置管理、服务发现、容错、分布式缓存、服务网关、rest客户端以及服务级负载均衡器。Spring Cloud 工程的初始化可以通过向工程中添加几个 Maven 依赖来实现。这个也可以利用 spring initialize 网站来实现。
许多对微服务的部署至关重要的 Spring Cloud 组件都来自于 Netflix 开放源代码软件 (Netflix OSS) 中心。
Spring Cloud 是在 Spring Boot 之上构建的, 而 Spring Boot 里面则包含了使用了最少量配置代码的嵌入式 tomcat 服务器。
下面是在 AWS 云上面使用微服务的分布式应用程序的架构图。
在上图中有三个不同的为服务,主服务使用一个REST客户端同服务A和服务B进行通信。服务A会在两个容器中被启动,而服务B也会在相同实体中的两个容器中被启动。被暴露给一个客户端的主服务将会通过服务客户端同服务A和服务B的实体进行交互,而服务客户端就是一个负载均衡器。这样就使得负载均衡不仅能在实体层面进行,也能在相同实体的服务层面进行。这被称作服务级别的负载均衡。当有服务请求发起时,客户端服务会通过向 Eureka 服务注解表和发现机制进行查询来获取到服务实体的地址。
Spring Cloud 已经支持了 Eureka 的服务注册功能, 而 Eureka 的服务注册功能则可以通过添加 @EnableEurekaServer 注解来启用。
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] args) {
System.setProperty("spring.config.name", "registration-server");
SpringApplication.run(EurekaServer.class, args);
}
}
还有用于注册服务的 application.yml:
eureka:
instance:
hostname: localhost
client: # Not a client, don't register with yourself
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
server:
port: 1111 # HTTP (Tomcat) port
微服务可以通过添加 @ EnableDiscoverClient 注解来将它自己注册到 Eureka 服务器上 , Eureka 服务会将地址,以及端口添加注册到 application.yml 或者 application.properties 文件里去。
eureka:
client:
serviceUrl:
defaultZone: http://localhost:1111/eureka/
fetchRegistry: true
Spring Cloud 提供了多种进程间通信的方式,包括Feign客户端还有rest模板。 Feign 客户端非常灵巧、干净,并且易于实现 。通过添加 @ EnableFeignClients 注解可以启用 Feign 客户端。
客户端应用程序要创建一个接口,里面要有一个带有 @RequestMapping 的方法,并且接口要带上 @FeignClient 注解,注解要带上服务 ID。
@FeignClient(value = "serviceA")
public interface ServiceClientA {
@RequestMapping(value = "/user/{userId}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public UserProfile getUserProfile(
@PathVariable("userId") Integer userId);
}
我们通过创建一个新的类 , ServiceClientBean.java,来调用这些接口方法。
@Component
public class ServiceClientBean {
@Autowired
private ServiceClientA serviceClientA;
@HystrixCommand(fallbackMethod = "defaultMethod")
public UserProfile getUserProfile(Integer userId) {
UserProfile user=serviceClientA.getUSerProfile(userId);
return user;
}
public UserProfile defaultMethod() {
return new UserProfile();
}
}
通过在 @EnableFeignClients 中加入 basePackageClasses 属性值,所有的 @FeignClient 类都被注册为 Spring bean。
@EnableFeignClients(basePackageClasses = ServiceClientA.class)
开发者可以在客户端和服务端之间共享相同的接口定义, 不过这样做会增加客户端和服务端之间轻微的耦合。
Feign 客户端会使用Ribbon自动对服务均衡进行支持。Ribbon 是一个客户端服务均衡器,它能对 HTTP 和 TCP 请求进行许多的控制。我们可以使用外部属性 client.ribbon.* 来对 ribbon客户端进行配置。
在 ServiceClientBean.java 中, 我们添加了一个 @HystrixCommand 注解来处理局部失败。这个命令会告诉 Spring,这个方法容易发生失败。Spring Cloud 库封装了这些方法,通过启用断路器来处理容错和延迟容忍。Hystrix 命令一般都会带有一个回退方法。为了应对发生错误的情况,Hystrix 会自动启用前述回退方法,并将流量转移到回退方法。
如果服务A不能及时响应或者可能关闭,那么 hystrix 就会调用回退方法来返回默认的响应消息。
通过添加 hystrix 仪表盘应用程序,我们可以查看到 hystrix 个各项运行指标并对其进行监控。
还有一个组件,交过 Zuul 代理,它是一个服务网关。这个在上面的架构图中没有提到。服务网关会在内部调用到多个微服务,对来自这些微服务的调用结果进行整合后发送给客户端服务。
Zuul 代理内部使用了 Eureka 服务进行服务发现,使用 Ribbon 进行服务实体间的负载均衡。
分布式应用程序需要某种类型的中央日志框架,而这个可以很容易的通过 ELK 技术栈来实现。