《微服务:从设计到部署》总结笔记

英文原文出自:https://www.nginx.com/blog/introduction-to-microservices/
笔记首发于个人语雀,有空来帮我点个稻谷呀。html

port_by_arsenixc-d90p0xb.jpg

1 简介

monolithic app(都写在一块儿)的劣势

image.png

  • 代码依赖极其复杂
  • 难以理解
  • 启动时间变长
  • CPU密集型逻辑和内存消耗型逻辑没法拆开优化,浪费云计算资源
  • 可靠性变差:一处内存泄漏,全家升天
  • 难以作架构升级,例如更换语言或框架

微服务

image.png

  • 把你的巨型应用拆解成小型的独立的互联的服务们
  • 服务之间互相之间提供服务和消费服务
  • 客户端经过API网关来调用服务们
  • 运行时的一组服务能够是多台物理机上的多个container
  • 服务拥有本身的独立的数据库存储,这意味着服务能够选择本身最适合的数据库类型。

微服务的优点

  • 拆解了复杂的应用,单个服务更好迭代和维护。
  • 每一个服务能够自由地选择最合适的技术栈。
  • 服务能够分开部署,能够更加自由地进行持续集成。

微服务的劣势

  • 必需要开发服务间通讯机制
  • 须要常常处理“部分失败”的状况,由于一个请求如今是是一组服务的调用。
  • 数据库结构设计和操做,例如跨表操做,数据一致性(事务)问题等。
  • 测试更加困难,由于要测试你的服务,你必须先启动其余服务。
  • 在维护或更新时,你一般须要更新多个服务。
  • 服务数量众多时,部署也是一个难题。

2 构建微服务:使用API网关

  • 【背景】例如在作一个电商宝贝详情时,你客户端须要调:购物车服务、订单服务、类目服务、评价服务、库存服务、发货服务、推荐服务....

不使用API网关,挨个调过去

  • 客户端经过移动网络发送N个网络请求,是很是不靠谱的
  • 服务可能用的并不是是web友好的协议,例如thrift,没法提供服务。
  • 让重构微服务们变得困难,由于它们被客户端直接依赖了

使用API网关

  • API网关能够调用多个微服务, 而后提供一个“item/detail?id=xxxx”的统一接口
  • API能够顺便作负载均衡、监控、受权和认证等等多种统一功能
  • 【优】封装了应用的内部结构设计,客户端只须要和API网关通讯
  • 【优】能够经过服务,自由组合出最适合客户端的API
  • 【劣】你须要单独开发、部署、管理API网关
  • 【劣】API网关可能成为开发的瓶颈,例如开发者暴露新的服务时要更新到API网关

实现API网关

  • 为了API网关的性能和可扩展性,应该支持异步NIO。
  • 在JVM上你可使用Netty、Vertx、Spring Reactor等NIO框架
  • 在非JVM上你可使用node.js来实现NIO
  • 你还可使用Nginx Plus(要钱的)

使用Reactive Programming

  • 对于互不相关的服务,应该同时一块儿调用。有前后顺序的服务可能要定义好前置和后置。
  • 使用传统的异步+回调的方式来书写组合服务的代码会让你很快陷入回调地狱。
  • Java、Scala、JS中都有响应式的方案,你还可使用ReactiveX来书写这类代码。

服务调用:

一般有两种调用方式java

  • 异步——消息队列
  • 同步——HTTP或者Thrift等

服务发现

  • 过去你一般手动控制服务的ip地址,可是在微服务体系中,你没法作到。
  • 因为各个服务的随时扩容和升级,它们的地址都是在动态变化的。
  • 因此API网关须要有服务注册,服务发现的能力。不管是server-side发现,仍是client-side发现。

处理部分失败

  • API网关不能够由于下游的失败而block住,它要根据场景来决定如何处理错误。node

    • 例如只是商品推荐服务挂了,商品详情页接口应该仍然返回其余全部数据
    • 若是是商品基础信息挂了,商品详情页接口应该反馈错误给到客户端。
  • 能够返回硬编码打底数据,或者缓存数据。(API网关作, 服务无感知)
  • 安利了一下Netflix Hystrix, 可是彷佛已经处于维护状态再也不更新了。

3 构建微服务:微服务架构中的跨进程通讯

  • 【背景】在monolithic应用中,组件之间互相经过语言级别的方法就能够进行调用,可是在微服务应用中,这些组件都被分布式地部署在不一样的机器上的不一样容器里。基本每一个服务都是一个进程,因此服务们不得不使用跨进程通讯(IPC)机制。

交互方式

  • 一对一通讯 vs 一对多通讯
  • 同步 vs 异步    

            Screen Shot 2019-12-19 at 2.32.43 PM.png

    • request/response: client发起请求而后同步等待响应结果。
    • notifications:client发出一个请求,但并不须要返回,也不等待。
    • request/async response: client发出一个请求,但响应是异步的,在等待响应的过程当中,client并不阻塞。
    • publish/subscribe: client发出一条消息,被一个或多个感兴趣的服务消费。
    • public/async responses: client发出一个请求消息,而后等待一段时间来接收感兴趣的服务的返回。
    • 每一个服务均可能用上述多种交互方式,例以下图image.png

    定义API

    • 服务的API是一种服务和它的客户端们之间的约定。
    • 使用某种接口定义语言(IDL)很是重要,例如Protobuf。
    • 你甚至可使用API-first这种方式,也就是先定义API再实现它,来进行开发。
    • API定义依赖于你使用的IPC机制,例如使用消息那么API就须要消息通道和消息类型。

    更新API

    • 在monolithic的app里,你通常改了API后,去代码里直接改全部的调用处.
    • 在微服务体系里, 更新API会困可贵多,你没办法让你服务的消费者挨个更新。你可能要增量地添加新版本的服务,线上可能同时存在两个版本的API,这是一种很是重要的更新策略。
    • 为了实现上述能力,一种可行的方式是添加版本号,让多个版本的API同时存在。

    处理部分失败

    • 在微服务体系中,部分失败是很是常见的,由于全部服务们都在不一样的进程里。
    • 假设有一个场景,你的产品详情页里须要使用推荐服务,而此时推荐服务挂掉了:image.png

    若是你按照一直等待去设计,那么颇有可能会消耗掉你全部的线程,致使产品详情服务完全挂掉linux

    • 下面有4条由Netflix推荐的部分失败错误处理策略nginx

      • 网络超时:永远不要一直等待,必定要有超时重试/超时失败机制。
      • 限制等待中请求数量:等待中的请求应该有一个上限值,一旦上限达到了,就不该该再处理请求,这些超出限额的请求应该当即失败。
      • 短路模式:当失败率达到必定程度时,后续的请求应该当即失败,再也不请求。短路后,应该每隔一段时间重试,若是重试成功,那么就再也不短路。
      • 提供降级:提供失败时的降级逻辑,例如得到缓存数据。

    IPC机制

    • 异步、基于消息的IPC机制git

      • client向某服务发送一条消息,但并不等待它。若是此次调用须要返回,被调用服务会也异步经过消息返回给client。client彻底基于异步,也就是不等待的方式来编写。
      • 消息通常由header和body构成,而且经过channel来交换。
      • 两种消息channel:1. 点对点 2. pub/sub
      • 案例:image.png
      • 出行服务发一条消息“一个新出行建立了!”到名叫“新的出行”的pub/sub型channel中,通知全部感兴趣的服务(好比派单服务)。派单服务找到了合适的司机之后,发送一条消息“司机接单了!”到一个名叫“派单”的pub/sub型channel中,通知全部感兴趣的服务。
      • 消息队列实现有很是多种:RabbitMQ、Kafka、ActiveMQ.....
      • 用消息机制的优势:github

        • 把服务的consumer和provider经过消息解耦了。
        • 消息可堆积,比起实时调用有更高的容错率。
        • 调用方式也解耦了,consumer和provider之间只须要遵照消息约定便可。
      • 用消息机制的缺点:web

        • 更高的运维复杂性,消息系统也是一个要维护的系统啊!
        • 要实现请求-响应这种同步请求会更加麻烦
        • 【做者补充】消息队列的高可用、不被重复消费、可靠性、顺序性都是很是复杂的课题。
    • 同步、请求/响应型的IPCdocker

      • 一般来讲,一个线程会在等待请求时阻塞。有些可能已经经过Future或者Rx Observables这种Ractive Pattern的东西把它变成了异步的方式。
      • 可是在这种类型的IPC里,client一般但愿请求可以尽快及时地返回。
      • 常见的主要由两种协议:Rest、Thrift
      • 基于HTTP方式的协议(Rest)的好处:数据库

        • 简单熟悉
        • HTTPAPI能够直接在浏览器、Postman、curl等多种环境下测试。
        • 直接支持 请求/响应 模式
        • 没有任何中间代理,系统架构简单
      • 基于HTTP方式的协议(Rest)的坏处:

        • 只支持 请求/响应 模式
        • provider和consumer都必须活着不能挂
      • 基于HTTP方式的一些IDL或平台:RAML、Swagger
      • Thrift

        • 使用一个C风格的IDL来定义API,而且使用Thrift编译器来生成客户端和服务端代码模板。
        • 支持C++、Java、Pyhton、Ruby、Erlang、Node.js
        • Thrift方法能够返回空值,也就是能够实现单向的通知,并不必定要返回。
        • 支持JSON/二进制等格式
    • 消息格式

      • 无论用什么方式和语言,最好选择语言无关的消息格式,你没法保证未来你不换语言。
      • 在文本和二进制中,tradeoff大概就是包大小or人类可阅读性。

    4 微服务架构中的服务发现

    为何使用服务发现?

    • 你之前,你可能把要调用的IP放在配置里,而后直接调用
    • 可是在云时代的微服务应用中,你应该不太能这么作,看图:

    image.png

    • 你会发现,服务实例们都有着动态分配的网络位置,并且这些位置因为扩容、更新等等还在变化。
    • 目前主要由两种服务发现:client侧服务发现、server侧服务发现

    client侧服务发现

    image.png

    • 这种模式下,consumer决定了所调用服务的网络地址。全部的服务provider,都把本身的网络地址,注册到服务注册中心,consumer拿到了一坨ip地址以后,本身选择一个load-balancing策略,而后调用其中一个。
    • 服务Provider在启动时向注册中心注册服务,在结束进程时给注册中心注销服务。服务的健康经过心跳机制来进行保障。
    • Netflix OSS是一个client侧服务发现的好例子,Netflix Eureka是一个REST型服务注册中心。
    • Netflix Ribbon是一个IPC客户端,可以和Eureka合做,提供load-balancing。
    • 优势:1. 简单直接  2. 因为consumer决定了路由,能够作灵活的、应用特定的负载均衡策略
    • 缺点:耦合了consumer和服务注册中心,你必须给每一个编程语言实现consumer侧的服务发现逻辑。

    server侧服务发现

    image.png

    • consumer经过一个load balancer来请求provider,load balancer要请求服务注册中心来得到全部可提供服务的实例以及它们的网络地址。也就是load-balancer决定了到底要请求谁。
    • 亚马逊的ELB就是一个server侧服务发现路由。ELB把服务注册直接作到了本身里面。
    • Nginx也能够充当server侧服务发现中的load balancer,好比这篇文章里,你可使用Consul Template来动态更新nginx的反向代理。
    • 一些部署环境例如K8S或者Marathon,在集群里的每一个宿主上都运行Proxy,这个Proxy就扮演了server侧服务发现中的load-balancer。若是你想给一个服务发请求,一个consumer必须经过proxy来调用。
    • 优势:

      • consumer和服务发现是彻底解耦的,无脑请求load-balancer就行了。
      • 不用再给每一个语言实现一套load-balancing机制了。
      • 有些部署环境甚至直接提供了这种load-balancing服务。
    • 缺点:若是没有提供好的load-balancing服务,你就要本身实现。

    服务注册中心

    • 概念详解

      • 它就是一个服务实例们网络位置的数据库。
      • 它必须高可用,并实时更新。
      • 它实际上是一坨实例们,这些实例们要经过某种副本协议来保持一致性
    • 好比Netflix Eureka,它是基于REST的。它经过POST方法来注册服务,每30秒经过PUT方法刷新一次,经过DELETE方法删除服务,经过GET方法来得到一个可用的服务实例。
    • Netflix为了实现服务注册的高可用性,经过运行N个Eureka实例,使用DNS的TEXT记录来存储Eureka集群的配置,这个配置也就是一个可用区->一组网络地址的map。当一个新的Eureka启动,它就请求DNS来得到Eureka集群配置,而且给它本身一个新的IP。Eureka的clients,services就请求DNS来发现Eureka实例们的地址,而且会尽量选相同可用区的Eureka实例。
    • 其余的服务注册中心们还有

      • etcd:K8S和Cloud Foundry用的它
      • consul
      • Zookeeper:你们最熟悉了
    • 在K8S、Marathon、AWS中并无显式的、单独的服务注册中心,由于它们是内置的基础设置。

    服务注册的方式

    • 目前主要有两种方式来处理服务的注册和注销:自注册模式和三方注册模式。
    • 自注册模式image.png

      • 这种状况下,服务实例本身负责注册和注销它提供的服务,同时服务本身还须要不停地发送心跳包来阻止本身的注册过时。
      • Netflix OSS Eureka client是一个很好的自注册例子。Spring Cloud中也能够直接使用注解来实现注册方式。
      • 优势:简单直接、不须要其余系统组件。
      • 缺点:把服务实例和注册中心耦合起来了,你要实现各个编程语言的注册代码。
    • 三方注册模式image.png

      • 顾名思义,服务实例们要向一个注册管理者(registrar)来进行注册,而注册管理者也经过健康检查机制来跟踪服务实例们的状况,随时注销挂掉的服务实例。
      • 有一个开源的注册管理者叫作Registrator ,它能自动注册和注销以docker container方式部署的服务实例。Registrator支持etcd和Consul。
      • 还有一个有名的注册管理者是NetflixOSS Prana,它主要是为了非JVM语言设计的,它是一个sidecar应用,也就是说它跟着每个服务实例运行。Prana和Eureka配合,向Eureka进行注册和注销服务。
      • 注册管理者,也是不少部署环境的内置基础设施,例如在AWS EC2和K8S中都是。
      • 优势:服务和服务注册中心解耦、你也再也不须要去实现特定语言的注册逻辑。
      • 缺点:若是部署环境没提供注册管理者,那你就要本身实现。

    5 微服务中事件驱动的数据管理

    背景

    • 在一个monolithic的应用中,使用关系型数据库的一个好处是,你可使用符合ACID原则的事务。
    • ACID原则

      • Atomicity 原子性: 变动要么都成功,要么都失败。
      • Consistency 一致性:数据库的状态永远是一致的。
      • Isolation 独立性:事务之间不会有交错执行的状态(由于可能会致使数据不一致)。
      • Durability 持久性:事务成功后,修改是持久的。
    • 另外一个好处是你可使用SQL,你能够轻松地进行多表操做,数据库帮你解决大部分性能问题。
    • 遗憾的是当切换到微服务架构之后,这些好处你就再也不可以享受,由于全部的数据都被各自的微服务所拥有,也就是说他们拥有各自独立的数据库,数据的访问只能经过API层面来进行。
    • 来吧,更糟糕的是:不一样的微服务可能还用了不一样类型的数据库。例如NoSQL系列的,graph型的(例如Neo4j)。因此微服务架构中一般混用各类类型的数据库,咱们称之为polyglot persistence 。

    挑战1:如何在多个微服务中实现事务

    • 假设有一个在线的B2B商店,“客户服务”维护了客户信息,“订单服务”管理订单们,而且保障一个新订单不会用完客户的余额。
    • 在过去的monolithic版的应用中,咱们用一个事务,就能搞定“检查余额够不够+建立订单”这件事。
    • 可是在微服务体系中,咱们的ORDER表和CUSTOMER表如今是私有的:image.png
    • 订单服务并没有法直接访问CUSTOMER表,它只能经过客户服务提供的API来间接修改CUSTOMER表。也许你可使用分布式事务,也叫two-phase-commit(2PC)来解决。可是在现代化的应用中,你很难使用2PC。著名的CAP theorem告诉咱们,在ACID中你要抉择要C仍是A,而通常更好的选择都是A。并且,在不少NoSQL的数据库中根本不支持2PC。

    挑战2:如何进行多表查询

    • 假设你的客户端须要展现客户的信息以及他全部的订单。
    • 若是订单服务提供了根据客户id查它订单的服务,那么你能够直接调这个服务。
    • 可是若是订单服务,没有提供这个服务,好比只支持按照订单id查询时,你该怎么办呢?

    事件驱动架构

    • 在大部分应用中,解决方案就是使用数据驱动架构。当一个值得注意的事情发生了之后,这个微服务发出一个事件,好比“它更新了某个entity”这件事。其余对此感兴趣的微服务订阅这些事件,当它们收到这件事情时,就作它们本身的业务逻辑,好比把本身对于这个entity的冗余字段也更新一下。
    • 你可使用事件来实现跨越多个服务的事务,这样的一个事务,包括的许多步骤,每一个步骤包括一个微服务更新本身的业务逻辑entity,而且发出一个事件通知下一些微服务。来看下面这个流程:

      • 图1:订单服务建立了一个新订单,发出一个“一个订单建立辣!!”的消息

            image.png

    • 图2:客户服务消费这个“一个订单建立辣!!”消息,而且给这个订单预留了余额,而后发送一个“已预留余额”事件

              image.png

    • 图3:订单服务消费“余额已预留”事件,而且把订单状态设为“建立成功”

    image.png

    - 这里面须要假设
    - 每一个服务自动地更新数据库,而且发布事件
    - 消息代理必须保证事件至少都被交付了一次
    • 尽管如此你也只是实现了事务,没有实现ACID的事务,这仅仅是提供了弱保障,例如eventual consistency. 这样的事务模型被称为BASE model.
    • 【解决查询问题】你还可使用事件来维护一个额外"物料化视图",这个视图包含的预先join好的,来自多个微服务的数据。例如能够专门有一个“客户订单视图”服务,来专门订阅相关事件(客户服务产生的事件、订单服务产生的事件等),而后更新这个视图。image.png

      • 你可使用文档化数据库例如MongoDB来实现这个额外视图,而且给每个客户都存一个document。这样当有一个客户来的时候,你能够光速地反馈这个客户的相关订单,由于你已经事先存好了。
    • 事件驱动架构的优势

      • 它使得跨多个服务的普通事务得以实现。
      • 它让应用能够经过“物料化视图”实现快速查询。
    • 事件驱动架构的缺点

      • 编程将更加更加复杂
      • 你必须得实现补偿事务(回滚等)来从应用级错误中恢复。例如:当你检查余额失败时,你应该取消订单。
      • 应用将要面对数据不一致的状况,例如“物料化视图”中的数据并不是最新的。并且还要处理事件相关的各类问题,好比重复消费(幂等性),漏消费等问题(其实也就是消息队列常见问题系列)。

    实现原子性

    • 【背景】在事件驱动架构里,你还会遇到一个原子性问题

      • 假设订单服务要插入订单表,同时发出一个“订单建立辣!”事件,这两步操做必需要原子地完成。
      • 假设服务在插入了订单表,发出事件以前挂掉了,那么系统就会出现一致性问题。
      • 一般标准的作法是使用分布式事务,可是如上面所说(好比CAP theorem),这并非咱们想作的。
    • 使用本地事务完成发送事件

      • 这个操做有个术语叫作multi‑step process involving only local transactions
      • 借助本地事务,其实就是你要一个本服务的事件表,它的做用相似消息队列:image.png

        • 事务1:订单服务插入一个新订单,而且在事件表里插入一个新的订单建立事件。
        • 事务2:事件发送线程读取事件表找到未发送的事件,更新时间并标记事件已发送。
        • 【笔记】这样就保障了“插表+创未发事件”和“事件发送并更新事件状态”的原子性,首先这两个由于是事务因此必定全成功或者全失败,其次是万一在二者之间的时候挂掉了,重启之后事件发送线程还会继续事务性地读取未发事件并从新发送。
      • 优点:不依赖2PC(分布式事务)也能保障每次更新的原子性,发送的也是仍然是业务逻辑层面的事件。
      • 劣势:写代码容易漏写(??我反正没看懂原文啥意思,由于复杂就特么能漏写??)。当使用NoSQL型数据库时会很困难,由于它们的查询能力和事务性都比较差。
    • 挖掘数据库事务日志

      • 另外一种不用2PC实现原子性的方法就是,有一个单独的线程去挖掘数据库事务或者commit日志。也就是数据库被更新了之后,就有日志,而这个挖日志线程就读日志,而后发消息给消息代理。image.png
      • 一种实现方式就是使用开源的LinkedIn Databus ,它能够挖掘Oracle事务日志并发送消息。LinkdeIn用它来保障多个数据存储的一致性。
      • 另外一个例子是streams mechanism in AWS DynamoDB,是一种托管的NoSQL数据库,它有一种DynamoDB流,按照时间顺序记录了24小时内的增删改查。应用能够读这些变化来发送事件。
      • 优点:保障了每次更新均可以发送事件。事务日志也能够简化应用逻辑,由于把事件发送和应用业务逻辑拆分开来了。
      • 劣势:每一个数据库的事务日志都不太同样,而且随着数据库版本变化,并且你还须要作高层业务逻辑到底层数据库日志的转换。
    • 使用数据溯源

      • 数据溯源是:不直接存储实体如今的状态,它则是存储数据变化的事件(是否是想到了Redux的Action和时间旅行?)。应用会回放全部的数据变化事件来更新实体的状态。至关于先事件再业务了,因此保障了原子性。
      • 举个🌰,按照传统的作法,一个ORDER实体对应数据库里ORDER表,可是在数据溯源方式里,表里存的都是状态变化:订单建立、确认、发货、取消等等。image.png
      • 事件都存储在了Event Store,这个Event Store其实就像架构里的事件代理。它提供了其余服务订阅这些事件的API。
      • 优点:

        • 顺便就实现了事件代理,一箭双雕。
        • 它用一种微服务的方式解决了数据一致性问题。
        • 因为是持久化事件而非实体,他基本避免了 object‑relational impedance mismatch problem
        • 业务逻辑和业务实体耦合程度低,这让它具有更好的迁移性。
      • 劣势:它是一种彻底不同的编程范式,有陡峭的学习曲线。Event Store只直接支持按主键查询业务实体。你必须使用Command Query Responsibility Segregation来实现查询。

    6 选择一种微服务部署策略

    动机

    • 部署monolithic应用时,你一般是在N台物理机(或虚拟机)上部署M个相同的服务实例。这种部署要比微服务要更直接、简单。
    • 微服务一般包括上百的服务,而且它们用了不用的语言和框架。为了让某个功能运行,你可能要起一坨服务才能work。每一个服务都有本身单独的部署、资源消耗、扩容、监控的方式。因此微服务的部署虽然很困难,可是也必需要保障快速、可靠、低耗。

    每一个宿主机多个服务实例模式

    • 这种模式就是,你在多台机器上部署多个不一样的服务实例,不一样的服务实例在不一样的端口上。

    image.png

    • 这种模式还有不少变种,例如

      • 每个服务实例是一个或一组进程

        • 部署一个Java服务实例做为一个web应用放在Apache Tomcat 上面。
        • 一个Node.js服务实例可能包含一个父进程和多个子进程。
      • 在同一个进程或进程组运行多个服务实例

        • 你能够在同一个Tomcat上运行多个java web应用
        • 在OSGI容器上运行多个OSGI的bundle
    • 优点

      • 资源利用相对合理有效,它们共享操做系统和服务器。例如:多web应用共享tomcat和JVM。
      • 部署很是快速,你只要把代码或者构建产物copy到机器上而后启动就好了。
      • 启动服务很是迅速,由于服务进程就是它本身,它就启动本身就行了。
    • 劣势

      • 服务实例之间没有太多隔离,就算你能够监控每一个服务用了多少资源,你没办法限制每一个服务实例能用多少。有一些服务也许能够消耗掉全部的内存和CPU。若是是同一个进程下的多个服务实例,就更加没有隔离了,他们可能共享好比JVM堆。
      • 运维团队必须知道部署你这个服务的细节,由于不一样的应用实现方式都不同,语言、框架、依赖、环境均可能不太同样。这会增长运维的风险。

    每一个宿主机一个服务实例模式

    • 另外一种方式就是,每一个宿主机只有一个服务实例。这种模式下主要由两种子模式:每一个虚拟机一个服务实例和每一个容器一个服务实例
    • 每一个虚拟机一个服务实例

      • 你把每个服务都打成一个虚拟机镜像,例如 Amazon EC2 AMI。每一个服务实例都是一个运行这个镜像的虚拟机image.png
      • Netflix就用这种方式来部署它的视频串流服务,它使用 Aminator来把每一个服务打成AWS EC2 AMI,每一个服务实例运行在一个EC2身上。
      • 还有不少其余工具你能够用来构建你本身的VM,你能够用一些持续集成工具(好比Jenkins)来调用Animator来打包成虚拟机镜像。Packer.io 也是一个不错的选择,它支持各类虚拟化技术,不只只有EC2。
      • CloudNative有一个叫Bakery的服务,它是一个用来建立EC2 AMI的SaaS。你能够配置你的CI服务器来调用Bakery(固然前提是你过了你的测试),Bakery会帮你把你的服务打包成成一个AMI。
      • 优点

        • VM之间相互独立,拥有固定的CPU和内存,不会互相攫取资源。
        • 能够借助成熟的云计算基础设施,例如AWS,来快速扩张和部署。
        • 把你的服务封装成了VM之后,就是黑盒了,部署不须要关心细节。
      • 劣势

        • 资源利用更加低效,由于有时候服务并不能榨干虚拟机的性能,而虚拟机就会有性能剩余。
        • 基于上一点,不少云服务按照虚拟机数量收费,并无论你的虚拟机忙仍是闲。
        • 当部署新版本的虚拟机时一般很慢,由于虚拟机大小一般都是比较大的,而且初始化,启动操做系统等等也须要时间。
        • 维护虚拟机自己也是一个很重的担子。
    • 每一个容器一个服务实例模式

      • 每一个服务实例运行在它本身的容器里。容器是一种 virtualization mechanism at the operating system level,你能够简单理解为更轻量级更厉害的虚拟机。一个容器包含了一个或多个运行在沙箱里的进程。你能够限制每一个容器的CPU和内存等资源。常见的容器技术有DockerSolaris Zones.image.png
      • 为了使用这种模式,你要把你的服务打包成一个容器镜像,容器镜像包含了一个完整的linux文件系统,服务自己的代码,相关的依赖等,一切的目的都是为了让这单个服务跑起来。好比你要打包一个java应用的容器镜像,你可能就须要一个java运行时,一个Tomcat,以及你编译后的jar包。
      • 当你须要在一个机器上运行多个容器的时候,你可能就须要服务编排工具了,例如Kubernetes 或者 Marathon。它基于容器对资源的需求以及如今剩下的资源,来决定容器到底放哪里。
      • 优点

        • 和VM同样,隔离了你的每一个服务实例。
        • 和VM同样,封装了你使用的技术,容器能够不关心细节就部署。
        • 不一样于VM,容器更轻,构建也更快,启动也更快.
      • 劣势

        • 容器常常部署在按VM数量收费的IaaS上,也就是说,你可能要花额外的钱。
    • 容器和VM的边界正在慢慢消失,二者正在互相靠拢。

    Serveless部署

    • AWS Lambda就是一个很是典型serveless部署,他支持你用各类语言写代码,这些代码做为一个函数,能够直接响应请求或事件。AWS会帮你解决下面的机器、内存等物理需求,你只须要关心业务逻辑就行了。
    • 一个Lambda函数是一个无状态服务,它能够直接和AWS其余的服务交互,好比当S3插进来一个新东西的时候,可让一个函数响应并作后处理。函数还能够调用其余三方服务
    • 你有这么些方法能够调用一个函数

      • 直接经过你的服务来请求。
      • 经过AWS其余服务产生的事件触发。
      • 提供给AWS的Api gateway来提供HTTP服务。
      • 周期性的运行,基于一种cron的时间表。
    • 你能看到,AWS Lambda是一种很是方便的部署微服务的方式,而且它还基于请求收费,你只须要为你的使用量付费。你也不用关心底层的IT基础设施.
    • 劣势

      • 它不适合用来作长时间运行的服务,好比要从某个消息代理持续消费消息.
      • 请求必须在300秒之内完成.(AWS的限制吧)
      • 服务必须无状态,并且上一次函数调用和下一次函数调用极可能不在一台机器上。

    7 从一个单应用重构为微服务

    • 你最好不要使用“Big Bang”策略,也就是彻底重写你的服务。既危险又耗时。
    • 你能够增量性地重构你的应用,你能够逐渐地构建一个基于微服务的新应用,和你的原来应用同时运行。随着时间的推移,全部的功能都被慢慢的从原应用迁移到微服务。

    策略1 中止挖坑

    • 不要继续把这个monolithic的天坑继续挖了,若是要加新功能,不要加到这个大应用里。你把新需求用微服务的模式来作。image.png
    • 如图,你新加了一个request router,把新功能的请求路由到你的新微服务里,老功能路由到老monolithic应用里。有点像API网关。
    • 你还加了一坨胶水代码,其实就是为了新老服务之间互相调用,由于他们之间也可能有交互。你能够用RPC、直接访问数据库、访问老数据库同步过来的数据等方式来实现这种调用。
    • 策略优点:它阻止了原应用变得更加难以维护。新开发的服务能够独立地开发和部署。你能够当即开始享受微服务带给你的好处。
    • 但这个策略没有对原应用作任何优化,你须要看策略2来如何改造原应用。

    策略2 分离先后端

    • 这个策略主要思路是,帮你分离展现层和业务逻辑层以及数据访问(DAO)层,从而作到让原来的monolithic应用缩小一些。一般一个典型的企业级应用有这些组件层:

      • 展现层:处理HTTP请求和展现web界面的组件们。
      • 业务逻辑层:应用实现业务逻辑的核心组件们。
      • 数据访问层:应用访问数据和消息代理的基础组件们。
    • 有一种常见的作法,你能够把你的应用按照下图,拆分红两个子应用:image.png

      • 一层子应用包括了展现层。
      • 另外一层子应用包含了业务逻辑和数据访问。
    • 策略优点

      • 它让你可以单独地部署和扩容两个应用,它们各自均可以快速迭代
      • 因为业务逻辑层和数据访问层单独抽离了一个应用,你的新微服务如今能够调用这坨UI无关的服务了。
    • 然而这个策略仍然只是一个部分解决方案,有可能拆分后两个应用仍是会变成难以维护的monolithic应用,因此你还须要看策略3。

    策略3 抽取服务

    • 这个策略就是要把现有的在你原应用里的模块转换成单独的微服务。每次你抽出来一个新的微服务,原应用就变小了。只要你抽得足够多,原来的这个大应用就会消失或者干脆也变成一个微服务。
    • 转换成微服务的优先级

      • 首先你最好先抽象容易抽象的模块,这样你就能先积累抽象微服务的经验
      • 而后你应该优先抽取能给你带来最大收益的模块,因此你须要给你的模块排一个优先级。
      • 你还能够先抽象要特别的物理资源的模块,好比某个模块特别须要内存数据库,你能够先抽这个模块,而后把它放到内存比较大的环境里。
      • 抽象模块的粒度,能够按照这样一个简单原则:好比某个模块和其余的模块的交流均可以经过异步消息完成,那么这个模块就能够抽出来。
    • 如何抽取一个模块

      • 首先要定义抽出来的模块如何和系统进行交互,一般是一组双向的API。但一般都比较难,由于这种API会和系统耦合得比较多。用了Domain Model pattern 的就更难重构了。
      • 一旦你实现了粗粒度的接口,你就要把模块抽出来作一个单独的服务了。这时候为了同心,你还须要使用IPC机制。image.png
      • 在上面的例子中,模块Z是要被抽象出来的模块。它的组件被模块X和模块Y使用了,因此

        • 第一步就是要定义出一组粗粒的API来让X和Y经过API和Z交互。
        • 第二步就是把模块弄成单独的服务。这时候你须要IPC通讯来完成他们之间的跨服务调用。
      • 你甚至能够按照新的API来从新写这个抽象的服务。你每抽出来一个服务,你就朝着微服务方向前进了一步,随着时间推移,你的原应用终将消散,而你就把它演进成了一套微服务。
    相关文章
    相关标签/搜索