Featured image of post RPC和分布式一致性协议

RPC和分布式一致性协议

分布式一致性协议

高内据,低耦合

Eureka注册中心

服务调用出现的问题

  • 服务消费者如何获取服务提供者的地址信息?
  • 如果有多个提供者,消费者该如何选择?
  • 消费者如何得知服务提供者的健康状态?

Eureka步骤

  • Eureka的搭建

  • 服务注册

Ribbon负载均衡

  • 整体流程

IRule接口的策略

  1. RoundRobinRule:简单轮询列表来选择服务器,它是Rinbon默认的负载均衡规则。
  2. AvailabilityFilteringRule:对以下两种服务器进行忽略:(1). 在默认情况下,这台服务器如果三次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级的增加。(2).并发数过高的服务器,如果如果一个服务器的并发连接数过高,配置了AVailabilityFilteringRule的客户端也会将其忽略。并发连接数的上限可以由客户端的..ActiveConnectionsLimit属性进行配置。
  3. WeightedResponseTimeRule:为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
  4. ZoneAvoidanceRule:以区域可用的服务器进行服务器的选择。使用zone对服务器进行分类,这个zone可以理解为一个机房,一个机架等。而后再对 zone里的多个服务进行轮询。
  5. BestAvailableRule:忽略哪些短路的服务器,并选择并发数较低的服务器。
  6. RandomRule:随机选择一个可用的服务器。
  7. RetryRule:重试机制的选择逻辑。
  • 配置负载均衡的方法:

  • Ribbon饥饿加载

Rinbbon默认为懒加载,当需要时才会创建信息。

总结

Nacos注册中心

Nacos是阿里巴巴的产品,现在是springcloud中的一个组件。相比Eureka功能更加丰富,在国内更受欢迎。

Nacos服务多级概念

Nacos将相同的地区的机房的多个服务统一在一起作为一个集群,一个中心服务下有多个集群,一个集群对应一个地区的多个服务。相同集群的服务尽量调用相同集群的其他服务,本集群这样在地理上尽可能的减少了延迟。

Nacos默认不会采用集群就近调用,需要配置开启。

优先访问本地集群,在本地集群内随机访问服务。

  • 权重

权重设置在0~1之间。当权重设置为0时,不会去访问0权重的服务。

环境隔离

Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层的隔离。

两个命名隔间之间的服务无法互相访问。

Nacos和Eureka的区别

  • 生产者

Eureka每30秒都会对每个服务进行健康检测。

此心跳检测是由服务向Eureka发送请求。

Nacos会把服务分为临时实例和非临时实例。

临时实例Nacos会进行心跳检测,如果检测到心跳不跳动,就会直接清除掉此服务。

非临时实例不会进行心跳检测,而是由Nacos主动发送请求来确认服务是否存活。如果检测不存活,Nacos也不会把此服务从列表中清除。

  • 消费者

Eureka如果发现生产者服务有所改变,需要消费者主动去向Eureka去拉去服务信息。

Nacos采用pull和push两种,既可以消费者主动去拉取服务信息,也可以由Nacos主动去通知消费者服务变动信息。

统一配置管理

统一配置文件的读取和修改,需要修改appliocation.yml中的配置,spring启动过程中会在读取application.yml之前先读取bootstrap.yml文件,所以把统一的模板配置配置到bootstrap.yml中就可以了。

  • 步骤

  • 配置热更新
  1. 在对应服务的controller上加@RefreshScope注解
  2. 加入configurationProperties(prefix=“变量”),约定大于配置。

多环境配置共享

  • 配置文件的优先级

Feign

  • Feign客户端的配置

  • feign的自定义配置

  • Feign的日志配置

统一网关Gateway

zuul是基于servlet的实现,属于阻塞式编程。而springCloudGateway则是基于spring5中提供的webFlux,属于响应式编程的实现,具备更好的性能。

  • 统一网关的搭建

  • 断言工厂

Docker

docker是一个快速交付应用,运行应用的技术:

  1. 可以将程序,运行环境和依赖一起打包为一个镜像,可以迁移到任意Linux操作系统。
  2. 运行时利用沙箱机制形成隔离容器,各个应用互不干扰。
  3. 启动,移除都可以通过一行命令完成,方便快捷。

Docker如何解决依赖的兼容性问题的?

  • 将应用的Libs(函数库),Deps(依赖)、配置与应用一起打包。
  • 将每一个应用放到一个隔离容器中去运行,避免互相干扰。

不同环境的操作系统不同,Docker如何解决?

  • Docker镜像中包含完整运行环境,包括系统函数库,仅依赖系统的Linux内核,因此可以在任意Linux操作系统上运行。

Docker和虚拟机的差异:

  • docker是一个系统进程:虚拟机是在操作系统中的操作系统。
  • docker体积小,启动速度快,性能好;虚拟机体积大,启动速度慢,性能一般。

docker镜像都是只读的。

docker的架构

docker命令

  • 拉取镜像:docker pull redis:{版本},不填版本默认下载最新版(latest)
  • 查看docker已安装镜像: docker images
  • 把docker安装的镜像保存到本地:docker save -o {本地存放镜像文件的目录,文件名以.tar结尾} 需要备份的镜像名:版本号。
1
docker save -o /home/rose/work/docker_redis.tar redis:latest
  • 删除docker中的镜像:docker rmi redis:latest
  • 把本地的镜像读取到doker中:docker load -i /home/rose/work/docker-redis.tar

docker 创建运行一个容器

docker run –name containerName -p 80:80 -d nginx

  • docker run:创建并运行一个容器
  • –name:给容器起一个名字
  • -p:将宿主机端口号与容器端口号映射,冒号左侧宿主机端口,右侧是容器端口
  • -d:后台运行容器
  • nginx:镜像名字,不加版本默认latest

  • docker容器挂在

Dockerfile自定义镜像

RPC设计

几种IO

  • 同步阻塞BIO

socket是典型的同步阻塞io模型,一个客户端和服务端建立一个线程连接,如果有一端没有发送数据,就一直处于阻塞IO状态。

  • 同步非阻塞NIO

服务端有一个线程,还要维护一个选择器,这个选择器在所有建立连接的客户端之间轮询。如果有一个客户的发送了一个IO请求,就交给服务端的线程去执行这个请求。

  • 异步非阻塞AIO

有一个中间应用,此应用要先根据客户端发送来的请求,处理完成之后再告诉对应的下游应用需要执行什么操作。就是说响应不是立即完成的,需要有一定的时间来返回响应。

NIO

  • NIO和BIO的比较
  1. BIO是用流的方式来处理数据,而NIO以缓冲区的方式处理数据,缓冲区IO的效率比流IO的效率高很多。
  2. BIO是阻塞的,NIO则是非阻塞的
  3. BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中。Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道

Netty

Netty介绍

原生NIO存在的问题

  1. NIO的类库和API复杂,使用麻烦。
  2. 需要具备其他额外技能:要熟悉java多线程,因为NIO编程涉及到Reactor模式,必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序。
  3. 开发工作量和难度都非常大:例如客户端面临断连重连,网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等。
  4. JDKNIO的Bug:臭名昭著的Epoll Bug,它会导致Selector空轮询,最终导致CPU 100%。直到JDK1.7该问题仍旧存在,没有被根本解决。(在LINUX环境下选择器可能直接返回。)

NIO是基于NIO的网络编程框架,是当前最流行的NIO框架,知名的Elasticsearch,Dubbo框架内部都采用了Netty。

优点

  1. 设计优雅,提供阻塞和非阻塞的Socket,提供了灵活可拓展的事件模型,提供高度可定制的线程模型。
  2. 具备更高的性能和更大的吞吐量,使用零拷贝技术最小化不必要的内存复制,减少资源的消耗。
  3. 提供安全传输特性。
  4. 支持多种主流协议,预置多种编解码功能,支持用户开发私有协议。

线程模型

  • 传统阻塞I/O模型

问题:

  1. 当并发数很大,就会创建大量的线程,占用很大系统资源。
  2. 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成线程资源浪费。
  • Reactor模型

Reactor模型,通过一个活多个输入同时传递给服务处理器的模式,服务器端程序处理传入的多个请求,并将它们同步分派到相应的处理线程,因此Reactor模式也叫Dispatcher模式。Reactor模式使用IO复用监听事件,收到事件后,分发给某个线程(进程),这点就是网络服务器高并发处理关键。

  • 单Reactor单线程

Selector是可以实现应用程序通过一个阻塞对象监听多路连接请求

Reactor对象通过Selector监控客户端请求事件,收到事件后通过Dispatch进行分发

是建立请求事件,则由Acceptor通过Accept处理连接请求,然后创建一个Handler对象处理连接完成后的后续业务处理。

  • 优点:

    模型简单。没有多线程,进程通信,竞争的问题,全部都在一个线程中完成

  • 缺点:

    1. 性能问题,只有一个线程,无法完全发挥多核CPU的性能,Handler在处理某个连接的业务时,整个线程无法处理其他连接事件,很容易导致性能瓶颈。
    2. 可靠性问题:线程意外终止或者进入死循环,会导致整个系统通讯模块不可用,不能接收和处理外部消息,造成节点故障。
  • 单Reactor多线程

Reactor对象通过Selector监控客户端请求事件,收到事件后,通过dispatch进行分发

如果建立连接请求,则右Acceptor通过accept处理连接请求

如果不是连接请求,则由reactor分发调用连接对应的handler来处理

handler只负责响应事件,不做具体的业务处理,通过read读取数据后,会分发给后面的worker线程池的某个线程处理业务

worker线程池会分配独立的线程完成真正的业务,并将结果返回给handler

Handler收到相应后,通过send将结果返回给client

  • 优点

    可以充分的利用CPU的多核处理能力

  • 缺点

    多线程数据共享和访问比较复杂,reactor处理所有的事件的监听和响应,在单线程运行,在高并发场景容易出现性能瓶颈

  • 主从Reactor多线程

Reactor主线程MainReactor对象通过select监听客户端连接事件,收到事件后,通过Acceptor处理客户端连接事件

当Acceptor处理完客户端连接事件之后(与客户端建立好Socket连接),MainReactor将连接分配给SubReactor(即:MainReactor只负责监听客户端连接请求,和客户端建立连接之后将连接交有SubReactor监听后面的IO事件)

SubReactor将连接加入到自己的连接队列进行监听,并创建Handler对各种事件进行处理

当连接上有新事件发生的时候,SubReactor就会调用对应的Handler处理

Handler通过read从连接上读取请求数据,将请求数据分发给Worker线程池进行业务处理

Worker线程池会分配独立线程来完成真正的业务处理,并将处理结果返回给Handler,Handler通过send想客户端发送响应数据

一个MainReactor可以对应多个SubReactor,即一个MainReactor线程可以响应多个SubReactor线程

  • 优点

    1. MainReactor线程与SubReactor线程的数据交互简单职责明确。MainReactor线程只需要接受新连接,SubReactor负责完成后续的业务处理
    2. MainReactor线程只需要把新连接传给SubReactor线程,SUbReactor线程无需返回数据
    3. 多个SubReactor线程可以应对更高的并发请求
  • 缺点

    这种模式的缺点是编程复杂度较高,但是由于其优点明显,在许多项目中被广泛使用,包括Nginx、Memcached、Netty等。这种模式也被叫做服务器的1+M+N模式。即使用该模式开发的服务器包含一个(或多个,一个表示相对较少)连接建立线程+M个IO线程+N个业务处理线程,这是业界成熟的服务器设计模式。

Netty线程模式

Netty的设计主要基于主从Reactor多线程模式,并做了一定的改进。

  • 简单的Netty模型

  1. BossGroup线程维护Selector、ServerSocketChannel注册到这个Selector上,主关注连接建立请求事件(主Reactor)
  2. 当接收的来自客户端的连接建立请求事件时。通过ServerSocketChannel.accept方法获得对应的SocketChannel,并封装成NioSocketChannel注册到WorkerGroup线程中的Selector,每个Selector运行在一个线程中(从Reactor)
  3. 当WorkerGroup线程中的Selector监听到自己感兴趣的IO事件后,就调用Handler进行处理
  • 进阶Netty模型

有两组线程池:BossGroup专门负责和客户端建立连接,WorkerGroup中的线程专门负责处理连接上的读写

两个线程池含有多个不断循环的执行事件处理的线程,每个线程都包含一个Selector,用于监听注册在其上的Channel

BossGroup:

  1. 轮询注册在其上的通道中的accept事件(OP_ACCEPT事件)
  2. 处理accept事件,对客户端建立连接,生成一个NioSocketChannel,并将其注册的WorkerGroup中的某个线程上的Selector上
  3. 再去以此循环处理任务队列中的下一个事件

WorkerGroup:

  1. 轮询注册在其上的NioSocketChannel的read/write事件(OP_READ/OPWRITE事件)
  2. 在对应的NioSocketChannel上处理对应的read/write事件
  3. 再去以此循环处理任务队列中的下一个事件
  • 详细版Netty模型

Netty抽象出两组线程池,每个线程池中都有NioEventLoopGroup线程池。

NioEventLoopGroup相当于一个事件循环组,这个组中含有多个事件循环,每个事件循环就是一个NioEventLoop

NioEventLoop:

  1. select:轮询注册在其上的通道中的accept事件(OP_ACCEPT事件)
  2. processSelectedKeys:处理accept事件,与客户端建立连接,生成一个NioSocketChannel,并将其注册的WorkerGroup中的某个线程上的Selector上
  3. RunAllTasks:再去以此循环处理任务队列中的其他任务

WorkerNioEventLoop:

  1. select:轮询注册在其上的NioSocketChannel的read/write事件(OP_READ/OPWRITE事件)
  2. processSelectedKeys:在对应的NioSocketChannel上处理对应的read/write事件
  3. RunAllTasks:再去以此循环处理任务队列中的其他任务

在以上两个ProcessSekectedKeys步骤中,会使用PipeLine(管道),PipeLine中引用了Channel,即通过PipeLine可以获取到对应的Channel,PipeLine中维护了很多的处理器(拦截处理器,过滤处理器,自定义处理等)。

Netty高级运用

  • java的编解码
  1. 编码:序列化,将对象序列化成字节数组,用于网络传输,数据持久化或者其他用途
  2. 解码:反序列化,他是从网络、磁盘等读取字节数组还原成原始对象(通常是原始对象的拷贝),以方便后续的业务逻辑操作

java序列化对象只需要实现java.io.Serializable;接口,并生产序列化ID,这个类就可以通过java.io.ObjectInput和java.io.ObjectOutput序列化和饭序列化。

缺点:无法跨语言,序列化后码流太大,序列化性能太低

  • Netty编解码器

Netty编解码器是由两部分组成的:编码器和解码器。

  • 解码器:负责将消息从字节或其他序列形式转换成指定的消息对象
  • 编码器:负责将消息对象转换为字节或其他序列形式在网络传输

Netty的编解码器是一种特殊的ChannelHandler(通道),所以依赖于ChannelPipline(管道),可以将多个编解码器连接在一起,以实现复杂的转换逻辑。

WebSocket

WebSocket是一种在单个TCP连接上进行全双工通讯的协议,WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,客户端和服务端只需要完成一次握手,两者之间就可以直接创建持久性的连接,并进行双向数据传输。

WebSocket和HTTP的区别

http协议是用在应用层的协议,他是基于tcp协议的。http协议建立连接也必须要有三次握手才能发送信息。http协议分为短连接,长连接,短连接是每次请求都要三次握手才能发送自己的信息。即每一个request对应一个response,长连接实在一定的期限内保持连接,保持TCP连接不断开。客户端与服务器通信,必须要由客户端先发起,然后服务端返回结果。客户端是主动的,服务器是被动的。客户端要想实时获取服务端消息就得不断发送长连接到服务端。

WebSocket实现了多路复用,他是全双工通道。在WebSocket协议下服务端和客户端可以同时发送信息。建立了WebSocket连接之后,服务端可以主动发送信息到客户端。而且信息当中不必再带有head的部分信息了与http的长连接通信来说,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。

Netty中粘包和拆包的解决方案

粘包和拆包的简介

粘包和拆包是TCP网络中不可避免的,无论是服务端还是客户端。当我们读取或者发送消息的时候,都需要考虑TCP底层粘包/拆包机制。

TCP是个流协议,所谓流,就是没有界限的一串数据。TCP底层并不了解上次业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

粘包和拆包的解决方案

  • 业内解决方案

由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的业务协议栈设计来解决,根据业务的主流协议的解决方案,可以归纳如下:

  1. 消息长度固定,累计读取长度和为定长的LEN的报文后,就认为读到了一个完整的信息
  2. 将换行符作为消息结束符
  3. 将特殊的分隔符作为消息结束的标志,回车换行符就是一种特殊的结束分隔符
  4. 通过在消息头中定义长度字段来标识消息的总长度
  • Netty中的粘包和拆包解决方案

Netty提供了4种解码器来解决,分别如下:

  1. 固定长度的拆包器,每个应用层的数据包都拆分成固定长度的大小
  2. 行拆包器,每个应用层的数据包,都以换行符作为分隔符,进行分割拆分
  3. 分隔符拆包器,每个应用层数据包,都通过自定义的分隔符,进行分割拆分
  4. 基于数据包长度的拆包器,将应用层数据包的长度,作为接收端应用层数据包的拆分依据,按照应用层数据包的大小,拆包。这个拆包器有一个要求,就是应用层协议中包含应用层的长度

Netty源码

线程组源码

EventLoopGroup是一组EventLoop的抽象,Netty为了方便的利用多核CPU资源,一般会有多个EventLoop同时工作,每个EventLoop维护着一个Selector实例。

  • 线程组源码流程图

创建NioEventLoopGroup线程组,首先判断有没有传线程数量,如果没有就取默认值(CPU核心数*2),利用for循环创建线程数量的NioEventLoop,每个NioEventLoop对应一个任务队列和选择器,创建任务队列和选择器,生成EventBootStrap对象。设置启动参数,绑定端口。

什么是RPC

远程过程调用,借助RPC可以想本地调用一样调用远程服务,是一种进程间的通讯方式,

比如两台服务器A和B,A服务器上部署一个应用,B服务器上部署一个应用,A服务器上的应用想调用B服务器上应用提供的方法,由于两个应用不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和表达调用的数据,需要主意嗯是RPC并不是一个具体的技术,而是指整个网络调用过程。

RPC架构

一个完整的RPC架构里面包含了四个核心的组件

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

RMI

远程方法调用,一种用于实现远程过程调用的java APi,能直接传输序列化厚的java对象,它的实现依赖于java虚拟机,因此它只支持一个JVM到另一个JVM的调用。

  1. 客户端从远程服务器的注册表中查询并获取远程对象引用。
  2. 桩对象于远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际上是由相应的桩对象代理完成的。
  3. 远程引用层在将桩的本地引用转换为服务器上的远程引用后,再将调用传送给传输层,由传输层发送TCP协议进行调用。
  4. 在服务器端,传输层监听入站连接,它一旦接收到客户端远程调用后,就将这个引用转发给其上层的远程引用层。
  5. 服务器端的远程引用层将客户端发送的远程引用转换成虚拟机的引用后,再将请求传输给骨架
  6. 骨架读取参数,又将请求传送给服务器,最后由服务器进行实际的方法调用。
  7. 如果远程方法调用之后有返回值,则服务器将这些结果又沿用“骨架->远程引用->传输层”向下传递。
  8. 客户端的传输层接收到返回值后,又沿用“传输层->远程引用层->桩”向上传递,然后由桩来饭序列化这些返回值,并将最后的结果传递给客户端程序。

分布式理论与分布式架构设计理论

一致性协议

  • 一致性的分类
  1. 强一致性

    这种级别是最符合用户直觉的,他要求系统写入什么,读出来的也会是什么,用户体验好,但是实现起来往往对用系统的性能影响大,但是强一致性很难实现。

  2. 弱一致性

    约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据可以达到一致,但会尽可能的保证到某个时间级别(比如秒级别)后,数据能够达到一直状态。

  3. 最终一致性性

    最终一致性也是弱一致性的一种,它无法保证数据更新后,所有后续的访问都能看到最新数值,而是需要一个时间,在这个时间之后可以保证这一点(就是在一段时间后,节点间的数据会最终达到一直状态),而在这个时间内,数据也许是不一致的,这个系统无法保证强一致性的时间片段称为“不一致窗口”。不一致窗口的时间长短取决于很多因素,比如备份数据的个数,网络传输延迟速度,系统负载等。

两阶段提交协议2PC

所有事务先全部运行但是不提交,当所有的服务都返回成功之后,同一改革提交事务。

  • 优点

    原理简单

  • 缺点:

    • 同步阻塞:

      第二阶段提交的执行过程中,所有参与该事务操作的逻辑都处于阻塞状态,即当参与者占有公共资源时,其他参与者访问公共资源处于阻塞状态。

    • 单点问题:

      若协调器出现问题,那么整个二阶段提交流程将无法运转,若协调者在二阶段出现问题时,那么其他参与者将一直处于锁定资源的状态中,而无法继续完成事务操作。

    • 数据不一致

      在阶段二中,执行事务提交的时候,当协调者向所有参与者发送commit请求后,发生了局部网络异常或者是协调者在尚未发送完commit请求之前自身发生了崩溃,导致最终只有部分参与者收到了commit请求,于是会出现数据不一致的现象。

    • 太过保守

      在事务提交询问的过程中,参与者出现了故障,导致协调者始终无法获取所有参与者的响应信息的话,此时协调者只能依靠自生的超时机制来判断是否需要中断事务,这样的策略过于保守,即没有完善的容错机制,任意一个节点的失败都会导致整个事务的失败。

三阶段提交协议3pc

  • 3pc存在的问题

    当处于第三阶段时,如果协调者突然宕机,一部分参与者收到了commit请求,一部分没有;没收到的那部分会在等待超时后提交事务,此时,数据是一致的。但如果协调者发送的是回滚命令,一部分接收到的参与者会回滚事务,但没有接收到的参与者会等待超时后提交事务,还是导致了数据不一致问题。

NWR协议

NWR是一种在分布式存储系统中用于控制一致性级别的一种策略,在亚马逊的云存储系统中,就应用NWR来控制一致性。亚马逊用的这种形式。

N: 在分布式存储系统中,有多少份备份数据

W:代表一次成功的更新操作要求至少有W份数据写入成功

R:代表一次成功的读取数据操作要求至少有R份数据成功读取

  • 原理

NWR值的不同组合会产生不同的一致性效果,当W+R>N的时候,整个系统对于客户端来讲能保证强一致性。

以常见的N=3,W=2,R=2为例:

N=3:任何一个对象都必须有3个副本

W=2: 对数据的修改操作只需要在3个副本中的2个上面完成就返回

R=2: 从3个对象中要读取到2个数据对象,才能返回

在分布式系统中,数据的单点是不允许存在的。即线上正常存在的备份数量N设置1的情况是非常危险的,因为一旦这个备份发生了错误,就可能发生数据的永久性错误。假如把N设置成2,只要有一个节点发成损坏,就会有单点的存在。所以N必须大于3。N越高,系统的维护和整体成本就越高,工业界通常把N设置为3.

Gossip协议(病毒式传播)

是一种去中心化的分布式协议,数据通过节点像病毒一样传播。因为是指数级传播,所以传播速度特别快。

  • 优点:

扩容性:允许节点的任意增加和减少,新增节点的状态最终会和其他节点一致

容错:任意节点的宕机和重启都不会影响Gossip消息的传播,具有天然的分布式系统的容错性。

去中心化:无需中心节点,所有节点都是对等的,任意节点无需知道整个网络状态,只要网络联通,任意节点可以把消息散播到全网。

最终一致性:Gossip协议实现信息指数级的快速传播,因此在有新信息需要传播时,消息可以快速的发送到全局节点,在有限的时间内能够做到所有节点都拥有最新的数据。

  • 缺点:

消息延迟:节点随机向少数几个节点发送信息,消息最终是通过多个伦次的传播才到达全网,不可避免的造成消息延迟。

消息冗余:节点定期随机选择周围节点发送消息,而收到消息的节点也会重复该步骤,不可避免的引起同一节点消息多次接收,增加消息处理压力。

常见应用有:p2p网络通信,redis cluster,Consul。

paxos协议

就是paxos算法,paxos算法是基于消息传递且具有高度容错特性的一致性算法,是目前公认的解决分布式一致性问题最有效的算法之一。

应用:谷歌的很多大型系统,Zookeeper,Mysql5.7之后的主从复制,都采用paxos来解决分布式一致性问题。

  • 角色介绍:

client客户端:

客户端向分布式系统发出请求,并等待响应。例如,对分布式文件服务器中文件的写请求。

prposer提案发起者

提案者提倡客户端请求,视图说服Acceptor对此达成一致,并在发成冲突是充当协调者,以推动协议向前发展。

Acceptor决策者,可以批准提案

Acceptor可以接受提案,并进行投票,投票结果是否通过以多数派为准,以如果某个提案被选定,那么该提案里的value就会被选定。

learnner:最终决策的学习者:

学习者充当该决策的复制因素(不参与投票)

  • basic paxos流程
  1. 提案者提出一个提案,编号为N,此N大于这个提案者之前提出的所有编号,请求决策者的多数接受这个提案
  2. 如果编号N大于此决策者之前接受的任意提案编号则接受,否则拒绝。
  3. 如果达到多数派,提案者会发出accept请求,此请求包含提案的编号和对应内容。
  4. 如果此提案者在此期间,没有接受到任何大于N的提案,则接受此提案内容,否则忽略。

活锁问题的解决方案:只需要在每个提案者再去提案的时候随机加上一个等待时间即可。

  • 选举-复制模型

第一次请求需要两次rpc调用,选举出一个决策者的leader,然后用过决策者复制这次请求给其他节点。

第二次请求直接将编号和值发给,决策者leader,由leader直接决定是否执行。只需要一次RPC调用。

Raft协议

  • 节点状态
  1. leader主节点:接受Client更新请求,写入本地后,然后同步到其他副本中。
  2. Follower从节点:从leader中接收更新请求,然后写入本地日志文件,对客户端提供读请求。
  3. Candidate候选节点:如果Follower在一定的时间内,未收到leader心跳。则判断leader可能故障,发起选主提议。节点状态从Follower变成Candidate,直到候选结束。

termid:任期号,时间被划分成一个一个任期,每次选举后都会产生一个新的任期,一个任期内只有一个master。

请求投票:候选者在选举过程中发起,收到多数派响应后成为leader。

  • lease机制

租约机制

  • 特点:
  1. lease是颁发者对一段时间内数据一致性的承诺
  2. 颁发者发出lease后,不管是否被接受,只要lease不过期,颁发者都会按照协议遵守承诺。
  3. lease的持有者只能在lease的有效期内使用承诺,一旦lease超时,持有者需要放弃执行,重新申请lease。

分布式系统设计策略

心跳检测

  • 周期心跳检测机制

Server端每隔t秒向Node集群发起检测请求,设定超时时间,如果超过超时时间,则判断死亡。

  • 累计失效检测机制

在周期检测心跳机制的基础上,统计一定周期内节点的返回情况(包括超时和正确返回),以此计算节点的死亡概率。另外,对于宣告斌零死亡的节点可以发起有限次数的重试,以作进一步判断。如果超过次数则可以把该节点踢出集群。

高可用

通过设计减少系统不能对外提供服务的时间。

  • 主备模式

当主机宕机时,备机接管主机的一切工作,待主机恢复正常后,按使用者的设定以自动或手动方式将服务切换到主机上运行。

场景:Mysql。Redis等通过主从复制来保证高可用。

  • 互备模式

两台主机同时运行各自的服务工作且相互检测情况。每个master都有读写能力,会根据时间戳或业务逻辑来合并版本。

场景:数据库双主模式。

  • 集群模式

有多个节点在运行,同时可以通过主控节点分担服务请求。集群模式需要解决主控节点本身的高可用问题,一般采用主从模式。

脑裂问题

高可用本身通过心跳检测来检测对方是否正常,当心跳线断开,高可用系统就会分裂成两个群体,由于互相失去了联系,都认为对方出现了故障,就会本能的去争抢公共资源,争起“应用服务”。

  • 导致的问题:
  1. 共享资源被瓜分,两边服务都起不来了。
  2. 两边服务都起来了,但同时读写共享存储,导致数据损坏。
  • 预防脑裂的方案
  1. 添加冗余的心跳线(即冗余通讯的方法)
  2. 同时用两条心跳线路(即心跳线也高可用),这样一条线路坏了,另一个还是好的
  3. 仲裁机制:当两个节点出现分歧时,由第三方决定听谁的。这个仲裁者,可以是一个锁服务,一个共享盘或者其他什么东西。
  4. Lease机制:租约机制,在租期内,即使出现问题也认为要听申请到lease的节点。
  5. 隔离机制:
    1. 共享存储:确保只有一个master往共享存储中写数据。
    2. 客户端:确保只有一个master可以响应客户端的请求。
    3. Slave:确保只有一个主节点可以向从节点发送命令。

容错性

系统对于错误包容的能力。非常典型案例就是缓存穿透问题。

存放null值,布隆过滤器。

负载均衡

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

应用:硬件有著名的F5。 软件:nginx,LV5,HAProxy。

分布式架构服务调用

服务调用

  • Http应用协议的通信框架
  1. httpURLConnection

    java原生是基于http协议的,支持get,post,put,delete等各种请求方式,最常用的就是get和post

  2. Apache Common HttpClient

    HttpClient是Apache Common下的子项目,可以用来提供高效的,最新的,功能丰富的支持HTTP协议的客户端编程工具包,并且它支持Http协议最新的版本。

  3. OKhttp3

    是当前主流的网络请求的开源框架,用于替代HttpUrlConnection和Apache HttpClient

    支持http2.0,对一台机器的请求共享一个socket。

    采用连接池技术,可以有效的减少http连接数量。

    无缝集成GZIP压缩技术

    支持Response Cache,避免重复请求

    域名多IP支持。

  4. RestTemplate

    Spring RestTemplate是Spring提供的用于访问Rest服务器客户端,RestTemplate提供了多种便捷访问远程http服务器的方法,能够大大提高客户端的编写效率,所以很多客户端比如安卓或者第三方服务商都使用RestTemplate请求restful服务。

    面向URL组件,必须依赖于主机+端口号+URL

    RestTemplate不依赖与服务接口,仅关注rest响应内容。

    spring Cloud Feign

  • RPC框架

RPC全称为remote procedure call,远程过程调用,借助RPC可以做到像本地调用一样调用远程服务,是一种进程间的通信方式。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计