随着自动化运维等相关技术的发展,微服务变得更容易管理,这给了微服务架构良好的发展机会;同时,Docker 等容器技术的发展,使微服务架构的落地变得更加方便,这更是为其成为主流技术铺好了道路。目前各家对微服务架构都有自己的理解与落地实践。
本次 InfoQ 采访到 58 到家技术委员会主席沈剑老师,请他讲讲他对微服务架构的一些思考。沈剑老师之前任职于 58 到家,目前在 58 速运,本次分享涉及到 58 到家与 58 速运的相关内容。本文即由采访整理而成。
本次分享主要从服务化的角度来看待微服务,主要是梳理一下微服务这个概念,不做深入地讲解。
互联网架构发展的过程中,当业务复杂度剧增,数据量剧增,吞吐量剧增的时候,就会出现一些技术痛点,下边几个都是最常见的:
痛点一:代码到处拷贝
举一个最常见的业务的例子:用户数据的访问。
绝大部分公司都有一个数据库用来存储用户数据,而各个业务都有访问用户数据的需求。
各个业务线都是自己通过 DAO(Data Access Object)写 SQL 访问 user 库来存取用户数据,这无形中就导致了代码的拷贝。
抽象出一个服务层之后,有统一的一份代码,那么就解决了代码复用的问题。同时业务方通过 RPC 访问用户数据,就像调用一个本地函数一样,比如使用
User = UserService::GetUserById(uid);
传入一个 uid,得到一个 User 实体,就像调用本地函数一样,不需要关心序列化、后端执行、网络传输、反序列化等复杂性,方便高效。
痛点二:复杂性扩散
这个内容主要讲一下下边两个点:
随着访问量越来越高,数据库成了瓶颈,需要加入缓存来降低数据库的读写压力,于是架构中引入了缓存,由于没有统一的服务层,各个业务线都需要关注缓存的引入导致的复杂性:
对于用户数据的写请求,所有业务线都要升级代码:
1、先淘汰 cache
2、再写数据
对于用户数据的读请求,所有业务线也都要升级代码:
1、先读 cache,命中则返回
2、没命中则读数据库
3、再把数据放入 cache
这个复杂性是典型的“业务无关”的复杂性,业务方需要被迫升级。
数据量越来越大,数据库需要进行水平拆分,于是架构中又引入了分库分表,这时又是由于没有统一的服务层,各个业务线都需要关注分库分表的引入导致的复杂性。
这个复杂性也是典型的“业务无关”的复杂性,业务方需要被迫升级。
有了服务层之后,只有服务层需要专注关注底层的复杂性了,向上游屏蔽了细节。
痛点三:库的复用与耦合
服务化并不是唯一解决上述两个痛点的方法,另一种方法是抽象出统一的“库”。比如抽象出一个 user.so,负责整个用户数据的存取,从而避免代码的拷贝。至于复杂性,现在也只剩下 user.so 这一个地方需要关注了。
但是这时候会引入新的问题:库的版本维护与业务线之间代码的耦合。
比如业务线 A 将 user.so 由版本 1 升级至版本 2,如果不兼容业务线 B 的代码,那就会导致 B 业务出现问题;业务线 A 如果通知了业务线 B 升级,这时的业务线 B 就会去升级,但这是与它“自身业务无关”的升级。
痛点四:SQL 质量得不到保障,业务相互影响
本质上 SQL 语句还是各个业务线拼装的,资深的工程师写出高质量的 SQL 没啥问题,经验没有这么丰富的工程师可能会写出一些低效的 SQL。业务线通过 DAO 访问数据库,假如业务线 A 写了一个全表扫描的 SQL,导致数据库的 CPU100%,影响的不只是这一个业务线,而是 所有的业务线。
有了服务层之后,所有的 SQL 都是服务层提供的,业务线不能再为所欲为了。底层服务对于稳定性的要求更好的话,可以由更资深的工程师维护,而不是像原来 SQL 难以收口,难以控制。
痛点五:疯狂的 DB 耦合
业务线不只访问 user 数据库,还会结合自己的业务访问自己的数据库。
典型的情况是,通过 join 数据表来实现各自业务线的一些业务逻辑。这样的话,业务线 A 的 table-user 与 table-A 耦合在了一起,业务线 B 的 table-user 与 table-B 耦合在了一起,业务线 C 的 table-user 与 table-C 耦合在了一起,最后的结果就是:table-user,table-A,table-B,table-C 都耦合在了一起。
服务化之后,底层的数据库被隔离开了,可以很方便的拆分出来,进行扩容。
像上边说的,服务层就是在这样的情况下被抽象出来的。概括起来,它就是用来统一完成一部分数据访问或者子业务逻辑。这就是指服务化。
而从这个角度来看,微服务本质上就是指粒度比较细的服务化的实施。
就像上边说的,随着业务越来越复杂,数据量越来越大,并发量越来越大,58 到家因为经历了这些阶段,所以系统架构走上了微服务之路。
具体来说,15 年的时候,58 到家的架构碰到了类似的种种问题:
垂直业务扩展,家政、丽人、速运、平台,一些相似的业务代码拷贝越来越严重
数据量、并发量提升,底层架构复杂性不断向上游扩散,所有调用方都需要关注缓存、分库、存储引擎等,效率逐步降低
jar 包耦合,多个系统依赖一个公用的 jar 包,一个业务升级导致兼容性问题,影响其他业务
数据库耦合,多个业务公用一个数据库,相互耦合,相互影响
SQL 质量低,业务相互耦合,一个业务撰写了一个低质量的 SQL,导致其他业务受影响
其实我们也不是一开始就直接采用微服务架构,这个也是经过了不同阶段而演进出来的。
简单地说,58 到家刚开始的时候,我们先 找到通用痛点,抽象出通用数据访问与子业务,然后将它们 下沉成微服务。
更具体地,早期 58 到家是抽象出用户中心,订单中心,支付中心等来构建微服务的。
我们从 58 速运的角度来讲,刚开始 58 到家是大一统阶段,就是系统没有进行业务拆分的时候,因为刚开始业务量也小,所以它还是可行的。
后来整个系统拆分成了站点、数据库、缓存这三个部分。
接着我们进行了垂直拆分,将平台、家政、丽人、速运这些业务拆分开来。
然后就具体到速运这一块进行服务化架构。
而最近我们还在演进这样一个架构,我们知道速运这一块其实它本身也有多个业务形态,有对小 C 的,有对小 B 的。小 C 是搬家服务,小 B 是货的服务,大 B 是优配服务。原来这三块它们都是耦合在一起的,现在也在进行拆分。
这其实也就是架构演进过程中必然会出现的,而具体再讨论下去,其实就是在做一些微服务架构上的事情了。
上边这些说的都是业务的垂直拆分,下边看一下我们的系统分层情况是怎么样的。
我们现在的系统分成了四层,如下:
第一层:站点平台
第二层:业务服务层。把基础数据通用的东西往上抽,就像上边说的,比如它解决代码拷备的痛点,不能让代码拷来拷去,所以把这份代码抽象成一个服务。解决库的耦合,如果之前没有服务化,可能用代码库,用 jar 包、DLL、SO 库来解决代码拷备这个问题,一个库,多个服务依赖,那库的版本升级,影响范围很大,可能多个服务因为一个库的原因耦合在一起。上边说的这些就是发生在这个层上,它解决的是底层复杂性屏蔽的问题。如果没有服务层,那么 牵一发而动全身。
第三层:基础数据服务层。不包含复杂的业务逻辑,只是数据访问的代理,对数据库层的 CRUD。原来设计为相对简单的“DAO 层”。
第四层:数据层。数据层包括缓存和 DB。
最开始的时候是没有业务服务层的,业务应用分别直接访问数据库,导致大量耦合;架构演进到第二个阶段的时候,我们抽象出一些通用的 业务无关 的基础服务,例如地理位置服务、经纬度服务、短网址服务、短信服务等;到了第三个阶段,我们抽象出一些 通用业务 的服务,例如 passport 服务、订单中心服务、支付中心服务等;未来,我们会进一步去抽象更多的通用业务服务。总之,整个微服务架构演进的思路就是:“共性 + 通用痛点”抽象下沉。
很多架构师只看到微服务的好处,但其实微服务会导致增加 系统运维的复杂性,增加 配置文件的复杂性,加大 追查问题与监控系统的难度。为了解决这些问题,需要有配套的技术体系支撑,例如:引入自动化上线平台解决运维复杂性问题,引入配置中心解决配置耦合的问题,引入监控平台与调用链平台解决监控与问题追查的问题。微服务体系,需要有一系列技术基础设施配套,而不只是引入一个简单的服务框架,而这些配套的技术基础设施,往往比服务框架本身复杂得多。
微服务体系配套基础设施包括但是不限于以下这些东西:
配置中心,解除系统之间因为配置文件导致的耦合,做逻辑上解耦(但物理上仍然保持上下游连接)
消息中心,解除系统之间调用关系导致的耦合,做逻辑上与物理上的双重解耦(物理上不再相互连接)
监控中心,立体化监控,实施机器、进程、接口、日志、用户层面多维度监控,及早发现问题
调用链跟踪系统,图形化,量化展现请求在系统中的调用路径,及早定位问题
像上边提到的,在微服务架构的实施过程中,抓住“共性”与“通用痛点”下沉,是一个最常见的原则。这里简单地再以 58 到家为例做一下总结。家政、丽人、速运各个业务都有自己的账户体系、订单体系、支付体系,这样成本显然是较高的,复杂性成本也是会不断增加的。对于这样的一些通用业务,就应该抽象出 passport 服务统一解决 SSO(Single Sign-On)问题;抽象出订单中心服务解决订单的集中存储与展现问题;抽象出支付中心服务来统一解决微信与支付宝的对接,统一解决对账等问题。
关注业务比研究架构更重要,任何脱离业务的架构设计都是耍流氓。找痛点、解决痛点比高瞻远瞩重要,架构是演进而来的,而不是设计而来的。几点个人浅见,共勉。
点击下方图片即可阅读
Redis 如何分布式,来看京东金融的设计与实践