分布式
分布式和集群的关系?
电商平台: 用户、 商品、订单、 交易
分布式: 一个业务拆分成多个子系统,部署在不同的服务器上
集群: 同一个业务,部署在多个服务器上
个人理解,集群是为了同一个服务的稳定,部署了多份相同的代码等,例如:redis集群,每一个redis都是完整的。
分布式是吧一个系统按业务或其他拆分为多个子系统,各个系统配合工作,缺一不可。
分布式事务
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务节点上,分布式事务需要保证这些小操作要么全部成功,要么全部失败;本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
分布式全局id
- Redis生成ID
我们知道redis是单线程的,不会受并发干扰,因此我们可以借助redis的API来生成唯一主键,API为redisTemplate.opsForValue().increment(key, 1),这个是个自增命令,就算在并发编程下,redis也可以保证线程的安全性。
例子:分布式系统下生成订单编号
使用mysql数据库时设置步长和初始值
我们知道mysql数据库的主键是唯一的且自增的,不会重复的,我们可以使用主键作为全局唯一id,但是如果数据库做了集群,就会出现在不同的数据库中出现相同的主键,此时全局Id就不是唯一的了,但是我们可以采用设置数据库主键的初始值和步长,所谓初始值就是主键从哪个值开始,数据库默认是1,所谓步长,就是每次自增的值,数据库默认是1,现在假设我们mysql数据库部署了三台:
可以设置数据库1的主键初始值为1,步长为3,则数据库1的主键值依次为1,4,7…
可以设置数据库2的主键初始值为2,步长为3,则数据库2的主键值依次为2,5,8…
可以设置数据库3的主键初始值为3,步长为3,则数据库3的主键值依次为3,6,9…
这样主键就不会重复了,可以作为全局唯一id,但是如果我们后期要扩展数据库,比如现在要增加2台,那么我们的初始值和步长就需要重新设置了,而且数据迁移也是一个问题。
重点:分布式一定要序列化,才能传输调用
分布式缓存
在上图中,当DB负载过高,我需要为Service机器加缓存时,就遇到一个基本问题:
如果使用local的内存做缓存,则其他Service机器就没办法共用此缓存。
因次,我需要一个可以让所有Service机器共享的缓存,这就是分布式缓存。
分布式session问题
在传统的单机版应用中,我们经常使用session。而当单机扩展到多机,单机的session就没办法被其他机器所访问。
此时就需要使用分布式session,把session存放在一个所有Tomcat都可以访问的地方。
分布式消息中间件
在上图中,没有提及到消息中间件。相对其他基本问题,这个需要一个更适合的业务场景来谈,在以后的章节中,会再详述。
常用的消息中间件,比如老一辈的ActiveMQ/RabbitMQ, 新一点的,阿里的RocketMQ,LinkedIn的Kafka等。
消息中间件的一个典型场景就是:通过最终一致性,解决上面的分布式事务问题。
分布式RPC
分布式系统的一个基本问题就是:机器与机器之间如何通信? 我们都知道底层原理是TCP/IP,Socket。
但一般很少有人会去裸写Socket,实现机器之间的通信。这里,最常用的组件就是RPC。
最简单的实现RPC的方式就是使用http。当然,业界有很多成熟的开源RPC框架,如Facebook的Thrift, 阿里的Dubbo,点评的Pigeon。
分布式面临的问题
通信异常
由于网络本身的不可靠性,出现消息丢失、消息延迟
网络分区
由于网络发生异常情况,导致分布式系统中部分节点之间的网络延迟不断增大,最终导致组成分布式系统中有部分节点能够正常通信,网络之间出现了网络不连通,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干个孤立的区域,分布式系统就会出现局部小集群,在极端情况下,这些小集群会独立完成原本需要整个分布式系统才能完成的功能,包括数据的事务处理,这就是对分布式一致性提出非常大的挑战。
负载均衡
负载均衡是分布式系统中的一个最最基本的问题。在上图中:
网关需要把请求分发给不同的Tomcat;
Tomcat需要把收到的请求,分发给不同的Service;
分布式设计的目标
1.提升系统性能
2.增加吞吐量
3.保证容错
分布式的设计
1.中心化
2.去中心化
CAP定理
1.一致性(Consistency)
2.可用性(Availability)
3.分区容错性(Partition tolerance)
BASE理论
1.基本可用(Basically Available)
2.软引用(Soft-state)
3.最终一致性(Eventually Consistent)
其核心思想:既然**无法做到强一致性(String consistency),但每个应用都可以根据自身的**业务特点**,采用适当的方式来使系统达到**最终一致性**(**Eventual consistency )
强一致性
这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大。
优点:用户体验好
缺点:性能开销大
弱一致性
这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能保证到某个时间级别后,数据能够达到一致性状态
最终一致性
最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型。
什么是2PC?
在分布式系统中,会有多个机器节点,每一个机器节点虽然能够明确地知道自己在进行事务操作过程中的结果是成功或失败,但无法直接获取到其他分布式节点的操作结果,因此当一个事务操作需要跨越多个分布式节点的时候,为了保证事务处理的ACID特性,就需要引入一个**“协调者”的组件来统一调度所有分布式节点的执行逻辑,这些被调度的节点则称为“参与者”,协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务真正进行提交。基于这个思想,就衍生了二阶段提交**和三阶段提交两种协议。
阶段一:提交事务请求
1.事务询问
协调者**向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并开始等待其他参与者的响应。
2.执行事务
各参与者节点执行事务操作,**并将Undo和Redo信息记入事务日志中(Undo能保证事务的一致性,Redo用来保证事务的原子性和持久性,两者也是系统恢复的基础前提)
3.各参与者向协调者反馈事务询问的响应
如果参与者成功执行了事务操作,**那么就反馈给协调者Yes响应,表示事务可以执行;如果参与者没有成功执行事务,就返回No给协调者**,表示事务不可以执行。
由于上面的内容在形式上近似是协调者组织各参与者对一次事务操作的投票表态过程,因此二阶段提交协议的阶段一也被称为“投票阶段”,即各参与者投票表**明是否要继续执行接下去的事务提交操作。**
阶段二:执行事务提交
在阶段二中,就会根据阶段一的投票结果来决定最终是否可以进行事务提交操作,正常情况下,包含两种操作可能:**提交事务**、**中断事务**。**
提交事务步骤如下:
假如协调者从所有的参与者获得的反馈都是yes响应,那么就会执行事务提交。
只要有一个是no,立即回滚整个所有事务
1.发送提交请求
协调者向所有参与者发出commit请求。
2.事务提交
参与者收到commit请求后,会正式执行事务提交操作,并在完成提交之后释放整个事务执行期间占用的事务资源。
3.反馈事务提交结果
参与者在完成事务提交之后,向协调者发送ACK信息。
4.完成事务
协调者接收到所有参与者反馈的ACK信息后,完成事务。
ACK:ACK字符是一些通信协议下用来做确认消息的字符,也有通信协议使用其他字符。
中断事务步骤如下:
假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接受到所有参与者的反馈响应,那么就会中断事务。
1.发送事务回滚请求
协调者向所有参与者发出Rollback请求。
2.事务回滚
参与者接受到Rollback请求后,会利用其在阶段一记录的Undo记录来执行事务回滚操作,并在完成回滚之后释放在整个事务执行期间占用的资源。
3.反馈事务回滚结果
参与者在完成事务回滚之后,向协调者发送ACK信息。
4.中断事务
协调者接收到所有参与者反馈的ACK信息后,完成事务中断。
从上面逻辑可以看出,二阶段提交就做了两件事情:投票、执行。
2PC优缺点
优点
缺点
同步阻塞,单点问题数据不一致,过于保守
同步阻塞:
二阶段提交协议存在最明显也是最大的一个问题就是同步阻塞,在二阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,也就是说,各个参与者在等待其他参与者响应的过程中,无法进行其他操作。这种同步阻塞极大的限制了分布式系统的性能。
单点问题:
协调者在整个二阶段提交过程中很重要,如果协调者在提交阶段出现问题,那么整个流程将无法运转,更重要的是:其他参与者将会处于一直锁定事务资源的状态中,而无法继续完成事务操作。
数据不一致:
假设当协调者向所有的参与者发送commit请求之后,发生了局部网络异常或者是协调者在尚未发送完所有commit请求之前自身发生了崩溃,导致最终只有部分参与者收到了commit请求。这将导致严重的数据不一致问题。
过于保守:
如果在二阶段提交的提交询问阶段中,参与者出现故障而导致协调者始终无法获取到所有参与者的响应信息的话,这时协调者只能依靠其自身的超时机制来判断是否需要中断事务,显然,这种策略过于保守。btw,二阶段提交协议没有设计较为完善的容错机制,任意一个阶段失败都会导致整个事务的失败。
一致性协议3PC
刚刚讲解了二阶段提交协议的设计和实现原理,并明确指出了其在实际运行过程中可能存在的诸如同步阻塞,单点问题,数据不一致,过于保守的容错机制等缺陷
而为了弥补二阶段提交的缺点,引入了三阶段提交协议。
什么是三阶段提交
将2PC的“提交事务请求”过程一分为二,共形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议。
阶段一:CanCommit
事务询问
协调者向所有的参与者发送一个包含事务内容的CanCommit请求,询问是否可以执行事务提交操作,并等待各参与者响应
各参与者向协调者反馈事务询问的响应
参与者在接收到来自协调者的包含事务内容的canCommit请求后,判断可以提交任务了返回Yes响应,否则No
阶段二:PreCommit
两种情况:
成功:执行事务预提交
失败:中断事务
发送预提交请求
协调者向所有参与者发出PreCommit请求,进入准备阶段
事务预提交
参与者收到请求后,执行事务操作,并将Undo、Redo信息记录到事务日志中(改日志信息可以用来回滚事务)
反馈执行结果
协调者收到反馈,确定是提交(发送doCommit请求)还是终止(发送abort请求)操作。
阶段三:doCommit
提交事务(收到doCommit请求将预提交状态->提交状态)、中断事务(收到abort请求),完成事务以后发送ACK给协调者。
2pc和3pc对比:
优点:
在2pc基础上有协调者新增了一个CanCommit阶段,会预先判断机器是否可以执行事务操作,而不是直接发送执行事务操作请求,这样做的优点是降低了参与者的阻塞范围(由于2pc是直接发送prepare,等待参与者事务处理完成并反馈结果),其次能够在单点故障后达成一致(由于第一阶段是判断服务器是否能够CanCommit,协调者会根据反馈结果判断有故障节点情况下,发送事务prepareCommit请求操作)
缺点:
如果参与者收到了PreCommit消息后,出现了网络分区,此时协调者和参与者无法通信,参与者等待超时后,会进行事务的提交(这种超时自动提交机制是3pc特性,就是为了解决同步阻塞情况),这必然出现分布式数据不一致问题
首先对于协调者和参与者都设置了超时机制(在2pc中,只有协调者拥有超时机制,即如果在一定时间内没有收到参与者的消息则默认失败)。其次在2pc的准备阶段和提交阶段之间,插入预提交阶段,这个阶段是一个缓冲,保证了在最后提交之前各参与节点的状态是一致的。
四、分布式系统中有哪些难点?
缺乏全局时钟:
分布式系统是由多个节点组成,通过多个进程之间的交换消息来进行通信。由于每个节点都拥有自己单独的时钟,没有一个全局的时钟,所以很难定义两个事件的先后顺序。对于这种情况,我们可以把这个工作交给一个单独的集群来完成,通过这个集群来区分多个动作的顺序。
面对故障独立性:
在分布式系统中,整个系统的一部分有问题而其他部分正常是经常发生的情况,我们称之为故障独立性。一个被大量工程实践所检验过的黄金定理是:任何在设计阶段考虑到的异常情况,一定会在系统实际运行中发生。并且,在系统实际运行过程中还会遇到很多在设计时未能考虑到的异常故障。所以,除非需求指标允许,在系统设计时不能放过任何异常情况。
处理单点故障
在分布式系统中,如果某个角色或者功能只有某台单机在支撑,那么这个节点称为单点,其发生的故障称为单点故障,也就是常说的SPoF(Single Point of Failure)。要尽量把每个单点都变为集群实现,如果不能变为集群实现,一般还有两种选择:
给这个单点做好备份,能够在出现问题时进行恢复,并且尽量做到自动恢复,降低恢复需要用的时间。
降低单点故障的影响范围。如在一个交易网站中,所有的交易数据放在一个数据库中,这就形成了单点。我们可以考虑拆分数据,将原来的一个数据库拆分为两个(根据一定规则做Sharding),那么,在一个数据库出现问题时,影响的就不会是全部范围了。
事务和数据一致性的挑战
在数据库理论中我们都了解过ACID,但是在分布式数据库中,数据分散在各台不同的机器上,如何对这些数据进行分布式的事务处理具有非常大的挑战。
四 柔性事务
所谓柔性事务是相对强制锁表的刚性事务而言。流程入下:服务器A的事务如果执行顺利,那么事务A就先行提交,如果事务B也执行顺利,则事务B也提交,整个事务就算完成。但是如果事务B执行失败,事务B本身回滚,这时事务A已经被提交,所以需要执行一个补偿操作,将已经提交的事务A执行的操作作反操作,恢复到未执行前事务A的状态。
缺点是业务侵入性太强,还要补偿操作,缺乏普遍性,没法大规模推广。
分布式服务架构
- 当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的业务,逐渐形成稳健的服务中心,使前端应用能更快速的响应多变的市场需求;
- 此时,用户提高业务复用及整合的分布式服务框架(RPC)远程调用是关键;
- RPC:独立的应用服务器之间,要依靠RPC(Romote Procedure Call)才能调用
- 缺点:假如物流服务不忙,其也是100台服务器,商品服务特别忙也是100台服务器,造成资源浪费
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐呈现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率
此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
SOA:面向服务架构(Service-Oriented Architecture),简单理解就是“服务治理”,例如:公交车站的调度员
BASE理论:
BASE是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency)。
基本可用
基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。
电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。
软状态
软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有三个副本,允许不同节点间副本同步的延时就是软状态的体现。mysql replication的异步复制也是一种体现。
最终一致性
最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
Dubbo简介
Dubbo是分布式服务框架,是阿里巴巴的开源项目,现交给apache进行维护
Dubbo致力于提高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案
简单来说,Dubbo是个服务框架,如果没有分布式的需求,是不需要用的
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式
RPC通信原理
在客户端将对象进行序列化
底层通信框架使用netty(基于tcp协议的socket),将序列化的对象发给服务方提供方
服务提供方通过socket得到数据文件之后,进行反序列化,获得要操作的对象
对象数据操作完毕,将新的对象序列化,再通过服务提供方的socket返回给客户端
客户端获得序列化数据,再反序列化,得到最新的数据对象,至此,完成一次请求
PRC两个核心:通信(Socket)和序列化(Serilizable)
缺点
因为dubbo这样的rpc会暴露服务地址,不安全
然后阿里的dubbo要重启但表示安全性不会去考虑完善,因为自己内部有一套网关。
Rpc框架开源
采用Socket通信、动态代理与反射与Java原生的序列化。
3|2动态代理
RPC的调用内部核心技术采用的就是jdk和Cglib
动态代理。
如何实现一个RPC框架?
思路:
服务设计:客户端、服务端、ZK注册中心,获取订单接口。
怎么知道服务端的信息? 如何去调用的?
先启动服务端: 将接口信息注册至ZK。
启动客户端: 从ZK拉取服务端接口信息。
Rpc调用处理流程:
客户端->通过动态代理调用服务端接口-> 选取不同的调用策略-> 异步方式调用-> 服务端(根据请求信息调用对应的接口)-> 客户端监听接收结果-> 关闭连接
为什么用tcp?为什么要序列化?
因为网络传输只认字节。所以互信的过程依赖于序列化。有人会问,FastJson 转换成字符串算不算序列化?对象持久化到数据库算不算序列化?**网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象**。对象是不能直接在网络中传输的,所以我们需要提前把它转成可传输的二进制,并且要求转换算法是可逆的,这个过程我们一般叫做“序列化”。这时,服务提供方就可以正确地从二进制数据中分割出不同的请求,同时根据请求类型和序列化类型,把二进制的消息体逆向还原成请求对象,这个过程我们称之为“反序列化”。
这种需要序列化的类必须实现Serializable。
常见的例子:把对象存储在Redis服务器中、RPC形式的远程方法调用(微服务使用Dubbo)
RPC框架的好处就显示出来了,首先(基于TCP协议的情况下)就是长链接,不必每次通信都要像http 一样去3次握手什么的,减少了网络开销;其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统 一化的操作。第三个来说就是安全性。最后就是最近流行的服务化架构、服务化治理,RPC框架是一个强力的支撑。
因为良好的rpc调用是面向服务的封装,针对服务的可用性和效率等都做了优化。单纯使用http调用则缺少了这些特性。
不带有报文头,请求行等类似复杂文件。 报文头非常占字节,元数据废编码
要想让服务A调用服务B中的方法,最先想到的就是通过HTTP请求实现。是的,这是很常见的,例如服务B暴露Restful接口,然后让服务A调用它的接口。基于Restful的调用方式因为可读性好(服务B暴露出的是Restful接口,可读性当然好)而且HTTP请求可以通过各种防火墙,因此非常不错。
然而,如前面所述,基于Restful的远程过程调用有着明显的缺点,主要是效率低、封装调用复杂。当存在大量的服务间调用时,这些缺点变得更为突出。
服务A调用服务B的过程是应用间的内部过程,牺牲可读性提升效率、易用性是可取的。基于这种思路,RPC产生了。
通常,RPC要求在调用方中放置被调用的方法的接口。调用方只要调用了这些接口,就相当于调用了被调用方的实际方法,十分易用。于是,调用方可以像调用内部接口一样调用远程的方法,而不用封装参数名和参数值等操作。
寻址:A服务器上的应用需要告诉RPC框架:B服务器地址、端口,调用函数名称。所以必须实现待调用方法到call ID的映射。
Http resful风格
面对是一个服务,而Rpc调用的是一个服务中的一个方法,HTTP调用的是整个服务
短连接
连接->传输数据->关闭连接
比如HTTP是无状态的的短链接,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
因为连接后接收了数据就断开了,所以每次数据接受处理不会有联系。 这也是HTTP协议无状态的原因之一。**像web网站这么频繁的成千上万甚至上亿客户端的连接用短连接更省一些资源。**试想如果都用长连接,而且同时用成千上万的用户,每个用户都占有一个连接的话,可想而知服务器的压力有多大。所以并发量大,但是每个用户又不需频繁操作的情况下需要短连接。总之:长连接和短连接的选择要视需求而定
长连接
连接->传输数据->保持连接 -> 传输数据-> ………..->直到一方关闭连接,多是客户端关闭连接。
长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。
长连接实现原理**:长连接的维持,是要客户端程序定时向服务端程序发送一个维持连接包。如果长时间未发送维持连接包,服务端程序将断开连接。**长连接多用于操作频繁,点对点的通讯(及时通讯),而且连接数不能太多
.数据库的连接用长连接。如果用短连接频繁的通信会造成socket错误,而且频繁的socket创建也是对资源的浪费。
如果一个给定的**连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段**,客户主机必须处于以下4个状态之一:
1客户主机依然正常运行**,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保证定时器复位。**
2.客户主机已经崩溃**,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。**
3.客户主机崩溃并已经重新启动**。服务器将收到一个对其保证探测的响应,这个响应是一个复位,使得服务器终止这个连接。**
4.客户机正常运行,但是服务器不可达,**这种情况与2类似,TCP能发现的就是**没有收到探查的响应。
Socket编程
Socket是什么
Socket是应用层与TCP/IP协议族通信中间软件抽象层,它是一组接口
主机A的应用程序要能和主机B的应用程序通信,必须通过Socket建立连接,而建立Socket连接必须需要底层TCP/IP协议来建立TCP连接。建立TCP连接需要底层IP协议来寻址网络中的主机。网络层使用的iP协议可以帮助我们根据IP地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信就要通过端口号来指定。这样就可以通过Socket实例唯一代表一个主机上的一个应用程序的通信链路了。
先打开服务端,否则报异常,连接失败的异常!!!
服务端
服务器端:
其过程是首先服务器方要先启动,并根据请求提供相应服务:
(1)打开一通信通道并告知本地主机,它愿意在某一公认地址上的某端口(如FTP的端口可能为21)接收客户请求;
(2)等待客户请求到达该端口;
(3)接收到客户端的服务请求时,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求(如UNIX系统中用fork、exec)。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。
(4)返回第(2)步,等待另一客户请求。
(5)关闭服务器
二.工作原理:对于服务器来说,**服务器先初始化socket,然后端口绑定(bind),再对端口监听(listen),调用accept阻塞,等待客户端连接请求。对于客户端来说,客户端初始化socket,然后申请连接(connection)。客户端申请连接,服务器接受申请并且回复申请许可(这里要涉及TCP三次握手连接),然后发送数据,最后关闭连接,这是一次交互过程。
服务端要写的过程 写端口号,创建ServerSocket对象,调用accept()方法等待对应客户端启动,创建三个流,系统输入流,socket输入流,socket输出流
最后关闭所有流,关闭socket连接**
客户端
(1)打开一通信通道,并连接到服务器所在主机的特定端口;
(2)向服务器发服务请求报文,等待并接收应答;继续提出请求……
(3)请求结束后关闭通信通道并终止。
服务端要写的过程 写端口号和ip地址,创建Socket对象,传入ip地址,端口号两个参数,方法等待对应客户端启动,创建三个流,系统输入流,socket输入流,socket输出流
最后关闭所有流,关闭socket连接
详细流程图
节点角色
- Provider: 服务的提供方
- Consumer :服务的消费方
- Registry :服务注册与发现的注册中心
- Monitor :监控服务的统计中心
- Container :服务运行容器
调用关系
服务容器负责启动,加载,运行服务提供者;
服务提供者在启动时,向注册中心注册自己提供的服务;
服务消费者在启动时,向注册中心订阅自己所需的服务;
在注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者;
服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用;
服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心
Dubbo快速入门
注册中心
官方推荐使用zookeeper注册中心;
注册中心负责服务地址的注册与查找,相当于目录服务;
服务提供者和消费者只在启动时与注册中心交互,注册中不转发请求,压力较小;
Zookeeper是apache hadoop的子项目,是一个树形的目录服务,支持变更推送,适合作为dubbo的服务注册中心,工业强度较高,可用于生产环境
可以将dubbo比作求职的人和招聘公司,zookeeper是求职网站/人才市场
高可用
zookeeper宕机
ZooKeeper 注册中心宕机,还可以消费 dubbo 暴露的服务
监控中心宕掉不影响使用,只是丢失部分采样数据
数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
注册中心对等集群,任意一台宕掉后,将自动切换到另一台
注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
服务提供者无状态,任意一台宕掉后,不影响使用
服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
服务降级
类似双十一秒杀,0点是订单服务器负载严重,而此时大家对广告服务、评价服务等其他服务的要求会很少,此时可以将这类服务停止或简单化,释放资源提供给订单服务运行。
服务降级,就是根据实际的情况和流量,对一些服务有策略的停止或换种简单的方式处理,从而释放服务器的资源来保证核心业务的正常运行。
为什么要服务降级
防止发生蝴蝶效应
如果一个请求发生超时,一直等待服务器响应,在高并发的情况下,很多请求都是这样等待服务器响应,知道资源耗尽产生宕机,而一个服务宕机后,其他调用该服务的服务也会出现资源耗尽而宕机的情况,这样下去就会使整个分布式服务都瘫痪,这就是蝴蝶效应(雪崩效应)
服务降级实现方式
屏蔽:mock=force:return+null表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用,用来屏蔽不重要服务不可用时对调用方的影响
容错:mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响
单体应用缺点:
为何要用微服务
- 部署成本高(无论是修改1行代码,还是10行代码,都要全量替换)
- 改动影响大,风险高(不论代码改动多小,成本都相同)
因为成本高,风险高,所以导致部署频率低(无法快速交付客户需求)
微服务又给我们带来了什么问题?
分布式系统的复杂性
- 部署,测试和监控的成本问题
- 分布式事务和CAP的相关问题
分布式和微服务关系
逻辑架构设计完后就该做物理架构设计,系统应用部署在超过一台服务器或虚拟机上,且各分开部署的部分彼此通过各种通讯协议交互信息,就可算作分布式部署,生产环境下的微服务肯定是分布式部署的,分布式部署的应用不一定是微服务架构的,比如集群部署,它是把相同应用复制到不同服务器上,但是逻辑功能上还是单体应用
微服务相比分布式服务来说,它的粒度更小,服务之间耦合度更低,后期运维将会很难
微服务:
小到一个服务只对应一个单一的功能,只做一件事。这个服务可以单独部署运行,服务之间可以通过RPC来相互交互,每个微服务都是由独立的小团队开发,测试,部署,上线,负责它的整个生命周期。
微服务架构又是啥?
在做架构设计的时候,先做逻辑架构,再做物理架构,当你拿到需求后,估算过最大用户量和并发量后,计算单个应用服务器能否满足需求,如果用户量只有几百人的小应用,单体应用就能搞定,即所有应用部署在一个应用服务器里,如果是很大用户量,且某些功能会被频繁访问,或者某些功能计算量很大,建议将应用拆解为多个子系统,各自负责各自功能,这就是微服务架构。
微服务详细博客参考细节
(76条消息) SpringCloud底层服务之间是怎么相互调用的?_喔易的博客-CSDN博客_springcloud服务间调用原理
Dubbo细节
那么dubbo是怎么判断哪个是生产者,**哪个是消费者,又是怎么把他们两者关联起来的?
其实很简单**,暴露服务的就是生产者,如下图的@Service注解
要注意,这里是dubbo包下的,可不是Spring包下的**。只要配置了@Service就相当于暴露了服务,启动以后,会被dubbo识别有个生产者注册接口,TestService。**
然后,看这边消费,**有个@Refrence注解,这个注解就是相当于去生产者中找到自己想要的服务。
所以生产者和消费者就关系,**就通过一个@Service,一个@Reference关联到一起。
重点:Dubbo实现微服务过程
大致原理
服务端按照协议解析出调用的信息;执行相应的方法;在将方法的返 回值通过协议传递给客户端;客户端再解析;
生产者消费者判断
那么dubbo是怎么判断哪个是生产者,
哪个是消费者,又是怎么把他们两者关联起来的?
其实很简单,暴露服务的就是生产者,如下图的@Service注解
要注意,这里是dubbo包下的,可不是Spring包下的。只要配置了@Service就相当于暴露了服务,启动以后,会被dubbo识别有个生产者注册接口,TestService。
注意是接口
@Reference注解服务引入的过程是这样的:
1.得到当前所引入服务对应的ServiceBean的beanName
2.根据@Reference注解的所有信息+属性接口类型得到一个referenceBeanName
3.如果referenceBeanCache没有ReferenceBean对象,则创建一个ReferenceBean,有则获取
4.根据referencedBeanName(ServiceBean的beanName)判断Spring容器中是否存在该bean
①:如果存在,则给ReferenceBean的ref属性(代理对象)取一个别名,别名为referenceBeanName。
②:如果不存在 ,则将创建出来的ReferenceBean注册到Spring容器中,由于ReferenceBean是一个FactoryBean,后续可以通过getObject()方法获取到ref代理对象
通过referenceBean.get()方法返回一个ref代理对象,作为注入点赋值对象!
答案:可以,但前提条件是在别的地方使用过@Reference注入了DemoService 接口服务!因为Dubbo在扫描到@Reference后,回向容器中注入一个ReferenceBean
ReferenceBean又实现了FactoryBean接口,可通过getObject方法向容器中注入DemoService代理对象
而且@Reference的bean的后置处理器是优先于@Autowired被加载的
此时,@Autowired从容器中获取DemoService代理对象时,容器中已有该代理对象存在,所以可以使用@Autowired注入某个服务!
Dubbo如何扫描@Service注解?
Dubbo没有使用Spring自带的扫描器,而是自定义了自己的扫描器DubboClassPathBeanDefinitionScanner,自定义扫描器继承于Spring的扫描器,不使用Spring默认的过滤逻辑,并添加新的过滤逻辑:只扫描Dubbo包下的@Service注解,这样既利用了Spring的包扫描逻辑,又自定义了扫描逻辑,这一点算是Dubbo对Spring框架的一点扩展!
3. Dubbo如何处理@Service
Dubbo通过@EnableDubbo(scanBasePackages = “org.apache.dubbo.demo.provider”)扫描到对应路径下的@Service注解 ,主要做了两件事情,得到两个Bean
扫描@Service标注的类,得到一个BeanDefinition,一个Spring中的Bean
在扫描完了之后,会针对所得到的每个BeanDefinition,都会额外的再生成一个ServiceBean类型的Bean对象。这个ServiceBean通过Ref属性与Spring中的Bean联系起来!
①:Dubbo为什么要多生成一个ServiceBean?
因为对于Dubbo来说@Service标注的对象是一个服务,并且,还需要解析@Service注解的配置信息例如:@Service(version = "timeout", timeout = 4000),因为这些都是服务的参数信息,所以需要一个额外的ServiceBean来存储这些信息,另外暴露服务也需要ServiceBean<br />ServiceBean<br />ServiceBean表示一个Dubbo服务,ServiceBean对象中的参数就表示服务的参数,比如timeout,该对象的参数值来至@Service注解中所定义的。还有其他参数,比如:<br />ref,表示服务的具体实现类<br />interface,表示服务的接口<br />parameters,表示服务的参数(@Service注解中所配置的信息)<br />application,表示服务所属的应用<br />protocols,表示服务所使用的协议<br />registries,表示服务所要注册的注册中心
Service如何暴露服务?(原理)
ServiceBean由于实现了ApplicationListener
2. 服务导出原理
Dubbo的服务导出主要做以下几件事情
- 根据配置方式的优先级,刷新Dubbo配置参数 一个Dubbo服务的配置参数有多种,比如version、group、delay、weight等等 因为LinkedList是有序集合,方便后续对配置按优先级顺序进行覆盖和更新
- 确定协议,生成URL 一个服务可以配置多个协议 源码中会遍历所有的协议,每一种协议导出一个单独的服务 上面所说的服务URL或者注册中心URL,并不是浏览器上的url连接地址,而是一个名为URL的类,类内部封装了端口号、ip、协议等注册信息
根据服务的参数信息,启动对应的网络服务器(netty、tomcat、jetty等),用来接收网络请求 有了确定的协议,服务名,服务参数后,自然就可以组装成服务的URL了
Dubbo在与其他组件交互的时候,会使用在服务URL中指定的协议,根据不同的协议启动不同的服务器,比如:
- Http协议就启动Tomcat、Jetty。
- Dubbo协议就启动Netty。
- 将服务的信息注册到注册中心 有了服务URL 和 注册中心的地址的URL,就可以向zookeeper注册服务,注册失败的话会进行重试!
启动监听器,监听动态配置修改
Dubbo如果使用的是zookeeper作为配置中心,那么服务配置就存储在zookeeper节点上,就需要利用Zookeeper的Watcher机制,监听的是节点变化。所以在一个服务进行导出时,需要在服务提供者端给当前服务生成一个对应的监听器实例
当动态配置中心修改了某个服务的配置后,就会触发OverrideListener中的notify对注册中心的URL进行重写覆盖,相当于重新发布服务,实现实时发布服务
微服务调用的幂等性
幂等性的通俗概念:
调用方,对一个系统进行重复调用(参数全部相同),不论重复调用多少次,这些调用对系统的影响都是相同的效果。就是不论调用我多少次你对我的影响以及你的影响都是不变的,不会随着次数的变化而改变。
天然幂等性
非幂等性
接口如果超时的情况下会出发什么策略?
解决办法:
我们可以为每一笔用户发起的转账请求赋予一个全局唯一的ID
1、数据库的唯一索引
对于插入性操作,新增记录等
2、基于redis实现一套幂等性防重框架
适用于 更新操作
思路:
将请求的方法名及参数 组合成一个key存到redis中
校验这个key是否存在
但需要考虑 虽然key在redis中 但是 确实执行失败了 需要再次执行,不能拦截
解决思路:
try-catch中将key删掉
1、说说 RPC 的实现原理
RPC是一种进程间通信方式,他会隐藏底层的通讯细节,在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。
客户端调用服务接口的一个代理实现,这个代理实现负责收集数据、编码成能够进行网络传输的消息体,并传输给服务器,然后等待服务器那边解码、调用本地方、返回结果。
RPC框架的目标就是将这些步骤都封装起来,让用户对这些细节透明。
本地消息表(幂等)
核心是把大事务转变为小事务,逻辑如下:
举例说明:我拿100元去买一瓶水
当你扣钱的时候,你需要在你扣钱的服务器上新增加一个本地消息表,你需要把你扣钱和减去水的库存写入到本地消息表,放入同一个事务(依靠数据库本地事务保证一致性)。
这个时候有个定时任务去轮询这个本地事务表,把没有发送的消息,扔给商品库存服务器,叫它减去水的库存,到达商品服务器之后,这时得先写入这个服务器的事务表,然后进行扣减,扣减成功后,更新事务表中的状态。
商品服务器通过定时任务扫描消息表或者直接通知扣钱服务器,扣钱服务器在本地消息表进行状态更新。
针对一些异常情况,定时扫描未成功处理的消息,进行重新发送,在商品服务器接到消息之后,首先判断是否是重复的。
如果已经接收,再判断是否执行,如果执行在马上又进行通知事务;如果未执行,需要重新执行由业务保证幂等,也就是不会多扣一瓶水。
本地消息队列是 BASE 理论,是最终一致模型,适用于对一致性要求不高的情况。实现这个模型时需要注意重试的幂等。
本地消息表(事务)
本地消息表方案应该是业界内使用最为广泛的,因为它使用简单,成本比较低。本地消息表的方案最初是由eBay提出(完整方案),核心思路是将分布式事务拆分成本地事务进行处理。
它的处理流程如下:
事务发起方把要处理的业务事务和写消息表这两个操作放在同一个本地事务里
事务发起方有一个定时任务轮询消息表,把没处理的消息发送到消息中间件
事务被动方从消息中间件获取消息后,返回成功
事务发起方更新消息状态为已成功
注意 扣库存和创建订单是不同的事务,不能放在通一个事务中,因为存在网络两将军问题
LCN分布式事务
一、什么是LCN 框架
LCN: 锁定事务单元(lock)、确认事务模块状态(confirm)、通知事务(notify)
它的宗旨 : LCN 并不生产事务,LCN 只是本地事务的协调工
在上图中,微服务A,微服务B,TxManager 事务协调器,都需要去Eureka 中注册服务。
Eureka 是用于TxManager 与其他服务之间的相互服务发现。
redis 是用于存放我们事务组的信息以及补偿的信息。
然后微服务A 与微服务B 他们都需要去配置上我们TxClient 的包架构(代码的包架构);
来支持我们的LCN 框架,以及他们的数据库。
LCN 执行步骤
创建事务组
事务组是指的我们在整个事务过程中把各个节点(微服务)单元的事务信息存储在一个固定单元里。
但这个信息并不是代表是事务信息,而是只是作为一个模块的标示信息。
创建事务组是指在事务发起方开始执行业务代码之前先调用TxManager 创建事务组对象,
然后拿到事务标示GroupId 的过程。
添加事务组
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息添加通知给TxManager 的操作。
关闭事务组
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager 的动作。
当执行完关闭事务组的方法以后,TxManager 将根据事务组信息来通知相应的参与模块提交或回滚事务。
LCN 的事务补偿机制
什么是补偿事务机制?
LCN 的补偿事务原理是模拟上次失败事务的请求,然后传递给TxClient 模块然后再次执行该次请求事务。
简单的说:lcn 事务补偿是指在服务挂机和网络抖动情况下txManager
无法通知事务单元时。(通知不到也就两种原因服务挂了和网络出问题)在这种情况下TxManager 会做一个标示;然后返回给发起方。
告诉他本次事务有存在没有通知到的情况。那么如果是接收到这个信息之后,发起方就会做一个标示,
标示本次事务是需要补偿事务的。这就是事务补偿机制。
为什么需要事务补偿?
事务补偿是指在执行某个业务方法时,本应该执行成功的操作却因为服务器挂机或者网络抖动等问题
导致事务没有正常提交,此种场景就需要通过补偿来完成事务,从而达到事务的一致性。
补偿机制的触发条件?
当执行关闭事务组步骤时,若发起方接受到失败的状态后将会把该次事务识别为待补偿事务,
然后发起方将该次事务数据异步通知给TxManager。TxManager 接受到补偿事务以后
先通知补偿回调地址,然后再根据是否开启自动补偿事务状态来补偿或保存该次切面事务数据。
CAP理论选择
CAP理论也就是说在分布式存储系统中,最多只能实现以上两点。而由于当前网络延迟故障会导致丢包等问题,所以我们分区容错性是必须实现的。也就是NoSqL数据库P肯定要有,我们只能在一致性和可用性中进行选择,没有Nosql数据库能同时保证三点。(==>AP 或者 CP)
提出一个想法,当你面对双十一这种业务处理时,你是选择AP还是CP呢?
个人想法是在面对这种业务处理时,先保证可用性也就是AP原则(服务器不能瘫痪),在过了双十一高峰,再核对数据,保证数据一致性。
Zookeeper和Eureka区别
分布式系统的数据在多个副本之间的复制方式
主从复制,Master-Slave模式;
所有写操作提交到主服务,再由主服务更新到从服务;写压力集中在主服务上,从服务分担读请求;
对等复制,Peer to Peer,
副本间不分主从,任何副本都可以接受写操作,然后副本间进行数据更新;但副本间数据同步时可能产生数据冲突;
Eureka使用对等复制
Eureka同步过程
- Eureka Server也是一个Client,在启动时,通过请求其中一个节点(Server),将自身注册到Server上,并获取注册服务信息;
- 每当Server信息变更后(client发起注册,续约,注销请求),就将信息通知给其他Server,来保持数据同步;
- 在执行同步(复制)操作时,可能会有数据冲突,是通过lastDirtyTimestamp,最近一次变更时间来保证是最新数据;
比如 Eureka Server A 向 Eureka Server B 复制数据,数据冲突有2种情况:
(1)A 的数据比 B 的新,B 返回 404,A 重新把这个应用实例注册到 B。
(2)A 的数据比 B 的旧,B 返回 409,要求 A 同步 B 的数据。
- 心跳检测(续约),来进行数据的最终修复
Zookeeper的设计理念就是分布式协调服务,保证数据(配置数据,状态数据)在多个服务系统之间保证一致性,这也不难看出Zookeeper是属于CP特性(Zookeeper的核心算法是Zab,保证分布式系统下,数据如何在多个服务之间保证数据同步)。Eureka是吸取Zookeeper问题的经验,先保证可用性。
Zookeeper简单原理概述
Zookeeper = 文件系统 + 通知机制
旦这些被观察的数据状态发生变化,Zookeeper就负责通知已经在Zookeeper上注册的那些观察者让他们做出相应的反应。
zookeeper日志有三类:快照(虽然不是日志但是它是数据)、事务日志(记录每次操作)、zookeeper自己系统日志。第三个不属于数据类所以这里不做说明。
参考博客
(79条消息) ZooKeeper作用和存储结构_赵广陆的博客-CSDN博客_zookeeper数据存储采用什么结构
Zookeeper基本原理
参考博客
(79条消息) ZooKeeper介绍及典型使用场景_看山的博客-CSDN博客_zookeeper什么时候用
Zookeeper可视化工具
连接地址
prettyzoo: 简介 PrettyZoo 是一个基于 JavaFX 和 Apache Curator 实现的高颜值开源 Zookeeper 图形化管理客户端 (gitee.com)
参考博客
(81条消息) Zookeeper可视化工具PrettyZoo_Alienware^的博客-CSDN博客_zookeeper可视化工具
Zookeeper数据模型
理解ZooKeeper的一种方法是将其看做一个具有高可用性的文件系统。但这个文件系统中没有文件和目录,而是统一使用节点(node)的概念,称为znode。znode既可以保存数据(如同文件),也可以保存其他znode(如同目录)。所有的znode构成一个层次化的数据结构,。
Persistent Nodes: 永久有效地节点,除非client显式的删除,否则一直存在
Ephemeral Nodes: 临时节点,仅在创建该节点client保持连接期间有效,一旦连接丢失,zookeeper会自动删除该节点
Sequence Nodes: 顺序节点,client申请创建该节点时,zk会自动在节点路径末尾添加递增序号,这种类型是实现分布式锁,分布式queue等特殊功能的关键
Dubbo协议
缺省协议,使用基于 mina 1.1.7 和 hessian 3.2.1 的 tbremoting 交互。
、dubbo 缺省协议 采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况
2、不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低。
连接个数:单连接
连接方式:长连接
传输协议:TCP
传输方式:NIO 异步传输
序列化:Hessian 二进制序列化
适用范围:传入传出参数数据包较小(建议小于100K),消费者比提供者个数多,单一消费者无法压满提供者,Tcp是分包传输尽量不要用 dubbo 协议传输大文件或超大字符串。
适用场景:常规远程服务方法调用
约束
参数及返回值需实现 Serializable 接口
参数及返回值不能自定义实现 List, Map, Number, Date, Calendar 等接口,只能用 JDK 自带的实现,因为 hessian 会做特殊处理,自定义实现类中的属性值都会丢失。
Hessian 序列化,只传成员属性值和值的类型,不传方法或静态变量,兼容情况 [1][2]:
常见问题
为什么要消费者比提供者个数多?
因dubbo协议采用单一长连接,假设网络为千兆网卡(1024Mbit=128MByte),根据测试经验数据每条连接最多只能压满7MByte(不同的环境可能不一样,供参考),理论上1个服务提供者需要20个服务消费者才能压满网卡
为什么不能传大包?
因dubbo协议采用单一长连接,如果每次请求的数据包大小为500KByte,假设网络为千兆网卡(1024Mbit=128MByte),每条连接最大7MByte(不同的环境可能不一样,供参考),单个服务提供者的TPS(每秒处理事务数)最大为:128MByte / 500KByte = 262。单个消费者调用单个服务提供者的TPS(每秒处理事务数)最大为:7MByte / 500KByte = 14。如果能接受,可以考虑使用,否则网络将成为瓶颈。
为什么采用异步单一长连接?
(一个服务者提供一个消费者,采用长链接多路复用)
因为服务的现状大都是服务提供者少,通常只有几台机器,而服务的消费者多,可能整个网站都在访问该服务,比如Morgan的提供者只有6台提供者,却有上百台消费者,每天有1.5亿次调用,如果采用常规的hessian服务,服务提供者很容易就被压跨,通过单一连接,保证单一消费者不会压死提供者,长连接,减少连接握手验证等,并使用异步IO,复用线程池,防止C10K问题。
接口增加方法,对客户端无影响,如果该方法不是客户端需要的,客户端不需要重新部署;
输入参数和结果集中增加属性,对客户端无影响,如果客户端并不需要新属性,不用重新
部署;
输入参数和结果集属性名变化,对客户端序列化无影响,但是如果客户端不重新部署,不管输入还是输出,属性名变化的属性值是获取不到的。