ZooKeeper、RPC原理
cap特性:持久性、可用性、分布式性
简单地说,持久性就是先同步数据再执行业务,可用性是先执行业务,再同步数据
所以很明显一个是最求数据的一致性问题,一个是追求业务的执行效率问题;
这两个特性其实是有一定的矛盾性的,所以很难做到能同时满足这3个特性;根据业务的具体情况选择较为适合的服务代理;
ZooKeeper是CP特性;Eukare就是ap特性
二阶段提交:为了实现这个持久性,要有一个协调者的角色;
第一阶段:这个协调者在收到事务之后第一阶段会先将事务都发送给各个参与者,这些参与者都执行了事务之后再返回
ack确认,注意这个时候事务是都还没提交的;
第二阶段:协调者在收到其他参与者的ack确认后,开始发送事务提交请求,各个参与者提交完事务后再给协调者一个ack确认;
如若有参与者事务执行出问题,则协调者会对所有参与者发送回滚事务请求,各个参与者回滚过后再给协调者一个ack确认;
二阶段提交缺陷点:
1、同步阻塞:协调者本身会处于一个同步阻塞,也就是发给参与者请求后会进入一个等待状态
2、单点故障:一旦协调者出现故障,会使得所有参与者处于堵塞状态
3、分布式的网络分区如果导致某个单点接收不到提交请求,也会造成数据不一致的问题
三阶段提交:
分为cancommit、precommit、docommit,其实就是把二阶段提交的第一个阶段拆分成两步:
先确认各个参与者是能进行操作事务的状态,再进行预执行,再最后提交事务;
期间如果有参与者返回超时或无法执行事务,则协调者会发送中断请求;
与二阶段提交相比:
设置了超时提交,解决了同步阻塞和单点故障的问题:协调者在一定时间内没收到参与者的反馈,会直接发送中断请求,同理,参与者在等待协调者发送
信号的一定时间内如果没收到会自动进行commit提交;
这样就依旧存在数据不一致的问题,比如在第三阶段协调者发送回滚操作,这时有个参与者因分区网络没收到指令,在一定时间过后就自动提交了事务,而
别的参与者是已经进行了事务回滚,这就导致了数据不一致问题;
zookeeper是什么?
是一种解决分布式数据一致性的方案;是一种协调服务;
这种官方解释可能有点难以理解;可以把zookeeper当作是一本多人使用的笔记本,且是一种带有监控功能的笔记本,写的人一旦对自己的笔记做出来修改,
就可以被监控到,把消息立马向读的人报告;
而解决数据一致性的原理是zookeeper的一种leader选举和超半数机制;这就很好地解决了我们之前所说的三阶段提交可能导致的数据不一致问题;
简单介绍下原理:
zookeeper在开启集群的时候,都要设置各自的一个myid做区分,且配置好集群配置信息后在开启时会自动做选举,这里举个例子帮助理解过程:
假设我们集群5台zookeeper,myid分别是1、2、3、4、5
myid为1的zookeeper启动了,为自己投一票,发现没有别的集群zookeeper启动,只有自己一台开启,小于集群数的一半,处于looking状态,默默等待;
myid为2的zookeeper启动了,为自己投一票,查看当前的投票信息,发现一个myid为1的zookeeper,myid比自己小,胜出;可是当前只有两台zookeeper启动,
依旧小于集群数一半,再次处于looking状态;默默等待;
myid为3的zookeeper启动了,为自己投一票,同理,它胜出,且已有3台zookeeper启动,符合超半数机制,自己立马当选leader,处于leading状态,其余的两台也变为following状态;
剩下myid为4、5再启动也于事无补了,因为leader已经选出来了,只能成为following状态;
这里介绍了zookeeper的3种角色状态:looking、following和leadering,还有一种观察者角色,英文忘记怎么写了,就是没有投票功能的following,主要的作用就是防止
集群数量太大,每次选举都耗损过大;因为这里的过半数指的是有投票权限的半数;
详细的原理建议看zookeeper源码;里面就有介绍一个epoch逻辑时钟的东西,主要是为了同步投票信息,记录的其实是投票的次数,再与投票信息做比较,
根据不同判断做不同操作;
zookeeper常用的作用:配置中心、负载均衡、命名服务、DNS服务、集群管理、分布式锁;
其实根据我之前类比的监控功能笔记本,应该可以很好理解是怎么实现这些功能的,分布式锁说一下:
核心的临时子节点,以锁名为节点,每次请求过来的时候就会创建一个临时子节点,且有序地进行排序等待,检查自己是不是头子节点,如果是,那就获得
锁,如果不是,那就监控排在自己前面的子节点,一旦发现它消失了,自己就进入就绪状态;
同为实现分布式锁功能的还有以数据库和redis为基础实现的分布式锁,原理是数据库的唯一索引和reidskey的不可重复性,但这两种有一个致命缺陷,如果有一台
分布式服务器发生意外,对某个锁名进行了加锁操作却没来得及解锁就宕机了,导致死锁;虽然redis设置过期时间可以有效防止(redission框架的原理,不仅设置了锁过期时间,还设置锁过期时间的刷新机制),但时效性还是没zookeeper好;
RPC:远程服务调用
既然都分布式了,想必大家应该了解这个东西的重要性,今天想写的Dubbo也只是众多rpc框架中的一种;
其实原理还是通过http、tcp去调用,只不过集群的存在,使得这些调用不能在程序中写死,要不在运维更新时异常麻烦;
而所谓的RPC,原理就是以服务提供者的服务接口名、实现类名、ip和端口为参数,放到一个map(接口名,map(url,class)),这里的url指的就是自己定义的包含ip和端口的对象类,class指的是接口对应的实现类;而在这个放的动作,也就是map.put,其实就是我们一直所说的服务注册,然后服务消费方就通过http、tcp连接,根据接口名、url找到实现类的类类型,然后通过反射获得想要的方法,再通过invoke执行方法,当然无论是提供方或消费方都少不了对方法参数的包装;
这时候有人就要问了:诶,你这搞了半天不还是把ip和端口写死放在map中吗?再有集群的服务提供端启动,调用方不还是要改调用的ip和端口吗?
所以我才会在讲了zookeeper之后再介绍RPC,所以才有RPC框架这种东西,我上面说的rpc,只是rpc最底层的原理;
如比较经典的dubbo+zookeeper组成的rpc框架,zookeeeper作为dubbo的注册中心,而dubbo本身就包含了rpc功能,实现了我们只需将服务提供方注册好(可直接将功能相同的项目服务器不同别名为同一个名字),这样我们服务消费方使用的时候就可以直接根据名字调用,这样就算某些服务器宕机或新启动,都毫不影响消费端的使用,也不需要改调用ip或端口,且dubbo有内置的负载均衡机制。