(1) 浅谈分布式架构
1. 什么是分布式架构
单体架构
image.png一个归档包(例如war格式或者Jar格式)包含了所有功能的应用程序,我们通常称之为单体应用。架构单体应用的方法论,我们称之为单体应用架构,这是一种比较传统的架构风格
单体架构拆分
- 水平拆分,也就是按照分层开发,例如分为:web 层,mapper 层,service 层,controller 层,不管怎么分还是一个应用
- 垂直拆分,也就是按照功能拆分,可以分为不同的应用部署在不同的服务器上,例如,用户管理,订单管理,购物车管理等
单体架构的优点
- 部署简单: 由于是一个完整的结构体,可以直接部署在一个服务器上。
- 技术单一: 项目不需要复杂的技术栈,往往一套熟悉的技术栈就可以完成开发。
- 用人成本低: 单个程序员就可以完成业务接口到数据库的整个流程。
单体架构的缺点
- 代码耦合,开发维护困难
- 系统错误隔离性差,任何一个模块的错误均可能造成整个系统的宕机;
- 可伸缩性差:系统的扩容只能对整个应用进行扩容,不能做到对某个功能点进行扩容;
- 单点容错率低,并发能力差
集群架构
image.png当单体架构无法承受日益增加的访问量,服务器性能出现瓶颈,通过集群架构进行横向扩容是最好的方式,我们可以把单体应用部署在不用的服务器上组成一个“集群”,集群中的每一台服务器就是这个集群的一个“节点”,每一个节点都提供相同的服务,理论上每增加一个节点,系统的处理能力相比较原来的单体应用会增加一倍
负载均衡服务器(Load Balancing Server)
当集群节点组件好之后,那么到底有哪一个节点来处理请求呢?最好每次能够让压力最小的服务器来处理请求,这样使得每个节点的压力都比较平均。要实现这个功能,就需要在所有节点之前增加一个“调度者”的角色,用户的所有请求都先交给它,然后它根据当前所有节点的负载情况,决定将这个请求交给哪个节点处理。这个“调度者”就是负载均衡服务器。
垂直架构(也就是功能的拆分)
image.png垂直架构主要是用来解耦系统业务的复杂度,提高代码的维护性和可扩展性,当单一的应用无法满足需求,此时为了应对更高的并发和业务需求,我们可以根据业务的功能对系统进行拆分,而拆分的每一个业务又可以做集群,系统性能也会大大提高。
优点
- 系统拆分实现了流量分担,解决了并发问题
- 可以针对不同模块进行优化
- 方便水平扩展,负载均衡,容错率提高
缺点
- 系统间相互独立,会有很多的重复开发工作,耦合性较大(可能不同的系统包含相同的源码信息)
- 服务之间相互调用,如果某个服务的端口或者ip地址发生改变,调用的系统得手动改变
- 系统性能扩展只能通过扩展集群结点,成本高、有瓶颈
分布式架构
image.png分布式架构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统。在分布式架构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过 RPC(Remote Procedure Call)方式通信。
优点
- 将基础服务进行抽取,系统之间相互调用,提高了代码的复用性和开发效率
- 吞吐量更大、执行效率更高
缺点
- 调用关系错综复杂,难以维护
SOA (Service Oriented Architecture) 面向服务架构
image.pngSOA 和分布式架构一样,但是在服务调用间多了一层注册中心,就是说不管是服务提供方还是服务消费方都需要去注册到注册中心,服务提供方告诉注册中心我能提供什么服务,服务消费方告诉注册中心我需要消费什么服务,那么注册中心就会自动去匹配,这就好比房屋中介所,房东提供房源在中介所登记,租户需要租房也在中介所登记,由中介所来为房东和租户匹配到最合适的那一个
优点
- 服务注册中心,实现服务的自动注册和发现,无需人为记录服务地址
- 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
缺点
- 系统之间交互需要使用远程通信,接口开发工作量增加
微服务架构
image.png微服务和 SOA 都是服务,都是对系统进行拆分,两者似乎一样,但其实不然,还是有一些差别的:
微服务的特点:
- 单一职责:微服务中每一个服务都应对应唯一的业务能力,做到单一职责
- 微: 微服务的服务拆分力度很小,例如一个用户管理就可以作为一个服务,每个服务虽小,但是“五脏俱全”
- 面向服务:也就是说每个服务都要对外暴露 Rest 风格的服务接口 API,并不关心服务的技术实现,做到与平台和语言无关,只需要提供 Rest 接口即可
- 自治:自治是说微服务间互相独立,互不干扰
- 团队独立:每个服务都是一个独立的团队开发,人数不多
- 技术独立:因为是面向服务,提供 Rest 接口,使用什么技术没有别人干涉
- 前后端分离:采用前后端分离开发,提供统一 Rest 接口
- 数据库分离:每一个服务都使用自己的数据源
- 部署独立:服务间虽然有调用,但服务重启不影响其他服务,每个服务都是独立的组件,可复用,可替换,降低耦合,易维护
2. 如何理解微服务隔离
image.png-
对于服务提供方来说服务隔离比较简单,由于每一个微服务都是一个独立的应用,这样某一个服务出现故障,不会相互影响,达到一种物理层面的隔离
image.png -
对于服务调用方来说,一个请求过来,占用调用方 Tomcat 的一个线程。然后,该线程去顺序调用用户服务,订单服务,库存服务,但是现在用户服务迟迟不能反回,这个 Tomcat 的线程就一直卡在那,与此同时,新的请求又会源源不断的过来,很快服务调用方 Tomcat 里头的线程资源会被全部消耗完毕!对于后面的请求,Tomcat 就无法响应! 因此,如果不针对被调服务做服务隔离,一但被调用的服务出问题,就将导致调用方服务不可用!那么该如何做呢?我们可以对每一个微服务都分配一个线程池,比如用户服务就会去用户服务的线程池中找到一个线程,一旦出现问题无法返回,那么用户服务的线程会很快被用光,这时怎么办呢?很简单用户服务只需要告诉服务调用方服务不可用,服务调用方无需等待,马上就可以拿到结果
image.png
但是上面这种方案,还是有一定的问题,如果这个服务已经不可用了,还不断地尝试去调用,那肯定是一种浪费。
这是又有一种解决方案使用熔断器,熔断器(也叫断路器),当这个熔断器关闭的时候,外面的请求可以直接调用,如果打开,就把外界的请求给阻断了。
具体的做法是:系统会检测请求失败的比率(失败数/总请求数), 一旦这个比率达到一个阈值的时候,熔断器就开启, 直接拒绝执行用户请求。然后休眠一段时间,尝试放过一部分流量(比如一个请求),如果调用成功,熔断器闭合,恢复到正常状态,否则继续进行休眠周期。
3. 如何实现微服务的无状态化
一般说来,微服务架构的目的之一,是通过多进程承载高并发,根据并发的压力用多个副本共同承担流量。阻碍单体架构变为分布式架构的关键点就在于状态的处理,如果状态全部保存在本地,无论在内存还是硬盘,都会给架构的横向扩展带来瓶颈,因为这样新启动的进程根本无法处理那些保存在原来进程的用户数据。
所以要将整个架构分成两个部分,无状态部分和有状态部分,而业务逻辑的部分往往作为无状态的部分,而将状态保存在有状态的中间件中,如缓存、数据库、对象存储、大数据平台、消息队列等。
这样无状态的部分可以很容易的横向扩展,在用户分发的时候,可以很容易分发到新的进程进行处理,而后端的中间件是有状态的,这些中间件设计之初,就考虑了扩容的时候,状态的迁移,复制,同步等机制,不用业务层关心
4. 什么是服务的幂等性
一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额发现多扣钱了,流水记录也变成了两条。在以前的单应用系统中,我们只需要把数据操作放入事务中即可,发生错误立即回滚,但是再响应客户端的时候也有可能出现网络中断或者异常等等
什么情况下需要保证幂等性
- 以SQL为例,有下面三种场景,只有第三种场景需要开发人员使用其他策略保证幂等性:
- SELECT col1 FROM tab1 WHER col2=2,无论执行多少次都不会改变状态,是天然的幂等。
- UPDATE tab1 SET col1=1 WHERE col2=2,无论执行成功多少次状态都是一致的,因此也是幂等操作。
- UPDATE tab1 SET col1=col1+1 WHERE col2=2,每次执行的结果都会发生变化,这种不是幂等的。
那么如何设计接口才能做到幂等呢?
-
方法一:单次支付请求,也就是直接支付了,不需要额外的数据库操作了,这个时候发起异步请求创建一个唯一的ticketId,就是门票,这张门票只能使用一次就作废,具体步骤如下:
- 1、异步请求获取门票
- 2、调用支付,传入门票
- 3、根据门票ID查询此次操作是否存在,如果存在则表示该操作已经执行过,直接返回结果;如果不存在,支付扣款,保存结果
- 4、返回结果到客户端
如果步骤4通信失败,用户再次发起请求,那么最终结果还是一样的.
-
方法二:分布式环境下各个服务相互调用
这边就要举例我们的系统了,我们支付的时候先要扣款,然后更新订单,这个地方就涉及到了订单服务以及支付服务了。用户调用支付,扣款成功后,更新对应订单状态,然后再保存流水。而在这个地方就没必要使用门票ticketId了,因为会比较闲的麻烦(支付状态:未支付,已支付)
步骤:- 1、查询订单支付状态
- 2、如果已经支付,直接返回结果
- 3、如果未支付,则支付扣款并且保存流水
- 4、返回支付结果
如果步骤4通信失败,用户再次发起请求,那么最终结果还是一样的
对于做过支付的朋友,幂等也可以称之为冲正,保证客户端与服务端的交易一致性,避免多次扣款。
参考 https://www.cnblogs.com/vveiliang/p/6643874.html
参考 https://blog.csdn.net/u012274988/article/details/100660305