作者介绍
伍欣 厦门声连网信息科技有限公司 技术副总监
你将看到
1、微服务的特性,微服务实施的要求、复杂程度以及相应的挑战;
2、企业如何衡量是否应该使用微服务架构;
3、微服务对运维的要求;
4、开发微服务选择框架要考虑的因素;
5、我司最佳实践案例分享
本文大概:5200字 阅读需要:8分钟
一、什么是微服务?
微服务 (Microservices) 是一种软件架构风格,它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础,利用模组化的方式组合出复杂的大型应用程序,各功能区块使用与语言无关 (Language-Independent/Language agnostic) 的 API 集相互通讯。微服务架构运用于软件架构风格的其中一项概念是甘露运算 (Dew Computing),意指由许多的小露水 (代表微服务的功能元件) 汇集而成的运算能力。
——摘自《维基百科》,微服务
2014年,Martin Fowler 与 James Lewis 共同提出了微服务的概念,定义了微服务是由以单一应用程序构成的小服务,自己拥有自己的进程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 HTTP API 通讯。同时服务会使用最小的规模的集中管理能力,服务可以用不同的编程语言与数据库等元件实作。
微服务架构的几个特征
1. 通过服务组件化
使用服务作为组件而不是使用库的原因主要有两方面:首先,服务是可独立部署的,其次,能有更加明确的组件接口。服务通过明确的远程调用机制可以避免组件间的紧耦合。
但服务组件化也有一些缺点是我们需要提前注意到的:远程调用比进程内调用更昂贵;远程API被设计成粗粒度,不便于使用。
2.围绕业务能力组织
对照单体架构和微服务架构我们可以看到——单体架构中最常用的形式是三层架构,这样的架构下,人员按照层次进行组织。但在微服务架构当中,我们以业务(或者是微服务)为单位进行人员的组织,这样一来团队就是跨职能的,模块是通过业务能力进行组织和管理的。
这样的效果是在某个业务领域里面会有UI专家、业务专家、技术专家。当然在小公司里,这些人员是可以复用的,但不是很推荐这么做,因为大家都知道如果一个人做多任务,其实工作效率是相对较低的,就像一个cpu需要做多个线程当中切换,成本是很大的,所以在人员充足的情况下,尽量把每个子模块的人员固定下来。
3.去中心化治理
集中治理的一个后果是技术平台的单一标准化发展趋势。
把单体的组件分裂成服务,在构建这些服务时可以有自己的选择,例如使用其他的语言进行开发(这也是使用HTTP API作为微服务之间通信方式的一个原因),还可以利用其它的web容器或者容器化技术进行部署。
在集中治理中,一个单体应用只能使用一种开发语言,最后的后果是技术平台的单一标准化发展。比如你选择了java作为开发语言开发单体项目,那么这个项目就只能用能运行在jvm的语言进行开发了。
相反,如果去中心化进行单体组件的开发,之间通过http webservice进行通信,那么只要是支持http webservice开发的语言都可以用,比如我们就目前有一些微服务就在使用golang进行开发,这使得我们的工程师不会因为语言的局限性限制解决方案的实现方法。
微服务更倾向于让每个服务管理自己的数据库,或者同一数据库技术的不同实例,或完全不同的数据库系统。
在微服务体系中,想要在其他的子模块写入数据,需要通过调用服务API的方式,而不应该直接访问服务的数据库。
4. 基础设施自动化
基于持续集成(CI)和持续部署(CD)进行自动部署上线。在一个大型系统中微服务的数量会非常多,如果靠运维人员手工进行部署工作量十分大,也不方便管理。在这样的场景下如果不使用CI和CD进行自动化的作业,那么效率会变得很低。
5. 为失败设计
其实这是所有分布式系统的特征。
使用服务作为组件的一个后果:应用程序需要被设计成能够容忍服务失效。任何服务调用都可能因为服务提供者不可用而失败,客户端必须尽可能优雅的应对这种失败。
与单体应用设计相比这是一个劣势,因为它引入额外的复杂度。单体架构下,如果应用down掉了,那么整个应用或单个实例就停止了工作。如果使用微服务架构,组件之间的调用通过网络进行,这样引入了更多的不确定性,网络的延迟、网络的抖动(瞬断)等等都可能导致API调用的失败。因此,微服务必须快速检测故障并自动恢复服务(自愈)。
微服务希望看到为每个单独的服务设置的完善的监控和日志记:
1、控制面板上显示运行(up)/关闭(down)状态
2、各种各样的运营和业务相关指标
3、断路器状态
4、当前吞吐量和时延
所以有一种说法,使用微服务的前提是你具备了良好的运维能力,因为需要花费大量的时间做基础设施的建设。这其中包括、日志的分布式收集、调用链的跟踪、指标的监控、告警等等。
二、企业该怎么权衡是否应该使用微服务?
在以下几种情况下,企业使用微服务是一个合理的选择——
1、系统已经足够的庞大,单体架构难以维护、机器资源浪费
2、人力充足、可按业务进行组织
3、了解并掌握开发微服务的框架
4、能够容许若干迭代的失败
5、有比较成熟的分布式系统运维经验
第2点中的人力充足其实也并不是绝对的,我相信在一些已经应用微服务的公司(包括我们)中,人员也不是绝对地按照业务进行划分,但是的确是一个人或少数人负责一个微服务的开发和维护。
这就要求工程师的水平比较高,推荐的一种方式是先成功在一个或两个模块彻底执行微服务的改造,然后把这一两个模块当成样板工程。
当然要把这个样板工程弄好的话,还有很多的前置工作,例如规范的制定,自动化测试、集成测试要怎么做?容错、熔断要怎么做?错误处理要怎么做等等。这些都是需要提前考虑的。
第4点,能够容许若干迭代的失败,为什么这么说呢?
其实就算理论掌握得再好,最后在执行起来总会有些东西考虑不全面,尤其是之前说到的容错(Design for failure)部分尤其要花很多时间去考虑和设计。
第5点是比较重要的一点,前面我们说到在微服务当中,很多环节可能会失败,所以我们得对这些情况了如指掌才能避免问题的发生。
因此,我们在构建微服务模块的时候,除了要监控CPU、内存、磁盘IO、网络等指标之外,模块的指标也是我们要抓取和统计的。
这些指标包括:接口调用的延迟率、失败率、某个API的调用次数甚至是请求体数据的大小等等。
三、微服务实践分享
关于技术选型
就如前面“去中心化治理”部分提到的,我们选型的时候希望不被具体的通信协议或者语言框架绑架,所以我们选型的考虑点先是跟语言无关的通信机制,再来才是性能,所以我们优先选择通过HTTP进行远程调用的框架。
DUBBO来自阿里巴巴,而DUBBOX来自当当(最近也加入了RESTful webservice)。当当在dubbo的基础上开发了dubbox(Dubbo eXtensions),支持REST风格远程调用。
Spring cloud是Spring社区基于Netflix(中文名网飞,是一间在世界多国提供网络视频点播的公司)在微服务方面的成功应用,将其开源成spring cloud项目。
2013年3月,在VMware投资者大会上,VMware和EMC联合宣布将成立Pivotal公司,由VMware前CEO保罗·马瑞兹出任新公司首席执行官,并宣布将专注开源PaaS和大数据应用的Cloud Foundry、Greenplum等业务。
为什么我们要用Spring Cloud?
1、背景:出自Spring家族,具有Pivotal和Netfix强大的后盾
2、活跃度:版本更新频繁,每月都基本更新一个版本
3、架构:具有一套完整的微服务解决方案
4、文档:官方文档完整(目前英文文档居多)
5、未来:微服务是一种趋势,它是实现微服务架构的首选
因此,综合考虑了各方面因素后,我们最后确定Spring Cloud作为开发框架。
关于API设计
首先,在API的设计方面,我们可以参考借鉴stormpath的文档,声连网本身也是借鉴stormpath的API文档进行设计和开发的;
那么首先给大家分享几个我们在实践中摸索出的经验:
1、要有一套消息格式标准,使用统一的方式处理错误
尤其是在错误消息格式的制定,把错误进行分类,服务之间能按照统一的规则进行处理,避免重复劳动。
给大家展示一下我们的错误码分类(当然这只是一部分),我们可以把错误码处理的逻辑封装起来。
在spring cloud里面,通过feign调用微服务 API,我们就可以把错误码的处理逻辑封装到feign的intercepter(拦截器)当中;
2. 接口必须是幂等的
可以使用唯一request id的方式,避免重复处理业务请求,造成状态不一致。
3. 接口粒度合理权衡
需要根据业务场景决定API的粒度。例如,是分别调用A、B、C三个服务完成三个业务还是提供一个接口一起做ABC三个业务。我们在这里提供一个参考,如果A/B/C相互之间比较独立,且能并行执行,建议分三个接口。而如果业务关联性很强,需要有先后的调用顺序,可以考虑做一个微服务调用的编排服务,然后对外提供服务。
4. 给API加上指标监控(如调用次数、duration等)& 告警
在指标的抓取方面,我们使用的是prometheus。
我们在API接口中加入我们关心的指标数据,然后通过prometheus进行收集存储,用grafana进行可视化展示,使用Prometheus的alert manager进行告警通知。
现在钉钉也支持webhooks了,所以我们可以把告警信息发布到钉钉运维群里面,或者额一些协作工具比如 Teambition。当然,合适的选择还有很多,具体的途径确认还要看公司使用的工具是什么。
关于Token管理
Token管理方面我们通常的做法是:
1、使用JWT(JSON Web Token)
2、 把JWT存放在token管理服务中,由proxy取出处理后传递(或者直接让客户端托管JWT )
3、在微服务之间传递解密后的自包含用户信息
每个服务应该都是无状态的,当服务需要获取上下文信息或者会话信息,比较常见的做法是将这些信息托管在外部服务,需要的时候进行访问。那么,我们能否使用另外一种方式达到同样的目的呢?
JSON Web Token(JWT)就是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
如果你的payload当中不会有什么敏感信息(例如userId就不是那么敏感),那么甚至可以考虑把JWT让客户端来托管。
当然如果让客户端托管,会遇到一个不方便的地方,如果token还没有过期,但是想让token立即过期,那么使用这个解决方案是没办法做到的,而使用proxy的方案却是可以的。
使用DDD进行服务的识别与划分
使用DDD之后,我们能切实感受到这些便捷:
1、方便快速发现和划分业务模块;
2、业务知识更加稳固,有利于产品与开发人员的沟通;
3、缩减代码量(精炼出模式,使用设计模式);
尽可能避免使用分布式事务
对于任何分布式系统而言,分布式事务都是难以处理的一个问题。
而我们的做法是,在设计的时候就尽量避免使用分布式事务的解决方案,使用最终一致性;
以下这些解决方案可以用于处理分布式事务:
1、两段式提交(或者叫TCC,Try-Commit-Cancel)
2、使用持久化队列(之所以要说那个持久化队列是因为消息不能丢失)
2.1 事务的补偿
2.2 事务的回滚
因此,我认为要区分场景来使用分布式事务——
例如在库存相对于无限,或比较没有限制条件的情况下,可以使用补偿机制达到数据的一致性;而若是对于有前提条件的事务,就需要通过回滚的方式避免做了错误的业务处理。
使用容器技术部署和管理微服务实例
使用容器技术部署微服务的优势:
1、把运行环境安装到镜像当中,简化运维人员的工作,避免出现“在我的环境下运行完全正常,在xx环境下就不正常”的情况出现;为DevOps做好基础工作
2、最大可能地使用集群中的机器资源(在自建IDC的时候尤其适用)
3、可以方便地修改端口的映射
4、使用工具方便地启动整个微服务体系(例如docker-compose)