分布式原理&网络IO&Netty

2,435 阅读14分钟

一、分布式理论

1. 分布式架构

1.1 概念

就是业务拆分,与集群的区别是:前者是 做不同的事,后者是做相同的事。

分布式系统的特点:分布性、对等性、并发性、缺乏全局时钟、故障总发生。

1.2 发展

阿里巴巴发起的"去 IOE"运动(IOE 指的是 IBM 小型机、Oracle 数据库、EMC 的高端存储)。为什么要去IOE?

  1. 升级单机处理能力的性价比越来越低
  2. 单机处理能力存在瓶颈
  3. 稳定性和可用性这两个指标很难达到

1.3 演变

单体->应用与数据库分离->集群->负载均衡->数据读写分离->➕ 搜索引擎 ->➕缓存->数据库水平/垂直拆分->应用拆分->服务化

2. 分布式问题

  • 通信异常:网络不可用,会导致分布式系统无法顺利进行一次网络通信

  • 网络分区:网络不连通,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干个孤立的区域,分布式系统就会出现局部小集群造成数据不一致。

  • 节点故障:服务器节点出现的宕机或"僵死"现象

  • 三态:即成功、失败和超时

3. 一致性

3.1 一致性分类

  • 强一致性 : 分布式很难实现

  • 弱一致性 : 不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致, 但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态。

  • 读写一致性 :用户读取自己写入结果的一致性,保证用户永远能够第一时间看到自己更新的内容。比如我们发一条朋友圈,朋友圈的内容是不是第一时间被朋友看见不重要,但是一定要显示在自己的列表上。

    解决方案:

    1. 一种方案是对于一些特定的内容我们每次都去主库读取。 (问题主库压力大)
    2. 我们设置一个更新时间窗口,在刚刚更新的一段时间内,我们默认都从主库读取,过了这个窗口之后,我们会挑选最近有过更新的从库进行读取
    3. 我们直接记录用户更新的时间戳,在请求的时候把这个时间戳带上,凡是最后更新时间小于这个时间戳的从库都 不予以响应。
  • 单调读一致性 : 本次读到的数据不能比上次读到的旧。多次刷新返回旧数据出现灵异事件。

    解决方案:通过hash 映射到同一台机器上。

  • 因果一致性 :如果节点 A 在更新完某个数据后通知了节点 B,那么节点 B 之后对该数据的访问和修改都是基于 A 更新后的值。于此同时,和节点 A 无因果关系的节点 C 的数据访问则没有这样的限制。

  • 最终一致性 :是所有分布式一致性模型当中最弱的。不考虑中间的任何状态,只保证经过一段时间之后,最终系统内数据正确。它最大程度上保证了系统的并发能力,也因此,在高并发的场景下,它也是使用最广的一致性模型。可以参考现在的共享单车场景。

4. CAP定理

CAP 理论含义是,一个分布式系统不可能同时满足一致性(C:Consistency),可用性(A: Availability)和分区容错 性(P:Partition tolerance)这三个基本需求,最多只能同时满足其中的2个。

  • C - Consistency : 一致性是值写操作后读操作可以读到最新的数据状态,查询数据宁可返回失败也不返回旧数据。
  • A - Availability :可用性是指任何操作都可以得到响应的结果,且不会出现响应超时或响应错误。(即使是旧数据,只要不返回超时就可以。)实现方案:1. 数据多节点同步 2. 不可以将数据库中资源锁定 3. 即使旧数据也要返回查询信息
  • P - Partition tolerance :分布式不可避免就是出现由于网络问题导致的节点通信失败或者服务挂掉,此时仍可以对外提供服务。实现方案:1. 用异步通知取代同步操作 2. 添加多个数据库节点

CAP只能3选2,因为在分布式系统中,容错性P肯定是必须有的,所以这时候无非就两种情况,网络问题导致要么错误返回,要么阻塞等待,前者牺牲了一致性,后者牺牲了可用性。

5. BASE 理论

BASE:全称:Basically Available(基本可用),Soft state(软状态),和 Eventually consistent(最终一致性)三个 短语的缩写,来自 ebay 的架构师提出。核心思想是:即使无法做到强一致性,但每个应用都可 以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 即各作出让步,理解为做事别走极端,中和下。 下面举几个例子:

  • 响应时间上的损失:出现故障或者高峰,查询结果可适当延长,以用户体验上限为主。
  • 功能上的损失:例如淘宝双11,为保护系统稳定性,正常下单,其他边缘服务可暂时不可用。

基于Base理论还提出了以下2个特性 :

  • Soft state(软状态):允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性,即允许系统在多个不同 节点的数据副本之间进行数据同步的过程中存在延迟。
  • Eventually consistent(最终一致性): 最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此最终 一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

6. 分布式事务

事务的基本特性:ACID,分布式事务就是微服务时代也要像单机时代一样,保证ACID。

7. 一致性协议 2PC

1. 2PC

2是指两个阶段,P是指准备阶段,C是指提交阶段。

优点: 简单

缺点:同步阻塞,单点问题,数据不一致(整个过程都不是原子性,任何一步失败都可能导致不一致),过于保守(出现故障而导致协调者始终无法获取到所有参与者的响应信息的话,这时协调者只能依靠其自身的超时机制来判断是否需要中断事务)

8. 一致性协议 3PC

3PC,全称 “three phase commit”,是 2PC 的改进版,将 2PC 的 “提交事务请求” 过程一分为二,共形成了由CanCommit、PreCommit和doCommit三个阶段组成的事务处理协议。

2PC对比3PC

  1. 首先对于协调者和参与者都设置了超时机制(在2PC中,只有协调者拥有超时机制,即如果在一定时间内没有收到参与者的消息则默认失败),主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
  2. 通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的 。
  3. PreCommit是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的。
  4. 3PC协议并没有完全解决数据不一致问题

9. 一致性算法 Paxos

Paxos为了解决2PC和3PC的单个协调者,和后续增加多个协调者集群,该听谁的,而产生的。在Paxos算法中,有如下角色:

  • Client 客户端:客户端向分布式系统发出请求,并等待响应。例如,对分布式文件服务器中文件的写请求。
  • Proposer 提案发起者 :提案者提倡客户请求,试图说服Acceptor对此达成一致,并在发生冲突时充当协调者以推动协议向前发展
  • Acceptor 决策者 :可以批准提案,Acceptor可以接受(accept)提案;如果某个提案被选定(chosen),那么该提案里的value就被选定了
  • Learners:最终决策的学习者,学习者充当该协议的复制因素

如何保证Paxos算法的活性?

活性:最终一定会发生的事情:最终一定要选定value

解决:通过选取主Proposer,并规定只有主Proposer才能提出议案。

10. 一致性算法 Raft

Raft 是一种为了管理复制日志的一致性算法。Raft将一致性算法分解成了3模块

  1. 领导人选举
  2. 日志复制
  3. 安全性

Raft算法分为两个阶段,首先是选举过程,然后在选举出来的领导人带领进行正常操作,比如日志复制等。

这里的两个算法就不过多阐述...

二、分布式系统设计策略

1. 心跳检测

  • 周期检测心跳机制(设定超时时间)
  • 累计失效检测机制(在周期检测心跳机制的基础上,统计一定周期内节点的返回情况,以此计算节点的“死亡”概率。另外,对于宣告“濒临死亡”的节点可以发起有限次数的重试,以作进一步判断。)**

2. 高可用设计

经过设计来减少系统不能提供服务的时间。系统高可用性的常用设计模式包括三种:主备(Master-SLave)、互备(Active-Active)和集群(Cluster)模式。一般采用主备模式。

3. 容错性

系统对于错误包容的能力

4. 负载均衡

其关键在于使用多台集群服务器共同分担计算任务,把网络请求及计算分配到集群可用的不同服务器节 点上,从而达到高可用性及较好的用户操作体验。

三、分布式架构网络通信

1. 基本原理

基于传输协议和网络IO来实现,其中传输协议比较出名的有tcp、udp等等,tcp、udp都是在基于Socket概念上为某类应用场景而扩展出的传输协议,网络IO,主要有bio、nio、aio三种方式

2. RPC

RPC全称为remote procedurecall,即远程过程调用。可以做到像本地调用一样调用远程服务,是一种进程间的通信方式,RPC并不是一个具体的技术,而是指整个网络远程调用过程。

RPC架构:一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub,这个Stub可以理解为存根。

  • 客户端(Client),服务的调用方。
  • 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
  • 服务端(Server),真正的服务提供者。
  • 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。

3. RMI

Java RMI 指的是远程方法调用 (Remote Method Invocation),是java原生支持的远程调用 ,采用JRMP(Java Remote Messageing protocol)作为通信协议,可以认为是纯java版本的分布式远程调用解决方案,这里的通信可以理解为一个虚拟机上的对象调用另一个虚拟机上对象的方法。

4. BIO、NIO、AIO

  • BIO:一个socket连接一个线程。简单易用但资源开销太高。
  • NIO:
    当一个连接创建后,不会需要对应一个线程,这个连接会被注册到多路复用器,所以一个连接只需要一个线程即可,所有的连接需要一个线程就可以操作,该线程的多路复用器会轮训,发现连接有请求时,才开启一个线程处理。大多数情况下,1w个连接里面同一时刻只有少量的连接有数据可读。
  • AIO:异步非阻塞IO。A代表asynchronize,主动通知,完成后会调用回调函数。使用场景:连接数目多且连接比较长(重操作)的架构,比如相册服务器。重点调用了OS参与并发操作,编程比较复杂。Java7开始支持

5. Netty

简化和流程化了 NIO 的开发过程,NIO 的 Bug。例如 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。

Netty模型:

Netty 抽象出两组线程池, BossGroup 专门负责接收客 户端连接, WorkerGroup 专门负责网络读写操作。NioEventLoop 表示一个不断循环执行处理 任务的线程, 每个NioEventLoop 都有一个selector,用于监听绑定在其上的socket网络通道。NioEventLoop内部采用串行化设计,从消息的读取->解码->处理->编码->发送, 始终由 IO 线 程 NioEventLoop 负责。

6. Netty核心组件

6.1 ChannelHandler 及其实现类

ChannelHandler 接口定义了许多事件处理的方法,我们可以通过重写这些方法去实现具 体的业务逻辑。我们经常需要自定义一个Handler类去继承ChannelInboundHandlerAdapter, 然后通过 重写相应方法实现业务逻辑,我们接下来看看一般都需要重写哪些方法:

  • public void channelActive(ChannelHandlerContext ctx), 通道就绪事件
  • public void channelRead(ChannelHandlerContext ctx, Object msg), 通道读取数据事件
  • public void channelReadComplete(ChannelHandlerContext ctx) , 数据读取完毕事件
  • public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause), 通道发生异常事件

6.2 ChannelPipeline

ChannelPipeline 是一个 Handler 的集合, 它负责处理和拦截 inbound 或者 outbound 的事 件和操作, 相当于一个贯穿 Netty 的链。

  • ChannelPipeline addFirst(ChannelHandler...handlers),把一个业务处理类(handler) 添加到链中的第一个位置
  • ChannelPipeline addLast(ChannelHandler...handlers),把一个业务处理类(handler) 添加到链中的最后一个位置

6.3 ChannelHandlerContext

这 是 事 件 处 理 器 上 下 文 对 象 , Pipeline 链 中 的 实 际 处 理 节 点 。 每 个 处 理 节 点ChannelHandlerContext 中 包 含 一 个 具 体 的 事 件 处 理 器 ChannelHandler , 同 时ChannelHandlerContext 中也绑定了对应的 pipeline 和 Channel 的信息,方便对 ChannelHandler 进行调用。常用方法如下所示:

  • ChannelFuture close(), 关闭通道
  • ChannelOutboundInvoker flush(), 刷新
  • ChannelFuture writeAndFlush(Object msg) , 将 数 据 写 到 ChannelPipeline 中 当 前
  • ChannelHandler 的下一个 ChannelHandler 开始处理(出站)

6.4 ChannelFuture

表示 Channel 中异步 I/O 操作的结果, 在 Netty 中所有的 I/O 操作都是异步的, I/O 的调 用会直接返回, 调用者并不能立刻获得结果, 但是可以通过 ChannelFuture 来获取 I/O 操作 的处理状态。 常用方法如下所示:

  • Channel channel(), 返回当前正在进行 IO 操作的通道
  • ChannelFuture sync(), 等待异步操作执行完毕

6.5 EventLoopGroup 和其实现类 NioEventLoopGroup

EventLoopGroup 是一组 EventLoop 的抽象, Netty 为了更好的利用多核 CPU 资源, 一般 会有多个 EventLoop同时工作。每个EventLoop维护着一个Selector实例。EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个 EventLoop 来处理任务。 在 Netty服务器端编程中,我们一般都需要提供两个EventLoopGroup,例如:BossEventLoopGroup和WorkerEventLoopGroup。

  • public NioEventLoopGroup(), 构造方法
  • public Future<?> shutdownGracefully(), 断开连接, 关闭线程

6.6 ServerBootstrap 和 Bootstrap

ServerBootstrap 是 Netty中的服务器端启动助手,通过它可以完成服务器端的各种配置; Bootstrap 是 Netty 中的客户端启动助手, 通过它可以完成客户端的各种配置。 常用方法如下 所示:

  • public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup),该方法用于 服务器端, 用来设置两个 EventLoop
  • public B group(EventLoopGroup group) , 该方法用于客户端, 用来设置一个 EventLoop
  • public B channel(Class<? extends C> channelClass), 该方法用来设置一个服务器端的通道实现
  • public B option(ChannelOption option, T value), 用来给 ServerChannel 添加配置
  • public ServerBootstrap childOption(ChannelOption childOption, T value), 用来给接收到的通道添加配置
  • public ServerBootstrap childHandler(ChannelHandler childHandler), 该方法用来设置业务处理类(自定义的 handler)
  • public ChannelFuture bind(int inetPort) , 该方法用于服务器端, 用来设置占用的端口号
  • public ChannelFuture connect(String inetHost, int inetPort) 该方法用于客户端, 用来连接服务器端

7. 实现基础RPC

主要注意编码器、解码器、序列化、客户端通过动态代理隐藏具体网络实现、服务端通过反射调用实现类来解藕。

源码地址-> gitee.com/znbsmd/lago…

《lagouedu》分布式原理总结