为什么要学习架构?
因为架构思路是编码的基石,如果脑海中没有架构思路,编码就是一叶障目,看不到全局也就不可能写出好的代码。先别急着撸代码,先看看理论,有个大致的概念。
原理可能看看就能懂,但是只有经历了实践,体会了痛点,才能理解和吸收。
当下互联网行业的火爆,用户量的激增,让各大公司开始考虑扩容和重构,微服务是首先引入眼帘的。然而据我所知,国内不少中小规模的技术团队对微服务的概念都不甚了解,对该不该引入微服务也不置可否。还有一些技术团队,没有考虑实际业务场景,只是为了追求技术热点,盲目引入微服务,但又缺乏相应的技术掌控能力,最后影响了业务的稳定性。
一、架构选型
组织沟通方式决定系统设计。
一定要根据自己的公司体量、业务形态来选择架构,好的架构都是一步步演进过来的。不要过于崇尚新技术、新架构。
架构的演进一定是源于痛点。
如果你不知道当前架构有什么问题,有什么痛点,那么一定无法演进。有很多公司或个人看到微服务很火爆,就开始盲目追求。这样只能是缘木求鱼。
对于微服务,我只能说,单挑死无全尸。
微服务的首要问题:
- 该不该引入微服务
- 微服务体系需要哪些技术
- 团队怎么演进,怎么落地
架构选型
根据康威定律:康威定律
“设计系统的架构受制于产生这些设计的组织的沟通结构。”
每个公司的组织架构不同,则会演进出不同的架构。这个很关键,后面会反复提到。
比如你的公司是垂直架构,那就是递进关系,只有一条线:后端--前端--测试--运维,这样开发过来,最后找问题肯定是反向来的。
如果你的公司是水平架构,那就是分布式关系,比如:
游戏部+视频部+直播部----大后端(中台),那么你的架构一定是分布式的,并且做了服务拆分,将公共逻辑做成了服务。
那么,有银弹么?没有!
就像阿里为什么要建立中台又拆?只有不断的业务改进和架构演进,一切都是为了更好地服务业务,减少开发成本。
所以:
那么,我们来讲讲痛点。
二、单体架构,巨石架构
在开聊微服务之前,我先要你和介绍下单体应用。如果你不知道单体应用的痛,那也不会深刻理解微服务的价值。
当公司团队规模很小的时候,单体应用是最好的选择。主要模式有:LANMP(Linux + Apache或Nginx + MySQL + PHP)和MVC(Spring + iBatis/Hibernate + Tomcat)两种。
其优点是学习成本低,开发上手快,测试、部署、运维也比较方便,甚至一个人就可以完成一个网站的开发与部署。
1-4人小团队首选,放开膀子就是撸。
别上来就微服务,几个开发人员的小公司,去追求微服务、去追求中台架构,这是追求完美吗?不是,是找死。
单体架构的优点
-
开发简单:
- 只有一个git,下载部署调试都很方便。
- 很容易看到全貌,所有的代码都在这里了。
-
易对应用程序进行大规模修改:
-
没有服务间的联调,没有多级调用。测试相对简单直观:
-
只有一个应用,扔上去执行就OK了。部署简单明了:
-
直接运行多个实例,开负载均衡即可。横向扩展不费吹灰之力:

单体架构的缺点
- 应用复杂,
。耦合性高
- 导致了重构困难、难以敏捷性开发,一崩则全崩。牵一发而动全身。
- 难以扩展,可能久了就谁都不敢动了。新同事一来需要阅读大量代码,学习成本极高。如果相关负责人离职,那将是雪崩效应。
- 开发困难
-
,代码都在一起,当然卡。IDE卡
-
。当单体应用的代码越来越多,依赖的资源越来越多时,应用编译打包、部署测试一次,甚至需要10分钟以上。编译慢、启动慢
-
-
,只能长期依赖某个可能已过时的技术栈。框架升级困难
- 升级就只能全升,不能有针对性地利用语言优势。
-
团队协作开发成本高
- 早期在团队开发人员只有两三个人的时候,协作修改代码,最后合并到同一个master分支,然后打包部署,尚且可控。
- 但是一旦团队人员扩张,超过5人修改代码,然后一起打包部署,测试阶段只要有一块功能有问题,就得重新编译打包部署,然后重新预览测试,所有相关的开发人员又都得参与其中,效率低下,开发成本极高。
- 如果分了团队开发模块,团队之间的时间安排会不一致,A团队的代码很可能对B团队的代码产生影响。
-
如果有一个大bug,内存泄漏、DB打爆,都是噩梦。无法隔离:
三、什么是服务化
最开始的启发来源于一本书《The Art of Scalability》,即《可扩展性的艺术》,探讨了现代企业的可扩展 Web 架构、流程和组织。
里面提到了三种扩展方式:
分别是:
-
:单体+负载均衡(随机算法)多个实例的负载均衡
【微服务理论】微服务概览 -
:单机+负载均衡(哈希算法)根据请求的属性路由请求
【微服务理论】微服务概览 -
:即服务。每个服务都是由一组专注的、内聚的功能职责组成。根据功能拆分业务
【微服务理论】微服务概览
围绕业务功能构建的,服务关注单一业务,服务间采用轻量级的通信机制,可以全自动独立部署,可以使用不同的编程语言和数据存储技术。
我们将单体应用中的逻辑分离,单独成立一个项目,独立开发、测试、上线、运维,然后将进程间调用变成远程RPC调用,各个模块之间不耦合,可以交给不同的团队管理,还可以根据各个模块的特点选择不同的语言。这样就可以解决单体应用膨胀、团队开发耦合度高、协作效率低下的问题。
微服务1.0(SOA,面向服务的架构模式)
什么东西过大,解决的最好办法就是把它变小。
分而治之,化繁为简。
在我看来,微服务也是分级别的。也就是微的程度。
比如可以先按大的逻辑拆分,用电商举例,商品展示、购买、物流这样分。
也可以商品服务、订单服务、支付服务等这样分。
- 纵向拆分:从业务维度进行拆分。比如可以先按大的逻辑拆分,用电商举例,商品展示、购买、物流信息。
- 横向拆分:从公共且独立功能维度拆分。标准是按照是否有公共的被多个其他服务调用,且依赖的资源独立不与其他业务耦合。比如商品服务、订单服务、支付服务、评价服务等。
【微服务理论】微服务概览
差别是什么?
前一种只是按接口分,我把展示的接口都放到一个单体写,那样这个单体挂了,购买的人不受影响。
问题就是公共模块(鉴权、底层方法)我要写多套。
适用于中型团队,因为公共模块也不是天天改。
好处
最大程度将业务耦合性降低,A模块挂了不影响B模块,而且模块可以分配给不同的人,单独治理。
线下麻烦点,但是线上稳定点。
举例:账号模块,此时可能是每个单体里面都有一套账号数据生成代码,然后用同一个git维护,一旦更新了就所有单体都要更新。
尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用。
问题
- 代码冗余严重。
- 单机扛不住只能上负载均衡,查日志可能要查多台机器。更新也是,要批量上代码。
- 数据库难以隔离,尤其是用户这种量最多的数据,可能一个bug导致全盘慢。
- 故障无法转移,A机器挂了, B机器的代码是一样的,基本也会挂。
- 影响的是一个整体业务,无法降级,无法熔断。挂就是一个业务挂。
微服务架构与SOA的异同
相似点:
- 都是特定的风格架构;
- 都以一系列服务方式组织系统;
不同点:
- 完全不同的技术栈(微服务架构采用轻量级、开源技术以及哑管道通信);
- 处理数据方式不同(微服务架构有自己的数据库);
- 服务尺寸、规模不同(微服务架构中的服务相对较小);
- 通信方式不同,SOA采用了ESB企业总线之类的重量级通信方式。
微服务2.0
另一种就是按模块分,或者叫按数据分,这才是真的微服务。
什么是微服务
:围绕业务功能构建的,服务关注单一业务,服务间采用轻量级的通信机制,可以全自动独立部署,可以使用不同的编程语言和数据存储技术。
重点:每个服务都有自己数据模型和数据库。
只有独占DB,才能谈隔离,才能有针对性地扩缩容、上集群。
适用于大型公司,业务量很大,每个模块都是一个小团队负责。
最大程度减少沟通成本。
当人员数量上来后,最大的消耗并不是编码时间,而是沟通时间。
举例:我将账号集中到一个服务,所有人需要账号信息都找我来拿,然后我这边用负载均衡、集群保证我的高可用,如果有人要更新用户数据,要我走我的接口,要么走我的消息队列,我来维护账号数据的一致性。
围绕业务或者数据构建,服务关注单一业务。服务间采用轻量级的通信机制,可以全自动独立部署,可以使用不同的编程语言和数据存储技术。微服务架构通过业务拆分实现服务组件化,通过组件组合快速开发系统,业务单一的服务组件又可以独立部署,使得整个系统变得清晰灵活。
- 小即是美:小的服务代码少,bug 也少,易测试,易维护,也更容易不断迭代完善的精致进而美妙。
- 单一职责:一个服务也只需要做好一件事,专注才能做好。
- 一个服务只做一件事
- 一个包只做一件事
- 一个函数只做一件事
- 错误例子:Common utils ,最后就只是垃圾桶。
- 尽可能早地创建原型:尽可能早的提供服务 API,建立服务契约,达成服务间沟通的一致性约定,至于实现和完善可以慢慢再做。
- 可移植性比效率更重要:服务间的轻量级交互协议在效率和可移植性二者间,首要依然考虑兼容性和移植性。
微服务的问题是什么?
Fred Brooks 在30年前写道,
“there are no silver bullets”。
但凡事有利就有弊,微服务也不是万能的。
- 服务的拆分与定义是一项挑战;
- 没有具体的、良好的算法可以完成拆分工作,这让服务的拆分更像一门艺术。
- 如果拆分的不好,会成分布式单体,会包含单体和微服务两者的弊端。
- 分布式系统带来各种复杂性,使开发、测试和部署变得困难;
- 跨服务的事务和查询。
- 数据一致性。
- 需要高度自动化。
- 需要统一的日志采集。
- 当部署跨越多个服务的功能时需要谨慎地协调更多开发团队;
- 需要根据服务依赖来制定开发计划。
- 需要制定统一的通讯协议。
- 开发者需要思考到底应该在应用的什么阶段使用微服务架构;
- 初创公司应该从单体开始。
- 测试困难
- 不知道是代码的bug还是环境的bug。
- 连锁反应
- 上游(调用者)写了个for循环,给下游(提供者),然后下游再给他的下游,可能就指数级放大了。
- 需要添加限流、熔断等策略。
- 依赖关系复杂
- 服务都成了模块化,彼此之间的依赖关系是极其混乱的,如果不用工具,很难维护调用关系。
常见问题案例
这样带来什么问题
:
问题1:for循环点查,qps被成倍放大。一个请求到了下游可能就成了100个。
问题2:串行请求,慢,而且不做降级就会直接超时导致500。
问题3:测试困难,不是代码的问题,而是部署的问题。
问题4:维护困难
• 日志查看,这么多台机器(或目录)。
• 联调:不知道是哪个环节慢。
• 报警:哪一环被打挂了也不知道。
解决qps放大:
- 粗粒度API,批量返回多个用户的元数据。
- 并行请求,多协程请求多个服务节点,再组装结果。
微服务的优势
既然这么多人选择微服务,肯定是利大于弊的。
- 使大型的复杂应用程序可以持续交付和持续部署
- 自动化测试—-持续交付和持续部署
- 独立部署
- 开发团队自主且松散耦合
- 每个服务都相对较小并容易维护;
- IDE快,启动快。
- 提高调试、部署等环节效率。
- 拆分后,代码量少了, bug也更好发现。
- 减少了沟通成本,每个人维护好自己的服务即可。
- 服务可以独立部署;
- 服务可以独立扩展;
- 有针对地选择服务器。
- 微服务架构可以实现团队的自治;
- 每个团队可以有自己的技术和管理方法。
- 更容易实验和采纳新技术;
- 方便试错和迭代。
- 更好的容错性;
- 故障隔离了,进程和数据库都隔离了。
- 耦合性极低。(去中心化)
- 数据去中心化: 独占DB,减少数据干扰。以前一个慢SQL打挂全服的情况不会有了。
- 技术去中心化:想用什么语言都可以,只要实现协议即可。便于重构和迭代。
- 治理去中心化:
-
:一个服务只做一件事情。原子服务
-
:独立进程、独立机器、独立发布。独立进程
-
:每个服务之间独立部署,可以避免相互影响,并且和按需进行分配资源,节省成本 。隔离部署
-
四、实现微服务
微服务这么好,我们该怎么实现它呢?
组件服务化
传统实现组件的方式是通过库(library),库是和应用一起运行在进程中,库的局部变化意味着整个应用的重新部署。
通过服务来实现组件,意味着将应用拆散为一系列的服务运行在不同的进程中,那么单一服务的局部变化只需重新部署对应的服务进程。
我们用 Go 实施一个微服务:
- kit:一个微服务的基础库(框架)。
- service:业务代码 + kit 依赖 + 第三方依赖组成的业务微服务
- rpc + message queue:轻量级通讯
本质上等同于,多个微服务组合(compose)完成了一个完整的用户场景(usecase)。
基础设施自动化
无自动化不微服务,自动化包括测试和部署。单一进程的传统应用被拆分为一系列的多进程服务后,意味着开发、调试、测试、监控和部署的复杂度都会相应增大,必须要有合适的自动化基础设施来支持微服务架构模式,否则开发、运维成本将大大增加
- 基建搭建好:消息队列、日志采集、容器编排等。
容器化是微服务的基石
- 文档写好,gRPC代码即文档。
- 自动部署,CICD:Gitlab + Gitlab Hooks + k8s
- 染色发布,方便开发和压测。
- 监控体系:k8s,以及一系列 Prometheus、ELK、Conrtol Panle
- Testing:测试环境、单元测试、API自动化测试
可用性
著名的 Design For Failure 思想(所有依赖的东西都会崩)
微服务架构采用粗粒度的进程间通信,引入了额外的复杂性和需要处理的新问题,如网络延迟、消息格式、负载均衡和容错,忽略其中任何一点都属于对“分布式计算的误解”。
- 隔离
- 超时控制
- 负载保护
- 限流
- 降级
- 重试
- 负载均衡
鲁棒是Robust的音译,也就是健壮和强壮的意思。 它也是在异常和危险情况下系统生存的能力。
比如说,计算机软件在输入错误、磁盘故障、网络过载或有意攻击情况下,能否不死机、不崩溃,就是该软件的鲁棒性。
所谓“鲁棒性”,也是指控制系统在一定(结构,大小)的参数摄动下,维持其它某些性能的特性。
兼容性
一旦采用了微服务架构模式,那么在服务需要变更时我们要特别小心,服务提供者的变更可能引发服务消费者的兼容性破坏,时刻谨记保持服务契约(接口)的兼容性。
伯斯塔尔法则伯斯塔尔法则:
Be conservative in what you send, be liberal in what you accept.
发送时要保守,接收时要开放。
按照伯斯塔尔法则的思想来设计和实现服务时,发送的数据要更保守,意味着最小化的传送必要的信息,接收时更开放意味着要最大限度的容忍冗余数据,保证兼容性。
既然分了部门。语言、习惯等都会有很多不一样,所以要多做兼容,多写文档(gRPC代码即文档)。
所有依赖的东西都会崩。
五、微服务的政治
想要推行一种新的架构,都是困难的,毕竟谁也不想做改变。
银弹现象:
过热期(迷恋和崇拜)— 低谷期(失望)— 成熟期(生产力的高地,理性地使用)
理性地看待新技术。
骑大象的人:大象代表思维中情绪化的部分,它完成了绝大部分决策。骑大象的人代表理性的部分,用来对大象的决策进行判断。
而架构的改变也必然会带来组织架构的改变。
比如以前,后端开发只是按功能大致分一分,谁写的就一直维护那一块,然后写完了就发布测试,然后打包给运维上线。
但是一旦线上有问题的时候,只能运维先看服务器有没有问题,没有问题就开始摇后端负责人,然后负责人大致看一下,看不出来就喊对应功能的负责人。
有的甚至连日志在哪里看都不知道,毕竟不知道运维怎么部署的。
现在转为了微服务,大家自己负责这个模块的开发、单元测试、运维部署、监控,自动化部署,日志采集也是统一的。
You build it , you run it
运维只需要提供好用的工具,测试只用黑盒测试,测用户场景和UI。
这样就形成了一个闭环团队,而且可扩展性大大提升。
总结
没有最好的架构,只有最适合自己的架构。根据自己的业务、团队来定制架构,能快速交付、快速迭代、持续重构的架构,就是好架构。
第一,要记住微服务不是解决所有问题的万能"银弹"。
第二,编写整洁的代码和使用自动化测试至关重要,因为这是现代软件开发的基础。
第三,关注微服务的本质,即服务的分解和定义,而不是技术,如容器和其他工具。
第四,确保你的服务松耦合,并且可以独立开发、测试和部署,不要搞成分布式单体(Distributed Monolith),那将会是巨大的灾难。
第五,也是最重要的,不能只是在技术上采用微服务架构。拥抱 DevOps 的原则和实践,在组织结构上实现跨职能的自治团队,这必不可少。还必须记住∶ 实现微服务架构并不是你的目标。你的目标是加速大型复杂应用程序的开发。
当问题足够大、有足够多的不确定性因素时,人们习惯把大的问题拆分成小的问题,通过分割、抽象和重用小而可靠的功能模块来构建整体的方案。
但是当这些小的、可重用的部分越来越多时,又会出现新的问题。
参考:
- 《微服务架构设计模式》