原文作者:Piotr Mińkowski
原文地址:https://dzone.com/articles/communicating-between-microservices
译者微博:@从流域到海域
译者博客:blog.csdn.net/solo95
(monolithic application, 下面全部译为整体应用程序,即是一个包含多个微服务的完整的应用程序,因为是一个微服务的集合体,自然跟单个微服务相比体量庞大,monolithic也有庞大的意思。)
开发微服务而不是整体的应用程序(monolithic application)最重要的方面之一是跨服务通信。在整体的应用程序中,运行于组件之间的单个进程调用是使用语言层面上的方法调用上实现的。如果在开发过程中遵循了MVC设计模式,通常会有将关系数据库映射到对象模型的模型类。然后,您会创建一些组件,这些组件暴露出一些方法以帮助在数据库的表上执行标准操作(如创建、读取、更新和删除)的方法。大家所熟知的DAO或存储库对象的组件不应该直接从控制器那里调用,而是通过一个附加组件层(来调用),如果有需要的话,也可以在组件上添加一些业务逻辑。
通常,当我和其他人讨论如何把一个整体的程序迁移到一个基于微服务的应用程序时,他们认为的最大挑战仅仅是改变他们的通信机制。如果您回想起一个典型的有数据库后端的整体应用程序的相关工作,您可能就会意识到如何正确地设计表之间的关系,然后将它们映射到对象模型中是多么的重要。在基于微服务的体系结构中,重要的是将这个通常非常复杂的结构划分成能够独立开发和部署的服务,这些服务也将形成具有多个通信链路的网络。通常,划分并不像它看起来的那么明显,并不是每一个包含了表相关逻辑的组件都要变成分离的微服务。
(做出)与这种划分相关的决策需要了解系统的业务方面,但是通信标准却可以容易地定义,而且无论我们决定实施哪种(通信)方法,它们都是不可改变的。如果我们讨论的是通信风格,有可能把它们分为两个核心。第一步要做的是去定义协议是同步的还是异步的。
大多数人认为,构建微服务是基于和使用JSON Web服务的REST相同的原则。当然,这是最常见的方法,但正如你所看到的,它不是唯一的方法。不仅如此,在某些文章中,您可能会看到同步通信是一种反模式,尤其是当呼叫调用路径中有许多服务时。
我们可以参考的另一个频繁进行的对比是将微服务与SOA架构进行了比较。在SOA,最常见的通信协议是SOAP。关于SOAP是否比REST好,或者相反,已经进行过大量的讨论。众所周知,它们都有优点和缺点,但REST是轻量级且独立于语言的种类,因此它赢得了现代应用程序的竞争,并且正在慢慢接管企业部门。老实说,如果有一个很好的理由,我不会反对任何基于SOAP的微服务。
让我们回顾一下不同类型通信的划分标准。我已经提到,我们可以将它们分类为同步与异步,后者定义了通信具有单个接收器还是多个接收器。在一对一通信中,每个客户端请求都由一个服务实例来处理,而每个请求可以由许多不同的服务处理。这里值得指出的是,一个消息是由不同的服务接收的,但通常不应该由单个服务的不同实例接收。微服务框架通常会实现消费者的分组机制,由此单个应用的不同实例会被放置在竞争的消费者关系中,而其中只有一个实例应该去处理传入的消息。
对于一对一同步服务而言,可以在客户端执行负载平衡机制来实现相同的服务。每个服务都有关于调用该服务的所有实例的位置地址信息。该信息可以从服务发现服务器(service discovery server)中获取,或者可以手动配置其属性来提供。每个服务都有一个内置的路由客户端,可以使用正确的算法来选择目标服务的一个实例,并在实例上发送请求。下面这些是最普遍的负载平衡方法:
Round Robin(轮询调度)-最简单和最常见的方式。请求顺序地分布在所有实例中。
Least Connections(最小连接)-请求转到当前正在处理最少数量的活动连接的实例。
Weighted Round Robin(加权轮询调度)-该算法为池中的每个实例分配权重,并且新的连接依照分配的权重按比例转发。
IP Hash(IP 哈希)-此方法从源IP地址生成唯一的哈希密钥,并确定哪个实例接收请求。
下面有一幅图,它描绘了基于微服务架构的不同类型的通信,假定每个服务有多个实例存在:
在更复杂的体系结构中,可以存在三种通信类型相互混合的情况。然后,一些微服务是建立在同步交互的基础上,另一些是基于一对一消息传递,其余是基于发布/订阅模型的。
最近有很多关于响应式微服务的讨论,所以我认为这值得一提。它基于响应式编程范式(Reactive Programming paradigm),面向数据流和更改的传播。这样的微服务是非阻塞的、异步的、事件驱动的,并且需要少量的线程来扩展。它们最大的优点是性能优良,资源消耗小。建立响应式微服务最流行的框架是Lagom和Vert.x。
让我们回到同步的请求/响应通信。在部分失败的情况下准备系统非常重要,尤其是对于基于微服务的体系结构,其中有许多应用程序在各自独立的进程中运行。来自客户角度的单个请求可能会通过许多不同的服务转发。有可能是因为失败,维护或仅仅可能是超载而导致其中一项服务中断,这会导致对进入系统的客户端请求的响应速度变得非常慢。我们已经有处理故障和错误的几个最佳实践。第一种方法建议我们应该始终设置网络连接超时和读取超时,以避免等待响应时间太长。第二种方法是在服务失败或响应时间过长的情况下限制接受请求的数量。
后两种模式彼此紧密相连。我正在考虑断路器模式和回退。这种方法的主要假设依赖于监测成功和失败请求的监测。如果有太多的请求失败或者服务需要耗费很长时间才能响应,那么配置的断路器就会被触发,并且所有进一步的请求都会被拒绝。另一方面,回退提供了一些(紧急)逻辑部分,如果请求失败或断路器跳闸,则该逻辑必须被执行。在某些情况下,它可能很有用,尤其是当服务返回的数据对客户端不重要或者不会频繁进行更改并且可能从直接缓存中获取时。Netflix Hystrix提供了上面所描述模式的最普遍的实现,许多Java框架都使用它,
使用Spring Cloud Netflix实现断路器非常简单。在主类中,可以使用一个注释来启用它:
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
为了与其他微服务进行通信,我们可以使用Feign REST客户端来处理回退。在这里,我们返回一个空的列表:
@FeignClient(value = “account - service”, fallback =
AccountFallback.class)
public interface AccountClient {
@RequestMapping(method = RequestMethod.GET, value = “/accounts/customer / {
customerId
}”)
List < Account > getAccounts(@PathVariable(“customerId”) Integer customerId);
}
@Component
public class AccountFallback implements AccountClient {
@Override
public List < Account > getAccounts(Integer customerId) {
List < Account > acc = new ArrayList < Account > ();
return acc;
}
}
Hystrix的默认设置可能会被配置属性覆盖。下面的可见属性设置了在等待响应时,调用者将收到超时信息:
hystrix.command.default.execution.isolation.thread.
timeoutInMilliseconds=500