CAP跟BASE
CAP
分布式不能同时满足一致性(Consisency),可用性(Available),分区容忍性(Partition Tolerance),最多只能同时满足其中两项。
一致性
一致性指的是多个数据副本是否能够保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
可用性
可用性指分布式系统在面对各种异常时可以提供正常的服务能力,可以用系统可用时间占总时间的比值来衡量,4个9的可用性表示系统99.99%的时间是可用的。
在可用性的条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
分区容忍性
网络分区指分布式系统中的节点被划分了多个区域,每个区域内部都可以通信,但是区域之间无法通信。
在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性的可用服务,除非整个网络都出现了故障。
权衡
在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的,因此,CAP理论实际上是要在一致性和可用性之间做权衡。
可用性跟一致性往往的冲突的,很难使它们同时满足,在多个节点之间进行数据同步时
- 为了保证一致性(CP):就需要让所有节点下线成为不可用状态,等待同步完成。
- 为了保证可用性(AP):在同步过程中允许读取所有节点的数据,但是数据可能不一致。
BASE
BASE是基本可用(Basically Available)、软状态(Soft Satue)和最终一致性(Eventually Consistent)三个短语的缩写。
BASE理论是对CAP中的一致性和可用性权衡的结果,它的核心意思:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
基本可用
指分布式系统在出现故障的时候,保证核心可用,允许损失部分可用性。
例如:电商在做促销的时候,为了保证购物系统的稳定性,部分消费者可用会被引导到一个降级的页面。
软状态
指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本进行同步存在时延。
最终一致性
最终一致性强调是系统中所有的数据副本,在经过一段时间内的同步后,最终能达到一致的状态。
ACID要求强一致性,通常运用在传统的数据库系统上。而BASE要求最终一致,通过牺牲一致性来达到可用性,通常运用在大型分布式系统中。
在实际的分布式场景,不同业务单元和组件对一致性的要求是不同的,因此ACID和BASE往往会结合在一起。
分布式事务
指事务的操作位于不同的节点,需要保证事务的ACID特性。
本地消息表
本地消息表与业务数据表
2PC
两阶段提交(two-phase Commit),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定参与者是否真正执行事务。
运行过程
准备阶段
协调者询问参与者事务是否执行承购,参与者回复事务执行结果。
在该过程,参与者执行了事务,但是还没提交。
提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
存在的问题
- 同步阻塞:所有的参与者都等待其他参与者响应之前都处于阻塞状态,无法进行其他操作。
- 单点问题:协调者在2PC中起到非常大的作用,发生故障将会造成很大影响,特别是在阶段二发生故障,所有参与者会一直处于等待状态,无法完成其他操作。
- 数据不一致:在阶段二,如果协调者只发送部分Commit消息,此时网络发生异常,那么只有部分参与者接收到Commit消息,也就是说只有部分参与者提交了事务,使得系统的数据不一致。
- 太过保守: 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
负载均衡
集群中的应用服务器(节点)通常被设计成无状态,用户可以请求任何一台服务器。
负载均衡器会根据集群中每个节点的负载情况,将用户请求转发到合适的节点上。
负载均衡器可以用来实现高可用以及伸缩性:
- 高可用:当某个节点故障时,负载均衡器将用户请求转发到另外的节点,从而保证所有服务持续可用。
- 伸缩性:根据系统整体负载情况,可以很容易地添加移除节点。
负载均衡过程包含两个部分
- 根据负载均衡算法得到转发的节点。
- 进行转发
负载均衡算法
轮询
轮询算法把每个请求轮询发送到每个服务器上。
实际上,由于进程部署环境的不同,每台服务器的处理能力一般也不同,任务处理时间也不尽相同。因此轮询算法策略并不能很好地将任务均摊到各个进程中。
加权轮询
加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值,性能高的服务器分配更高的权值。
加权轮询策略考虑了进程处理能力的不同,因此更接近实际的应用。可是,加权派发策略也没有考虑任务处理的要求。
最少链接
由于每个请求的连接时间不一样,使用轮询或者加权轮询算法,可能会让一台服务器当前连接数过大,而另一台服务器的连接小,造成负载不均衡。
最少连接算法就是将请求发送给最少连接数的服务器上。
加权最少连接
在最少连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。
随机算法
把请求随机发送到服务器上。
和轮询法类似,该算法比较适合服务器性能差不多的场景。
原地址哈希法
原地址哈希通过堆客户端ip计算哈希值之后,再对服务器数量取模得到目标服务器的序号。
可以保证同一ip的客户端的请求会转发到同一台服务器上,用来实现会话粘滞。
转发实现
HTTP重定向
负载均衡器计算得到服务器的ip之后,将该地址写入HTTP重定向报文中,状态码为302。客户端收到重定向报文之后,需要重新向服务器发起请求。
缺点:
- 需要两次请求,因此访问延迟比较高。
- 重定向服务器的并发能力制约着整个系统的并发处理能力。
- 如果重定向服务器出现故障,站点就会瘫痪。
DNS域名解析
DNS负载均衡的实现原理是在DNS服务器中为同一个主机名配置多个IP,在应答DNS查询时,DNS服务器对于每个查询将以DNS记录的IP地址按顺序返回不同的解析结果,将客户端的访问引导到不同的机器上,使得不同客户端访问不同的服务器,从而达到负载均衡。
大型网站基本使用了DNS做为第一级负载均衡手段,然后在内部使用其他方式做第二级负载均衡。也就是说,域名解析的结果为内部的负载均衡服务器IP地址。
反向代理
- 正向代理:发生在客户端,由用户主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端。
- 反向代理:发生在服务器端,用户不知道反向代理的存在。
反向代理服务器位于源服务器前面,用户的请求需要先经过反向代理服务器才能达到源服务器。反向代理可以用来进行缓存,日志记录等,同时也可以用来作为负载均衡服务器。
在这种负载均衡转发方式下,客户端不直接请求源服务器,因此源服务器不需要外网IP地址,而反向代理服务器需要配置内网和外网两套IP。因为其工作在OSI结构中的应用层,所以基于反向代理服务器的负载均衡也称七层负载均衡。
常用的反向代理服务器Nginx,Apache,Haproxy等
网络层
在操作系统内核获取网络数据包,根据负载均衡算法计算源服务器的IP地址,并修改请求数据包的目的IP地址,最后进行转发。
数据链路层
在链路层根据负载均衡算法计算源服务器的MAC地址,并修改请求数据包的目的MAC地址,并进行转发。
一致性hash
一致性hash算法通过一个一致性hash环的数据结构来实现,这个环的起点为0,终点为2^32-1,并且起点跟终点连接,环中间的整数按逆时针发布,故整个环的证书分布范围是[0,2^32-1]。
求出机器的哈希值放进hash环中,再求对象的哈希值放入hash环中,在hash环上顺时针查找距离最近的的机器,即这个对象所属的机器。
虚拟节点
通过虚拟节点来解决负载不均很的问题。
将每台机器虚拟成一组虚拟机器,将虚拟机器放置到hash环上,如果需要确定对象的机器,先确定对象的虚拟机器,再由虚拟机器确定无理机器。
更详细的可参考:一致性Hash(Consistent Hashing)原理剖析
集群下的Session管理
一个用户的Session信息如果存储在一个服务器上,那么当负载均衡器把用户的下一个请求转发到另一个服务器,由于服务器没有用户的Session信息,那么该用户就需要重新进行登录等操作。
Sticky Session
将一个用户的所有请求都路由到同一个服务器,这样就可以把用户的Session存放在该服务器中。
缺点:
- 当服务器宕机时,将丢失该服务器上所有的session。
Session Replicaiton
在服务器之间进行Session的同步操作,每个服务器都有所有用户的Session信息,因此用户可以向任何一个服务器进行请求。
缺点:
- 占用内存过多。
- 同步过程中占用网络带宽以及服务器处理器时间。
Session Server
使用一个单独的服务器存储Session数据,可以使用传统的mysql,也可以使用Redis或者memcache内存数据库。
优点:
- 为了使得大型网站具有伸缩性,集群中的应用服务器通常需要保持无状态,那么应用服务器不能存储用户的会话信息。Session Server将用户的会话信息单独进行存储,从而保证了应用服务器的无状态。
消息队列
消息类型
点对点
生产者向消息队列发送一个消息之后,只能被消费者消费一次。
发布/订阅
消息生产者向频道发送一个消息之后,多个消费者可以从频道订阅到这条信息并消费。
发布与订阅模式与观察者模式有以下不同:
- 观察者模式中,观察者和主题都知道对方的存在,而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。
- 观察者模式是同步的,当事件触发时,主题会调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,发布者向频道发出一个消息之后,就不需要关心订阅者何时去订阅这条消息,可以立刻返回。
使用场景
异步处理
发送者将消息发送给消息队列之后,不需要同步等待消息接收者处理完毕,而是立刻返回进行其他操作,消息接收者从消息队列中订阅消息之后异步处理。
例如在注册流程中通常需要发送验证邮件来确保注册用户身份的合法性,可以使用消息队列使发送验证邮件的操作异步处理,用户在填写完注册信息之后就可以完成注册,而将发送验证邮件这一消息发送到消息队列中。
只有在业务流程允许异步处理的情况下才能这么做,例如上面的注册流程中,如果要求用户对验证邮件进行点击之后才能完成注册的话,就不能再使用消息队列。
流量削锋
在高并发的情况下,如果短时间有大量的请求达到会压垮服务器。
可以将请求发送到消息队列中,服务器按照其处理能力从消息队列中订阅消息进行处理。
应用解耦
如果模块之间不直接调用,模块之间的耦合性就会很低,那么修改一个模块或者新添一个模块对其他模块的影响就会很小,从而实现可扩展性。
通过消息队列,一个模块只需要向消息队列中发送消息,其他模块可以选择性地从消息队列中订阅消息从而完成调用。
可靠性
发送方的可靠性
发送端完成操作后一定能将消息发送到消息队列。
实现方法:
- 在本地数据库建一张消息表,将消息数据与业务数据保存在同一数据库实例中,这样就可以利用本地数据库的事务机制。事务提交成功之后,将消息表中的消息转移到消息队列中,若转移成功则删除消息表中的数据,否则继续重传。
接收端的可靠性
接收端能够从消息队列中消费一次消息。
两种实现方法:
- 保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次信息,最后处理的结果都是一样的。
- 保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。
缓存
缓存特征
命中率
当某个请求能够通过访问缓存得到响应时,称为缓存命中。
缓存命中率越高,缓存的利用率也就越高。
最大空间
缓存通常位于内存中,内存的空间通常比磁盘小很多,因此缓存的最大空间不可能非常大。
当缓存存放的数据量超过最大空间,就需要淘汰部分数据来存放到达的数据。
淘汰策略
- FIFO(First In First Out):先进先出策略,在实时的场景下,需要场景访问最新的数据,那么就可以使用FIFO,使得先进入的数据(最晚的数据)被淘汰。
- LRU(least Recently Used):最近最久未使用策略,优先淘汰最久未使用的数据,也就是上次被访问时间距离现在最久的数据,该策略可以保证内存中的数据都是热点数据,也就是经常被访问的数据,从而保证缓存命中率。算法可基于双向链表+hashMap实现。
缓存位置
浏览器
当HTTP响应允许进行缓存时,浏览器会将HTML、CSS、JavaScript、图片等静态资源进行缓存。
ISP
网络服务提供商(ISP)是网络访问的第一跳,通过将数据缓存在ISP中能够大大提高用户的访问速度。
反向代理
反向代理位于服务器之前,请求与相应都需要经过反向代理。通过将数据缓存在反向代理中,在用户请求反向代理时直接就可以直接使用缓存进行相应。
本地缓存
使用Guava cache将数据缓存在服务器本地内存上,服务器代码可以直接读取本地内存中的缓存,速度非常快。
分布式缓存
使用Redis、Memcache等分布式缓存将数据缓存在分布式缓存系统中。
相对于本地缓存来说、分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都已访问分布式缓存,而本地缓存需要在服务器集群之间进行同步,实现难度和性能开销非常大。
数据库缓存
Mysql等数据库管理系统具有自己的查询缓存机制来提高查询效率。
CDN
Content Delivery Network,即内容分发网络。
基本思路:
- 尽可能避免互联网可能影响数据传输速度和稳定性的瓶颈环节,使内容传输更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络,CDN系统能够实时地根据网络流量和各节点的连接、负载情况以及到用户的距离和响应时间等综合信息将用户的请求导向离用户最近的服务器节点上。
ZooKeeper
ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务,它提供了一项基本服务:分布式锁服务。 由于ZooKeeper的开源特性,后来开发者在分布式锁的基础上,摸索出了其他的使用方法:配置维护、组服务、分布式消息队列、分布式通知/协调等。
ZooKeeper性能上的特点决定了它能够在大型的、分布式的系统当中应用。从可靠性来讲,它并不会因为一个节点的错误而崩溃。除此之外,它严格的序列访问控制意味着复杂的控制原语可以应用在客户端上。Zookeeper在一致性、可用性、容错性的保证,也是zookeeper的成功之处。它获得的一切成功都与它采用的协议-Zab协议密不可分。
Zookeeper所提供的服务主要通过:数据结构+原语+Watcher机制。
数据结构
数据模型Znode
Zookeeper拥有一个层次的命名空间,这个和标准的文件系统非常相似。
Zookeeper树中的每个节点都被称为——Znode。和文件系统的目录树一样,Zookeeper树种的每个节点可以拥有子节点。但也有不同之处。
引用方式
通过路径引用,但是路径必须绝对的,因此它们必须由/字符开头。而且路径必须是唯一的。
Znode结构
Znode兼文件和目录两种特点,即像文件一样维护着数据、元信息、ACL、时间戳等数据结构,又像目录一样可以作为路径标识的一部分吗。每个Znode由3部分组成:
- stat:此为状态信息,描述该Znode的版本,权限等信息。
- data:与该Znode关联的数据。
- children:该Znode下的子节点。
Zookeeper虽然可以关联一些数据,但并没有被设计成常规的数据库或者大数据存储,相反用来管理调度数据,比如分布式的配置文件信息、状态信息、汇集位置等。
数据访问
Zookeeper中每个节点存储的数据要被原子操作。另外每个节点都拥有自己的ACL(访问控制列表),这个列表规定了用户的权限,即限定了特定用户对目标节点可以执行的操作。
节点类型
- 临时节点:生命周期依赖于创建它们的会话。不允许拥有子节点。
- 永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
顺序节点
当创建Znode的时候,用户可以请求在Zookeeper的路径结尾中添加一个递增的计数。这个计数对于此节点的父节点来说是唯一的。当计数大于2^32-1时,计数器将溢出。
观察
客户端可以在节点设置watch,我们称为监视器。当节点状态发生改变时(Znode的增、删、改)将会触发watch所对应的操作。当watch被触发时,Zookeeper将会向客户端发送且仅发送一条通知,因为watch只能被触发一次,这样可以减少网络流量。
Zookeeper中的时间
Zookeeper有多种记录时间的形式,其中包含以下几个主要属性:
Zxid
致使Zookeeper节点状态改变的每一个操作都将使节点接收到一个Znode格式的时间戳,并且这个时间戳全局有序。也就是说,每个节点的改变都会产生一个唯一的Zxid。如果Zxid1的值小于Zxid2的值,那么Zxid1所对应的事件发生在Zxid2所对应的事件之前。实际上,Zookeeper的每个节点维护着三个Zxid的值。分别为:cXzid、mXzid、pXzid。
- cXzid:是节点的创建时间所对应的Zxid格式时间戳。
- mXzid:是节点的修改时间所对应的Zxid格式时间戳。
实际上Zxid是一个64位的数字。它高32位是epoch用来标识leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch。低32位是一个递增计数。
版本号
对节点的每一个操作都将致使这个节点的版本号增加。每个节点维护着三本版本号,他们分别为:
- version:节点数据版本号
- cversion:子节点版本号
- aversion:节点所拥有的ACL版本号
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。