为什么要学习架构?
因为架构思路是编码的基石,如果脑海中没有架构思路,编码就是一叶障目,看不到全局也就不可能写出好的代码。
先别急着撸代码,先看看理论,有个大致的概念。原理可能看看就能懂,但是只有经历了实践,体会了痛点,才能理解和吸收。
架构选型
组织沟通方式决定系统设计。
一定要根据自己的公司体量、业务形态来选择架构,好的架构都是一步步演进过来的。不要过于崇尚新技术、新架构。
架构的演进一定是源于痛点。
如果你不知道当前架构有什么问题,有什么痛点,那么一定无法演进。有很多公司或个人看到微服务很火爆,就开始盲目追求。这样只能是缘木求鱼。
对于微服务,我只能说,单挑死无全尸。
微服务的首要问题:
- 该不该引入微服务
- 微服务体系需要哪些技术
- 团队怎么演进,怎么落地
该系列文章对于这些问题发表了一些个人的拙见,希望能抛砖引玉,欢迎大佬指点。
单体架构,巨石架构
在开聊微服务之前,我先要你和介绍下单体应用。如果你不知道单体应用的痛,那也不会深刻理解微服务的价值。
当公司团队规模很小的时候,单体应用是最好的选择。主要模式有:LANMP(Linux + Apache或Nginx + MySQL + PHP)和MVC(Spring + iBatis/Hibernate + Tomcat)两种。其优点是学习成本低,开发上手快,测试、部署、运维也比较方便,甚至一个人就可以完成一个网站的开发与部署。
1-4人小团队首选,放开膀子就是撸。
别上来就微服务,几个开发人员的小公司,去追求微服务、去追求中台架构,这是追求完美吗?不是,是找死。
后面有时间我写一篇关于康威定律的吧。
单体架构的优点
- 快,除了快还是快,都在一起,函数直接调用,怎么不快,日志也在一起,什么牵一发而动全身,我就是全身。
单体架构的缺点
- 应用复杂,都耦合在一起。
- 导致了重构困难、难以敏捷性开发,一崩则全崩。牵一发而动全身。
- 难以扩展,可能久了就谁都不敢动了。新同事一来需要阅读大量代码,学习成本极高。如果相关负责人离职,那将是雪崩效应。
- IDE卡,代码都在一起,当然卡。
- 编译慢、启动慢。当单体应用的代码越来越多,依赖的资源越来越多时,应用编译打包、部署测试一次,甚至需要10分钟以上。
- 框架升级困难。升级就只能全升,不能有针对性地利用语言优势。
- 团队协作开发成本高。以我的经验,早期在团队开发人员只有两三个人的时候,协作修改代码,最后合并到同一个master分支,然后打包部署,尚且可控。但是一旦团队人员扩张,超过5人修改代码,然后一起打包部署,测试阶段只要有一块功能有问题,就得重新编译打包部署,然后重新预览测试,所有相关的开发人员又都得参与其中,效率低下,开发成本极高。
微服务1.0
什么东西过大,解决的最好办法就是把它变小。
分而治之,化繁为简。
在我看来,微服务也是分级别的。也就是微的程度。
比如可以先按大的逻辑拆分,用电商举例,商品展示、购买、物流这样分。
也可以商品服务、订单服务、支付服务等这样分。
差别是什么?
前一种只是按接口分,我把展示的接口都放到一个单体写,那样这个单体挂了,购买的人不受影响。
问题就是公共模块(鉴权、底层方法)我要写多套。
适用于中型团队,因为公共模块也不是天天改。
最大程度将业务耦合性降低,A模块挂了不影响B模块,而且模块可以分配给不同的人,单独治理。
线下麻烦点,但是线上稳定点。
举例:账号模块,此时可能是每个单体里面都有一套账号数据生成代码,然后用同一个git维护,一旦更新了就所有单体都要更新。
尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用。
问题
- 代码冗余严重。
- 单机扛不住只能上负载均衡,查日志可能要查多台机器。更新也是,要批量上代码。
- 数据库难以隔离,尤其是用户这种量最多的数据,可能一个bug导致全盘慢。
- 故障无法转移,A机器挂了, B机器的代码是一样的,基本也会挂。
- 影响的是一个整体业务,无法降级,无法熔断。挂就是一个业务挂。
微服务2.0
另一种就是按模块分,或者叫按数据分,这才是真的微服务。SOA(面向服务的架构模式)
适用于大型公司,业务量很大,每个模块都是一个小团队负责。
最大程度减少沟通成本。
当人员数量上来后,最大的消耗并不是编码时间,而是沟通时间。
举例:我将账号集中到一个服务,所有人需要账号信息都找我来拿,然后我这边用负载均衡、集群保证我的高可用,如果有人要更新用户数据,要我走我的接口,要么走我的消息队列,我来维护账号数据的一致性。
围绕业务或者数据构建,服务关注单一业务。服务间采用轻量级的通信机制,可以全自动独立部署,可以使用不同的编程语言和数据存储技术。微服务架构通过业务拆分实现服务组件化,通过组件组合快速开发系统,业务单一的服务组件又可以独立部署,使得整个系统变得清晰灵活。
- 小即是美:小的服务代码少,bug 也少,易测试,易维护,也更容易不断迭代完善的精致进而美妙。
- 单一职责:一个服务也只需要做好一件事,专注才能做好。
- 一个服务只做一件事
- 一个包只做一件事
- 一个函数只做一件事
- 尽可能早地创建原型:尽可能早的提供服务 API,建立服务契约,达成服务间沟通的一致性约定,至于实现和完善可以慢慢再做。
- 可移植性比效率更重要:服务间的轻量级交互协议在效率和可移植性二者间,首要依然考虑兼容性和移植性。
问题是什么?
Fred Brooks 在30年前写道,
“there are no silver bullets”。
但凡事有利就有弊,微服务也不是万能的。
- 天然的复杂性
- 每个模块之间的调用会不会有bug?
- A模块调B、C、D,一个环节出了问题,是不是每个模块报错日志都要看一下?
- 数据一致性怎么保证?
- 沟通成本短期内甚至会上升。
- 原本是我本地的一个方法,现在要转成远程调用,接口或RPC,我是不是还要判断一下ping得通不。
- 原本我可以直接看到代码里面的逻辑的,现在调用有问题是不是要去问,然后提供者还要加文档。
- 测试困难
- 原来单体应用一更新,测就完事了。现在模块B的代码更新了,不兼容或者有bug,是不是依赖他的模块全挂?
- 避免连锁反应。上游(调用者)写了个for循环,给下游(提供者),然后下游再给他的下游,可能就指数级放大了。
- 兼容问题。一旦大家都不一样了,必然会有很多兼容问题。
- 依赖关系复杂。可能到了最后,服务之间的相互依赖会成一团乱麻。
- 分布式事务问题。
怎么解决?
- 基建搭建好:消息队列、日志采集、容器编排等。
- 文档写好,gRPC代码即文档。
- 自动部署,CICD:Gitlab + Gitlab Hooks + k8s
- 染色发布,方便开发和压测。
- 监控体系:k8s,以及一系列 Prometheus、ELK、Conrtol Panle
- Testing:测试环境、单元测试、API自动化测试
好处
既然这么多人选择微服务,肯定是利大于弊的。
- 耦合性极低。(去中心化)
- 数据去中心化: 独占DB,减少数据干扰。以前一个慢SQL打挂全服的情况不会有了。
- 技术去中心化:想用什么语言都可以,只要实现协议即可。便于重构和迭代。
- 治理去中心化:
- 独立进程,隔离部署。有针对性的上集群,热点服务就加机器。
- 监控、日志都在一起,可以很快发现问题。
- 可以用并行思路去调用数据,比串行更快。
- 减少了沟通成本,每个人维护好自己的服务即可。
- 拆分后,代码量少了, bug也更好发现。
实现微服务
- kit:一个微服务的基础库(框架)。
- service:业务代码 + kit 依赖 + 第三方依赖组成的业务微服务
- rpc + message queue:轻量级通讯
本质上等同于,多个微服务组合(compose)完成了一个完整的用户场景(usecase)。
可用性
这是一个大话题,后面慢慢讲到。
- 隔离
- 超时控制
- 负载保护
- 限流
- 降级
- 重试
- 负载均衡
兼容性
Be conservative in what you send, be liberal in what you accept.
发送时要保守,接收时要开放。
按照伯斯塔尔法则的思想来设计和实现服务时,发送的数据要更保守,意味着最小化的传送必要的信息,接收时更开放意味着要最大限度的容忍冗余数据,保证兼容性。
所有依赖的东西都会崩。
所有依赖的东西都会崩。
所有依赖的东西都会崩。
总结
没有最好的架构,只有最适合自己的架构。根据自己的业务、团队来定制架构,能快速交付、快速迭代、持续重构的架构,就是好架构。