一、分布式架构
1、分布式特点
- 分布性
- 对等性。分布式系统中的所有计算机节点都是对等的
- 并发性。多个节点并发的操作一些共享的资源
- 缺乏全局时钟。节点之间通过消息传递进行通信和协调,因为缺乏全局时钟,很难定义两个事件谁先谁后
- 故障总是会发生。系统设计时,需要考虑到任何异常情况
2、分布式环境的各种问题
- 通信异常。分布式系统中的某些节点之间无法正常通信
- 网络分区。这有部分节点可以正常通信,有些无法正常通信。这种现象称为网络分区,也称为“脑裂”
- 三态。节点之间的一次通信存在三种状态:成功、失败、超时
- 节点故障。节点机器宕机、失去回应
3、传统事务的ACID理论。
- 原子性(Atomicity)。事务内的所有操作要么全部成功,要么全部失败
- 一致性(Consistency)。事务的执行不能破坏数据库的一致性。如果事务执行一半停了,一部分的修改写入的数据库。这时候,数据库就处于一种不正确的状态,或者说不一致的状态
- 隔离性(Isolation)。多个事务并发执行,彼此不会受影响。事务的隔离级别:读未提交(可能发生脏读、重复读、幻象读)、读已提交(肯能发生重复读、幻象读)、可重复读(可能发生幻象读)、串行化。隔离级别越高,对并发的性能影响越大,越能保证数据库的一致性。
- 持久性(Durablility)。一旦事务成功提交,它对于数据的修改就被永久保存下来
4、分布式事务的CAP理论和BASE理论
- CAP理论。一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance),在分布式系统中,最多只能满足其中的两项。
- BASE理论。基本可用(Basically Available)、软状态(Soft state)、最终一致性(Eventually consistent)。基本可用指系统出现故障时,允许损失部分可用性,包括响应时间的损失和功能上的系统降级;软状态指允许节点间的通讯出现中间状态;最终一致指系统的所有的数据副本,在一定时间的同步后,最终能够达到一致的状态
二、一致性协议
1、2PC。二阶段提交协议
阶段一,执行事务
- 事务询问。协调者向所有参与者发送事务内容,等待参与者回应
- 执行事务。参与者执行事务,记录Undo和Redo日志
- 反馈事务询问响应,参与者返回给协调者Yes或No响应。全部返回Yes,进入提交事务阶段;存在No返回或者超时,进入中断事务阶段
阶段二,提交事务
- 发送事务提交请求
- 各个参与者提交事务
- 参与者反馈事务提交结果
- 如果参与者全部返回Yes,完成事务;存在返回No,进入中断事务阶段
中断事务阶段
2PC的缺点
- 同步阻塞。各个参与者在等待其他参与者响应的同时,无法进行任何操作,处于阻塞状态
- 单点问题。过度依赖协调者,一旦协调者出现问题,系统将无法正常运转
- 数据不一致。同上一条,一旦协调者出现问题,就可能出现各个参与者数据不一致的问题
- 太过保守。一旦参与者出现故障,协调者只能通过自己的超时机制发现。
2、3PC。三阶段提交协议
阶段一,CanCommit,事务询问
- 事务询问,询问各个参与者能否完成事务
- 各个参与者返回响应,全部返回Yes,进入PreCommit阶段
阶段二,PreCommit,事务预提交
- 发送预提交请求
- 事务预提交。参与者执行事务操作,记录Undo和Redo日志
- 参与者返回响应。全部返回Yes,继续三阶段;返回No,进入中断事务阶段
阶段三,DoCommit,真正的事务提交
- 发送提交请求
- 参与者正式执行事务提交操作
- 返回协调者事务执行结果
- 协调者完成事务。如果存在返回No或者返回超时,进入中断事务阶段
中断事务阶段
3PC优缺点
- 相较于2PC,3PC最大优点就是降低了参与者的阻塞范围
- 缺点是,还是避免不了出现数据不一致的情况
3、Paxos算法
拜占庭将军问题,拜占庭帝国的不同军队处于不同的地理问题,他们之间只能通过通讯员进行通讯,但是通讯员是不可靠的,可能篡改消息
由于加密算法和校验算法的出现,所有实际的分布式系统之间的通讯,不存在数据被篡改的可能。
四、ZooKeeper和ZAB协议介绍
1、初识ZeeKeeper
ZooKeeper可以做什么
可以基于它实现负载均衡、集群管理、Master选举、分布式队列、分布式锁、命名服务、数据发布/订阅等功能
Zookeeper能保证的分布式一致性特性
- 顺序一致性。同一个客户端发起的请求,会严格按照发起顺序执行
- 原子性。对于一个事务请求,集群中的所有机器的执行情况是一致的
- 可靠性。一旦服务端成功的应用了一个事务,并完成了对客户端的响应。服务端的状态会一直保存下来
- 实时性。保证在一段时间内的实时性
- 单一视图。无论客户端连接的是哪一个服务器,看到的服务端的数据模型都是一致的
Zookeeper的设计目标
- 简单的数据模型。Zookeeper将去数据存储在内存中,采用树形结构存储,树由ZNode节点构成
- 可以构建集群。只有集群中超过一半机器能够正常运作,整个集群就可以正常对外提供服务。
- 顺序访问。每个来自客户端的请求,都会分配一个全局唯一的递增编号
- 高性能。全量数据存储在内存中,3台3.4.3的Zookeeper集群,100%读请求场景的压测结果是12-13W的QPS
Zookeeper的基本概念
- 集群角色。存在Leader、Follower、Observer三种角色。Leader服务器提供读和写服务,Follower和Observer提供读服务,但是Observer不参与选举过程,也不参与写操作的“过半写”策略,因此,Observer可以在不影响写性能的情况下提高读性能
- 客户端会话。客户端通过TCP长连接和服务端相连,第一次建立连接就代表客户单会话开始了。客户端能够通过心跳检测与服务器保持有效的会话。Session的SessionTimeout值用来设置会话的超时时间,连接断开后,只要在超时时间之内重新连接上了集群中的任何一台服务器,那么之前创建的会话仍然有效
- 节点。一方面只集群中的每一台机器,称为机器节点;另一方面指数据模型中的数据单元,称为数据节点Znode。Znode又分为持久节点和临时节点,临时节点的生命周期和会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。
- 版本。每个Znode,会维护一个叫做Stat的数据结构,记录了Znode的三个版本:version(当期版本)、cversion(当前Znode子节点的版本)、aversion(当前Znode的ACL版本)。
- Watcher。事件监听器,允许用户在一些节点上注册一些watcher。特性事件触发的时候,服务器会将事件通知到感兴趣的客户端上去。
- ACL。权限控制策略(Access Control Lists)。定义了五种权限:Create(创建子节点)、Delete(删除子节点)、read(读取节点数据和子节点列表)、write(更新节点数据)、admin(设置节点ACL的权限)
2、ZAB协议(Zookeeper Atomic Broadcast,原子消息广播协议)
所有事务请求都由Leader服务器来处理分发,如果集群中的其他服务器收到了来自客户端的请求,这些非Leader服务器会首先将这个事务请求转发给Leader服务器,Leader负责将请求封装成Proposal(提议)分发给集群中所有Follower,一旦收到超过半数的正确反馈,Leader就会再次向所有的Follower分发Commit消息,要求他们将前一个Prosocal提交
两种基本模式:崩溃恢复模式和消息广播模式
- 消息广播模式。类似于二阶段提交,去掉了中断逻辑,当Leader服务器收到了超过半数的Follower的ACK响应后,就会广播一个Commit消息给所有的Follower进行事务提交。
- 崩溃恢复。一旦Leader服务器出现崩溃,或者由于网络原因失去了一半Follower服务器的联系,就会进入崩溃恢复模式。
- 崩溃恢复需要确保已经被Leader提交的Proposal也能被所有Follower提交
- 确保丢弃只在Leader服务器上提出的事务。(此处的提出指的是二阶段第一阶段)。重新选举出来的Leader拥有集群中所有服务器最高编号的事务Proposal
- 正常情况的数据同步:Leader服务器为每一个Follower服务器准备一个队列,将那些没有被Follower服务器同步的事务以Proposal消息的形式逐个发送给Follower,等待所有事务都同步到了Follower并成功应用到了Follower的本地数据库中后,Leader服务器就将该Follower服务器加入到真正的可用列表中
- 事务编号ZXID,是一个64位的数字。前32位存储Leader届数,后32位记录本届Leader处理的消息数。
3、深入ZAB协议
运行分析,每一个进程都有可能处于以下三种状态之一
- LOOKING:leader选举阶段
- FOLLOWING:Follower服务器和Leader服务器保持同步状态
- LEADING:Leader服务器作为主进程领导状态
五、使用Zookeeper
1、服务端部署与运行
- 初次使用,需要把/conf目录下的zoo_sample.cfg文件重命名为zoo.cfg。配置如下:
- server.1=IP1:2888:3888,每一行这样的配置代表一个集群中的一个机器,server.1中的1代表ServerID,同时在每台机器上需要在数据目录(dataDir指定的目录)下创建一个myid文件,文件内容就是ServerID,id范围是1~255
- 集群中每个机器的zoo.cfg文件都应该是相同的,最好使用git或者svn把配置管理起来
- 启动服务。/bin/zhServer.sh start
- 停止服务。/bin/zkServer.sh stop
2、客户端脚本
- 启动。/bin/zkCli.sh -server ip:port(不加server参数,默认连接本机)
- 创建节点。create 【-s】【-e】path data acl。acl用来进行权限控制
- 读取节点下子节点。ls path。例如:ls / 查看根节点下的所有子节点
- 读取节点数据。get path
- 更新节点数据。set path data 【version】
- 删除节点。delete path 【version】。无法删除一个包含子节点的节点
3、开源客户端Curator的使用
直接看我的github代码:https://github.com/leon66666/zookeeper-client
六、Zookeeper的典型应用场景
1、典型应用场景及实现
(1)数据发布和订阅
分为推(push)模式和拉(pull)模式,push模式服务端主动把数据更新推送给所有订阅的客户端,而拉模式是由客户端定时轮询拉取的方式来获取最新数据
应用场景:分布式系统统一配置,例如机器列表信息、运行时的开关配置、数据库配置信息等,这些全局配置交给Zookeeper统一管理
这些配置具有以下特点:
- 数据量通常比较小
- 数据内容在运行时动态变化
- 集群中各机器共享,配置一致
(2)负载均衡
DDNS,动态DNS解析。局域网内部一般采用host绑定的方式来进行ip和域名的映射。一旦机器规模变大,这种做法就会相当的不方便。
通过Zookeeper实现,每个应用都可以创建一个属于自己的数据节点作为域名配置的根节点,在这个节点上,每个应用都可以将自己的域名配置上去。通过注册Watcher实现域名变更通知功能。
上图为整体的DDNS系统架构。
- Register集群负责域名的动态注册。每个服务者启动的时候,都会把自己的域名信息注册到Register集群中去。
- Dispatcher集群负责域名的解析。服务消费者在使用域名的时候,会向Dispatcher集群发出请求,获取相应的IP:PORT信息。
- Scanner集群负责检测及维护服务状态(探测服务可用性、屏蔽异常服务节点)。(一种是心跳检测,需要客户端和服务端建立起tcp长连接。另一种是服务端主动进行状态汇报,一旦超过5秒没有收到汇报,就认为该IP地址不可用,进行域名清理)
- SDK,提供各个语言的系统接入
- Monitor负责收集集群信息以及对DDNS自身的监控
- Controller是一个后台管理,负责授权管理、流量控制、服务配置和手动屏蔽服务等功能。
(3)命名服务
即分布式环境下,生成全局唯一ID的方法。大家一般会联想到UUID,是通用唯一识别码的简称。主流ORM框架HIbernate就有对UUID的直接支持。
但是,UUID有如下缺点:
- 长度过长,需要更多的存储空间
- 含义不明,根据字符串开发人员从字面上根本看不出他的含义
利用Zookeeper来实现这类全局唯一ID的生成。当客户端创建一个顺序子节点的时候,zookeeper会自动以后缀的形式在其子节点上添加一个序号,利用了zookeeper顺序节点的特性
(4)分布式协调/通知
Zookeeper实现分布式协调通知,通常的做法是不同的客户端都对Zookeeper上同一个数据节点进行Watcher注册。监听节点数据的变化。
(5)通用的分布式系统机器间通信方法
分布西系统机器间通信包括:心跳检测、工作进度报告、系统调度
- 心跳检测。不同机器之间需要检测到彼此是否正常运行。传统方法通过机器之间能否互相ping通来判断;更复杂的通过机器之间建立起Tcp长连接,通过tcp固有的心跳检测机制来实现上层机器的心跳检测;基于Zookeeper的临时节点特性也可以实现心跳检测,不同的机器在指定节点下创建临时节点,不同机器可以通过判断临时节点是否存在来判断客户端机器是否存活。减少了系统耦合
- 工作进度报告。每个客户端创建临时子节点,各个任务机器会实时的将自己的执行进度存储到对应的子节点上,也可以判断子节点是否存在判断机器是否存活
- 系统调度。一个分布式系统由控制台和客户端组成。后台管理人员在控制台做一些操作(实际上就是修改Zookeeper上某些节点的数据),Zookeeper以事件通知的形式发送给对应的订阅客户端
(6)集群管理
集群管理需求点
- 知道集群中工作的机器数量
- 对集群中每台机器的运行状态进行收集
- 对集群中的机器进行上下线操作
传统的基于agent的管理方式,集群中每台机器部署一个agent,负责本机器的监控和向中心系统汇报
- 大规模升级困难
- 统一的agent无法满足多样的需求。无法深入应用内部,对一些业务状态进行监控
- 编程语言多样性。不同机器需要提供不同语言的agent
利用zookeeper监控集群
- 客户端可以对zookeeper节点进行监听,节点变更会受到通知
- 在zookeeper上创建临时节点,一旦会话失效,改临时节点会被自动删除
- 监控系统在/clusterServers节点上注册一个Watcher监听,添加机器会在监听节点下创建临时子节点。
zookeeper监控应用:分布式日志收集系统
- 注册收集器机器。以/logs/controller作为收集器的根节点,每个收集器启动的时候都会在收集器节点下创建自己的节点
- 任务分发。收集系统把日志源机器按照一定策略分配给注册的收集器机器,将机器列表写入到对应的收集器节点上
- 状态汇报。收集器节点下面创建状态子节点,每个注册的收集器机器定时向该节点写入自己的状态信息和日志收集进度信息(可以看做是一种心跳检测),根据更新时间来判断是否存活
- 动态分配。检测到收集器的减少或者增加之后,需要进行重新分配。通常有两种做法:全局分配(影响面大);局部动态分配(低负载优先分配)
- 节点类型。收集器节点,需使用持久节点,需要保存该节点上的日志源机器列表
- 收集器节点监听。放弃监听设置,采用定期轮询,节省网卡流量,但是具有一些延时,考虑到日志收集需求,延时是可以接受的