单体的优缺点
单体应用就是将应用程序的所有功能都打包成一个独立的单元,最终以一个WAR包或JAR包存在,没有外部的任何依赖,里面包含DAO、Service、UI等所有的逻辑。单体应用有以下优点:
便于开发:只需借助IDE的开发、调试功能即可完成
易于测试:只需要通过单元测试或浏览器即可完成测试
易于部署:打包成单一可执行jar包,执行jar包即可完成部署
不幸的是,这种简单的单元有很大的局限性。应用程序随着业务需求的迭代,功能的追加扩展,最终成为一个庞然大物,变得更加复杂,逻辑耦合严重,难以理解。团队开发人员职责不清,部署困难,回归测试成本巨大,交付效率大大降低,总结下来,单体应用有以下5大缺点:
1、复杂性高
代码难以理解
在业务规模和团队规模发展的一定阶段,这些不足表现的更加明显,单体架构的不足首先表现在复杂性上, maven模块增多,多个模块耦合在一起,代码结构混乱,使得团队成员没有一个人理解整个代码逻辑;
难以理解导致代码质量低,复杂性进一步增加
难以理解导致代码复用度降低,因为你不知道哪些可以复用的;即便修改,影响范围也不好确定,这导致这样开发宁愿新建一个新方法和新的类,进一步导致重复代码越积越多;
代码难以被修改和重构
不理解代码当然也就写不出高内聚低耦合的代码,和代码质量持续下降;复杂性进一步增加
随着复杂度的增加,耦合度越来越高,代码牵一发而动全身,代码已经很难修改和重构了。
团队职责不清晰
高度耦合的单体工程使得逻辑边界模糊不清,新的业务需求开发任务无法有效分配到人,团队人员职责不清晰,沟通成本增加。
2、交付效率低
构建和部署耗时长,难以定位问题,开发效率低
代码量比较庞大,首先是编译耗时变长,开发调试将大部分时间花在重新编译上,代码量的增加又很难定位bug,导致开发效率进一步降低,在代码合并过程中极易遇到代码冲突,又花上不少时间用在解决代码冲突上;这都是导致开发效率地下的因素;
代码复杂和变更影响难以理解,需要数天完成全量测试
当我们开发完一个新的功能或者修复一个bug,代码的变更影响是很难预估的,所以每次发布之前都要进去全量功能的回归测试;
全量部署耗时长、影响范围广、风险大,发布频次低
正因为这种全量部署耗时长、影响范围广、风险大,导致我们将很多功能和修复聚集在一起进行开发完成,这导致了产品发布频次降低,新的功和更换的体验能不能及时呈现给用户,甚至被竞争对手赶超
3、伸缩性(scalable)差
单体只能按整体横向扩展,无法分模块垂直扩展
IO密集型模块和CPU密集型模块无法独立升级和扩容
业务模块对资源的需求是不一样的,由于所有模块部署到一起,单体架构IO密集型模块和CPU密集型模块无法独立升级和扩容的,比如图片压缩,加解密这些 都是cpu资源密集的应该升级CPU,而IO密集型的模块比如日志收集服务IO操作比较多需要更大的内存,使用比如SSD性能更好的磁盘。
4、可靠性差
一个bug有可能引起整个应用的崩溃
由于所有模块都是部署在一个实例中,一个bug会引起整个应用的崩溃,比如一个不重要的模块的内存泄露就将会导致所有应用实例一个个crash掉
5、阻碍技术创新
受技术栈限制,团队成员使用同一框架和语言
受技术栈限制,团队成员必须使用同一框架和语言,模块得不到拆分,不能使用新的语言和框架;
升级和变革技术框架变得困难
当有符合业务场景的新技术产生或者新版本时,升级和变革技术框架所带来的重构成本和风险变革很高
尝试新语言变得困难
想尝试新的语言也变得很困难,因为开发成本的上升,重构和新需求迭代无法协调,所以最终只能是妥协继续使用原来的框架和语言
那么如何解决单体的不足呢,通过迁移到微服务架构来解决,我们看一下什么是微服务?
微服务的定义
微服务架构:将单体应用拆分为多个高内聚低耦合的小型服务,每个小服务运行在独立进程,由不同的团队开发和维护,服务间采用轻量级通信机制,独立自动部署,可以采用不同的语言及存储。
单体架构整个团队维护开发一个大工程及一个单库,到了微服务架构,用户请求经过API Gateway被路由到下游服务,服务之间以轻量级通信协议进行通信,服务通过注册中心发现彼此,每个服务都有专门的开发维护团队,每个服务对应独立的数据库,服务独立开发,独立部署和上线。
接下来我们总结下微服务的优点。
微服务的优点
1、易于开发与维护
微服务相对小,易于理解;
启动时间短,开发效率高。
2、独立部署
一个微服务的修改不需要协调其它服务。
3、伸缩性强
每个服务都可以在横向和纵向上扩展;
每个服务都可按硬件资源的需求进行独立扩容。
4、与组织结构相匹配
微服务架构可以更好将架构和组织相匹配;
每个团队独立负责某些服务,获得更高的生产力。
5、技术异构性
使用最适合该服务的技术;
降低尝试新技术的成本。
微服务的挑战
没有任何技术是银弹,微服务也是如此 ,都或多或少有一些缺点和问题。那么我们就必须针对这些问题一一解决,也是我们接下来章节重点去做的。
我首先面对就是要将单体拆分成多个服务。
1、服务拆分
微服务拆分原则:领域模型、限定上下文、组织架构、康威定律
现实中没有一个具体明确的方法可以将拆分一步到位,而是遵守一定的原则,比如根据领域模型、组织架构、单一职责这些进行拆分在拆分的过程中还要结合经验判断,并且随着需求迭代,架构持续优化演进,优化服务的拆分。
每个微服务拥有独立数据库
服务拆分的同时还要考虑到存储数据库也要独立,当多个服务直接读写数据库中同一张表时,对这些表做任何改动都需要协调这些相关服务的部署。
这一点违背了服务相互独立这一原则。
共享的数据存储很容易不经意间造成耦合。每个服务需要有自己的私有数据。比如订单表被订单服务和商品服务所共享,商品服务单独做统计并不知道自己一天多少商品被卖出,不知道哪些数据由本服务产生的,就无法进行技术产品规划,对表结构的修改也要通知多个服务,这是所不能容忍的。
每个服务需要自己的数据库,但这些数据库可共置在一台共享的数据服务器上,数据库私有的重点在于不应让服务知道其他服务底层数据库的存在。可用一台共享数据服务器先开始开发,以后如果数据量和并发量变大,服务器可以进行隔离。服务器隔离后,只要更改配置即可将不同服务的数据库隔离起来。
微服务之间确定服务边界,通过共享模型建立联系
每个微服务都具备自己的业务能力,那么服务之间交互的部分即是服务边界;
确定服务边界也是一个难题,需要对自己的产品和业务有足够的了解才能确定最自然的服务边界确定服务边界坚持的原则是要高内聚弱耦合,弱耦合就是一个服务与其他服务的任何通信都应通过公开暴露的接口(API、事件等)实现,这些接口需要妥善设计以隐藏内部细节。这样我们的服务之间保持独立,在未来我们可以轻松重构,高内聚力就是密切相关的多个功能应尽量包含在同一个服务中,这样可将服务之间的干扰降至最低。
2、数据一致性
在单体架构中,我们通过数据库事务完成的操作放在分布式微服务架构下无法完成了,因为实例被部署不同服务器上,比如订单服务进行下单操作,下单操作和扣减库存应该放在同一个事务中。在微服务架构下,下单操作和扣库存操作被分布在不同服务器上,就需要进行分布式事务操作,而分布式事务具有延迟较高、nosql数据库不支持等缺点。
这些缺点导致分布式事务无法应用到微服务中在微服务场景下,我们通常使用最终一致性来代替强一致性:
可靠性事件模式
补偿模式-sagas模式
3、服务通信
通信技术方案: RPC vs REST vs 异步消息
RPC、REST API、异步消息,异步消息我们可以借助一些消息队列框架来实现比如kafka、rrabbitMQ,那现在我们说下rpc和使用http协议的类似REST API之间 如何选择,TTP的好处是方便调试、跨语言、门槛低、广泛接受,同样缺点是协议文档不好维护,协议较为繁琐,性能较TCP要差是http协议的不足。
Rpc通信通常基于Tcp,常用的技术选型是thrift、grpc、dubbo,像thrift、grpc需要定义idl文件,通过idl文件来生成java代码,通过rpc的好处是IDE友好,有代码提示,协议维护在代码中,传参和响应结果都通过代码可以知道。但同时也有缺点比如很多rpc方案不知道跨语言,所支持的语言有限,需要定义和维护idl文件,且有一定的学习成本,另外rpc不容易方便的调试和测试。
服务注册和发现
在服务实例变化不定的环境中,用硬编码指定IP地址的方式是行不通的,需要通过某种发现机制让服务能相互查找。这就需要我们将我们的服务信息注册到一个分布式存储中,这些服务信息就叫做服务注册表服务注册表可以作为信息的权威来源。其中包含有关可用服务的信息,以及服务网络位置比如ip、端口号这些信息。那借助什么组件进行实现呢,一般有Eureka和zookeeper,除此之外我们还可以借助etcd,consul,redis这些。
负载均衡
有了注册发现功能,客户端通过服务注册表发现实例清单并决定要连接哪个实例,在客户端做负载均衡,基于客户端做负载均衡相比服务端负载均衡有诸多好处:首先节省了硬件均衡设备,减少了运维成本,其次可以实现多种负载均衡策略比如响应感知的负载均衡策略。
4、服务网关
API Gateway
我们的微服务如果和终端用户通信,势必要考虑身份认证、安全防御等这些方面,如果每个微服务都与终端用户打交道,那么这些方面代码需要拷贝多份植入到每个微服务业务代码中。这造成了业务代码和身份认证代码的耦合,降低了代码的复用性。这就需要在网络边界实现一个服务网关(这里的网络边界可以认为是内网和外网之间的边界),将身份认证,安全防御,流量控制这些功能放到服务网关中,向业务服务屏蔽网络边界服务的细节,使得业务服务专注于业务逻辑的开发维护和测试。
为前端服务的后端(Backends For Forntends)
服务网关可以根据终端产品形态来划分,比如公共API,桌面客户端,移动客户端分别对应一个服务网关,而服务网关可以是API Gateway只输出api,或者是为前端服务的后端,这里的为前端服务的后端,比如将来自多个服务的数据聚合到一起返回给前端。
身份认证、路由服务、限流防刷、日志统计
5、高可观察
健康检测、集中监控
每个服务和使用的组件都有提供健康检测机制,使得我们可以及时发现异常的节点,然后做出判断和调整,将所有的监控指标进行聚合输出可视化图表和界面帮助我们快速直观发现问题。
日志聚合及检索
比如在电商app我们发现无法进行下单操作,在分布式架构下,日志散落在多个服务多个服务器中,我们不知道错误日志打在哪台服务器上,如果每台服务器去登陆去看是极其低效的。这要求我们做到日志格式标准化,并通过一些手段聚合到一起进行检索查询。同时可跨越所有服务、特定的某个服务,或服务的某个实例搜索日志;将日志发送至集中化日志系统所用的代码可包含在共享库中或通过代码脚手架提供。
分布式追踪
在微服务架构场景中,一个客户端发起的请求要经过多个服务的调用最终聚合数据结构返回给客户端,但我们不知道这个请求不知道经过哪些服务,调用哪个服务出现了问题,每个服务的输入输出是什么,这给我们定位问题带来了困扰,除此以外,如果一个请求耗时较长,我们不知道到底哪个服务耗时最长,好有针对性的性能优化。随着架构的演进,我们在架构设计规划时需要知道 服务之间的依赖关系,这有需要什么技术来实现呢,这就是我们要介绍的分布式追踪,分布式追踪借助关联id,在请求源头创建这个关联id,并且在服务间进行透传,最终将关联id等信息聚合到一起进行查询分析。
6、可靠性
在讲单体的过程中,我们讲到,一个业务模块的内存泄露会导致整个进程退出。
在微服务场景下,如果一个服务出现内存泄露是不会影响 没有依赖关系的服务的。
但是却可以因为该异常服务的僵死或不可用造成上游服务线程hang住,进而产生级联效应,故障进一步向上游传播。
流量控制,超时控制
可靠性技术通过流量的控制和超时控制,保证服务消费者不被下游服务拖慢,及时对业务线程循环复用。
舱壁隔离,熔断机制
熔断是指服务调用出错次数在一定时间达到一定数量,自动关闭对该服务的调用开关,改为返回错误或者将请求转交给降级方法,降低资源耗尽的风险,当服务不可用时,作为服务消费者应该对接口方法编写一个降级方法。
服务降级, 幂等重试
由于服务间通信是通过网络传输的,网络异常和网络分区故障 就会经常出现,我们遇到这种情况可以进行调用重试,重试时要注意两点,一个是接口必须是幂等的,无论运行一次或多次,最终结果必须相同。幂等性保证了重试不会产生负面影响。在重试过程时休眠时间应该是指数增长的,否则会产生惊群效应,比如:故障后的服务恢复上线后,如果有大量其他服务正在同一个重试窗口内重试,此时很容易给系统造成巨大压力。
提炼代码脚手架
除了开发业务逻辑,我们还需要搭建一套微服务工程可用框架,这样是是为了加快团队工作效率,微服务解决方案保持统一,增强代码复用性,统一进行优化,微服务要解决的问题非常多,所以抽取服务代码模板是有必要的,它包括服务注册发现、服务通信、监控、日志、异常处理等等。
从单体迁移到微服务
1、何时迁移微服务?
讲了这么多微服务的好处,我们是不是可以从此抛弃单体架构,一切项目直接投向微服务的怀抱了呢?
Martin Fowler在《MicroservicePremium》一文说
“don’t even consider microservices unless you have a system that’s too complex to manage as a monolith”,翻译过来就是:如果你的系统不到足够复杂的程度不要考虑使用微服务。
当业务不复杂,团队规模不大的时候,单块架构比微服务架构具有更高的生产率(productivity)。因为建立微服务架构需要额外的开销来支持和管理微服务,从而降低生产率;但是随着业务复杂性的增加和团队规模的扩大,单块架构比微服务架构的生产率下降更趋明显。
当复杂性达到一个临界点,微服务架构的生产率会优于单块架构,因为微服务的松散耦合自治特性减缓了生产率的下降趋势。
所以我们在做项目时,一定要根据自己的团队和业务复杂性来判断,何时应用微服务。
以我的经验给大家的建议时,一个全新项目在1-3团队时,可以先拆分成一个API 网关和一个集合所有业务的后端服务,API网关关注鉴权和路由、处理部分失败;后端服务要划分好业务模块;项目初期,由于流量不多,可以不必添加流量控制和断路器等组件。
同时即使向微服务架构迁移之后,拆分的粒度也要由粗到细演进式发展,一定要根据自己的业务规模复杂性和团队规模来定。
2、如何迁移到微服务上来?
一个策略是:不要大规模(big bang)重写代码(只有当你承担重建一套全新基于微服务的应用时候可以采用重写这种方法)。重写代码听起来很不错,但实际上充满了风险最终可能会失败,就如Martin Fowler所说:“the only thing a Big Bang rewrite guarantees is a Big Bang!”
相反,应该采取逐步迁移单体式应用的策略,通过逐步生成微服务新应用,与旧的单体式应用集成,随着时间推移,单体式应用在整个架构中比例逐渐下降直到消失或者成为微服务架构一部分。这个策略有点像在高速路上限速到70迈对车做维护,尽管有挑战,但是比起重写的风险小很多。
Martin Fowler将这种现代化策略成为绞杀应用,名字来源于雨林中的绞杀藤(strangler vine),也叫绞杀榕(strangler fig)。绞杀藤为了爬到森林顶端都要缠绕着大叔生长,一段时间后,树死了,留下树形藤。这种应用也使用同一种模式,围绕着传统应用开发了新型微服务应用,传统应用会渐渐退出舞台。
SOA VS 微服务
SOA和微服务的对比是一个老生常谈的话题,我认为两者最大的不同是提出时所处的技术背景和环境。
SOA的出现其实是为了解决历史问题:企业在信息化的过程中会有各种各样互相隔离的系统,需要有一种机制将他们整合起来,所以才会有ESB的出现。同样的,也成了SOA初期的服务是很大的概念,通常指定的一个可以独立运作的系统。
微服务没有历史包袱,服务的尺寸通常不会太大。从服务粒度来讲,SOA更像是单体的简单组合,而微服务是粒度更细小的服务,其次数据拆分SOA倾向于共享数据库,微服务一个服务对应一个数据库。
想要了解架构技术知识点的,可以关注我一下,我后续也会整理更多关于分布式架构这一块的知识点分享出来,另外顺便给大家推荐一个交流学习群:697--57--9-75-1,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多,以下的知识体系图也是在裙里面获取。
作者:美的让人心动
链接:https://www.jianshu.com/p/022ce557c50e