fourinone分布式协调设计解析

24 篇文章 0 订阅
14 篇文章 0 订阅

分布式协同是分布式应用中不可缺少的,通常担任协调者的角色,或者说是将多机协同的职责从分布式应用中独立出来,以减少系统的耦合性和增强扩充性。Apache的Zookeeper, google的Chubby都是分布式协同的实现者。fourinone实际上可以单独当做Zookeeper用,它使用最少的代码实现了Zookeeper的所有功能,并且力图做到功能更强但是使用更简洁。

一、实现原理
fourinone对分布式协同的实现, 是通过建立一个domain,node两层结构的节点信息去完成,domain可以是分类或者包,node可以是具体属性,domain和node都是自己根据需求设计命名,比如可以将domain命名为“a.b.c...”表示一个树型类目。
一个domain下可以有很多个node,每个node只指定一个domain,可以通过domain返回它下面所有的node。
domain不需要单独建立,通常在建立node时,如果不存在domain会自动创建。
如果domain下没有node了,该domain会自动删除。
如果删除domain,该domain下面node也都会删除。
每个node下可以存放一个值,可以是任意对象。
所有的节点信息存放在parkserver里,parkserver提供协同者的功能。如下图所示:


从上图可以看到,其他分布式进程可以通过parkserver的用户接口ParkLocal,对节点进行增加、修改、删除、指定心跳、指定权限等操作,并且结合parkserver提供同步备份、领导者选举、过期时间设置等功能,共同来实现众多分布式协同功能,比如:
1、分布式配置,多个机器的应用公用一个配置信息,并且挂掉能够领导者选举,详细见指南和demo
2、分布式锁,多个机器竞争一个锁,当某个机器释放锁或者挂掉,其他机器可以竞争到锁继续,详细见指南和demo
3、集群管理,集群内机器可以互相感知和领导者选举,详见指南和demo

二、核心API
ParkLocal核心api说明:
//创建node,可以根据是否需要权限和心跳属性调用不同方法
public ObjectBean create(String domain, Serializable obj);//自动创建node
public ObjectBean create(String domain, String node, Serializable obj);
public ObjectBean create(String domain, String node, Serializable obj, AuthPolicy auth);
public ObjectBean create(String domain, String node, Serializable obj, boolean heartbeat);
public ObjectBean create(String domain, String node, Serializable obj, AuthPolicy auth, boolean heartbeat);

//更新node
public ObjectBean update(String domain, String node, Serializable obj);

//获取node
public ObjectBean get(String domain, String node);
//获取最新node,需要传入旧node进行对照
public ObjectBean getLastest(String domain, String node, ObjectBean ob);
//获取最新domain
public List<ObjectBean> get(String domain);
//获取最新domain下所有node,需要传入旧的node集合对照
public List<ObjectBean> getLastest(String domain, List<ObjectBean> oblist);
//删除node
public ObjectBean delete(String domain, String node);
//强行设置domain可删除
public boolean setDeletable(String domain);
//删除domain及下所有node
public List<ObjectBean> delete(String domain);
//添加node的事件监听
public void addLastestListener(String domain, String node, ObjectBean ob, LastestListener liser);
//添加domain的事件监听
public void addLastestListener(String domain, List<ObjectBean> oblist, LastestListener liser);


三、权限机制:
public ObjectBean create(String domain, String node, Serializable obj, AuthPolicy auth);
通过上面方法创建node时,可以指定一个权限参数,有只读(AuthPolicy.OP_READ)、读写(AuthPolicy.OP_READ_WRITE)、所有(AuthPolicy.OP_ALL)三种属性,默认为AuthPolicy.OP_ALL

注意:这里的权限属性是指创建进程对其他使用进程的权限约束,而不包括它自己。也就是对node的创建进程来说,它拥有对该node和domain所有操作权限(读写删,只要它不退出或者中止)

建设现在创建了一个domain为d,node为n的节点, 对于其他使用进程来说,操作权限如下表所示:

从上表可以发现,当创建进程指定node的权限为AuthPolicy.OP_ALL时,其他使用进程可以删除该node,但是不能删除其domain,这是为什么呢?
因为domain下通常还有其他node,它们的权限并不都是AuthPolicy.OP_ALL,比如还有一个n1的node权限为AuthPolicy.OP_READ,按照正常操作,该使用进程无法删除n1,假设它可以删除domain,那么它最后间接删除了n1,于是发生了悖论,因此,为了避免风险,所有的使用进程只能根据权限删除node,但是无法删除domain。

不过你允许承担这样的删除风险,也可以在创建进程里强行指定该domain可删除,通过在domain创建后,调用:
public boolean setDeletable(String domain);
该方法只能被domain的创建进程调用,其他使用进程没有权限调用。
强行指定可删除后,其他进程可以直接删除该domain及所含node并忽略后果。

四、相对于zookeeper的优势
Zookeeper无疑是一款成功的开源产品,并拥有广泛的信任者和应用场景,和以往一样,老外作者在apache网站上发布了一款产品,我们的工程师马上会虚心的学习和忠心的捍卫,而国产原创的产品往往会遭到百般质疑,因为我们的原创更多是抄袭和粗制滥造,我们的国产更多是框架集成而不是架构设计,所以这种情感上的倾向性不是一天能改变。
做产品对比和列举优势往往容易引起激烈争论,会被认为是在宣传和引导产品使用,实际上在都能满足功能需求的情况下,选择使用哪款产品更多的是个政治问题,而不是技术问题,领导意志及工程师本身的熟悉程度和爱好等等都是决定因素。

这里我们仅仅从技术角度阐述几点优势,Zookeeper做为一个chubby和paxos模仿品,缺乏创新型的设计改进,它仍然存在以下缺点:
1、树型配置节点的繁琐复杂,性能低下。为了保证这种结构,Zookeeper需要维持一套虚拟文件结构的开销,对于目录结构深的树节点,造成性能影响,而配置信息结构实际上往往不一定需要树结构。

2、“观察”(watch)机制的僵化设计:zookeeper没有获取最新版本信息的方法支持,它只能粗暴的在每次写入更新等方法时注册一个watch,当这些方法被调用后就回调,它不考虑信息内容是否变化,对于没有使信息内容发生改变的更新,zookeeper仍然会回调,并且zookeeper的回调比较呆板,它只能用一次,如果信息持续变化,必须又重新注册watch。而fourinone的事件处理则可以自由控制是否持续响应信息变化。

3、领导者选举机制实现的太过局限,集群只有两个节点,zookeeper无法进行领导者选举,zookeeper的领导者选举必须要奇数节点的奇怪限制。另外,ZooKeeper的领导者选举实现虽然比原始的Paxos要简化,但是它仍然存在领导者(Leader)、跟随者(Follower)、观察者(observer)、学习者 (Learner)等众多角色和跟随状态(Following)、寻找状态(Looking)、观察状态(Observing)、领导状态 (Leading)等复杂状态。相对于fourinone的领导者选举,zookeeper仍然不够直观简洁,难以用较少配置和代码演示。

4、Windows系统上几乎不支持,需要安装linux壳,并且仅建议用于学习研究。Fourinone支持windows、linux集群混合使用。

Fourinone提出一种新的分布式协同系统设计,在满足zookeeper所有功能下,并克服了以上缺点,提出了新的配置结构、变化事件机制、简化的领导者选举实现,能更好的满足分布式协调需求。

五、演示demo
下面是一个操作节点的演示demo,请留意各自节点的权限范围,程序说明:
1、 ParkServerDemo: 启动parkserver(它的IP端口已经在配置文件的PARK部分的SERVERS指定
2、 ParkSet:往parkserver里创建了d1n1、d2n2、d3n3、d4n4共4个节点,分别对应只读、读写,所有,所有+强行删除权限
3、 ParkGet:依次对d1n1、d2n2、d3n3、d4n4进行读、写、删除、删除domain操作,观察结果输出,如果没有权限操作,parkserver会输出信息,并且操作返回的结果对象为空

启动命令和顺序:
Javac –classpath fourinone.jar; *.java
Java  –classpath fourinone.jar; ParkServerDemo
Java  –classpath fourinone.jar; ParkSet
Java  –classpath fourinone.jar; ParkGet

如果没有fourinone.jar,可以到以下地址下载:
http://www.skycn.com/soft/68321.html

下面是demo源码:
// ParkServerDemo
import com.fourinone.BeanContext;
public class ParkServerDemo{
 public static void main(String[] args){
  BeanContext.startPark();
 }
}

// ParkSet
import com.fourinone.BeanContext;
import com.fourinone.ParkLocal;
import com.fourinone.ObjectBean;
import com.fourinone.AuthPolicy;
public class ParkSet{
 public static void main(String[] args){
  //获取parkserver用户接口
  ParkLocal pl = BeanContext.getPark();
  
//在domain d1下创建节点node n1,指定权限为只读
  ObjectBean d1n1 = pl.create("d1","n1","v1",AuthPolicy.OP_READ);
  if(d1n1!=null)
   System.out.println("d1n1 with AuthPolicy.OP_READ create success!");
  
  //在domain d2下创建节点node n2,指定权限为读写
  ObjectBean d2n2 = pl.create("d2","n2","v2",AuthPolicy.OP_READ_WRITE);
  if(d2n2!=null)
   System.out.println("d2n2 with AuthPolicy.OP_READ_WRITE create success!");
  
  //在domain d3下创建节点node n3,指定权限为所有
  ObjectBean d3n3 = pl.create("d3","n3","v3",AuthPolicy.OP_ALL);
  if(d3n3!=null)
   System.out.println("d3n3 with AuthPolicy.OP_ALL create success!");
  
  //在domain d4下创建节点node n4,指定权限为所有,并且创建完成强行设置为其他进程可删除
  ObjectBean d4n4 = pl.create("d4","n4","v4",AuthPolicy.OP_ALL);
  if(d4n4!=null)
   System.out.println("d4n4 with AuthPolicy.OP_ALL create success!");
  boolean r = pl.setDeletable("d4");
  if(r)
   System.out.println("set d4 deletable!");
 }
}

// ParkGet
import com.fourinone.BeanContext;
import com.fourinone.ParkLocal;
import com.fourinone.ObjectBean;
import java.util.List;
public class ParkGet{
 public static void main(String[] args){
  //获取parkserver用户接口
  ParkLocal pl = BeanContext.getPark();
  
  //获取节点d1n1,节点权限为AuthPolicy.OP_READ
  ObjectBean d1n1 = pl.get("d1","n1");//获取节点
  System.out.println("get d1n1:"+(String)d1n1.toObject());
  d1n1 = pl.update("d1","n1","v1-update");//更新节点
  if(d1n1!=null)
   System.out.println("update node d1n1 success!");
  else
   System.out.println("update node d1n1 failure!");
  List<ObjectBean> d1 = pl.delete("d1");//删除domain
  if(d1!=null)
   System.out.println("delete domain d1 success!");
  else
   System.out.println("delete domain d1 failure!");
  d1n1 = pl.delete("d1","n1");//删除节点
  if(d1n1!=null)
   System.out.println("delete node d1n1 success!");
  else
   System.out.println("delete node d1n1 failure!");
  
  //获取节点d2n2,节点权限为AuthPolicy.OP_READ_WRITE
  ObjectBean d2n2 = pl.get("d2","n2");
  System.out.println("get d2n2:"+(String)d2n2.toObject());
  d2n2 = pl.update("d2","n2","v2-update");
  if(d2n2!=null)
   System.out.println("update node d2n2 success!");
  else
   System.out.println("update node d2n2 failure!");
  List<ObjectBean> d2 = pl.delete("d2");
  if(d2!=null)
   System.out.println("delete domain d2 success!");
  else
   System.out.println("delete domain d2 failure!");
  d2n2 = pl.delete("d2","n2");
  if(d2n2!=null)
   System.out.println("delete node d2n2 success!");
  else
   System.out.println("delete node d2n2 failure!");
  
  //获取节点d3n3, 节点权限为AuthPolicy.OP_ALL
  ObjectBean d3n3 = pl.get("d3","n3");
  System.out.println("get d3n3:"+(String)d3n3.toObject());
  d3n3 = pl.update("d3","n3","v3-update");
  if(d3n3!=null)
   System.out.println("update node d3n3 success!");
  else
   System.out.println("update node d3n3 failure!");
  List<ObjectBean> d3 = pl.delete("d3");
  if(d3!=null)
   System.out.println("delete domain d3 success!");
  else
   System.out.println("delete domain d3 failure!");
  d3n3 = pl.delete("d3","n3");
  if(d3n3!=null)
   System.out.println("delete node d3n3 success!");
  else
   System.out.println("delete node d3n3 failure!");
  
  //获取节点d4n4,节点权限为AuthPolicy.OP_ALL
  ObjectBean d4n4 = pl.get("d4","n4");
  System.out.println("get d4n4:"+(String)d4n4.toObject());
  d4n4 = pl.update("d4","n4","v4-update");
  if(d4n4!=null)
   System.out.println("update node d4n4 success!");
  else
   System.out.println("update node d4n4 failure!");
  //由于创建进程已经强行指定该domain可删除setDeletable(d4),因此这里可以删除掉
  List<ObjectBean> d4 = pl.delete("d4");
  if(d4!=null)
   System.out.println("delete domain d4 success!");
  else
   System.out.println("delete domain d4 failure!");
  d4n4 = pl.delete("d4","n4");//这里删除节点会失败,因为上面已经删除了该domian下所有节点
  if(d4n4!=null)
   System.out.println("delete node d4n4 success!");
  else
   System.out.println("delete node d4n4 failure!");
 }
}

FourInOne(中文名字“四不像”)是一个四合一分布式计算框架,在写这个框架之前,我也看了老外写的其他开源框架,也对分布式计算进行了长时间的思考,当我们把复杂的hadoop当作一门学科学习时,似乎忘记了我们想解决问题的初衷:我们仅仅是想写个程序把几台甚至更多的机器一起用起来计算,把更多的cpu和内存利用上,来解决我们数量大和计算复杂的问题,当然这个过程中要考虑到分布式的协同和故障处理。如果仅仅是为了实现这个简单的初衷,为什么一切会那么复杂,我觉的自己可以写一个更简单的东西,它不需要过度设计,只需要看上去更酷一点,更小巧一点,功能更强一点。于是我将自己对分布式的理解融入到这个框架中,考虑到底层实现技术的相似性,我将Hadoop,Zookeeper,MQ,分布式缓存四大主要的分布式计算功能合为一个框架内,对复杂的分布式计算应用进行了大量简化和归纳。 首先,对分布式协同方面,它实现了Zookeeper所有的功能,并且做了很多改进,包括简化Zookeeper的树型结构,用domain/node两层结构取代,简化Watch回调多线程等待编程模型,用更直观的容易保证业务逻辑完整性的内容变化事件以及状态轮循取代,Zookeeper只能存储信息不大于1M的内容,FourInOne超过1M的内容会以内存隐射文件存储,增强了它的存储功能,简化了Zookeeper的ACL权限功能,用更为程序员熟悉rw风格取代,简化了Zookeeper的临时节点和序列节点等类型,取代为在创建节点时是否指定保持心跳,心跳断掉时节点会自动删除。FourInOne是高可用的,没有单点问题,可以有任意多个复本,它的复制不是定时而是基于内容变更复制,有更高的性能,FourInOne实现了领导者选举算法(但不是Paxos),在领导者服务器宕机情况下,会自动不延时的将请求切换到备份服务器上,选举出新的领导者进行服务,这个过程中,心跳节点仍然能保持健壮的稳定性,迅速跟新的领导者保持心跳连接。基于FourInOne可以轻松实现分布式配置信息,集群管理,故障节点检测,分布式锁,以及淘宝configserver等等协同功能。 其次, FourInOne可以提供完整的分布式缓存功能。如果对一个中小型的互联网或者企业应用,仅仅利用domain/node进行k/v的存储即可,因为domain/node都是内存操作而且读写锁分离,同时拥有复制备份,完全满足缓存的高性能与可靠性。对于大型互联网应用,高峰访问量上百万的并发读写吞吐量,会超出单台服务器的承受力,FourInOne提供了fa?ade的解决方案去解决大集群的分布式缓存,利用硬件负载均衡路由到一组fa?ade服务器上,fa?ade可以自动为缓存内容生成key,并根据key准确找到散落在背后的缓存集群的具体哪台服务器,当缓存服务器的容量到达限制时,可以自由扩容,不需要成倍扩容,因为fa?ade的算法会登记服务器扩容时间版本,并将key智能的跟这个时间匹配,这样在扩容后还能准确找到之前分配到的服务器。另外,基于FourInOne可以轻松实现web应用的session功能,只需要将生成的key写入客户端cookie即可。 FourInOne对于分布式大数据量并行计算的解决方案不同于复杂的hadoop,它不像hadoop的中间计算结果依赖于hdfs,它使用不同于map/reduce的全新设计模式解决问题。FourInOne有“包工头”,“农民工”,“手工仓库”的几个核心概念。“农民工”为一个计算节点,可以部署在多个机器,它由开发者自由实现,计算时,“农民工”到“手工仓库”获取输入资源,再将计算结果放回“手工仓库”返回给“包工头”。“包工头”负责承包一个复杂项目的一部分,可以理解为一个分配任务和调度程序,它由开发者自己实现,开发者可以自由控制调度过程,比如按照“农民工”的数量将源数据切分成多少份,然后远程分配给“农民工”节点进行计算处理,它处理完的中间结果数据不限制保存在hdfs里,而可以自由控制保存在分布式缓存、数据库、分布式文件里。如果需要结果数据的合并,可以新建立一个“包工头”的任务分配进行完成。多个“包工头”之间进行责任链式处理。总的来说,是将大数据的复杂分布式计算设计为一个链式的多“包工头”环节去处理,每个环节包括利用多台“农民工”机器进行并行计算,无论是拆分计算任务还是合并结果,都可以设计为一个单独的“包工头”环节。这样做的好处是,开发者有更大能力去深入控制并行计算的过程,去保持使用并行计算实现业务逻辑的完整性,而且对各种不同类型的并行计算场景也能灵活处理,不会因为某些特殊场景被map/reduce的框架限制住思维,并且链式的每个环节也方便进行监控过程。 FourInOne也可以当成简单的mq来使用,将domain视为mq队
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值