编者按:前2月,向朱攀兄弟约稿,畅聊甚欢,引为知己。今刊发以飨读者!
微服务为当今群雄混战局面,从dubbo问世数载,然业界尚未有完整打包结局方案,前有小剑之《老司机带你玩PPmoney微服务》,德比软件作为前驱,集数年研究与实战,搭建平台,填埋深坑,则业界之幸!
适逢1024程序员节,一并祝各位程序猿/媛节日快乐,早日步入老司机行列
前言
微服务概念被提出来后,短时间内就成了当前互联⽹圈的⼀个技术热点,有很多互联⽹公司计划或正在进⾏微服务化改造,那么,实施微服务我们应该怎么开始呢?需要哪些基础设施来⽀持呢?我所在的公司德⽐软件从 2009 年就开始采⽤⾯向服务架构( SOA) 来重新实现数据对接平台,我们积累了丰富的⾯相服务架构的经验,⽽微服务架构是⾯向服务架构思想的⼀种实现,只是服务职责更单⼀,粒度更⼩,过程更⾃动化。我们在多年的服务化架构实践过程中,已经不⾃觉的将服务的粒度细化了。本⽂想分享德⽐软件在实施微服务架构过程中积累的⼀些基础设施以及这些基础解决的痛点问题,希望对⼤家有所帮助和启发。
德比软件数据对接平台总体架构
数据对接平台的架构⼤体上分三个层次,最外⾯是 API 层,由 API Gateway 和各业务的 API 服务组成,中间层是服务层,平台的⼤部分的功能在这层实现,服务之间通过依赖或组合,可以组成功能更强的服务,最底层是存储层服务,由缓存层和存储层组成。
数据对接平台采⽤微服务架构,为了⽀撑这个架构,我们提供了必要的基础设施来保障上层各个服务和应⽤的可⽤性。
德比软件微服务架构基础设施
基础设施全景图
如图所示,我将我们的基础设施分为了⼋⼤部分,分别是服务框架、基础服务、 API ⽹关、⾃动化、服务降级、监控报警、⽇志处理和调⽤链追踪。其中服务框架、基础服务、 API ⽹关和服务降级是为了提⾼服务的连续⼯作时间;⾃动化为了简化开发和部署过程;监控报警、⽇志处理和调⽤链追踪是为了快速发现问题,定位问题并解决问题。
API 网关 DGateway
API ⽹关是微服务架构中⼀个不可或缺的部分。 API ⽹关是对外提供的服务,是系统的⼊⼝,所有的外部系统接⼊系统都需要通过 API ⽹关, DGateway 提供安全认证、流控、路由、 API 版本管理等功能。流控维度分⽤户类型、⽤户来源、 IP、业务 API 等,保护服务或资源不被滥⽤或攻击。
DGateway 基于开源软件 Tyk 做了⼆次开发。
服务框架
服务框架分两部分,高可用的 RPC(Derbysoft-RPC)和服务依赖管理。
高可用 RPC
RPC 是微服务架构最重要的基础组件 ,⼀个鲁棒的 RPC 能有效降低实施微服务架构的成本,RPC 封装了远程调⽤的复杂细节,让服务使⽤⽅感觉像调⽤本地⽅法⼀样调⽤远程⽅法,服务提供⽅感觉像实现本地接⼝⼀样来实现服务。 DerbySoft-RPC 分为 Client 和 Server 两部分。
Client 主要有以下功能:
客户端故障检测,识别并标示太慢或崩溃了的服务器器;
熔断机制,如果发现服务超时比例过限,启动熔断,客户端快速失败返回,减轻服务端的压力,定时允许部分请求通过,根据请求返回的健康程度来决定是否恢复熔断;
负载均衡策略,通过记录到每个节点的未完成请求数来决定下一个请求发到哪里;
容错机制,对某些错误自动重试,比如连接的服务节点不可用的时候可以自动将请求再次发送到其他节点,可定义重试次数;
序列化和反序列化,选择序列化工具最重要的考量点有:性能,多语言支持、序列化后的消息大小、是否向前兼容等。我们尝试过各种序列化工具,Protocol Buffer在性能和空间占用上都表现优异,是我们用的最多的序列列化工具;
敏感数据加密,比如信用卡等敏敏感信息,需要在 Client 端加密传输。
超时管理,可设定每个请求的超时时间。
Server 部分相对简单,主要提供超时管理,序列化和反序列化,敏感信息解密,队列管理、线程管理等功能。
刚才提到序列化⼯具选择的时候需要考虑是否⽀持多语⾔,这个很重要, RPC 最好能多语⾔实现,这样能让技术团队突破编程语⾔限制,技术栈更丰富灵活。在这个上⾯我们是被坑过的,公司早期的时候,⼤概是 2009 年,当时因为业务量不⼤,当时主要⽤ Java,第⼀个 RPC 版本为了⽅便我们采⽤了 Java 序列化机制,导致后⾯想换编程语⾔的时候⼤受限制,为此我们做了第⼆版RPC,将序列化机制换掉,为了兼容第⼀个版本,我们采⽤ Java 的字节序⼀直到现在。⽬前我们的DerbySoft-RPC 是第三版了,实现了 Golang、 Scala 和 Java 三个版本。原则上我们不限制服务的实现语⾔,只要能按要求实现服务接⼝并满⾜性能要求就⾏。微服务化后有个好处就是根本不担⼼你⽤⼩众编程语⾔,如果每个服务的粒度⾜够⼩,最坏的情况就是如果没⼈能维护这个⼩众语⾔的服务,花点时间⽤熟悉的语⾔重写了。
服务依赖管理
微服务化之后,系统⾯临的⼀个突出问题是服务虽⼩,但是数量极多,如果这些服务全部在⼀个可⽤区内,服务之间的依赖还⽐较好管理,可以做服务⾃动注册和发现来实现依赖管理,但是如果服务分布在很多可⽤区域,尤其是跨国跨地区的可⽤区之间的依赖和调⽤,管理起来就⽐较麻烦。
另外,跨可⽤区服务之间的调⽤还需要考虑通讯安全的问题,我们每个服务开不同的特殊端⼝,如果服务跨区域调⽤,访问权限配置也是⼀件麻烦的事情。
下面是众多服务的依赖图示例
上⾯的服务依赖是不是很乱?我们的服务节点分布在全球 14 个可⽤区⾥,⼤部分在 AWS 上,⼩部分在⾃建的私有云⾥,可⽤区之间的服务有复杂的依赖关系。为了解决这种情况下服务之间的依赖调⽤问题、 API 安全问题和服务的⾼可⽤问题,我们设计开发了路由服务,解耦服务的调⽤者和提供者,简化⽹络拓扑图,简化服务器的安全配置。引⼊路由之后,服务之间的依赖关系简化为如下图所示:
基础服务
基础服务分四部分,分别是配置中心,安全数据服务,数据存储服务,订单服务。基础服务大多和存储有关,提供高性能高可用的服务。
⼀个依赖配置中⼼的服务,启动的时候,会先查找本地⽂件中有没有配置数据的缓存,如果有,从⽂件中加载配置数据到内存中,启动服务,接着调⽤变量接⼝对⽐本地数据和配置中⼼的数据是否是同⼀版本,如果数据有变化,则更新数据到本地缓存。如果本地⽂件中没有配置数据的缓存,则先调⽤⼀次全量接⼝缓存所有配置数据到本地缓存。最坏的情况是配置中⼼的所有节点都挂掉,也不影响各个依赖它的服务现有节点的正常⼯作。
因为每个服务都会缓存依赖的配置数据,所以对配置中⼼的性能要求不是太⾼,我们⽤ AWSRDS 解决配置数据存储的⾼可⽤问题, AWS RDS 是⼀个托管关系数据库服务。
安全数据服务
⾼可⽤的安全数据服务,提供数据加密和解密服务,定时清理过期的数据,解决敏感数据的安全临时存储和传输问题,⽐如银⾏卡和信⽤卡信息的临时存储和传输。需要传输敏感信息的时候,在数据传输⽅调⽤数据加密服务的加密接⼝并明确信息有效时间,加密服务会将信息加密后存储,并返回⼀个全局唯⼀ ID,数据传输⽅发送这个唯⼀ ID 给数据接收⽅,如果数据接收⽅收到这个 ID
之后,调⽤数据解密接⼝,加密服务会根据 ID 找到相应的数据,返回解密之后的数据。
数据存储服务
DStorage ⾼可⽤的酒店动态数据存储服务,解决酒店海量动态数据存储问题,提供⾼性能的数据存取接⼝,提供变量数据的同步接⼝。根据性能和容量需求,⽤户可选的存储引擎有: Redis,Codis, Ardb, DynamoDB 等。 DStorage ⽀持多可⽤区部署,通过变化发现服务实现多可⽤区的数据同步,保证数据最终⼀致性。
DContent ⾼可⽤的酒店静态数据缓存服务,解决酒店海量静态数据的缓存问题。底层⽀持的存储引擎: Ardb、 Redis 等。 DContent ⽀持⼀主多从多可⽤区部署,也⽀持多主多可⽤区部署,保证数据最终⼀致性。
AWS S3,⾼可⽤的⽂件存储服务,存储⽂件数据,如:图⽚,视频,⽇志⽂件,备份数据等。
订单服务
订单服务是因为订单数据⽐较特殊,所以单独将订单相关功能作为独⽴服务。订单服务聚合所有系统节点的订单信息,让订单⽐较容易追溯,提供⾼可⽤的订单存储和查询服务,解决订单数据的单点存储问题,更好的⽀持其他依赖订单数据的系统的⽔平扩展和迁移。订单服务对可⽤性要求极⾼,底层存储依赖 AWS RDS ,在全球每个可能⽤到订单服务的可⽤区,部署订单服务集群。
服务降级
根据业务要求对每个服务及其依赖的资源,按重要程度进行分级,限制每个依赖服务的超时时间,定义各级服务的 SLA指标,对强依赖的基础服务或资源实行更高的 SLA标准,根据 SLA指标制定容错方案。
制定服务自动降级策略略,设置服务开关,关键时刻弃车保帅,对弱依赖的服务进行降级,比如日志服务是会被降级的,保障重要的服务不受影响。
对超大数据或流量用户进行隔离,在隔离的环境给这部分用户提供独立的服务,避免影响其他的正常用户服务。
自动化
微服务化后,服务的数量很多,为了节约开发成本,必须尽可能的自动化完成一些有固定步骤的工作,如测试自动化和部署自动化。
微服务化后,服务的数量很多,为了节约开发成本,必须尽可能的⾃动化完成⼀些有固定步骤的⼯作,如测试⾃动化和部署⾃动化。
(1)测试⾃动化
单元测试⾃动化,我们⽬前要求单元测试覆盖率要求⼤于90%,通过 Jenkins 整合 Sonar,每次提交代码后⾃动跑单元测试, Sonar ⾃动给出代码评分,测试覆盖率不够或代码不达标,则单元测试失败;
性能测试⾃动化、平台化,我们开发了⾃动化测试平台 DTesting,关键服务的每次变更,需要在平台上跑性能测试,并⽣成性能测试报告,对⽐历史的性能测试数据;
功能测试⾃动化、平台化,每个应⽤都有完整的功能测试⽤例和脚本,可⾃动化回归功能测试
(2)部署⾃动化
我们引⼊了 Docker,减少⼈为部署时的误操作。
日志处理
⽇志处理我们分为两部分,⼀部分是实时⽇志,另⼀部分是冷⽇志。所有应⽤的⽇志都需要写⼊ Kafka 和⽂件, Kafka 中有所有实时⽇志,会被 DLog 系统及时消费处理,但是实时⽇志保留时间不⻓,⼀般保留⼀个⽉。写⼊⽂件的所有⽇志会被定时备份到AWS S3 上永久保留,如果需要⼀个⽉前甚⾄更久以前的系统⽇志,可以在 DLog 系统选择需要的⽇志范围, DLog 会将符合条件的⽇志从 S3 上下载并重新建⽇志索引。⽇志主要是为监控提供基础源数据,为定位问题提供依据。
调用链追踪
随着服务的粒度越来越⼩,系统的服务变得越来越多,不同的服务可能由不同的团队实现,⼀个服务的实现可能会依赖⼏个甚⾄⼗⼏个服务,出现问题后,如何快速准确的定位故障呢?如何追踪业务的处理顺序和结果?业内已经有了⼀些实践和解决⽅案, Google Dapper 是 Google 研发的分布式跟踪系统,并发表论⽂阐述了分布式跟踪系统的理论基础,给分布式跟踪的实现提供了⾮
常好的参考示范。对于 Java 编写的服务,我们采⽤ Twitter Zipkin 来做调⽤链跟踪, Zipkin 是Google Dapper 系统的开源实现,采⽤ Scala 编写;⽽对于 Golang 实现的服务,我们⽬前还没有好的⽅案,做的还⽐较粗糙。⼀个好的调⽤链追踪系统,能为定位和排查故障提供强⼒的⽀持,对于微服务架构,调⽤链追踪是必备的基础设施。
服务健康状态
很多情况下,解决故障可能很快,难点在于发现故障和定位故障,这需要我们的系统有全⽅位的监控和报警能⼒。基于业务和技术对可⽤性的需求,我们开发了服务健康状态监控和报警系统,从业务层,服务层,硬件基础设施层分别进⾏监控和报警。
报警
报警系统定义所有报警种类,报警级别,处理流程,提供报警接⼊机制,各个层次的监控都可以通过⾃定义报警引擎接⼊报警系统来触发报警。 7 * 24 ⼩时服务监控团队会根据各个报警的处理流程,依次联系最⾼优先级的处理⼈来处理故障,如果在规定时间没有处理完,系统会根据报警的种类和级别⾃动升级报警响应级别,提⾼处理优先级来保障重要的报警能得到及时处理。
监控
监控分为基础设施层,服务层和业务层监控三⼤类。
1. 硬件基础设施层监控的内容包括:操作系统、内存、 CPU、⽹络流量和状态、连接数等,基础设施层的监控我们⽤ Zabbix 来做,通过 Zabbix 的 API 将数据整合进我们的监控系统 DMonitor。
2. 服务层监控包括基础组件监控、基础服务监控、常规服务监控和外部接⼝调⽤监控。其中基础组件监控包括 Etcd、 AWS RDS、 Redis、 Docker、 Kafka 等基础组件的使⽤情况监控;基础服务监控包括配置中⼼服务、认证服务、安全存储服务、路由服务、 API Gateway 等关键服务的监控;
常规服务监控是指监控所有的⼀般服务的状态;外部接⼝调⽤监控是指分析系统依赖的外部的访问情况,包括访问量、错误信息、超时状况等,为快速定位问题提供依据。
3. 业务层监控是指从业务⻆度出发,收集和分析业务层的访问量,根据各个业务的历史数据和已知的影响因素,预测现在和未来的业务情况,定义监控报警⽬标。⽐如:在最近半个⼩时内某个业务的请求量⼩于预期值;在最近 1 个⼩时内对某个客户的API调⽤次数超过预期值;在最近 3 个⼩时内某酒店的订单量显著下降等。由于业务层的监控⽐较复杂,我们把业务层的监控独⽴出了⼀
个⼦系统,业务层监控报警的决策来源历史数据和未来可能影响的因⼦,我们对历史海量的数据进⾏汇总加⼯和分析,从各个维度给出报警的预期值。
服务层监控和业务层的监控都离不开各个应⽤的⽀持,有些监控⽬标会对应⽤的实现有侵⼊性,⽐如需要写业务⽇志,各个应⽤根据监控的⽬标来设计和实现,为业务层监控提供必要的技术⽀持,因此,从系统架构层⾯⼀开始就需要考虑业务的可⽤性需求。
发布管理
最后我想说说发布管理,虽然它不属于基础设施,但是它太重要了,对服务的高可用影响很大。基础设置都做到位了就万事大吉了吗?不,还差一口气,还需要确保最后一公里万无一失,那就是服务的发布管理,最后一公里主要规划以下三点。
(1)容量规划,通过测试报告评估应用的类型,是IO密集型、CPU密集型还是混合型?根据应用的类型,申请合理的硬件资源;单台节点最大处理能力是多少?线上有多少容量正在被使用?集群的最大处理能力是多少?这些在发布前都需要合理的评估和测试。
(2)冗余规划,需要考虑异地多可用区部署,服务实例不小于 N+2 部署,服务实例硬件配置需相同,避免大小不一部署实例。为什么是 N+2 而不不是 N+1 呢?为了防止在服务更新的时候同时又挂掉一个服务节点,而发布失败是高概率事件。
(3)发布控制,线下充分测试,能在线下做的测试绝不不能放线上做。刚才说了,发布失败是高概率事件,所以发布必须要支持回滚,必须拒绝一切没有回滚方案的更新,嗯,是必须拒绝。
写在最后
微服务系统本质上是一个分布式系统,而分布式系统就有其固有的复杂性,对测试,部署甚至团队的组织结构都会带来很大挑战。有了这些微服务架构的基础设施,能有效帮我们解决并规避一些问题,但我们仍然不能低估采用微服务架构带来的复杂性。微服务架构不是银弹,更不是免费的午餐,实施微服务改造是要付出代价的,根据业务的需求选择合适的架构,不要为了微服务而微服务。
本文转载自微信公众号 中生代技术 freshmanTechnology