单体架构图参考网络:
1. 什么是单体应用
单体应用就是将应用程序的所有功能都打包成一个独立的单元,最终以一个WAR包或JAR包存在,没有外部的任何依赖,里面包含DAO、Service、UI等所有的逻辑。
优点:
1.便于开发:只需要借助IDE的开发,调试功能即可。
2.易于测试:只需要通过单元测试或浏览器即可完成测试。
3.易于部署:打包成单一可执行jar包,执行jar包即可完成部署。
可是,这种简单的单元有很大的局限性。应用随着业务需求迭代,功能的追加扩展,最终成为一个庞然大物,变得更加复杂,逻辑耦合很严重,难以理解。团队开发人员职责不清,部署困难,回归测试成本巨大,交付率大大降低,总结下来单体应用有以下5大缺点:
1.复杂性高
代码难以理解 部署困难 应用越来越大 测试越来越难
在业务规模和团队规模发展到一定阶段,这些不足表现的更加明显。单体架构的不足表现在复杂性上,maven模块增多,多个模块耦合在一起,代码结构混乱,很难理解整个代码逻辑,容易导致代码质量低,复杂性进一步增加,同时代码复用度降低,不清楚某段代码的修改后的影响范围。
代码难以被修改和重构
不理解代码当然也写不出高内聚低耦合的代码,代码质量持续下降;复杂性进一步增加,然后耦合度越来越高,代码牵一而动全身,代码就很难修改和重构了。
团队职责不清楚
高度耦合的单体工程使得逻辑边界模糊不清,新的业务需求开发任务无法有效分配到人,团队人员职责不清楚,沟通成本增加。
2.交付效率低
构建和部署耗时长,难以定位问题,开发效率低。代码比较庞大,编译时间就会很长,开发调试将大部分时间花在重新编译上,代码量的增加又很难定位bug,导致开发效率进一步降低,在代码合并过程中很容易造成代码冲突,又花不少时间解决代码冲突,这些都可以导致开发效率降低。还有就是当我们开发一个新的功能或者修复一个bug,代码的变更影响是很难预估的,每次发布之前都要进行全部功能的回归测试;部署耗时长,影响范围广,风险大,发布频次低也会造成产品不能及时呈现给用户,甚至被对手赶超。
3.伸缩性(scalable)差
单体应用只能按整体横向扩展,无法分模块垂直扩展。IO密集型模块和CPU密集模块无法独立升级和扩容。业务模块对资源的需求是不一样的,比如图片压缩,加解密这些 都是cpu资源密集的应该升级CPU,而IO密集型的模块比如日志收集服务IO操作比较多需要更大的内存,使用比如SSD性能更好的磁盘。
4.可靠性差
一个bug有可能引起整个应用的崩溃。比如一个不重要的部分的内存泄露就会导致所有应用实例一个个宕机掉
5.阻碍技术创新
受技术栈限制,团队成员使用同一框架和语言。升级和变革技术框架困难。
2. 什么是微服务
2.1 微服务的定义
微服务架构图参考网络:
微服务架构是将单体应用拆分为多个高内聚低耦合的小型服务,每个服务运行在独立的进程,可由不同的团队开发和维护,服务间采用轻量通信机制,独立自动部署,可以采用不同的语言及存储。
单体架构整个团队为何开发一个大工程和一个单库,在微服务架构中,用户请求经过API Gateway被路由到下游服务,服务之间以轻量级通信协议进行通信,服务通过注册中心发现彼此,每个服务都有专门的开发维护团队,每个服务都可以有自己独立的数据库,服务独立开发,部署,上线。
微服务的优点
1.易于开发与维护:微服务相对较小,易于理解,启动时间短,开发效率高。
2.独立部署:一个服务的修改不需要协调其他服务
3.伸缩性强:可以横向和纵向扩展
4.与组织结构匹配:更好的将架构和组织匹配,每个团队独立负责某些服务。
5.技术异构性:使用最适合该服务的技术,降低尝试新技术的成本。
说完优点,那么微服务有什么挑战呢?
2.2 微服务的挑战
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,Nocas,除此之外我们还可以借助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倾向于共享数据库,微服务一个服务对应一个数据库。
3. 为什么采用Spring Cloud Alibaba
Spring Cloud Alibaba架构图参考网络:
3.1 Spring Cloud Alibaba 真实应用场景
- 大型复杂的系统,例如大型电商系统(原因:业务复杂)
- 高并发系统,例如大型门户,秒杀系统(原因:去中心化,能够承载更高的负载压力,并且提供个一款很好用的容错组件`Sentinel`可以进一步提升应用可用性、容错性)
- 需求不明确,且变更很快的系统,例如创业公司业务系统(原因:修改模块方便)
3.2 Spring Cloud Alibaba 和 Spring Cloud 有什么区别和联系呢?
Spring Cloud Alibaba
是Spring Cloud
的子项目
Spring Cloud 第一代 | 状态 | Spring Cloud Alibaba | 状态 |
---|---|---|---|
Eureka | 2.0孵化失败 | Nacos Discovery | 性能强劲,感知更快 |
Ribbon | 进去维护状态,预计2020年1月停止维护,新的标准已形成:spring-clound-loadbalancer,但暂无参考实现。Spring Clound Hoxton 才会孵化出替代品 | ||
Hystrix/Hystrix Dashboard/Turbine | 进入维护状态,预计2020年1月停止维护 | Sentinel | 可视化配置,上手更简单 |
Zuul | 进入维护状态,预计2020年1月停止维护 | Spring Cloud Gateway | 性能是Zuul的1.6倍 |
Spring Clound Config | 搭建复杂,约定多,设计繁重,没有洁面,难维护,难以上手 | Nacos Config | 搭建简单,有可视化界面,配置管理更高效,学习曲线低 |
总体来将 Spring Cloud Alibaba
组件性能更强,良好的可视化界面,搭建简单,学习曲线低,文档丰富并且是中文
3.3 Spring Clound Alibaba 的重要组件
服务发现 Nacos | 实现负载均衡 Ribbon | 声明式HTTP客户端-Feign |
---|---|---|
服务发现原理 | 负载均衡的常见模式剖析 | 如何使用Feign |
Nacos Server/Client | RestTemplate整合Ribbon | Feign配置自定义 |
高可用Nacos搭建 | Ribbon配置自定义 | 如何扩展Feign |
0 | 如何扩展 Ribbon | 0 |
服务容错 Sentinel | 消息驱动 RocketMQ | API网关 Gateway |
---|---|---|
服务容错原理 | Spring Clound Stream | 整合Gateway |
Sentinel | 实现异步消息推送与消费 | 三大核心 |
Sentinel Dashboard | 聚合微服务请求 | |
Sentinel 核心原理分 |
用户认证与授权 | 配置管理 Nacos | 调用链监控Sleuth |
---|---|---|
认证授权的常见方案 | 配置如何管理 | 调用链监控原理剖析 |
改造Gateway | 配置动态刷新 | Sleuth使用 |
扩展Feigh Dashboard | 配置管理的最佳实践 | Ziplin使用 |