# 微服务架构 技能图谱
## 理论基础
### 概念
#### 多微合适
- 非代码函数
- 非重写时间
- 适合团队最重要
- 独立业务属性
- 全功能团队
#### 进程隔离
- 服务运行在独立的进程中
#### 轻量级通信
- 协议跨平台
- 格式语言无关
#### 独立性
- 独立开发
- 独立测试
- 独立部署
### 本质
- 服务作为组件
- 围绕业务组织团队
- 产品驱动而非项目驱动
- 技术多样性
- 业务数据独立
- 基础设施自动化
- 演进式架构
### 优点
- 按需伸缩
- 独立部署
- 业务独立
- 技术多样性
### 缺点
#### 1. 运维成本高
- 环境配置(Provisioning)
- 部署
- 监控
#### 2. 测试成本高
- 自动化测试
- 契约测试
#### 3. 依赖管理成本高
- 版本管理
- 服务依赖
- 服务治理
### 与SOA的差异点
- 实现方式
- 服务粒度
- 集成方式
- 部署方式
## 常用模式
### 部署模式
- 单机多实例
- 单机单实例
- 容器多实例
- 容器单实例
### 服务发现
- 客户端发现
- 服务器端发现
#### 常用库/工具
- Consul
- Eureka
- SmartStack
- Etcd
### API网关
- 请求转发
- 响应合成
- 协议转换
- 安全认证
### 服务注册
- 自注册
- 第三方注册
#### 常用库/工具
- Consul
- Eureka
### 服务配置
## 组织结构
- 全功能团队
- 去中心化
- 康威定律
## 数据相关
- 数据库
- RDBMS
- NOSQL
- 数据伸缩
- 读
- 写
- 缓存
- 业务数据隔离
## 安全策略
- 单点登录
- 点对点验证
- 安全网关
## 通信机制
### 同步通信
- RPC/RMI
- Java RMI
- Thrift
- Protocol Buffer
- REST
- HAL
### 异步通信
#### 消息队列
- ActiveMQ
- MSMQ
- RabbitMQ
#### 后台任务
- Sidekiq
- Resque
## 开发实践
### 开发模板
#### JAVA栈
- SpringBoot
- SpringCloud
- DropWizard
#### Ruby栈(其他脚本语言类似)
- 代码结构
- 测试结构
##### 配置文件模板
- WEB服务器配置
- 日志格式
- 监控文件配置
- 告警文件配置
##### 部署脚本模板
- Shell
- Chef
- Puppet
- Ansible
##### 持续集成模板
- Jenkins
- Bamboo
### 服务说明文件
- 服务描述
- 责任人
- 请求/响应描述
- 开发环境搭建
- 运行环境
- 测试策略
- 部署方式
- 监控告警
### 服务结构
- 模型表示层
- 业务模型
- 业务逻辑
- 模型存储
- 集成网关
## 测试实践
### 单元测试
- 基于Mock/Stub
- 基于真实依赖
### 集成测试
- 服务间集成
- 数据库集成
- 与第三方接口集成
### 组件测试
- 进程内
- 进程外
### 契约测试
- PACT
- PACTO
### E2E测试
- Selenium
- WebDriver
### 性能测试
- Gatling
- JProfiler
- JMeter
- Simperf
### 部署实践
#### 部署环境
- 数据中心/VM
- 公有云(IAAS/PASS)
- 私有云(IAAS/PASS)
- 容器化
#### 应用部署
- 包部署(Tar、RPM、War)
- 映像部署(AMI/...)
- 容器部署(Docker)
#### 基础设施部署工具
- Chef
- Puppet
- Ansible
- CloudFormation
#### 部署策略
- 蓝绿部署
- Immutable Deployment
#### 自动化策略
- 依赖持续部署流水线
- 基础设施自动化
- 应用部署自动化
#### 伸缩策略
##### Scaling Cube
- X轴水平克隆
- Y轴功能性按需伸缩
- Z轴数据分离
### 运维实践
#### 监控
##### 系统监控
- CPU
- 内存
- 磁盘
##### 应用监控
- 健康性
- 响应时间
- 关联ID(Correlation ID)
- 业务相关Metrics
##### 工具
###### SAAS
- NewRelic
- OneAPM
###### Hosted
- Nagios
- Zabbix
#### 告警
##### 告警方式
- 电话
- 短信
- 邮件
- 即时通信工具
##### 告警级别
- OnCall
- Backup
- Owner
- Leader
##### 工具
- Splunk
- Nagois
- PagerDuty
#### 日志聚合
- 聚合每个服务实例的日志
- 关联相关日志
##### 工具
- Splunk
- ELK(ElasticSearch/LogStash/Kabana)
- Fluent
- Flume
首先,微服务简单来说就是细粒度的独立的服务。在微服务架构里面这些服务都是独立部署的,服务是独立开发测试变更。这些服务都有自己的数据,这是微服务架构。
微服务架构概念解析
作者介绍:Chris Richardson,是世界著名的软件大师,经典技术著作《POJOS IN ACTION》一书的作者,也是 cloudfoundry.com 最初的创始人,Chris Richardson 与 Martin Fowler、Sam Newman、Adrian Cockcroft 等并称为世界十大软件架构师。
微服务在当下引起广泛关注,成为文章、博客、社交媒体讨论和大会演讲的热点;在 Gartner 的 “Hype Cycle” 上排名也非常靠前。与此同时,在软件社区也有人质疑微服务并非新事物。反对者认为微服务只是 SOA (Service Oriented Architecture)的二度包装。然而,无论是追捧还是质疑,微服务架构拥有巨大优势,尤其是它让敏捷开发和复杂的企业应用交付成为可能。
本系列包含 7 篇文章,介绍了微服务的设计、构建和部署,并与传统的单体架构进行了比较。本系列将分析微服务架构的各种因素,你也将了解微服务架构模型的优劣、是否适合你的项目,以及如何应用。
构建单体应用
假设我们要开发一款全新的与 Uber 和 Hailo 竞争的打车软件。在前期的会议和需求整理后,你要么需要手动创建一个新项目,要么可以使用 Rails、Spring Boot、Play 或者 Maven 来生成。这个新应用可能采用了六边形架构模块,如下图所示:
应用的核心是商业逻辑,它由定义服务、域对象和事件各模块来完成。各种适配器围绕核心与外部交互。适配器包括数据库访问组件、生产和消费 信息的消息组件,以及提供 API 或者 UI 访问支持的 web 模块。
尽管拥有逻辑缜密的模块化设计,整个应用仍然以整体打包和部署,实际格式依赖于应用的语言和框架。譬如,许多 Java 应用被打包为 WAR 文件,部署在 Tomcat 或者 Jetty 这样的应用服务器。有些 Java 应用本身就是包涵 JARs 的软件包。与此类似,Rails 和 Node.js 应用也通过目录层级打包。
采用此种风格的应用非常普遍。由于 IDE 和其他工具擅长构建单一应用,这类应用也易于部署。这类应用也非常容易测试。你可以非常轻松地进行端到端测试,使用 Selenium 测试 UI 。整体应用也便于部署,只需将软件包复制到服务器。你也可以通过运行多个包和负载均衡实现扩展。在项目早期这么做非常有效。
踏入单体架构的地狱
很不幸,这一简单的方法有着巨大的局限。成功的应用最终会随着时间变得巨大。在每个 sprint 阶段,开发团队都会新加许多行代码。几年后,原本小而简单的应用会变得臃肿。举个极端的例子,我最近与一位开发者交流,他正在开发一款小工具,来分析他们应用(包括几百万行代码)中的几千个 JARs 的依赖。我相信每年都会有大量开发者不遗余力地对付这种麻烦。
一旦你的应用变得庞大、复杂,你的开发团队将饱受折磨,苦苦挣扎于敏捷开发和交付。一大原因就是应用已经格外复杂,庞大到任何一个开发者都无法完全理解。最后,修复 bug 和实施新功能也就极其困难且耗时颇多。更可怕的是,这是一个向下的螺旋发展。代码库越难理解,正确的修改就越难。最后你会深陷庞大的、无法估量的泥淖之中。
而这种应用的尺寸也会拖慢开发进度。应用越大,启动时间越长。譬如在最近的调查中,不少开发者指出启动时间长达 12 分钟。我也听说有的应用启动时间居然得 40 分钟。如果开发者不得不频繁重启应用服务器,那大量时间就被浪费,生产效率也饱受其害。
庞大且复杂的单体应用的另一大问题就是难以进行持续部署。现在, SaaS 应用的发展水平足以在单日内多次将修改推送到生产环境。然而要让复杂的单个应用达到此水平却极为棘手。想更新应用的单个部分,必须重新部署整个应用,漫长的启动时间更是雪上加霜。另外,由于不能完全预见修改的影响,你不得不提前进行大量人工测试。结果就是,持续部署变得不可能。
如果单体应用的不同模块在资源需求方面有冲突的话,那应用的扩展也很难。比如,模块之一需要执行 CPU-intensive 图像处理逻辑,最好部署到 AWS 的 EC2 Compute Optimized instances;而另一模块需要内存数据库,最好适配 EC2 Memory-optimized instances。由于这两个模块需要共同部署,你不得不在在硬件选择方面做妥协。
单体应用的另一问题就是可靠性。由于所有模块都运行在同一进程中,任何模块中的一个 bug,比如内存泄漏都可能弄垮整个进程;此外,由于应用中的所有实例都是唯一,这个 bug 将影响整个应用的可用性。
最后,单体应用会让采用新框架和语言极其困难。举例来说,你有两百万行使用 XYZ 框架的代码,如果要使用 ABC 框架重写代码,无论时间还是成本都将非常高昂,即便新框架更好。这也就成为使用新技术的阻碍。
总结:这个一开始曾经成功关键业务应用,最终却变成一个臃肿的、无法理解的庞然大物。它使用老旧、陈腐、低效的技术,几乎吸引不到出色的开发者。这个应用非常难于扩展,也不稳定可靠。最终,敏捷开发和交付几乎成为不可能。
你该何去何从?
微服务–直击痛点
诸如亚马逊、eBay、Netflix 等公司已经通过采用微服务架构范式解决了上文(第一部分)提到的问题。不同于构建单一、庞大的应用,微服务架构将应用拆分为一套小且互相关联的服务。
一个微服务一般完成某个特定的功能,比如订单管理、客户管理等。每个微服务都是一个微型应用,有着自己六边形架构,包括商业逻辑和各种接口。有的微服务通过暴露 API 被别的微服务或者应用客户端所用;有的微服务则通过网页 UI 实现。在运行时,每个实例通常是一个云虚拟机或者 Docker 容器。
对于前文所述的系统,一种可能的系统分解图如下:
应用的每个功能区都由其自身微服务实施。此外,整个网页应用被拆分为一套简单的网页应用(比如我们的打车软件拆分为乘客应用和司机应用),从而能够轻松地针对特定用户、设备或者用户案例进行单独部署。
每个后端服务包括一个 REST API 和由其它服务提供的服务消耗 API。例如,司机管理服务使用“通知”服务器来告知司机即将的行程。UI 服务唤醒其它服务,从而呈现网页。这些服务也可能用到基于信息的异步通信。内部服务通信会在本系列文章中详述。
有的 REST API 也对司机和乘客的移动应用开放。这些应用并不能直接访问后端服务器,相反,通信由名为 API Gateway 的中间人调解。API Gateway 负责负载均衡、缓存、访问控制、API 计费、监控等,通过 NGINX 高效实施。本系列的后续文章将会讲解 API Gateway。
上图是 Scale Cube 的 3D 模型,来自《The Art of Scalability》 一书。微服务架构范式对应 Y 轴,X 轴由负载均衡器后端运行的多个应用副本组成,Z 轴(数据分割)将需求路由到相关服务。
应用通常同时使用这三种不同类型的扩展。Y 轴扩展将应用分解为如图一所示的微服务。在运行时维度,X 轴扩展在输出和可用性的负载均衡后运行多个实例。部分应用会使用 Z 轴扩展来对服务进行数据分割。下图展示了行程管理服务(Trip Management)是如何使用 Docker 部署到 AWS EC2 上的。
在运行时,行程管理服务包括多个服务实例,每个服务实例都是一个 Docker 容器。为了实现高可用性,这些容器运行在多个云虚拟机上。在应用实例前面是 NGINX 这样的负载均衡,将请求分发给全部实例。负载均衡也可以处理缓存、访问控制、 API 测量和监控等。
微服务架构范式对应用和数据库的关系影响巨大。每个服务都有自身的数据库计划,而不与其它服务共享同一个数据库。一方面,这个方法类似企业级数据模型。同时,它也导致部分数据的重复。然而,要想从微服务中获益,为每个服务提供单个的数据库计划就非常必要,这能保证松散耦合。下面的图表展示了示例应用的数据库架构。
每个服务都有其自己的数据库。此外,单个服务可以使用符合自己需要的特定类型的数据库,即多语言一致性架构。例如,为了发现附近乘客,驾驶员管理服务必须使用高效支持地理位置请求的数据库。
表面上看,微服务架构范式与 SOA 非常类似,这两种架构都包括一套服务。然而,微服务架构范式被看作不包含某些功能的 SOA 。这些功能包括网络服务说明( WS-* )和 Enterprise Service Bus (ESB) 的商品化和请求包。基于微服务的应用更青睐 REST 这样简单的、轻量级的协议,而不是 WS-* 。他们也极力避免在微服务中使用 ESBs 及类似功能。微服务架构范式也拒绝 SOA 的其它部分,比如 canonical schema 的概念。
微服务架构的好处
微服务架构模式有很多好处。
首先,通过分解巨大单体应用为多个服务方法解决了复杂性问题。在功能不变的情况下,应用被分解为多个可管理的分支或服务。每个服务都有一个用 RPC- 或者消息驱动 API 定义清楚的边界。微服务架构模式给采用单体式编码方式很难实现的功能提供了模块化的解决方案,由此,单个服务很容易开发、理解和维护。
第二,这种架构使得每个服务都可以有专门开发团队来开发。开发者可以自由选择开发技术,提供 API 服务。当然,许多公司试图避免混乱,只提供某些技术选择。然后,这种自由意味着开发者不需要被迫使用某项目开始时采用的过时技术,他们可以选择现在的技术。甚至于,因为服务都是相对简单,即使用现在技术重写以前代码也不是很困难的事情。
第三,微服务架构模式使得每个微服务独立部署,开发者不再需要协调其它服务部署对本服务的影响。这种改变可以加快部署速度,譬如 UI 团队可以采用 AB 测试并快速部署变化。微服务架构模式使得持续化部署成为可能。
最后,微服务架构模式使得每个服务独立扩展。你可以根据每个服务的规模来部署满足需求的实利。甚至于,你可以使用更适合于服务资源需求的硬件。比如,你可以在 EC2 Compute Optimized instances 上部署 CPU 敏感的服务,而在 EC2 memory-optimized instances 上部署内存数据库。
微服务架构的不足
Fred Brooks 在 30 年前写道 “there are no silver bullets”,像任何其它科技一样,微服务架构也有不足。其中一个跟他的名字类似,“微服务”强调了服务大小,实际上,有一些开发者鼓吹建立稍微大一些的,10-100 LOC服务组。尽管小服务更乐于被采用,但是不要忘了微服务只是结果,而不是最终目的。微服务的目的是有效的拆分应用,实现敏捷开发和部署。
另外一个不足之处在于,微服务应用是分布式系统,由此会带来固有的复杂性。开发者需要在 RPC 或者消息传递之间选择并完成进程间通讯机制。此外,他们必须写代码来处理消息传递中速度过慢或者不可用等局部失效问题。当然这并不是什么难事,但相对于单体式应用中通过语言层级的方法或者进程调用,微服务下这种技术显得更复杂一些。
另外一个关于微服务的挑战来自于分区的数据库架构。同时更新多个业务主体的事务很普遍。这种事务对于单体式应用来说很容易,因为只有一个数据库。在微服务架构应用中,需要更新不同服务所使用的不同的数据库。使用分布式事务并不一定是好的选择,不仅仅是因为 CAP 理论,还因为当前高扩展性的 NoSQL 数据库和消息传递中间件并不支持这一需求。最终你不得不使用一个最终一致性的方法,从而对开发者提出了更高的要求和挑战。
测试一个基于微服务架构的应用也是很复杂的任务。比如,对于采用流行的 Spring Boot 架构的单体式 web 应用,测试它的 REST API,是很容易的事情。反过来,同样的服务测试需要启动与它有关的所有服务(至少需要这些服务的 stubs)。再重申一次,不能低估了采用微服务架构带来的复杂性。
另外一个挑战在于,微服务架构模式应用的改变将会波及多个服务。比如,假设你在完成一个案例,需要修改服务A、B、C,而 A 依赖 B,B 依赖 C。在单体应用中,你只需要改变相关模块,整合变化,部署就好了。对比之下,微服务架构模式就需要考虑相关改变对不同服务的影响。比如,你需要更新服务 C,然后是 B,最后才是 A。幸运的是,许多改变一般只影响一个服务,而需要协调多服务的改变很少。
部署一个微服务应用也很复杂,一个单体应用只需要在复杂均衡器后面部署各自的服务器就好了。每个应用实例是需要配置诸如数据库和消息中间件等基础服务。相比之下,一个微服务应用一般由大批服务构成。根据 Adrian Cockcroft 的分享,Hailo 由 160 个不同服务构成,而 NetFlix 则超过 600 个服务。每个服务都有多个实例,这就形成大量需要配置、部署、扩展和监控的部分。除此之外,你还需要完成一个服务发现机制(后续文章中发表),以用来发现与它通讯服务的地址(包括服务器地址和端口)。传统的解决问题办法并不能解决这么复杂的问题。最终,成功部署一个微服务应用需要开发者有足够的控制部署方法,并高度自动化。
自动化的方法之一是使用譬如 Cloud Foundry 这样的 PaaS 服务。PaaS 能让开发者轻松部署和管理微服务,让他们无需为获取并配置 IT 资源劳神。同时,配置 PaaS 的系统和网络专家可以采用最佳实践和策略来简化这些问题。另外一个自动部署微服务应用的方法是开发自己的基础 PaaS 系统。通常的起步方式是 Mesos 或 Kubernetes 这样的集群管理方案,配合 Docker 使用。作为一种基于软件的应用交付方法,NGINX 能够方便地在微服务层面提供缓冲、权限控制、API 统计、以及监控。我们会在后续的文章中分析它如何解决这些问题。
总结
构建复杂的应用的确非常困难。单体式的架构更适合轻量级的简单应用。如果你用它来开发复杂应用,那真的会很糟糕。微服务架构模式可以用来构建复杂应用,当然,这种架构模型也有自己的缺点和挑战。
微服务架构实践
1. 单体应用架构传统的单体应用架构是将应用程序所有功能部署为一个单一的文件或者同一个目录下的文件合集,可以是JAR、WAR等格式,而且所有应用程序代码都运行在相同的进程中。
单体应用有如下优点:
(1)为人们所熟知:现有大部分工具、应用服务器、框架和脚本都是这种应用程序;
(2)IDE友好:Eclipse、IntelliJ等开发环境都是针对开发、部署、调试单个应用而设计的;
(3)便于共享:单个打包文件即包含所有功能,便于在团队之间以及不同的部署阶段共享;
(4)易于测试和部署:单体应用一旦部署,所有的服务或特性就都可以使用,没有额外依赖,每项测试都可以在部署完成后立刻开始。目前为止,单体应用已经很好地服务了我们,未来无疑还会继续发挥重要作用。但是,不管如何模块化,单体应用最终都会因为团队壮大、成员变动、应用功能扩展等因素而出现问题。
单体应用的主要不足有:
(1)不够灵活:对应用程序做任何细微的修改都需要将整个应用程序重新构建、重新部署。开发人员需要等到整个应用程序部署完成后才能看到变化,降低了团队的灵活性和交付频率;
(2)妨碍持续交付:单体应用可能较大,构建和部署时间也相应地比较长,不利于频繁部署,阻碍持续交付;
(3)技术栈限制:对于单体应用,技术是在开发之前经过慎重评估后选定的,每个团队成员都必须使用相同的开发语言、持久化存储及消息系统,且要使用类似工具,无法根据具体场景做出其它选择;
(4)技术债积累:系统设计或写好的代码难以修改,应用程序的其它部分可能会以意料之外的方式使用它,随着时间推移、人员更迭,必然会增加应用程序的技术债。
维护大型复杂单体应用系统的苦逼程序员,其中是不是也有我们自己的身影?
2. 微服务架构MSA随着业务需求的快速发展变化,敏捷性、灵活性和可扩展性需求不断增长,迫切需要一种更加快速高效的软件交付方式。微服务架构MSA就是一种可以满足这种需求的软件架构风格,采用多个服务间互相协作的方式构建应用,每个服务独立运行在不同的进程中,服务与服务之间通过轻量级通信机制交互,并且每个服务可以通过自动化方式独立部署。
微服务架构具有如下特点:
(1)领域驱动设计:应用程序功能分解通过DDD中明确定义的规则实现;每个团队负责与一个领域或业务功能相关的全部开发;团队拥有全系列的开发人员,具备用户界面、业务逻辑和持久化存储等方面的开发技能;
(2)单一职责:每个服务只负责该功能的一个单独的小的部分,也是SOLID原则之一;
(3)明确发布接口:每个服务都会发布一个定义明确的接口,且保持不变;服务消费者只关心接口,而对于被消费的服务没有任何运行依赖;
(4)独立部署、升级、扩展和替换:每个服务都可以单独部署及重新部署而不影响整个系统,使得服务很容易升级扩展;
(5)可以异构/采用多种语言:每个服务的实现细节与其它服务无关,使得服务之间能够解耦,团队可以针对每个服务选择最合适的开发语言、持久化存储、工具和方法;
(6)轻量级通信:服务间通信使用轻量级通信协议,如RPC、RESTful等。
相应地,微服务架构MSA具有如下优点:
(1)易于开发、理解和维护;
(2)独立进程部署,比单体应用启动快;
(3)局部修改容易部署,有利于持续集成和持续交付;
(4)故障隔离,一个服务出现问题不会影响整个应用,只影响自己;
(5)用合适的工具来做合适的事情,不会受限于任何技术栈;
(6)每个服务只需要做好一件事,更加专注和简单。
单体应用架构与微服务架构对比实例:
3. 单体架构与MSA扩展性对比单体架构由于单进程的局限性,水平扩展只能基于整个系统进行,无法针对一个功能模块进行按需扩展。而微服务架构则可以很好解决伸缩性扩展问题,可以根据需要,实施细粒度的自由扩展。另外,传统开发模式在分工时往往以技术为单位,比如UI团队、服务端团队和数据库团队,这样的分工会导致任何功能上的改变都需要跨团队沟通和协调。而微服务则倡导围绕服务来分工,不同的服务可以采用不同的技术来实现,团队需要具备服务设计、开发、测试到部署所需的所有技能,因此更有利于形成全功能团队。
4. 基于MSA的云测试系统实践对云测试系统基于微服务架构进行重构,根据业务逻辑具体拆分为如下图中11项微服务(任务调度Scheduler、资源匹配Matcher、产品管理Product、任务管理Job、版本管理Version等),每个服务独立运行部署,服务按需单独配置DB,服务之间基于RESTful接口通讯,图中的箭头表示服务间有依赖关系。采用MSA架构重新实现云测试系统后,系统的可扩展性得到显著增强,可以根据各种产品的测试需求增加相应的服务实现,比如针对不同的测试环境模型提供不同的资源匹配服务(Matcher-XXX),以及针对不同的测试框架(如ATPI和Robot)开发不同的测试用例解析服务等。另外,由于各个微服务都是在独立的进程运行,各个服务采用的编程技术也可以不同,只要能满足RESTful通信接口即可,本系统中大多数业务模块采用Go实现,资源匹配模块(MATCHER)则采用Python开发。Version为服务的接口定义实例如下表所示:例如,对VERSION服务的Get接口进行测试,在浏览器中输入对应的URL请求便可获取到用例库的详细信息(JSON格式),便于独立调试:
5. 总结经过半年多的摸索实践,基本完成了云测试系统微服务架构的建立,通过拆分微服务以及标准化接口定义,有效降低了服务模块之间的耦合关系,为后续基于Docker和OpenStack的分布式部署做好准备。