应对海量并发请求,首席布道师谈微服务的应用架构设计

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介:
  20160805091901834.jpg 何李石

七牛云首席布道师

 

  • 《Go语言程序设计》译者,Go语言/容器虚拟化技术布道师、实践者。

  • 5年以上互联网创业经验和企业级产品研发、运营经验,同时也是互联网产品基础架构解决方案专家。

 


随着互联网网民数的爆发式增加以及人们对随时随地接入互联网诉求的加强,互联网产品需要面对的并发请求量越来越大,云计算的诞生和普及为海量并发请求的应用提供了弹性的硬件支撑。


本案例分享基于微服务的应用架构设计,内容涉及如何构建一个微服务应用,服务注册与发现,微服务测试和典型的微服务架构设计模式,以及微服务架构在七牛的实践案例。


1大纲


  • 构建一个微服务应用

  • 服务注册与发现

  • 微服务测试

  • 典型微服务架构设计模式

  • 七牛微服务架构实践


2构建一个微服务应用


首先我们通过一个最简单的例子来看下如何构建一个微服务应用。

图 1 是一个完整服务的代码,它和普通的应用程序没什么区别,只是功能非常少,业务非常简单。把它编译之后部署在服务端就能跑起来,我们从上往下解释一下这段代码干了什么事情:


  1. 在第 6 行我们引入一个包 “github.com/koding/kite”,这是一个开源的微服务框架包,使用它可以快速方便的构建一个微服务应用;

  2. 第 11 和 12 行我们定义一个微服务,并将其取名为 first;

  3. 第 15 行开始我们为这个微服务添加了一个 square 操作指令供外部调用,这是负责完成具体功能的业务单元,在这里它负责将外部传入过来的值进行求平方计算后返回给调用方;

  4. 最后在第 21 行我们把它注册到框架提供的某个地方,并声明了它的访问地址和协议。


20160805091936170.jpg

图 1. 服务端代码


这个服务实现了一个供外部调用的功能,我们把它称之为 server 端。接下来我们再实现一个调用这个它的客户端,我们称之为 client 端。在真实的微服务环境中,并没有严格区分 server 端和 client 端,只要有需要,它们都可能会相互调用。


同样的,我们从上往下看看图 2 这段代码干了什么事情:


20160805091953518.jpg
图 2. 客户端的代码


  1. 除了第 6 行引入的 “github.com/koding/kite” 包之外,我们还引入了另外一个 “github.com/koding/kite/protocol” 包,它用于做环境中的微服务发现;

  2. 第 15 行开始我们通过配置环境中的信息在环境中查找所有名字为 “first” 的微服务。之所以说「查找所有」,是因为在更复杂的生产环境中,一个微服务往往可能包含多个副本,并且这个副本数是动态伸缩的,因此需要专门的服务注册和发现过程来动态注册和动态查找,我们后面会讲到更具体的模式;

  3. 在第 22 行中我们选择第 1 个结果,也就是第一个微服务进行访问;

  4. 在第 26 行中传入参数 4,调用指令 “square” 求平方,得到结果;


这是一个服务调用另一个服务的演示过程,从中可以看出它涉及到服务的构建、注册和发现。后面这个 client 端例子在演示中虽然没有监听端口,只是访问别的微服务,但是它也能监听端口对外提供服务,本质上 server 和 client 两端从微服务架构角度说并没有严格区分,只是在不同时候扮演不同的角色履行不同的职责。


上面两个示例中我们提到服务注册和发现。实际生产环境中,微服务的种类和每个微服务副本数可能都非常多,特别是在相互调用之后,它们杂糅在一起的业务可能非常复杂。同时,复杂多变的环境决定了系统中运行的微服务随时可能被启停,这也是拥抱微服务的价值点之一:主动拥抱系统的不稳定性,通过消除这种不稳定性带来的影响来达到高可用的目的。为此,需要引入服务注册和发现机制来帮助系统治理这些服务。


3服务注册与发现


我们再来看一下,为什么这么简单的代码中还需要多一个服务注册的步骤?


一个微服务起来之后,要对外提供服务,必须让别人知道它的存在,因此需要有一个地方让它「注册」,我们把它称为「注册中心」。由于微服务的启停是随时都可能发生的,和启停对应的是服务的注册和解除注册,因此在这里我们说到注册的时候默认也隐含解除注册,后续不再赘述。解除注册是为了保证下线的服务不会被调用方访问到。


假如这两个服务都有多个实例副本,它部署起来如图 3 所示。在实际生产环境中,每个服务实例 A、B 和 C 都是动态变化的,整个服务的过程中可能一直有多个副本在那里跑着,但不一定都是相同的实例,它们的 IP 在服务动态伸缩过程中会发生变化。那么如何让客户端及时的知道这种变化呢?我们知道,微服务提倡多个服务解耦比较干净,因此让一个服务主动通知另外一个服务它的变化是不太现实的,这样侵入对方业务太深了,耦合太紧。同时,在后端有多个服务实例的情况下,如何将客户端的请求负载均衡的分发到各个实例中呢?


20160805092003120.jpg

图 3. 没有服务发现的问题


在这些问题面前,和传统的单体架构相比,微服务中服务的注册和发现能力非常重要,甚至有些微服务框架自带了服务的注册和发现功能(比如我们给的例子中用到的 kite 这个框架就自带了这样的功能)。


一个服务起来之后,它的注册和解除注册过程可以由自己去完成,也可以由第三方工具去完成。比如在客户端和微服务端之间的负载均衡器可能会帮助微服务完成服务的注册和解除注册等操作,客户端和微服务自身都不需要关心,也即不需要他们各自的业务逻辑里面实现这些操作。还有一些情况是在客户端自带服务查询模块,它先从服务注册中心查询可用的服务,然后再按照这个查询结果去请求后端服务实例。下面我们分别解释这两种模式。


20160805092012382.jpg

图 4. 客户端服务发现


图 4 中给出的是以客户端查询为主的服务注册和发现机制,后端服务实例起来之后,会以主动或者被动的形式注册到「注册中心」。客户端自带的「查询模块」会从「注册中心」查询可用的服务,然后按照查询结果去请求后端服务实例。


这样做的好处是:

  • 「注册中心」在服务之外维护,使用简单,对已有的微服务架构侵入小;

  • 客户端直接请求后端实例,查询完成后请求链路不需要经过其它中间环节。


其缺点在于:

  • 客户端和「注册中心」绑定;

  • 客户端的实现取决于具体语言或者框架,每个客户端都得自己去实现。


20160805092023437.jpg

图 5. 服务端服务发现


我们再来看下服务端实现的服务注册和发现机制,如图 5 所示。同样的,后端服务实例起来之后,会通过主动或者被动的方式注册到「注册中心」,但是客户端不需要实现查询模块去查询服务了,取而代之的是我们在服务端添加一个「路由」环节,它负责代理客户端的所有请求,从后端实例获取结果之后返回给客户端。与客户端服务发现机制相比,它具有以下优点:


  • 客户端不需要做额外的变更;

  • 有些云服务公司已经提供类似产品可以满足需求了,可以直接接入。

  • 但它也有它的缺点:

  • 除非是托管在云服务提供商那里,否则还需要额外的服务端来部署「路由」或者「负载均衡器」部分,这也就意味着需要保证这个部分的可靠性和可用性,需要额外的系统设计和运维工作;

  • 请求多了一个路由代理的步骤,增加系统总体耗时。


4微服务测试

20160805092031692.jpg

图 6. 测试金字塔


我们今天介绍微服务,首先假设系统到了一定的复杂度,使用传统的单体架构已经不能满足需求了,必须将单体架构上的功能拆分成职责和功能单一的微服务才能更好的跟上业务的发展。因此,由微服务构建的系统往往是一个复杂的分布式系统,它的测试涉及到从里到外的各个环节。


接下来我们分三部分来介绍分布式系统中涉及到的测试:常规测试、混沌测试和流量重现。


我们先来看一个测试金字塔,如图 6 所示,它包含:单元测试、集成测试、组件测试、端到端测试和探索性测试。接下来我们要介绍的「常规测试」包含金字塔地下的四部分,它还包含另外一种不太常见类型的测试,也即契约测试,下面会介绍到。而混沌测试或者猴子测试则属于探索性测试。


5常规测试


1. 单元测试


单元测试是几乎所有系统开发都会涉及到的环节,它覆盖最细粒度的测试范围,一般基于类甚至是方法来进行自动化测试。它测试的范畴一般不涉及跨网络的调用,因此相对简单。在传统的 Web 开发中,非常流行的测试驱动开发 TDD 里面讲的测试一般基于单元测试,单元测试用例不仅定义了业务边界,还将业务模块进行了细分。


2. 集成测试


集成测试将相关模块组合在一起,在业务上构成一个子系统进行测试,它的主要目的在于验证构成一个子系统的所有路劲上的调用是否正确,模块组合在一起后调用产生的结果是否符合预期。比如两个服务之间的调用,或者服务和数据库之间的通信,一般都属于集成测试的范畴。


3. 组件测试


与集成测试相反,组件测试关注的是组件内部功能的完备性。对于微服务应用来说,一个微服务即一个组件,为了测试这个组件内部功能的完备性,需要尽量减少外部环境对其造成的影响。当该微服务依赖于一些外部服务或者数据库调用的时候,我们可以 mock 一些外部服务,同时使用内存数据来代替数据库数据,这样可以尽量减少外部服务调用对它产生的影响,只关注单个微服务自身的测试。


4. 契约测试


契约测试和组件测试的所需的边界非常像,对于微服务来说,它们的所涉及的边界都是微服务本身。但和组件测试不同的是,契约测试更关注输入参数和输出结果的合法性,一定的合法输入必须得到一定的合法输出,也即测试一个服务是否满足一定的契约;另外,契约测试还关注相应的结果是在多长时间内得到的,也即服务响应的性能。这时候对一个服务的测试,其所依赖的服务不能通过 mock 或者内存数据来代替。


5. 端到端测试


为了验证一个系统是否符合客户需求和商业目标,需要一个覆盖产品完整链路的测试。对于有用户 UI 界面的产品,端到端的测试意味着包括 UI 界面的测试和后端服务的测试。由于涉及到的环节最多,因此这样的测试运行起来也比较慢,出现故障的时候排查问题也比较麻烦。为了加快这个测试环节,通常建议:


  1. 将尽可能多的测试需求自动化。

  2. 不必每个组件的修改都触发端到端测试,可以在所有更小粒度的测试完成之后再做一遍端到端测试。


之所以把上面所有的测试称为「常规测试」,是因为这些测试基本上在所有类型的现代化软件开发中都可能涉及到,它其实不是微服务架构都有的,微服务架构下的这些测试只是边界不太一样。统计对比表明,上线之前完整的测试可以避免 90% 以上的错误导致的故障。


关于常规的测试,我们只在这里做简单的介绍,想详细了解每种测试覆盖的范畴或者最佳实践的同学可以参考这个 Martin Fowler 网站上 Toby Clemson 关于微服务测试的分享:http://martinfowler.com/articles/microservice-testing/


6混沌测试


在分布式系统领域,Netflix 发明了一种更为古怪的测试,叫混沌测试,Netflix 称之为猴子测试。什么意思呢?它假设,如果你的系统足够健壮,那么随便启停某些服务并不会影响系统的整体运行,用户并不会感知到服务的故障。我们经常讲要构建容忍故障的高可用服务,但是如果故障没有来,就没法验证这样的服务是否可以容忍某些极端情况的故障。为此,Netflix 在系统中引入了一系列搞破坏的「猴子」,它会主动给系统的各个部分制造麻烦,比如随时不小心关闭一台机器,但是你的服务还得继续运行,所有故障必须自动恢复,并且不能被用户感知到。


20160805092043918.jpg

图 7. Netflix猴子军团


Netflix 的猴子军团:

  • Chaos Monkey: 随机杀死实例

  • Latency Monkey: 人为引入延迟

  • Chaos Gorilla: 模拟整个可用区突然断电


猴子军团中的几种不同类型的机器人分别代表了不同的破坏性,它在稳定的线上环境中随机选择破坏。这样的测试模拟了自然灾难,对线上环境进行了全黑盒式的演练,让线上系统产生「免疫」。遗憾的是,即便所幸躲过所有这些「自然灾难」,也无法说明系统是没问题的。


更多关于猴子军团的信息可以参考以下链接:

  • Chaos Monkey Released Into The Wild: http://techblog.netflix.com/2012/07/chaos-monkey-released-into-wild.html

  • What is Simian Army? https://github.com/Netflix/SimianArmy/wiki

  • Automated Failure Testing: http://techblog.netflix.com/2016/01/automated-failure-testing.html


7流量重现


我们前面提到端到端的测试很难做,主要是难在它整个链路太长,耗时太长,环节也难以控制。另外一个难点在于,我们通常很难获得和线上一样真实的流量去进行端到端的测试,即便某个环节能够成功模拟相应的请求,但也不是所有请求的比例和线上都是一致的。


为了模拟线上请求,我们可以讲线上流量截获后导入到测试环境中通过「流量重现」的方式,在一个更加真实的模拟环境中观察和调整。在这里介绍一个用 Go 写的开源工具:Gor https://github.com/buger/gor

20160805092053206.jpg

图 8. 流量重现工具 Gor


通过监听线上服务的请求,它能够截获线上环境的流量,并将其在测试和开发环境中重现,如上图所示。


8典型微服务架构设计模式


20160805092101742.jpg

图 9. 典型微服务设计模式


图 9 是一个典型的微服务架构图。服务注册和发现的架构图前面讲过,为了简化图的结构,此图不再提及。客户端的请求首先会到达一个负责对外沟通的 API Gateway,它负责识别客户端类型,解析和理解客户端的请求,并将请求分发到后端对应的微服务中完成。


一般来讲,基于 HTTP REST API 的请求都是同步执行的,但是有些场景使用异步的方式更为合理,比如大视频转码服务,它很难在很短时间内完成。同时,为了最大化微服务之间的解耦,应尽量减少和简化微服务之间的通信。为此,我们引入消息队列作为异步通信的通道。


在微服务的后端,如果每个微服务都按照三层结构来部署,除了服务本身之外,还包括缓存层和持久化的数据库层。


这样就构成了一个典型的微服务应用架构,各项服务可以自由伸缩,相互之间依赖最小化,通过共享队列的方式来进行数据共享和信息同步。当然,这样一个架构要通过猴子军团的测试,首先得保证每个组件都是高可用甚至是跨机房部署的,比如 API Gateway、消息队列以及每个服务依赖的 Cache 和 DB 都随时可能挂掉。


9七牛的微服务架构实践



20160805092109105.jpg

图 10. 七牛微服务设计实践


图 10 展示的是七牛数据处理平台的微服务架构示意图。七牛的数据处理平台每天处理接近百亿的数据处理请求,这些请求包括图片缩放裁剪等可以实时完成的同步请求,也包括音视频转码等无法实时完成的异步请求。所有这些请求通过统一的网关进入负载均衡器,再由负载均衡器分发给后端的处理实例。后端的处理实例中,每个实例只部署一种类型的服务,同时维持一个 Agent 可以从存储中读取数据,以及一个 Cache 用于维持状态。由于业务相对简单,Cache 可以直接基于内存,并且没有持久化的要求。所有持久化的工作都由客户端主动发起,由后端 Agent 完成后对存储进行读写。


Q&A


Q1:关于微服务的注册和发现,目前有哪些成熟的产品?

A1:比较成熟的用于协同的是 Zookeeper,在开源容器产品里面用的比较多的是 etcd。

 

Q2:微服务健康状况,运行情况监控是如何做的?

A2:如果是使用容器化的部署方式,监控方面目前很多都是基于 Google 的 cAdvisor 来做的。

 

Q3:微服务测试,打桩是怎么做的?

A3:打桩测试不太了解,有朋友介绍使用这个工具来做http://www.mbtest.org/

 

Q4:请问微服务是否部署了多个实例,同服务的不同实例间是否有分布式锁来保护?

A4:如果需要保证服务之间的原子性,可以使用分布式锁,但应该和微服务的初衷有点冲突。建议尽量改成异步通信的方式,借助 message queue 等来通信。

 

Q5:微服务的部署,老师比较推荐哪种方式?蓝绿、金丝雀,或者是其他?

A5:我们的业务是灰度。

 

Q6:针对服务总线、API GateWay、OpenAPI之间的区别是什么呢?您刚才所讲的服务发现属于这里面谁的范畴?

A6:服务总线是针对数据传输讲的。API Gateway 的针对请求处理和分发的。OpenAPI 功能上应该差不多,主要是面向第三方平台的开发者。API Gateway 除了终端(Web/iOS/Android)提供 API 功能之外,还提供了统一的入口、微服务接口聚合以及授权认证、后端服务负载均衡等功能。

 

Q7:怎么解决动态拓展Docker,网络问题?

A7:要看具体是啥问题…… 网络方面 Docker 官方有提供方案,k8s 也有,一般私有部署可以直接使用开源的版本。

 

Q8:如果应用已经基于Spark这样的分布式框架构建了,再想做微服务拆分有什么比较好的方案吗?

A8:我理解你跑在 Spark 上面的计算任务更依赖的是 Spark,而不是微服务的优势。因此如果你要考虑微服务话或者为了充分利用容器云平台的能力,可以考虑先把你依赖的 Spark 容器化,后面再考虑计算任务容器化的事情。

 

Q9:老师,一个客户端应用调用多个微风服务,数据一致性怎么解决?

A9:我理解多个微服务之间的数据一致性是指他们共用了相同的数据或者数据库,有多个微服务对他们进行读写。这个问题可以使用事件驱动的编程模型来解决,简单来讲就是维护一个事件服务端,让各个服务对数据的修改都以事件的形式通知它,再由它去通知其它所有相关的服务。参考 Event Sourcinghttp://martinfowler.com/eaaDev/EventSourcing.html

 

Q10:以什么标准来进行微服务颗粒度划分?

A10:大家都说以业务的最小单元来拆分服务,但对业务的最小单元却没有统一的标准,也不可能有统一的标准,比如我们做存储业务的和你电商业务的标准就没法统一起来。我个人的看法是,就像康威定律里面说的,服务的复杂性决定了服务的大小和拆分的合理性,除了「以业务的最小单元来拆分」之外,维护某个微服务的团队不应该太大。还有,拆分之后本来应该是尽量解耦的,所以可以看拆分之后是否带来更复杂的异步通信。 


本文来自云栖社区合作伙伴"DBAplus",原文发布时间:2016-08-05

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
7天前
|
机器学习/深度学习 API 语音技术
|
10天前
|
API 数据库 开发者
构建高效可靠的微服务架构:后端开发的新范式
【4月更文挑战第8天】 随着现代软件开发的复杂性日益增加,传统的单体应用架构面临着可扩展性、维护性和敏捷性的挑战。为了解决这些问题,微服务架构应运而生,并迅速成为后端开发领域的一股清流。本文将深入探讨微服务架构的设计原则、实施策略及其带来的优势与挑战,为后端开发者提供一种全新视角,以实现更加灵活、高效和稳定的系统构建。
18 0
|
24天前
|
负载均衡 测试技术 持续交付
高效后端开发实践:构建可扩展的微服务架构
在当今快速发展的互联网时代,后端开发扮演着至关重要的角色。本文将重点探讨如何构建可扩展的微服务架构,以及在后端开发中提高效率的一些实践方法。通过合理的架构设计和技术选型,我们可以更好地应对日益复杂的业务需求,实现高效可靠的后端系统。
|
24天前
|
监控 持续交付 API
构建高效可扩展的微服务架构
在当今快速迭代和竞争激烈的软件市场中,构建一个高效、可扩展且易于维护的后端系统变得尤为重要。微服务架构作为一种流行的分布式系统设计方式,允许开发者将应用程序划分为一系列小型、自治的服务,每个服务负责执行特定的业务功能。本文将探讨如何利用现代技术栈搭建一个符合这些要求的微服务架构,并讨论其潜在的挑战与解决方案。我们将涵盖服务划分策略、容器化、服务发现、API网关、持续集成/持续部署(CI/CD)以及监控和日志管理等关键主题,以帮助读者构建出既可靠又灵活的后端系统。
|
8天前
|
Kubernetes 安全 Java
构建高效微服务架构:从理论到实践
【4月更文挑战第9天】 在当今快速迭代与竞争激烈的软件市场中,微服务架构以其灵活性、可扩展性及容错性,成为众多企业转型的首选。本文将深入探讨如何从零开始构建一个高效的微服务系统,覆盖从概念理解、设计原则、技术选型到部署维护的各个阶段。通过实际案例分析与最佳实践分享,旨在为后端工程师提供一套全面的微服务构建指南,帮助读者在面对复杂系统设计时能够做出明智的决策,并提升系统的可靠性与维护效率。
|
19天前
|
存储 监控 Kubernetes
探索微服务架构下的系统监控策略
在当今软件开发领域,微服务架构因其灵活性、可扩展性和容错性而日益受到青睐。然而,这种架构的复杂性也为系统监控带来了新的挑战。本文旨在探讨在微服务环境下实现有效系统监控的策略,以及如何利用这些策略来确保系统的健壮性和性能。我们将从监控的关键指标入手,讨论分布式追踪的重要性,并分析不同的监控工具和技术如何协同工作以提供全面的系统视图。
|
19天前
|
监控 Java 开发者
构建高效微服务架构:后端开发的新范式
在数字化转型的浪潮中,微服务架构以其灵活性、可扩展性和容错性成为企业技术战略的关键组成部分。本文深入探讨了微服务的核心概念,包括其设计原则、技术栈选择以及与容器化和编排技术的融合。通过实际案例分析,展示了如何利用微服务架构提升系统性能,实现快速迭代部署,并通过服务的解耦来提高整体系统的可靠性。
|
1天前
|
消息中间件 运维 监控
现代化软件开发中的微服务架构设计与实践
本文将深入探讨现代化软件开发中微服务架构的设计原则和实践经验。通过分析微服务架构的优势、挑战以及常见的设计模式,结合实际案例,帮助开发者更好地理解如何构建可靠、可扩展、高效的微服务系统。
|
1天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
2天前
|
监控 JavaScript 安全
构建微服务架构下的API网关
【4月更文挑战第15天】在微服务架构中,API网关扮演着至关重要的角色。它作为系统的唯一入口,不仅负责请求的路由、负载均衡和认证授权,还涉及到监控、日志记录和服务熔断等关键功能。本文将探讨如何构建一个高效且可靠的API网关,涵盖其设计原则、核心组件以及实现策略,旨在为后端开发人员提供一套实用的指导方案。
14 4