BeginMan blog

《微服务设计》读书笔记

一. 微服务概念

也就是做精做小,专注于某个边界之内,同时可独立部署,暴露Api来服务间通信,也要隐藏一些细节,避免与消费方耦合。

  • 小,专注于做好一件事(高内聚、单一职责原则)
  • 自治性(独立、隔离、松耦合)

如下是微服务的原则:

主要好处:

  • 技术异构性:不局限编程语言或技术范畴
  • 弹性:很好地处理服务不可用和功能降级问题。
  • 易扩展:分布式
  • 简化部署,复杂分解,可组合性等。

二. 如何建模服务

最终目标:

  • 松耦合
  • 高内聚

构建条件:限界上下文(bounded context)

  • 共享的隐藏模型
  • 模块和服务
  • 识别出一些粗粒度的限界上下文

三. 集成技术

集成是微服务相关技术中最重要的一个。微服务间通信方式:SOAP 、RPC 、REST 、Protocol Buffers 等,不管哪种方式,都需要保证APi一致性、技术无关性以及易用性。

3.1 同步与异步

这两种不同的通信模式有着各自的协作风格,即请求 / 响应或者基于事件。对于请求 / 响应来说,客户端发起一个请求,然后等待响应。这种模式能够与同步通信模式很好地匹配,但异步通信也可以使用这种模式。我可以发起一个请求,然后注册一个回调,当服务端操作结束之后,会调用该回调。

对于使用基于事件的协作方式,客户端不是发起请求,而是发布一个事件,然后期待其他的协作者接收到该消息,并且知道该怎么做。基于事件的系统天生就是异步的。业务逻辑并非集中存在于某个核心大脑,而是平均地分布在不同的协作者中。基于事件的协作方式耦合性很低。客户端发布一个事件,但并不需要知道谁或者什么会对此做出响应,这也意味着,你可以在不影响客户端的情况下对该事件添加新的订阅者。

当你需要低延迟的时候,通常会使用异步通信,否则会由于阻塞而降低运行的速度。

同步调用比较简单,而且很容易知道整个流程的工作是否正常。如果想要请求 / 响应风格的语义,又想避免其在耗时业务上的困境,可以采用异步请求加回调的方式。另一方面,使用异步方式有利于协同方案的实施,从而大大减少服务间的耦合,这恰恰就是我们为了能独立发布服务而追求的特性。

针对请求 / 响应方式,可以考虑两种技术:RPC(Remote Procedure Call,远程过程调用)和 REST(REpresentational State Transfer,表述性状态转移)。

RPC VS REST

3.2 数据库

  • 无论如何避免数据库集成。
  • 把数据库映射相关的代码和功能代码放在同一个上下文中,可以帮助我们理解哪些代码用到了数据库中的哪些部分
  • 打破外键关系
  • 服务暴露的 API 来访问数据,而不是直接访问数据库。

对于共享数据的处理:

需要把共享数据抽象具象化,作为一个中间步骤,我们可以创建一个新的包,然后让A和B调用这些包,通过 API 来访问此新创建的包。

共享表

在不同上下文中共享的表,要根据服务来分离共享表。

重构数据库

如果已经找到了应用程序中的接缝,按照限界上下文对它们进行分组,并且也找到了数据库中的接缝,尽量对其进行了分离。这里推荐你先分离数据库结构,暂时不对服务进行分离

逐步对服务进行分离,先分表结构,再分服务。

四. 部署与测试

首先,专注于保持服务能够独立于其他服务进行部署的能力,有以下原则:

  • 一个服务一个代码库(版本控制)
  • 对于每个微服务一个 CI(持续集成),CD(Continuous Delivery,持续交付)
  • 可能的话,将每个服务放到单独的主机 / 容器中(Docker)
  • 自动化一切

在《敏捷软件测试》一书中,用来将不同测试类型分类的测试象限

4.1 单元测试

测试的范围小但粒度细密,通常测函数或方法的调用,推荐采用 TDD(Test-Driven Design,测试驱动开发)。单元测试这块范畴也很大,具体的测试依赖编程语言。

4.2 服务测试

这里推荐一个Mountebank 的mock 服务器。通过指定端口、HTTP、Method、请求参数、返回值等 Mock一个网络调用Response.

五. 监控

基于微服务的系统,监控小的服务,然后聚合起来看整体

5.1 服务监控

有Nogios、Ganglia、Graphite这类开源工具

5.2 日志

logstash(http://logstash.net),它可以解析多种日志文件格式,并将它们发送给下游系统进行进一步调查。Kibana(https://www.elastic.co/products/kibana)是一个基于 ElasticSearch 查看日志的系统。

5.3 关联标识

像查看栈跟踪那样,查看调用链的上下游。

在这种情况下,一个非常有用的方法是使用关联标识(ID)。在触发第一个调用时,生成一个 GUID。然后把它传递给所有的后续调用,如图 8-5 所示。类似日志级别和日期,你也可以把关联标识以结构化的方式写入日志。使用合适的日志聚合工具,你能够对事件在系统中触发的所有调用进行跟踪:

15-02-2014 16:01:01 Web-Frontend INFO [abc-123] Register
15-02-2014 16:01:02 RegisterService INFO [abc-123] RegisterCustomer ...
15-02-2014 16:01:03 PostalSystem INFO [abc-123] SendWelcomePack ...
15-02-2014 16:01:03 EmailSystem INFO [abc-123] SendWelcomeEmail ...
15-02-2014 16:01:03 PaymentGateway ERROR [abc-123] ValidatePayment ...

Zipkin这样的软件,也可以跨多个系统边界跟踪调用。

总结:

  • 最低限度要跟踪请求响应时间。做好之后,可以开始跟踪错误率及应用程序级的指标。
  • 最低限度要跟踪所有下游服务的健康状态,包括下游调用的响应时间,最好能够跟踪错误率。一些像 Hystrix 这样的库,可以在这方面提供帮助。
  • 标准化如何收集指标以及存储指标。

六. 规模化微服务

  • 功能降级
  • 架构性安全
    • 认证与鉴权
    • 做好超时,给所有的跨进程调用设置超时,并选择一个默认的超时时间。
    • 断路器
    • 舱壁

6.1 断路器

当对下游资源的请求发生一定数量的失败后,断路器会打开。接下来,所有的请求在断路器打开的状态下,会快速地失败。一段时间后,客户端发送一些请求查看下游服务是否已经恢复,如果它得到了正常的响应,将重置断路器

dlq

如何实现断路器依赖于请求失败的定义,但当使用 HTTP 连接实现它们时,我会把超时或 5XX 的 HTTP 返回码作为失败的请求。通过这种方式,当一个下游资源宕掉,或超时,或返回错误码时,达到一定阈值后,我们会自动停止向它发送通信,并启动快速失败。当它恢复健康后,我们会自动重新发送请求。

正确地设置断路器会有点棘手。你不想太轻易地启动断路器,也不想花太多时间来启动。同样,你要确保在下游服务真正恢复健康后才发送通信。跟超时一样,我会选取一些合理的默认值并在各处使用,然后在特定的情况下调整它们。

当断路器断开后,你有一些选项。其中之一是堆积请求,然后稍后重试它们。对于一些场景,这可能是合适的,特别是你所做的工作是异步作业的一部分时。然而,如果这个调用作为同步调用链的一部分,快速失败可能更合适。这意味着,沿调用链向上传播错误,或更微妙的降级功能。

如果我们有这种机制(如家里的断路器),就可以手动使用它们,以使所做的工作更加安全。例如,如果作为日常维护的一部分,我们想要停用一个微服务,可以手动启动依赖它的所有系统的断路器,使它们在这个微服务失效的情况下快速失败。一旦微服务恢复,我们可以重置断路器,让一切都恢复正常。

6.2 舱壁

舱壁(bulkhead),是把自己从故障中隔离开的一种方式。

们应该为每个下游服务的连接使用不同的连接池。如果一个连接池被用尽,其余连接并不受影响。这可以确保,如果下游服务将来运行缓慢,只有那一个连接池会受影响,其他调用仍可以正常进行。

6.3 服务发现

在特定环境下有哪些微服务在运行,一些最常见的服务发现解决方案:

  • DNS
  • 动态服务注册
    • Zookeeper
    • Consul
    • Eureka

小结:最好实战一下,才会有更深的体会。(完)~