在七牛云与 ECUG 联合推出的线上系列分享活动【七牛云架构师实践课】中,七牛云技术副总裁、go-zero 作者万俊峰为我们带来微服务系统设计专题的首个议题——单体和微服务的选择。
以下内容根据实践课整理。
大家好,我是万俊峰,熟悉我的朋友都知道,我是做微服务相关工作的,是开源的 go-zero 作者。
我们在做服务端的时候,其实有另一种选择方案——单体。在我的实践中,很多大型业务也都是从单体服务一步步演进过来的。所以今天我主要想和大家探讨的问题是,单体和微服务应该如何选择?从单体到微服务的改造,应该如何进行?
单体和微服务如何选?
首先从单体说起。很多人在业务初期会有这样的想法,先找个微服务框架,再做一系列设计和架构。其实在业务起步之初,这种情况很常见。大家都知道对一个业务,特别是创业公司来讲,成功概率是非常低的,可能做了一年半载,用户只有几万乃至几千,项目不一定能起来,此时就未必要使用微服务的方案。
所以我根据自己的经验,想和大家分享创业或小项目,应该如何一步步往前走。
针对目前的情况,移动互联网肯定是占大头,所以业务有手机端,如果更友好一点会做桌面端。那么对我们来讲,项目之初可能只有一个单体服务,后面挂一个 DB。在一般情况下,我们会有静态资源,挂一个FileServer,比如说 Nginx 之类的,这样都可以进行访问,对小业务而言已经可以跑通了。
但是随着业务发展,这种场景能支撑的量是比较小的,而且它没有对故障容忍的方案。所以说当业务有一点发展以后,我们可能就会用到这样的方式。也就是在前面挂一个 Nginx 作为反向代理,后面再挂三五台主机,这样就可以支撑好几倍的量了。
其实这种方案跟第一版的架构没有太多变化,但是其实比较容易得到横向的扩展,不需要去改写,只是布了多个节点。只要 DB 稍微升级一下,特别是云原生时代大家使用云主机,升级非常容易,在业务发展之后稍作改造,就又可以承载业务往前发展了。
当业务继续发展,可能一台 Nginx 就不够了,需要配置多台 Nginx,比如把 Nginx 在 DNS 上面去配一些记录,就可以在 Nginx 做随机负载均衡。同时面对文件系统变多的情况,我们可以加一点投入,比如买七牛云的 OSS 存储,因为对于 OSS 来讲,很多时候 SDK 是集成在客户端的,你可以直接去使用,就会把存储从 Nginx 剥离掉。
那么对于 Nginx 来讲,可能就用几台服务器,组成之前那样简单随机的轮循。从单体来讲,在不做任何拆分的情况下,就可以放到后端的 Kubernetes 集群里面去了。此时还是一个单体的状态,只是放在 Kubernetes 上面,也就是在多个节点上跑。之后就可以直接买云厂商的 RDS、DB,特别是云厂商的 DB 扩容简单,可以承载很大的规格。在这种架构下,能承载相对来讲比较高的并发量和用户量,此时再用redis做一些缓存、临时存储,我认为这种架构就可以承载很多初创公司的前期业务发展阶段了。而且这个架构非常非常简单,不需要有太多的运维成本。
在这样的情况下,可以打一个 docker 的镜像,不一定用完全自动化的 CICD 流程,业务价值已经得到很好的体现。所以在做架构的时候,我一直强调不能脱离业务去看技术。正如把大公司的复杂架构放到小公司就行不通,技术架构过于复杂反而会阻碍业务的发展。因此我做 go-zero 有一个信条——一定要保持简单。在业务适当的时期,我们要保持业务架构足够简单。
当然在简单的同时,我们有弹性和前瞻性。所以我想和大家聊聊业务架构什么时候该去升级。
在系统出现一些故障,开始冒烟的时候就要进行规划,永远不要等到它真的着起火来。同时要结合业务,和业务部门或者公司老大多沟通预期。但同时不要过于超前,用一个巨大的复杂系统来跑简单业务,也是不合适的。我以前做过的好几个大的项目,都是从单体一步步往前走,后面再拆成几十个上百个微服务,支撑大体量的业务。
讲了这么多,我们来看一下单体的优点和缺点。
单体的优缺点
首先第一个优点,单体对我们来讲开发真的很简单。对 go 来讲就是一个二进制文件,你 build 完了,打一个 docker 镜像,把它上传到云厂商一般都会的有私有仓库,你也可以用 go-zero 提供方法,帮你一键做出一个大概 10M 的 docker 镜像。这和以前 java 几百 M 甚至几个 G 的镜像是很不一样的, go-zero 也自带Dockerfile 的生成方法。
同时单体的测试很容易。在社区里经常有人问微服务怎么调试?这是很难的问题,如果我有上百个微服务,在本机上全跑起来,公司要么给每个人配一个顶级的电脑,成本高昂,要么提供开发的环境,需要多租户,否则相互之间会有影响。但是单体的测试就很容易了。
单体的部署也非常简单,因为相互之间没有依赖,不会互相影响。我们写单体的时候,发版就好了,不会说要找谁分析依赖关系,大家没有太多的心智负担。
优点说完了我们来看缺点,我认为最大的缺点就是扩展难。业务发展以后,功能越来越多,但凡加一个服务,全局可能受到影响。同时小改动导致全量更新,我们曾经就遇到过类似问题,更新了一个小功能结果导致整站挂掉。所以任何一个改动就要全站更新。而据行业不完全统计,基本上 80% 以上的故障都是更新导致的。所以小问题引发大故障,是蛮严重的问题。最后就是单体承载的业务规模非常有限,因为业务规模大了以后,人、业务、数据都会很多,很难通过服务拆分的方式,去承载业务规模的增长。
以上就是我为大家总结的单体优缺点。
微服务概览
咱们接下来可以看下图,如图所示有 5 个微服务,每个微服务都有各自的 DB,每个 DB 上面对应自己的微服务。
所以我一直在强调,我们每一个微服务,应该有自己的 data store,不应该直接访问别人的 DB,这是非常非常重要的点。我们在微服务上有 API getaway,通过 API getaway,让 A 服务就到 A 服务,B 服务就到 B 服务,我们会梳理倒流过去,这是微服务的架构。看起来很简单,但是其中每一个环节里面都有承载者,都包含着刚才单体服务的引进。这是微服务的概览。
微服务优缺点
刚才讲单体我们曾提到,不一定每个小功能都独立成微服务,一般从粗到细来逐步分拆,每个里面都包含了刚才单体的一些 CICD 流程等,可以说这就是把不同服务之间做了比较好的隔离。
在这种情况下,我们先来看看微服务的优点。
我们可以边界清晰地把业务做拆分。正如刚才的图中,五个微服务底下分别都挂了自己的 DB,DB 之间、微服务之间不可以访问别人的 DB,就让数据得到清晰拆分。而数据拆分是微服务的根基所在,数据拆分做不好,微服务基本是白搞。
同时,由于每一个服务已经进行了拆分,所以当我的业务发展到一定程度之后,开发起来就比较容易,便于大家理解和维护。
微服务中的技术栈相对来讲也是比较独立的,你可以使用不同的技术栈来开发各自的微服务,当然我不推荐这样,我的推荐是技术栈要统一,这样技术交流比较方便。但是如果公司比较大,可能不同的服务需要使用不同的语言,那么技术栈独立就是一个优点了。
易于做持续集成和部署,也是微服务的优点。它任何时候都可以更新,不至于影响全站,更新有问题以后也可以降级。比如说某个服务有问题了,在go-zero里面提供了降级的fallback机制。
在微服务中,对不同的服务需要有不同的治理等级,重要业务重点治理,非重要任务因为请求量较小,我们也不会有那么复杂的治理。
微服务的优点中,很重要甚至是最重要的,就是稳定性比较容易保障。某一个服务把整个全栈拉垮,是基本不存在的。
但是微服务的缺点也很明显。
首先就是看起来复杂了,本来单体可以搞定,现在一大堆放在一起。另外数据拆分的复杂度也不能忽视。对于数据拆分就需要一定功底,如果说微服务交给一个新手来做,其实挺难。这也就是我说在业务早期,可以先用单体的原因。同时微服务调试比较困难,比较难做详尽的测试。跨服务的修改,相对来讲也是比较比较麻烦的。微服务的部署也是一个难点,因为它有依赖关系,我在团队里要求所有的服务要按照时间来打标签,需要我们每天都检查所有的deployment 的部署时间。在实践中,我强制要求我们团队,所有的部署必须在 3 个月的时间里尝试更新一次,确保不会留过多的技术债务,这也是一个可以分享的点。
以上就是单体和微服务各自的优点和缺点。那么新的问题就来了——单体和微服务,到底怎么选?
到底怎么选?
以下图为例,左边就是单体,把各种服务放在一个五边形里面。右边的是微服务,各自拆分开,变成了五个。所以说简单肯定是左边单体,微服务比较复杂。选择起来我觉得要从四方面考虑。
一是看业务。小的公司小的业务,刚开始尝试的时候没必要引入过多的复杂度,是可以用单体的方式来往前跑;二是看团队。比如在一个大的公司里面,其中本身就是微服务体系,其实没有任何开发的心智负担、运维的心智负担,同时一般做一个业务会有体量预估,有一堆资源去推动业务往前走。这种时候你是可以根据自己的需要,来选微服务的;三是从简单入手。以我现在做的业务为例,体量还是不小的。但要快速满足业务价值的交付,一开始也是从单体起步,后面再来拆分。只要把前后事情隔离清楚,就是非常容易做的。因为对前端都是无感的,那我后面从单体往微服务转,只是后端的事情,这也是第四个要点——预留可能性。
从单体到微服务转换的信号
如果一开始用单体走了一段时间,那么什么时候该去考虑往微服务转呢?我分享以下这些信号供大家参考。
首先就是你感觉这个单体服务已经过度复杂了。这是一个主观的体感,就是你觉得这个单体我写了也不敢部署,一部署就怕全站挂,代码已经搞不清楚了,要问一圈人去判断改代码的影响。如果你有这个感觉,可能就是要转换的时候了。
另外就是单体已经不能满足当前的业务发展需要。比如我之前曾接手一个大型系统,业务基本上每周都要宕机好几次。当前的架构满足不了高并发和性能的要求,这个时候需要去转。
当研发效率低下的时候,也需要转。虽然我管团队很多年,但是我基本上每天写代码,也跟大家一起看代码。我喜欢了解大家时间是怎么安排的,有一阵子大家跟我说,我们最后一天是留着去merge代码的。我觉得这个成本挺高挺浪费的,就去了解为什么。大家merge代码,出现几百个文件都已经修改并要 merge的情况,这样就可能把别人提交的代码冲没了。这种局面下,研发效率已经很低,改也就是必须的了。
还有一个判断的点,就是当运维和开发不太敢更新的时候,就需要改变了。在单体中,高峰时间不能更新,更新风险的大家也不愿承担,你也等我也等,等到有一堆修改才去更新,这样有问题也很难知道到底是谁的代码导致的了。
最后我想说的情况,是团队人比较多的时候。很多人在在一个服务一个单体上搞,十有八九是要出问题的。这种情况下要考虑做转变了。
单体到微服务怎么转?
我做过多次单体到微服务的转型,也总结出很多经验和教训,希望能给大家一些借鉴。
我在几次从单体到微服务的转型过程中,都是可以做转型决定的,但也肯定会跟 CEO 商量,讨论对业务影响要怎么样去规避,对公司层面给出一个预期。如果我投入比较多的人力做转型,肯定会影响到业务的迭代,造成一系列的问题。这一定要有心理预期,转型过程中,不太可能零故障。所以说转型过程好比开着飞机换引擎,这是大家经常听到的一句话。
这个过程中,最难的一件事是什么?就是敢做这个决定。这件事情是头等重要的事,需要一个勇于扛责任的领导,有决心的领导。如果没有决心,那么后面一切都是空谈,而心够决,这件事就成功了一半。
在下定决心之后,我们要充分调研必要性、可行性。或者说下决心之前就要有充分的调研。云原生发展到今天,基本上来讲不会有可行性的问题,必要性是重中之重。我分享一下我做技术选型的经历,之前系统是 java 做的,是很大的一个整体。那么出了问题以后,谁也搞不清楚,只有靠重启能搞定了,有时候重启就要花两个小时。所以我接手这个团队的时候,就坚决要转,只要还想继续业务发展,就必须要重构,必须得重写,必须得做技术转型,所以我们就转。
转的时候也需要充分的技术选型考虑。比如在上述情况下,我将 java 转成了 go,其中一个很大原因,是我不希望原来的团队从原来的代码里面,拷贝出原来的业务逻辑。因为你要知道,程序员想做一件事情,最快的方法就是复制粘贴。我完全换了一个语言,就只有理解原有代码,去重写它。我们既然从头开始,我就希望项目一定是从根本出发,排除原来的干扰因素,去除一些惯性的思维。
据说谷歌每三年重新书写一遍系统,原因就在于它要保证 4 个 9,在系统写完后,大家对新业务系统了如指掌,有任何问题可以在很短的时间里分析并解决。我们大概花了五个月的时间写完,之后三年时间都没有线上重大故障。
虽然最终的结果令人满意,但是项目之初,是没有成果可以展示的,如果刚好遇到述职,你也没有东西可讲。万一领导说这个不行,没有产出不要干了,整个事情就报废了。所以说得到公司和部门领导支持,也是非常重要的。
最后我想分享的是,大家一定要关注迁移核心团队的能力,包括经验、执行力、自驱力。组成精干核心团队的时候,一定要选择有经验、执行力强、有自驱力的团队成员,大家遇到问题不放过,形成最佳实践,然后才能逐步推广团队做改造。
微服务的改造策略
我们在微服务的改造中,需要要讲究策略。
初期要挑选精兵,在改完之后这些精兵可以变成项目的核心人员。比如我之前例子中微服务的五个部分,我每一个都需要核心人员把控质量。在前期的时候,打磨的不光是产品和架构,更重要的是人。人打磨出来以后,后面的项目往前推进,就会非常容易。
同时在改造过程中,我们要把握从外往内、从边缘到核心的原则。一定要有业务预期,一定要有故障的预期,从外往内也就是让故障可控,让问题在非核心的地方先暴露,这样再往核心逐步推进。
接下来我们要关注数据的拆分、迁移和验证。数据如果不正确,基本上来讲改造是白改。而且在改造的过程中,特别特别重要的一点就是一定要可回滚。你不能说改了,上了,挂了,业务没了。所以说改造的过程中,一定要让业务可以回滚到原来的系统里,让它能跑,这样一来就不用那么提心吊胆,心态上就轻松很多了。
我们在改造的时候,最好每天都要复盘,改造过程一般不会太长,半年时间基本上主体的改造就能完成。这个过程中,越早期越要多进行复盘,这样能越早总结经验教训,加速改造。
最后一点也非常重要,就是成果要及时回报。比如之前的项目做了五个月,开学前上线后服务增长了四倍,非常平稳效果很好。但是这五个月中如果缺少汇报,领导就不知道你在干什么,价值在哪里。所以汇报不是为了邀功,更重要的是管理预期,让改造能平稳地执行下去。很多技术人不太关注这个点,但其实挺重要的。
以上就是我今天想和大家分享的内容,在下一讲中,我会和大家分享微服务的设计痛点,欢迎大家持续关注。