zookeeper
概述
分布式:由多台服务器共同完成一件比较复杂的事,可以理解为分布式
具体说:设计网站可扩展架构的核心思想是模块化,并在此基础上,降低模块间的耦合性,提高模块的复用性。
所谓分布式业务系统,就是把原来一个大块系统,根据功能,拆分成多个独立的子系统,这些独立的子系统(模块)部署在独立的服务器(集群上),从物理上解耦合这些模块之间的关系,降低模块之间的耦合度,提高模块的复用性。
这些子模块或者说子系统通过消息传递以及依赖调用的方式聚合成一个完整的系统。
什么是集群
集群(cluster)是**一组(即多个)**计算机、服务器,他们作为一个整体向用户提供一组网络资源,这些单个的计算机或者服务器就是集群的节点。
分布式和集群的区别是什么?
集群是个物理形态,分布式是个工作方式,也可以理解为一种思想。
分布式:将一个业务拆分成多个子业务,每个业务部署在不同的服务器上
集群:多个不同的服务器合起来构成一个cluster,并作为一个整体完成业务。
分布式理论
CAP
概述
当我们的单个数据库的性能产生瓶颈的时候,我们可能会对数据库进行分区,这里所说的分区是指物理分区,分区之后可能不同的库就处于不同的服务器上了,也可以理解成分库,分库分区是一个意思,这个时候单个数据库的ACID已经不能适应这种情况了,而在这种集群环境下,再想保证进群的ACID几乎是很难达到,或者即使能达到,那么性能和效率也会大幅度下降,这个时候如果再追求集群的ACID会导致我们的系统变得很差,此时我们就需要引入一个新的理论原则来适应这种集群的情况,就是CAP原理
CAP原理又被称作布鲁尔定理,它指出对于一个分布式系统来说,不能同时满足以下三点:(CAP原理针对的是分布式系统,而ACID针对的是持久化数据库)
一致性(Consistence)
一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态
可用性(Availability)
可用性指的是分布式系统在面对各种异常时可以提供正常服务的能力
在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
分区容错性(Partition tolerance)
网络分区是指分布式系统中的节点,被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信
在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。
CAP仅适用于原子读写的NOSQL场景中,并不适用于数据库系统,现在的分布式系统具有更多特性,比如扩展性、可用性等等,在进行分布式系统搭建或者开发的时候,我们不应该仅仅局限于CAP特性上。
分区容错性(Partition tolerance)我们是必须要实现的。
BASE理论
在分布式系统中,我们往往追求的是可用性。Redis是CP,也就是一致性和分区容错性
BASE理论是对CAP理论的进一步扩充
BA:是指基本可用,Basically Available(基本可用)
S:Soft-state(软状态)
E:Eventually Consistent(最终一致性)
BASE理论的核心思想:
(Redis是CP,是一致性和分区容错性),而我们在设计分布式系统中,往往更加追求一些其他的特性,比如说扩展性,可用性,不应该只是局限于CAP,所以BASE理论作为CAP理论的扩展,来满足我们对分布式系统的设计理论
核心思想:
牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或不一致时,仍需要保持系统整体是主要可用的。
从这里也可以看出这种场景,对于数据来说,不是强一致性的(因为我们追求高可用,而可用性和一致性在CAP原理下无法并存,那么在追求可用性的同时,就牺牲掉一致性),体现在软状态。
针对数据库领域,BASE思想的主要实现是对业务数据进行拆分,不同数据分布在不同的机器上,以提升系统的可用性。(可以通过按照业务功能划分、也可以通过分片的形式,让数据散布在不同的服务器上。)
BASE理论三要素
BA:基本可用
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。
- 响应时间上的损失:正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒
- 系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面
S:软状态
软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
牺牲掉数据的一致性,来追求可用性
E:最终一致性
最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
Zookeeper入门
概述
zookeeper是一个开源的分布式的,为分布式框架提供协调服务的apache项目
zookeeper从设计模式的角度来理解:是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据(比如服务器信息),然后接受观察者(客户端)的注册,一旦这些数据(服务器的信息)的状态发生变化,zookeeper就将负责通知已经在zookeeper上注册的那些观察者(客户端)做出相应的反应。
zookeeper相当于是一个文件系统,加通知机制。
zookeeper本身是一个集群,有多台服务器
zookeeper:多台服务器,有一个领导者Leader,多个跟随着Follower组成的集群。
特点:
集群中只要有半数以上节点存活,zookeeper集群就能正常服务,所以zookeeper适合安装奇数台服务器
全局数据一致,每个server保存一份相同的数据副本,Client无论连接到哪个server,数据都是一致的。
更新请求顺序执行,来自同一个client的更新请求按其发送顺序依次执行。
数据更新原子性,一次数据更新要么成功,要么失败。
实时性:在一定时间范围内,client能读到最新数据,有一个同步数据的过程
zookeeper的数据结构
zookeeper的数据模型的结构与unix文件系统很类似,整体上可以看作是一棵树,每个节点称作一个ZNode,每一个ZNode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识
zookeeper应用场景
统一命名服务
在分布式环境下,经常需要对应用(服务)进行统一命名,便于识别
例如:IP不容易记住,而域名容易记住。
我们访问哪台服务器,zookeeper会根据负载情况进行一个分配
统一配置管理
分布式环境下,配置文件同步非常常见,一般要求一个集群中,所有节点的配置信息是一致的,比如kafka集群
对配置文件修改后,希望能够快速同步到节点上。
配置管理可交由zookeeper实现
- 可将配置信息写入zookeeper上的一个znode
- 各个客户端服务器监听这个znode
统一集群管理
分布式环境中,实时掌握每个节点的状态是必要的
可根据节点实时状态做出一些调整
- 可将节点信息写入zookeeper的一个znode
- 监听这个znode可获取它的实时状态变化
服务器节点动态上下线
软负载均衡
zookeeper配置
tickTime = 2000,通信心跳时间,zookeeper服务器与客户端心跳时间,单位毫秒
服务器与服务器之间也可以进行心跳通讯,互相发送信号。
initLimit = 10, LF初始通信时限
Leader和Follower初始连接时能容忍的最多心跳数
如果超过 initLimit*tickTime还没有连接成功的话就认为连接失败
syncLimit = 5 LF同步通信时限
Leader和Follower之间通信时间如果超过syncLimit * tickTime,Leader认为Follower死
从服务器列表中删除Follower
dataDir:保存zookeeper中的数据
注意:默认的tmp目录,容易被linux系统定期删除,所以一般不使用默认的tmp目录
clientPort=2181:客户端连接端口,通常不做修改
zookeeper集群最少是三台
zookeeper选举机制
第一次启动
只要集群中已经有了Leader,后面启动的服务器不会因为myid大而当选Leader,因为如果集群中已经有了Leader,那么前面的服务器都不是LOOKING状态,所以不会更改选票信息,新启动的服务器仍然会将票投给自己,此时新启动的服务器就会少数服从多数,更改选票信息会之前的Leader服务器
相关概念:
非第一次启动
zookeeper节点类型
持久:客户端和服务器端断开连接后,创建的节点不删除
短暂:客户端和服务器端断开连接后,创建的节点自己删除
带序号:创建znode时设置顺序标识,znode名称后会附加一个值,顺序号是一个单调递增的计数器,由父节点维护
注意:在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序
zookeeper监听器原理
将zookeeper客户端去服务器端进行注册,注册说要监听哪一个节点数据的变化,zookeeper服务器端的对应节点数据如果发生变化了,就会通知客户端,这就是这个过程。
监听器原理
- 首先要有一个main()线程
- 在main线程中创建zookeeper客户端,这时就会创建两个线程,一个负责网络通信(connect),一个负责监听(listener)
- 通过connect线程将注册的监听事件发送给zookeeper服务器
- 在zookeeper服务器的注册监听器列表中将注册的监听事件添加到列表中
- zookeeper服务器监听到有数据或路径变化,就会将这个消息发送给listener线程
常见的监听
监听节点数据的变化
get path [watch]
注册一次,只能监听一次,想再次监听,需要再次注册
监听子节点增减的变化
ls path [watch]
注册一次,只能监听一次,想再次监听,需要再次注册
客户端向服务器端写数据流程
如果客户端直接访问Leader
只要有半数的节点认为已经写完了,那么Leader就会告诉客户端即发送给客户端确认ack,说已经写完了。
如果客户端的写入请求发送给follower节点
被访问的follower会把写请求发送给leader,leader来处理并写数据,自己先写一份,并且发写命令给follower节点,follower需要写并且再发送ack给leader,超过半数以上节点认为写完,那么leader会再发ack给客户端访问的follower节点,被访问的follower节点给客户端发ack
服务器动态上下线监听
服务器启动时去注册信息(创建都是临时),在zookeeper上创建对应的节点
客户端获取到当前在线服务器列表,并且注册监听,就是监听器原理
这里说的服务器和客户端对于zookeeper集群来说都是客户端,只不过服务器去调的zookeeper的创建节点方法,客户端去监听
分布式锁
原生zookeeper实现分布式锁方法
什么叫分布式锁
比如说进程1在使用该资源的适合,会先去获得锁,进程1获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,进程1用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就保证了分布式系统中多个进程能够有序地访问该临界资源,我们把这个分布式环境下的锁叫做分布式锁。
步骤:
在/lock目录下,创建临时顺序节点,序号最小的节点可以获得锁,处理业务
节点需要判断自己是不是当前节点下序号最小的节点,如果是,获取到锁,如果不是,对前一个节点进行监听,监听这一步很重要,因为就是要监听到前一个节点的释放也就是删除,我们自己写的上锁这个过程才能完成!否则前一个节点没有释放,即没有删除,当前节点永远不可能完成上锁,这里要通过代码实现,可以通过CountDownLatch实现,当没有监听到前一个结点删除的适合,一直锁住,监听到前一个节点删除了,通过CountDownLatch释放锁,我们自己上锁的代码才完成。
我们自己写的上锁这个方法, 可以通过CountDownLatch来控制流程,通过CountDownLatch的释放锁来使我们的方法执行完成。两者不要搞混。
获取到锁,处理完业务后,delete节点释放锁,然后下面的节点将接收到通知,重复第二步判断。
成熟的Curator框架实现分布式锁
- 原生地通过zookeeper和JavaAPI实现分布式锁存在的问题
- 会话连接是异步的,需要自己去处理,比如使用CountDownLatch
- watch需要重复注册,不然就不能生效
- 不支持多节点的创建和删除,需要自己去递归
- Curator是一个专门解决分布式锁的框架,解决了原生JavaAPI开发分布式遇到的问题