Twitter Storm的新利器Pluggable Scheduler 【转】

转自:http://www.51studyit.com/html/notes/20140403/51.html




版本:

storm0.9.1  kafka0.8.1

可插拔式的任务分配器(Pluggable Scheduler)实现了,将在0.8.0版本里面跟大家见面。这篇文章先给大家尝尝鲜,介绍下这个新特性。

在Pluggable Scheduler之前,Twitter Storm里面对于用户提交的每个Topology进行任务分配是由nimbus来做的,nimbus的任务分配算法可是非常牛逼的哦,主要特点如下

  • 在slot充沛的情况下,能够保证所有topology的task被均匀的分配到整个机器的所有机器上
  • 在slot不足的情况下,它会把topology的所有的task分配到仅有的slot上去,这时候其实不是理想状态,所以。。
    • 在nimbus发现有多余slot的时候,它会重新分配topology的task分配到空余的slot上去以达到理想状态。
  • 在没有slot的时候,它什么也不做

一般情况下,用这种默认的task分配机制就已经足够了。但是也会有一些应用场景是默认的task分配机制所搞定不了的,比如

  • 如果你想你的spout分配到固定的机器上去 — 比如你的数据就在那上面
  • 如果你有两个topology都很耗CPU,你不想他们运行在同一台机器上

这些情况下我们默认的task分配机制虽然强大,却是搞不定的,因为它根本就不考虑这些。所以我们设计了新的Pluggable Scheduler机制,使得用户可以编写自己的task分配算法 — Scheduler来实现自己特定的需求。下面我们就来亲自动手来看看怎么才能实现上面提到的默认Scheduler搞不定的第一个场景,为了后面叙述的方便,我们来细化一下这个需求:让我们的名为special-spout的组件分配到名为special-supervisor的supervisor上去

要实现一个Scheduler其实很简单,只要实现IScheduler

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public interface IScheduler {
    /**
     * Set assignments for the topologies which needs scheduling. The new assignments is available
     * through <code>cluster.getAssignments()
     *
     *@param topologies all the topologies in the cluster, some of them need schedule. Topologies object here
     *       only contain static information about topologies. Information like assignments, slots are all in
     *       the <code>clusterobject.
     *@param cluster the cluster these topologies are running in. <code>cluster contains everything user
     *       need to develop a new scheduling logic. e.g. supervisors information, available slots, current
     *       assignments for all the topologies etc. User can set the new assignment for topologies using
     *       <code>cluster.setAssignmentById
     */
    public void schedule(Topologies topologies, Cluster cluster);
}

这个接口会提供两个参数,其中Topologies包含当前集群里面运行的所有Topology的信息:StormTopology对象,配置信息,以及从task到组件(bolt, spout)id的映射信息。Cluster对象则包含了当前集群的所有状态信息:对于系统所有Topology的task分配信息,所有的supervisor信息等等 — 已经足够我们实现上面的那个需求了,让我们动手吧

找出我们的目标Topology

首先我们要确定我们的topology是否已经提交到集群了,很简单,到topologies对象里面找找看,找到了的话就说明已经提交了。

1
2
// Gets the topology which we want to schedule
TopologyDetails topology = topologies.getByName("special-topology");

只要这个topology不为null的话就说明这个topology已经提交了。

目标Topology是否需要分配

紧接着我们要看看这个topology需不需要进行task分配 — 有可能之前分配过了。怎么弄呢?很简单,Cluster对象已经提供了api可以使用

1
boolean needsScheduling = cluster.needsScheduling(topology);

这里要说明的一点是,有关Scheduler编写的几乎所有api都是定义在Cluster类里面,大家只要把这个类搞熟悉,编写起Scheduler起来应该就得心应手了。如果这个topology需要进行task分配我们还要看下有那些task需要进行分配 — 因为可能有部分task已经被分配过了

1
2
// find out all the needs-scheduling components of this topology
Map<String, List<Integer>> componentToTasks = cluster.getNeedsSchedulingComponentToTasks(topology);
我们的目标spout是否需要分配?

因为我们的目标是让名为special-spout的组件运行在名为special-supervisor的supervisor上,所以我们要看看这些task里面有没有是属于special-spout的task,很简单,上面返回的componentToTasks就是从component-id到task-ids的一个映射。所以要找出special-spout就很简单了

1
List<Integer> tasks = componentToTasks.get("special-spout");
找出目标supervisor

找到我们要分配的task之后,我们还要把我们的special-supervisor找出来,Cluster同样提供了方便的方法:

01
02
03
04
05
06
07
08
09
10
11
// find out the our "special-supervisor" from the supervisor metadata
Collection<SupervisorDetails> supervisors = cluster.getSupervisors().values();
SupervisorDetails specialSupervisor = null;
for (SupervisorDetails supervisor : supervisors) {
    Map meta = (Map) supervisor.getSchedulerMeta();
 
    if (meta.get("name").equals("special-supervisor")) {
       specialSupervisor = supervisor;
       break;
    }
}

这里要特别说明一下Map meta = (Map) supervisor.getSchedulerMeta();, 我们前面说名为special-supervisor的supevisor,其实在storm里面supervisor是没有名字的,这里我们所谓的名字是从supervisor.getSchedulerMeta里面找出来的,这个schedulerMeta是supervisor上面配置的给scheduler使用的一些meta信息,你可以配置任意信息!比如在这个例子里面,我在storm.yaml里面配置了:

1
2
supervisor.scheduler.meta:
  name: "special-supervisor"

这样我们才能用meta.get("name").equals("special-supervisor")找到我们的special-supervisor到这里我们就找到了我们的special-supervisor,但是要记住一点的是,我们的集群里面有很多topology,这个supervisor的slot很可能已经被别的topology占用掉了。所以我们要检查下有没有slot了

1
List<WorkerSlot> availableSlots = cluster.getAvailableSlots(specialSupervisor);

判断上面的availableSlots是不是空就知道有没有空余的slot了,如果没有slot了怎么办?没别的topology占用掉了怎么办?很简单!把它赶走

1
2
3
4
5
6
// if there is no available slots on this supervisor, free some.
if (availableSlots.isEmpty() && !tasks.isEmpty()) {
    for (Integer task : specialSupervisor.getAllPorts()) {
        cluster.freeSlot(new WorkerSlot(specialSupervisor.getId(), task));
    }
}
最后一步:分配

到这里为止呢,我们要分配的tasks已经有了,要分配到的slot也搞定了,剩下的就分配下就好了(注意,这里因为为了保持例子简单,代码做了简化)

1
2
3
4
5
6
// re-get the aviableSlots
availableSlots = cluster.getAvailableSlots(specialSupervisor);
 
// since it is just a demo, to keep things simple, we assign all the
// tasks into one slot.
cluster.assign(availableSlots.get(0), topology.getId(), tasks);

我们的目标实现了! 随着cluster.assign的调用,我们已经把我们的special-spout分配到special-supervisor上去了。不难吧 :)

别的任务谁来分配?

不过有件事情别忘了,我们只给special-spout分配了task, 别的task谁来分配啊?你可能会说我不关心啊,没关系,把这个交给系统默认的分配器吧:我们已经把系统的默认分配器包装到backtype.storm.scheduler.EvenScheduler里面去了,所以你简单调用下就好了

1
new backtype.storm.scheduler.EvenScheduler().schedule(topologies, cluster);
让Storm知道我们的Scheduler

哦,有一件事情忘记说了,我们完成了我们的自定义Scheduler,怎么让storm知道并且使用我们的Scheduler呢?两件事情:

  • 把包含这个Scheduler的jar包放到$STORM_HOME/lib下面去
  • 在storm.yaml 里面作如下配置:
    1
    storm.scheduler: "storm.DemoScheduler"

这样Storm在做任务分配的时候就会用你的storm.DemoScheduler, 而不会使用默认的系统Scheduler

总结:

上面知识对0.8版本的一个说明整理,针对0.9版本,我们给出如下的一些说明

1、首先类:

package com.wy.storm.topology;

import java.util.Collection;
import java.util.List;
import java.util.Map;

import backtype.storm.scheduler.Cluster;
import backtype.storm.scheduler.ExecutorDetails;
import backtype.storm.scheduler.IScheduler;
import backtype.storm.scheduler.SupervisorDetails;
import backtype.storm.scheduler.Topologies;
import backtype.storm.scheduler.TopologyDetails;
import backtype.storm.scheduler.WorkerSlot;

public class TestScheduler implements IScheduler {

   
@Override
   
public void prepare(Map map) {
       
// TODO Auto-generated method stub

   
}

   
@Override
   
public void schedule(Topologies topologies, Cluster cluster) {
       
//获取指定的topology
       
TopologyDetails topology = topologies.getByName("special-topology");
       
if(topology!=null){
           
//判断topology是否已经分配过
           
boolean needsScheduling = cluster.needsScheduling(topology);
           
if(needsScheduling){
               
找出所有需要分配的executor
               
Map<String, List<ExecutorDetails>> componentToTasks = cluster.getNeedsSchedulingComponentToExecutors(topology);
               
//找出需要分配的executor
               
List<ExecutorDetails> tasks = componentToTasks.get("special-spout");
               
               
//获取所有的supervisor
               
Collection<SupervisorDetails> supervisors = cluster.getSupervisors().values();
               
SupervisorDetails specialSupervisor = null;
               
               
for (SupervisorDetails supervisor : supervisors) {
                   
Map meta = (Map) supervisor.getSchedulerMeta();
                   
//找出指定的supervisor
                   
//supervisor.scheduler.meta:
                   
//      name: "special-supervisor"
                   
if (meta.get("name").equals("special-supervisor")) {
                       
System.out.println("---------special-supervisor   success---------");
                       specialSupervisor
= supervisor;
                       
break;
                   
}
               
}
               
if(specialSupervisor!=null){
                   
System.out.println("---------specialSupervisor!=null   success---------");
                   
//查看是否有可用的slot
                   
List<WorkerSlot> availableSlots = cluster.getAvailableSlots(specialSupervisor);
                   
//没有可用的slot,剔除其他的
                   
if (availableSlots.isEmpty() && !tasks.isEmpty()) {
                       
for (Integer task : specialSupervisor.getAllPorts()) {
                            cluster
.freeSlot(new WorkerSlot(specialSupervisor.getId(), task));
                       
}
                   
}
                   
//分配
                    availableSlots
= cluster.getAvailableSlots(specialSupervisor);
                    cluster
.assign(availableSlots.get(0), topology.getId(), tasks);
                   
//剩下的分配给storm默认的分配器
                   
new backtype.storm.scheduler.EvenScheduler().schedule(topologies, cluster);
                   
//storm.scheduler: "com.wy.storm.topology.TestScheduler" 指定
               
}
           
}
       
}
   
}

}



编写上面代码的时候,首先要引入storm jar包,编写完成之后打成jar,放到storm集群的所有集群的lib下面即可。

配置storm.yaml文件

x00:

 supervisor.scheduler.meta:
    name: "special-supervisor0"
 storm.scheduler: "com.wy.storm.topology.TestScheduler"

x01:

 supervisor.scheduler.meta:
    name: "special-supervisor"
 storm.scheduler: "com.wy.storm.topology.TestScheduler"

x02:

 supervisor.scheduler.meta:
    name: "special-supervisor2"
 storm.scheduler: "com.wy.storm.topology.TestScheduler"

注意supervisor的name都不一样。

2、测试

Topology

package com.wy.storm.topology;

import storm.kafka.Broker;
import storm.kafka.BrokerHosts;
import storm.kafka.KafkaSpout;
import storm.kafka.SpoutConfig;
import storm.kafka.StaticHosts;
import storm.kafka.StringScheme;
import storm.kafka.ZkHosts;
import storm.kafka.trident.GlobalPartitionInformation;
import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.StormSubmitter;
import backtype.storm.spout.SchemeAsMultiScheme;
import backtype.storm.topology.TopologyBuilder;

import com.google.common.collect.ImmutableList;
import com.wy.storm.bolt.Counter;

public class CounterTopology {

   
/**
     * @param args
     */

   
public static void main(String[] args) {
       
try{
           
/*GlobalPartitionInformation gi = new GlobalPartitionInformation();
            gi.addPartition(0, new Broker("x00",9092));
            gi.addPartition(1, new Broker("x01",9092));
            gi.addPartition(2, new Broker("x02",9092));*/

           
String kafkaZookeeper = "x00:2181,x01:2181,x02:2181";
           
BrokerHosts brokerHosts = new ZkHosts(kafkaZookeeper);
           
SpoutConfig kafkaConf = new SpoutConfig(brokerHosts, "test3", "/newkafka", "id");
           
           
           
            kafkaConf
.scheme = new SchemeAsMultiScheme(new StringScheme());
           
            kafkaConf
.zkServers =  ImmutableList.of("x00","x01","x02");
            kafkaConf
.zkPort = 2181;
           
            kafkaConf
.forceFromStart = true;
           
           
KafkaSpout kafkaSpout = new KafkaSpout(kafkaConf);
           
           
           
           
TopologyBuilder builder = new TopologyBuilder();
            builder
.setSpout("special-spout", kafkaSpout, 10);
            builder
.setBolt("printer", new Counter(),45).shuffleGrouping("special-spout");
           
           
Config config = new Config();
            config
.setDebug(true);
           
           
if(args!=null && args.length > 0) {
                config
.setNumWorkers(8);
               
               
StormSubmitter.submitTopology(args[0], config, builder.createTopology());
           
} else {        
                config
.setMaxTaskParallelism(3);
   
               
LocalCluster cluster = new LocalCluster();
                cluster
.submitTopology("special-topology", config, builder.createTopology());
           
               
Thread.sleep(500000);
   
                cluster
.shutdown();
           
}
       
}catch (Exception e) {
            e
.printStackTrace();
       
}

   
}

}


Blot

package com.wy.storm.bolt;

import backtype.storm.topology.BasicOutputCollector;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseBasicBolt;
import backtype.storm.tuple.Tuple;

public class Counter extends BaseBasicBolt {

   
private static long i = 0;
   
@Override
   
public void execute(Tuple tuple, BasicOutputCollector collector) {
       
       
System.out.println("i="+i++);

   
}

   
@Override
   
public void declareOutputFields(OutputFieldsDeclarer arg0) {
       

   
}

}



3、将上面的topology打包运行即可


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值