apache helix是一个通用的集群管理框架,既能做分布式task,也能做分布式存储,还可以做服务发现,分布式锁等,大大简化了你系统的设计,分布式系统开发过程中,一些通用的部分,它全部给你抽象实现,基于zookeeper。
那么既然是通用,那么必须要高度抽象,只有理解了抽象出来的几个概念,我们才能更好地读懂它的实现和设计原理,ok废话不多说了,撸一把。
假如我们要设计分布式task系统,那么对于一个task,有两个基本的属性,一个是位置,一个是状态。同样对于分布式存储,一个partition或者region也有位置和状态,同时,这种有位置和状态属性的东西,可以统一抽象成资源,资源分布式化,因此需要有一种平衡的理想状态。
"TASK_NAME" : { "LOCATION" : "STATE" }
如果你希望在N1上启动myTask,那么理想状态也可以描述成这样:
{ "id" : "MyTask", "mapFields" : { "myTask" : { "N1" : "ONLINE", } } }
分区,分区概念一般在消息系统上比较常见,比如kafka,每个topic有多个partition;对于分布式task来讲,如果一个task单机单进程搞不定,那么可以映射成子任务,这种子任务,你可以理解成storm作业的spout,bolt task,那么这种东西,统一抽象成partition,代表最小的资源颗粒。
"myTask_0", "myTask_1", "myTask_2" 运行在N1, N2 和 N3 上,那么理想状态描述成下面这样,其实这里helix的理想状态就相当于storm里的每个topology的assignment,即任务分配细节。
{ "id" : "myTask", "simpleFields" : { "NUM_PARTITIONS" : "3", } "mapFields" : { "myTask_0" : { "N1" : "ONLINE", }, "myTask_1" : { "N2" : "ONLINE", }, "myTask_2" : { "N3" : "ONLINE", } } }
Partitioning是说将任务或者数据分成更细粒度地分片,然后通常为了可用性,每个分片还有多个副本。这种例子到处都是,hdfs的文件由很多block组成,每个block又有三个或者多个副本;storm作业每个task都有并发度,当然jstorm的并发度是不是task副本的概念,要看grouping的方;elasticsearch的索引文件,同样有多个副本。
helix 统一将这种分片的副本或者备份,称之为replica,了解hdfs的同学,很容易理解这种概念。那么这种情况下,资源的assignment就是这样子。
{ "id" : "myIndex", "simpleFields" : { "NUM_PARTITIONS" : "3", "REPLICAS" : "2", }, "mapFields" : { "myIndex_0" : { "N1" : "ONLINE", "N2" : "ONLINE" }, "myIndex_1" : { "N2" : "ONLINE", "N3" : "ONLINE" }, "myIndex_2" : { "N3" : "ONLINE", "N1" : "ONLINE" } } }
状态,这种例子也很多,比如zookeeper,每个节点分为leader和follow;kafka的partition也区分master,slave;hdfs在做block recovery的时候,也要持有副本的datanode选举出master和slave,即解决一致性的问题,副本之间通常要选出来一个master负责接收请求,再同步给其它slave。
比如我用helix组织mysql实例做一个mysql集群,一个数据库做主备,那么assignment(idea state)是这样子:
{ "id" : "myDB", "simpleFields" : { "NUM_PARTITIONS" : "1", "REPLICAS" : "2", }, "mapFields" : { "myDB" : { "N1" : "MASTER", "N2" : "SLAVE", } } }
状态机和状态转换,这个基本上是failover的东西,即容灾,资源做好了ideastate那么helix就会想办法达成这种状态,类似storm里的assignment,mkAssignment之后,就要调度分配槽位,执行task。helix在一个集群中有一个controller的角色,它相当于storm里的nimbus,或者hbase里的hbasemaster,负责状态转换等,目的就是为了让集群,让你的资源达到这种ideastate。
问题是controller怎么知道应该怎么做?这就需要有一个状态机,第一次接触状态机是在yarn里,后来jstorm中nimbus也有一个状态机,那么状态转换关系提前定制好,controller自然就能计算出,下一步应该怎么走,因此,helix需要用户提前定义好状态机
MasterSlave ,最简单的状态机模型示例:
OFFLINE | SLAVE | MASTER _____________________________ | | | | OFFLINE | N/A | SLAVE | SLAVE | |__________|________|_________| | | | | SLAVE | OFFLINE | N/A | MASTER | |__________|________|_________| | | | | MASTER | SLAVE | SLAVE | N/A | |__________|________|_________|
Helix 支持灵活的状态机配置,一种资源配一个状态机,因此在同一个集群中,其实可以有多种资源,多个状态机,你想在一个集群中管理一个数据库,同时再管理一个索引存储系统,也是可以的:
Current State
CurrentState 用于表示一个节点,即分布式系统的一个node的当前状态,比如storm里的supervisor的心跳信息,里面包括机器名,槽位名,运行的拓扑,taskid等信息,下面的例子是说node1这个节点持有的资源 ,以及资源的状态。
{ "id":"MyResource" ,"simpleFields":{ ,"SESSION_ID":"13d0e34675e0002" ,"INSTANCE_NAME":"node1" ,"STATE_MODEL_DEF":"MasterSlave" } ,"mapFields":{ "MyResource_0":{ "CURRENT_STATE":"SLAVE" } ,"MyResource_1":{ "CURRENT_STATE":"MASTER" } ,"MyResource_2":{ "CURRENT_STATE":"MASTER" } } }
集群的每个节点都有自己的currentstate,节点,在helix里称为participants。
helix把集群客户端称为spectators,相当于storm里的nimbusclient,比如storm用户想看到我的作业分配情况,其实获取assignment就行了,ExternalView其实跟ideastate结构类似,只不过数据结构更加简单,专门暴露给客户端的数据结构,叫做ExternalView
{ "id":"MyResource", "mapFields":{ "MyResource_0":{ "N1":"SLAVE", "N2":"MASTER", "N3":"OFFLINE" }, "MyResource_1":{ "N1":"MASTER", "N2":"SLAVE", "N3":"ERROR" }, "MyResource_2":{ "N1":"MASTER", "N2":"SLAVE", "N3":"SLAVE" } } }
rebalance也比较容易理解,集群资源分布不均,或者task分布不均,可能导致热点问题;存储里面比较容易理解,分布式计算的rebalance 有时候可能指作业task的重新调度,helix在节点从起,stop,软件硬件错误,集群新增节点,配置变更,或者idea state发生变化的时候,helix controller模块会触发rebalance
rebalance算法简单说做三个事情:
动态idealstate其实就是支持动态调整资源的状态,这很重要,helix可以感知集群变化,然后动态调整idealstate,同时触发rebalance
helix支持三种方式实现动态调整idealstate