I'll take a quiet life. A handshake of carbon monoxide.

0%

《微服务架构》知识总结

原创文章,转载请标明出处:Soul Orbit
本文链接地址:《微服务架构》知识总结

虽然之前做过后台开发,很喜欢微服务的概念,也用了部分微服务的思想,但是回头再看之前很多事情的做法,觉得路子还是相当野的,特别是在微软呆了几年之后,对于Engineering System的理解加深了不少。最近看到了这本书,感觉算是对于这种后台设计思路有了一个非常好的总结,所以写一个小博客把所有的知识点总结一下。

1. 什么是微服务

  • 定义:微服务是一些协同工作的小而自治的服务。通过将单一职责原则应用在独立的服务上,从而避免由于代码库过大而衍生出的各种问题。
  • 服务多小合适:一个微服务应该可以在两周内被完全重写。但是服务越小,微服务架构的优点和缺点也就越明显。使用的服务越小,独立性带来的好处就越多,但是管理也会越复杂。
  • 微服务的自治性:一个微服务是一个独立的实体;服务之间通过网络调用进行通信,避免紧耦合;服务可以彼此间独立进行修改和部署,而不影响其他服务。
  • 微服务的好处:
    • 技术异构性:微服务帮助你轻松地采用不同的技术。
    • 弹性:如果系统中一个组件不可用,不会导致级联故障,而影响其他组件。
    • 扩展:可以只对需要扩展的服务进行扩展,把那些不需要扩展的服务运行在更小的、性能稍差的硬件上。
    • 简化部署:可以更快地对特定部分的代码进行部署。出了问题,也只会影响一个服务,并且容易快速回滚。
    • 与组织结构相匹配:自治性,简化管理,提高团队工作热情。
    • 可组合性:在微服务架构中,系统会开放很多接缝供外部使用。人们可以通过不同的方式使用同一个接缝,从而当情况发生改变时,可以使用不同的方式构建应用。
    • 对可替代性的优化:可以在需要时轻易地重写服务,或者删除不再使用的服务。
  • 微服务不是银弹:了解你的系统,找到最适合自己业务的方式。

1.1. 其他代码分解技术和其优缺点

  • 共享库:将代码库分解为多个库,并在多个服务中重用这些代码。
    • 优点:基本所有语言都支持这种分解,从而让不同的团队和服务共享功能。一些和业务领域无关的代码(网络通信不算),就非常适合通过这种方式进行分解和重用。
    • 缺点:
      1. 无法选择异构的技术。一般来讲,这些库只能在同一种语言中,或者至少在同一个平台上使用。
      2. 会失去独立地对系统某一部分进行扩展的能力。
      3. 缺乏一个比较明显的接缝来建立架构的安全性保护措施,从而无法确保系统的弹性。
      4. 除非使用动态链接库,否则每次当库有更新的时候,都需要重新部署整个进程,以至于无法独立地部署变更。
  • 模块:通过动态的在运行的进程中加载或停止模块,达到在不停止整个进程的前提下对某个模块进行修改,比如OSGI和Erlang。虽然其有所改进,但是还是有很多缺点:
    1. 如果没有语言级别的支持,那么就需要作者来对模块进行适当的隔离。
    2. 和共享库一样,我们无法选择异构的技术,会失去独立对服务进行扩展的能力,并且有可能会导致使用过度耦合的集成技术,同时也会缺乏相应的接缝来进行架构的安全性保护。

2. 演化式架构师

  • 架构师的职责:
    • 愿景:确保在系统级有一个经过充分沟通的技术愿景,这个愿景应该可以帮助你满足客户和组织的需求。
    • 同理心:理解你所做的决定对客户和同事带来的影响。
    • 合作:和尽量多的同事进行沟通,从而更好地对愿景进行定义、修订及执行。
    • 适应性:确保在你的客户和组织需要的时候调整技术愿景。
    • 自治性:在标准化和团队自治之间寻找一个正确的平衡点。
    • 治理:确保系统按照技术愿景的要求实现。创建治理小组,帮助进行治理。
      • 代码治理:通过合作创建并提供范例和服务代码模板保证所有开发人员达成共识。
      • 技术债务:理解债务层次及其对系统的影响。
      • 例外管理:记录例外。如果出现多次,通过修改原则和实践固化我们的理解。
      • 建设团队:帮助队友成长,理解技术愿景,并保证他们可以积极地参与到愿景的实现和调整中来。
  • 架构师,建筑师与城市规划师:建筑师和工程师所具备的精确性和纪律性是计算机行业遥不可及的,如果让我们来造房子和造桥,那么简直就是灾难,因为我们要创造的东西无法一开始就设计的完美无缺,它从设计上来说就是要足够灵活,有很好的适应性,并且能够根据用户的需求进行演化。比起建筑师和工程师,软件架构师和城市规划师更像,城市会不时的发生变化,而未来的变化很难预见,所以与其对所有变化的可能性进行预测,不如做一个允许变化的设计。
  • 代码的分区:城市区域就是服务的边界,架构师应该主要关注区域之间的事情,而不应过多关注每个区域内发生的事情。
  • 愿景:
    • 战略目标:公司的走向以及如何才能让自己的客户满意,比如开辟新的市场。
    • 原则:为了达到战略目标,我们会制定一些具体的规则,并称为原则,比如服务必须方便部署。原则最好不要超过10个,不然很难记住而且容易互相重叠和发生冲突。
    • 约束:有些原则是约束,它和其他原则的区别在于,它很难改变,而原则是我们自己决定的。随着项目发展,每隔一段时间我们可以回顾一下所有的约束,看看它们是否还成立。
    • 实践:实践指导我们如何完成任务,从而保证原则能够得到实施,比如代码规范等等。
  • 如何定义实践:系统允许多少可变性。
    • 监控:在系统级别进行考虑,要能清晰地描绘出跨服务系统的健康状态。
    • 接口:只选用少数几种明确的接口技术,这样有助于新消费者的集成。
    • 架构安全性:每个服务都可以应对下游服务的错误请求。

3. 建模服务

  • 接缝:
    • 高内聚:把相关的行为聚集在一起,把不相关的行为放在别处。
    • 松耦合:修改一个服务就不需要修改另一个服务。
  • 限界上下文:一个由显式边界限定的特定职责。它是寻找接缝的一个重要工具。
    • 共享和隐藏模型:限界上下文之间暴露的模型不一定要与内部实现完全一样。
    • 发现限界上下文:不要从共享数据的角度来考虑,而应该从提供的功能来考虑。
    • 建模限界上下文:对限界上下文使用模块建模,并使用共享和隐藏模型,为以后应用微服务架构做准备。
    • 不要过早划分限界上下文:先建立单块服务,理解服务,然后再逐步划分。先考虑比较大的粗粒度的上下文,等进一步发现合适的缝隙后,再做更细粒度的划分。

4. 集成

  • 原则:
    • 避免破坏性的修改
    • 保证API的技术无关性
    • 使你的服务易于消费方使用
    • 隐藏内部实现细节
  • 无论如何避免共享数据库
    • 共享数据库使得外部系统能够查看内部实现细节,并与其绑定在一起。
    • 共享数据库使得消费方与特定的技术选择绑定在了一起。
    • 如果消费方直接操作数据库,那么它们都需要对这些逻辑负责。对数据库进行操作的相似逻辑可能会出现在很多服务中。
  • 方法:编排和协同
    • 编排(Orchestration):依赖于某个中心大脑来指导并驱动整个流程。由于中心控制点承担了太多职责,它会成为网状结构的中心枢纽及很多逻辑的起点,而与其打交道的那些服务通常都会沦为贫血的、基于 CRUD 的服务。
      • 远程过程调用(RPC):允许你进行一个本地调用,但事实上结果是由某个远程服务器产生的。其虽然易于使用,但是其带来的代价要远远大于一开始快速启动的好处。
        • 存在技术的耦合,如Java RMI,与特定的平台紧密绑定。
        • 其隐藏了远程调用的复杂性,而网络并不可靠,导致无法很好的区分其故障模式并进行处理。
        • 脆弱性:一旦RPC接口发生改变,则需要为服务端和所有的客户端都重新生成桩代码,并一起重新部署。
      • 表述性状态转移(REST):声明了一组对所有资源的标准方法。服务可以根据请求内容创建资源的不同的表示方式,从而将其与内部存储解耦合。
        • REST与HTTP:REST并不是HTTP,但是HTTP提供了很多功能,对于实现REST非常有用,所以我们经常看见使用HTTP实现的REST。
        • 超媒体作为程序状态的引擎(Hypermedia As The Engine Of Application State,HATEOAS):通过向客户端提供指向其他资源的链接来和服务端进行交互,从而避免客户端和服务端产生耦合。但是其缺点是客户端和服务端之间的通信次数会比较多,因为客户端需要不断的发现链接,请求,再发现链接,知道找到自己想要进行的操作。
        • REST与XML:REST并没有限制资源的表示形式,使用XML或者JSON属于我们自己的选择。
        • 基于HTTP的REST的缺点:一个是容易回到基于HTTP的RPC上去,第二是如果要求低延迟的场景,每个HTTP请求的封装开销可能会是个问题。
    • 协同(Choreography):仅仅告知系统中各个部分各自的职责,而把具体怎么做的细节留给它们自己。其缺点是无法直观的看见业务流程。
      • 基于事件的异步协作主要有两个部分:微服务发布事件机制和消费者接收事件机制。
        • 如果使用消息队列,则尽量让中间件保持简单,而把业务逻辑放在自己的服务中。
        • 还可以使用HTTP来传播事件,比如Atom Feed。
      • 异步的复杂性:灾难性故障转移(catastrophic failover),失败处理,监控等等。
    • 注意点:
      • 理解 REST 和 RPC 之间的取舍,但总是使用 REST 作为请求 / 响应模式的起点
      • 相比编排,优先选择协同
  • 服务即状态机:把关键领域的生命周期显示建模出来。方便在唯一的地方处理状态冲突和封装行为。
  • 响应式扩展:把多个调用的结果组装起来并在其基础上执行操作。
  • 微服务 vs DRY原则和代码重用:
    • 在微服务内部不要违反DRY原则,但是在跨服务的情况下可以适度违反DRY,因为共享代码可能造成过度耦合。
    • 使用第三方开发的SDK,以便于解耦合,或者仅仅使用客户端库处理与服务无关的事情,比如日志记录,错误处理等等。
  • 按引用访问:在持有一个本地副本时,同时持有一个指向原始资源的引用,以便更新。
  • 微服务的版本管理:
    • 避免破坏性修改、理解 Postel 法则、使用容错性读取器
    • 利用契约来及早发现破坏性修改
    • 使用语义化的版本管理,使得版本兼容性变得易于查看
    • 让不同的接口共存以降低接口修改的影响
    • 如果实在代价过高,则同时使用多个版本的服务,并将老系统的请求逐步移动到新系统中
  • 用户界面:将其视为一个组合层;如果与后台交互过于频繁,可以为特定的用户界面创建单块代理服务,对其逻辑进行编排,但是不要将太多逻辑放入其中。
  • 与第三方服务集成:使用第三方服务隐藏自己的服务,或者使用外观服务加绞杀者模式来隐藏第三方服务。

5. 分解单块服务

  • 原则
    • 关键是找接缝:抽取相对独立的一部分代码,对这部分代码的修改不影响系统的其他部分,另外接缝之间应该可以形成一个有向无环图。
    • 代码应该与组织相匹配,表示限界上下文的这些包之间的交互应该与组织中不同部分的实际交互方式一致。
    • 不要为了抽取而抽取,考虑抽取什么收益最大。
  • 数据库的处理
    • 分离存储层:打破外键关系(SchemaSpy),使用配置共享静态数据,分离共享表
    • 先分离数据库结构,再分离服务
    • 处理事务方法:最终一致性,终止操作或者利用分布式事务。
    • 分离报告数据库:利用数据导出工具或者视图生成报告数据库,或者导出事件数据并创建报告服务重建报告数据库。

6. 部署

  • 持续集成(Continuous Integration,CI):保证新提交的代码与已有代码进行集成,从而让所有人保持同步。
    • 你是否每天签入代码到主线?
    • 你是否有一组测试来验证修改?
    • 当构建失败后,团队是否把修复CI当做第一优先级的事情来做?
  • 同步发布(Lock-step release):必须一次性部署多个服务。
    • 解决方法:分离CI构建——分离每个微服务的代码,并为其创建CI构建。
  • 持续交付(Continuous Delivery,CD):将每次提交都当成候选发布版本来对待。
    • 构建流水线:将构建分解成多个阶段,从而很好的跟踪软件构建进度,并快速反馈构建结果。
    • 平台特定的构建物:利用Puppet和Chef这样的自动化配置管理工具部署这些构建物,如Apache和Nginx。
    • 操作系统构建物:创建脚本部署这些构建物,如系统服务等等。
    • 定制化镜像:加快运行环境的配置,如Packer,并将镜像本身也作为构建物,实现不可变服务器
    • 环境问题:应该最小化环境件配置的差异,配置环境应与生产环境尽量一致,但需要持续的做权衡。
    • 选择合适的部署方式:单主机多服务,应用程序容器,单主机单服务和平台即服务(PaaS)。
    • 自动化:管理大量主机。
    • 主机虚拟化:虚拟机,虚拟云(Vagrant),Linux容器,Docker。
    • 部署接口:使用统一接口来部署给定的服务。

7. 测试

  • 测试类型:
    types-of-tests
  • 测试金字塔:单元测试,服务测试,端到端测试。从左到右范围更大,更有信心,从右到左运行更快,隔离越好。
    • 权衡:为不同的目的选择不同的测试来覆盖不同的范围,优化快速反馈。
    • 比例:从左至右,前一个的数量要比后一个多一个量级。
  • 服务测试:
    • 打桩:用一些预设的响应模拟真实的服务,隔离问题。常见的打桩服务器如Mountebank。
  • 端到端测试:确保部署新的服务到生产环境后,变更不会破坏新服务的消费者,但是其缺点很多。
    • 测试文件版本控制问题和大量重叠的测试覆盖问题:使用扇入模型解决。
    • 测试脆弱:任何地方的问题都可能引起端到端测试出错,所以应该使用更小范围的测试来代替。
    • 测试场景笛卡尔积式爆炸:测试重心放在少量的核心场景上。
  • 消费者驱动契约(CDC):代替端到端测试,并作为CI流水线的一部分,保证消费者不会被破坏。
  • 部署后再测试:部署不等于上线
    • 冒烟测试和蓝/绿部署
    • 金丝雀发布(灰度发布)
    • 平均修复时间(Mean Time To Repair,MTTR)比平均故障间隔时间(Mean Time Between Failures,MTBF)要重要
  • 跨功能测试:比如性能测试,如果运行时间很长,无法经常运行,则可以采用每天测试来保证尽快发现问题。

8. 监控

  • 如何实践监控:监控小的服务,然后聚合起来看整体。
  • 收集服务器信息:如CPU,内存等等,这样就可以跟踪流氓进程和进行容量规划。可以使用Nagios或者New Relic。
  • 收集日志:
    • 单服务器:logrotate
    • 较少量的多服务器:ssh-multiplexers
    • 大量服务器:需要专门的日志子系统,集中管理所有的日志。(logstash + kibana)
    • 关联标识:用以搜索和某一个具体请求相关的所有日志,他们可能来自于不同的服务。
  • 收集数据指标:了解系统的秘诀,收集足够长时间的数据,直到出现清晰的模式。(Graphite)
    • 永远无法知道什么数据是有用的,所以倾向于暴露一切数据,自己服务的数据指标也应该公开。
  • 综合监控:最终用户到底能不能正常使用我们的系统。
    • 语义监控:创建和合成事务,确保系统行为在语义上的正确性。
  • 级联:每个服务的实例都应该追踪和显示其下游服务的健康状态,以避免级联故障。
  • 标准化:使用标准的格式记录日志。
  • 考虑受众:收集这些数据是为了帮助其他人完成工作,提醒他们现在需要知道的东西。
  • 统一管理:利用事件服务器对事件进行聚合和路由,从而将不同的指标(运营指标和业务指标)统一处理,这样简化架构还方便我们分析数据。(Riemann,Suro)

9. 安全

  • 主体,身份验证和授权:确认身份的过程叫身份验证,进行身份验证的人或事叫主体,将主体映射到他可以进行的操作中叫授权。
  • 单点登录:用一个单一标识表示一个主体并只需要进行一次验证。
    • 一般实践:当主体试图访问一个资源时,他会被定向到一个身份提供者那里进行身份验证。一旦身份提供者确认主体已通过身份验证,它会发消息给服务提供者,让服务提供者来决定是否允许他访问资源。
    • 单点登录网关:使用网关集中处理重定向用户的行为,提供粗粒度的身份验证,并将主体信息加入请求中传给服务提供者,但是这样会带来一种虚假的安全感,因为网关是唯一的安全防御。
  • 服务间的身份验证和授权:
    • 在边界内允许一切:对中间人攻击毫无防备,但是这种形式依然被大多数组织采用。
    • HTTP(S)基本身份验证:使用HTTP时,用户名和密码没有以安全的方式发送,HTTPS能解决这个问题,但是在管理多台机器的SSL证书时会出现问题,而且其流量无法被反向代理缓存。
    • 使用SMAL或者OpenID Connect:创建服务账户,并把所有服务的访问控制集中在中央目录服务器。不过如果想避免中间人攻击,我们仍然需要通过HTTPS来路由通信。而且安全存储凭证和重复和繁琐的身份验证代码也是一些问题。
    • 客户端证书:使用TLS建立安全地通信链路,但是证书管理的工作会比服务器端证书更加繁重,诊断问题也会更难。
    • HTTP之上的HMAC:使用HMAC对请求进行签名,是OAuth规范的一部分。其缺点有三:客户端和服务器需要一个共享的密钥;它不是一个标准,所以有不同的实现方式;虽然第三方无法篡改请求,但是请求中的其他数据对网络嗅探是可见的。
    • API密钥:通过API密钥识别出谁在进行调用,然后对他们进行限制,其重点关注的是对程序的易用性。通过与已有目录服务的连接,我们也可以控制这些密钥的生命周期。
    • 注意混淆代理人攻击:在服务间通信的上下文中,攻击者采用一些措施欺骗代理服务,让它调用其下游服务,从而做到一些他不应该能做的事情。
  • 静态数据安全:数据加密。
    • 使用众所周知的加密算法
    • 使用安全设备来单独加密和解密数据,或者使用单独的密钥库
    • 选择好需要加密的数据,因为加密解密计算开销很重
    • 在需要的时候才进行解密
    • 加密备份
  • 深度防御:
    • 防火墙
    • 通过日志检测是否发生了不好的事情,以便恢复系统。这里需要注意敏感信息,不要存在日志里。
    • 入侵检测和预防系统
    • 网络隔离,将服务放进不同的网段,控制服务间的通讯。
    • 操作系统,给用户尽量少的权限,按时打补丁,并关注操作系统本身的安全模块的发展。
  • 一些好的实践:
    • 保持节俭:只存储完成业务运营或满足当地法律所需要的信息,因为如果你不存储它,就没人可以偷走它,也没人可以要它。
    • 人的因素:考虑一个心怀不满的前雇员会如何损坏你的系统。
    • 黄金法则:不要实现自己的加密算法,不要发明自己的安全协议。
    • 内建安全:熟悉OWASP十大列表和安全测试框架。
    • 外部验证:进行渗透测试。

10. 康威定律和系统设计

  • 康威定律:任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。
    • 推论:组织的耦合度越低,其创建的系统的模块化就越好,耦合也越低;组织的耦合耦合度越高,其创建的系统的模块化也越差。
    • 两个披萨团队:没有一个团队应该大到两个披萨不够吃。
    • 协调成本:当协调变化的成本增加后,有一件事情会发生:人们要么想方设法降低协调/沟通成本,要么停止更改。前者导致新边界的产生,而后者导致我们最终产生庞大的、难以维护的代码库。
    • 团队结构与限界上下文:团队应该与限界上下文保持一致。这样哪怕是修改频率很低的服务也会有实际的所有者。
    • 反向康威定律:无论系统有什么设计缺陷,我们都不得不通过改变组织结构来推动系统的更改。
  • 服务所有权:拥有服务的团队负责对该服务进行更改。只要更改不破坏服务的消费者,团队就可以随时重新组织代码。所有权程度的增加会提高自治和交付速度,也使其对工作更负责。
  • 共享服务的原因:
    • 拆分服务成本太高:大型单块系统中比较常见。
    • 特性团队:由于特性团队负责开发某一特性的所有功能,所以需要跨越组件或服务的边界。其缺点是大范围采用特性团队后,所有的服务都会变成共享的,最终导致代码守护者的缺失。
    • 避免交付瓶颈:人员缺失或者突然出现大量变更需求,我们不得不在多个组之间共享服务。
  • 内部开源:如果无法找到方法来避免共享服务,可以使用内部开源的模式。
    • 守护者:分理处一组受信任的提交者作为核心团队,负责对所有的更改做某种程度的审批。
    • 服务成熟度:服务越不稳定或越不成熟,就越难让核心团队之外的人提交更改。
  • 人的调整:
    • 了解你的员工能够承受的变化,不要改变的太快。
    • 无论方法是什么,需要和员工阐明在微服务的世界里每个人的责任及其重要性。
    • 如果没有把每个人都拉到一条船上,你想要的任何变化从一开始就注定失败。

11. 规模化微服务

  • 故障无处不在:从统计学来看,规模化后故障将成为必然事件。所以我们可以少花一些在阻止不可避免的故障上,而花更多时间来优雅地处理它。
  • 关键思想:假设一切都会失败。
  • 扩展系统前,理解以下需求:
    • 响应时间/延迟:负载和对应的操作时间。
    • 可用性:比如,能不能接受系统故障?
    • 数据持久性:比如,数据丢失和保存时间。
  • 功能降级:当功能分散在多个不同的、有可能宕掉的微服务上时,能够安全地降级功能。
  • 架构性安全措施:确保如果事情真的出错了,不会引起严重的级联影响。
    • 反脆弱:通过引发故障来确保其系统的容错性(Simian Army);从故障中学习,不指责。
    • 超时:给所有的跨进程调用设置超时,并选择一个默认的超时时间。当超时发生后,记录到日志里看看发生了什么,并相应地调整它们。
    • 舱壁:把自己从故障中隔离开,比如,分离连接池,拆分服务,断路器,减载等等。
      • 减载(load shedding):系统资源达到饱和,则主动拒绝服务。
      • 断路器:当对下游资源的请求发生一定数量的失败后,断路器就会被打开。对于堆积的请求,我们可以稍后重试,或者快速失败。
    • 隔离:减少服务间的依赖。
  • 利用幂等处理错误:对幂等操作来说,其多次执行所产生的影响,均与一次执行的影响相同。
  • 扩展系统:帮助处理失败,提升性能。
    • 垂直扩展:使用更强大的主机。
    • 拆分负载:拆分服务,拆分部署。
    • 分散风险:不要把鸡蛋放在一个篮子里,多机部署,多数据中心部署,异地部署。
    • 负载均衡:避免单点故障,配置多实例处理请求;利用HTTPS终止负载均衡器和VLAN保证安全。
    • 使用基于Worker的系统:和负载均衡类似,只是一个推一个拉;保存待办作业列表可能需要较高的可靠性,使用支持持久化的消息代理。
    • 重新设计:最初的架构和能应对很大负载容量的架构可能是不同的;不要在前期为准备大量负载而构建系统,因为可能负载永远不会来。
  • 扩展数据库:
    • 关键:理解服务的可用性和数据的持久性
    • 扩展读操作:缓存和只读副本。
    • 扩展写操作:升级硬件,数据库分片或者考虑使用更适合的数据库系统。
    • 共享数据库基础设施:如果数据库出现故障,那么它会影响多个微服务,这可能导致灾难性故障。
    • 命令查询职责分离(CQRS):它不仅仅是读写分离,命令和查询的模型本身可以完全独立,使用不同的服务或部署在不同的硬件上,甚至使用不同的数据存储。比如:我们可以选择把命令作为事件,存储起来,查询模型可以根据存储的事件推算出领域对象的状态,或者只是获取一个聚合,然后更新其他的存储。
  • 缓存:
    • 缓存分类:客户端、代理和服务器端缓存
    • HTTP缓存:cache-control和ETag
    • 一些使用场景:
      • 为写使用缓存:后写式缓存,先写入本地缓存,并在之后的某个时刻写入下游数据源中。
      • 为弹性使用缓存:如果源服务故障,可以先使用缓存中的数据。
      • 隐藏源服务:如果大片缓存突然消失,超过源服务处理能力的请求会被透传给源服务,这时可以让源服务推送数据给缓存,而隐藏源服务以保证服务的可用性。
    • 保持简单:缓存越多,数据就越可能失效。
    • 缓存中毒:设置错误导致数据永远不失效。
  • 自动伸缩:
    • 条件:完全自动化地创建虚拟主机以及部署你的微服务实例。
    • 触发:预测型伸缩和响应型伸缩,另外不要太仓促缩容。
  • CAP定理:一致性(consistency)、可用性(availability)和分区容忍性(partitiontolerance),三个中间我们最多只能保证两个。
    • 定义:
      • 一致性:访问多个节点时能得到同样的值。
      • 可用性:每个请求都能获得响应。
      • 分区容忍性:集群中的某些节点在无法联系后,集群整体还能继续进行服务的能力。
    • 实践:
      • 牺牲一致性(AP):利用最终一致性来保证所有的节点在将来的某个时候都能看到更新后的数据。
      • 牺牲可用性(CP):如果节点之间无法通信,我们唯一的选择就是拒绝响应请求。实现正确的一致性实在太难了,所以请使用现有的数据存储和锁服务,不要试图自己发明。
      • 牺牲分区容忍性(CA):如果系统没有分区容忍性,就不能跨网络运行,所以CA系统在分布式系统中根本不存在。
  • 服务发现:找到现在正在运行的微服务。其原理基本都是提供一个机制给微服务实例注册,然后提供一种方法来搜索微服务。
    • DNS:将一个名称与一种服务关联在一起。优点是所有的技术栈都支持,缺点是因为TTL,更新DNS条目会有延迟,使用负载均衡器可以缓解这个问题。
    • 动态服务注册:Zookeeper,Consul和Eureka。
  • 文档服务:描述接缝暴露出来的API。(Swagger和HAL)
  • 自描述系统:从活系统中抽取出一些数据,来形成静态Web页面或Wiki。

12. 小结

  • 微服务的原则:
    • 围绕业务概念建模
    • 接受自动化文化
    • 隐藏内部实现细节
    • 让一起都去中心化
    • 可独立部署
    • 隔离失败
    • 高度可观察
  • 什么时候不应该使用微服务:如果你不了解一个领域,请不要使用微服务,因为无法找到正确的限界上下文。
  • 其他建议:
    • 尽量缩小每个决策的影响范围
    • 学会拥抱演进式架构的概念,持续地改变和演进系统