InputDeclarer接口定义了不同的流分组方式。每当TopologyBuilder的setBolt方法被调用就返回该对象,用于声明一个Bolt的输入流,以及这些流应该如何分组。InputDeclarer接口的完整定义代码如下:
public interface InputDeclarer<T extends InputDeclarer> {
// 字段分组
public T fieldsGrouping(String componentId, Fields fields);
public T fieldsGrouping(String componentId, String streamId, Fields fields);
// 全局分组
public T globalGrouping(String componentId);
public T globalGrouping(String componentId, String streamId);
// 随机分组
public T shuffleGrouping(String componentId);
public T shuffleGrouping(String componentId, String streamId);
// 本地或者随机分组
public T localOrShuffleGrouping(String componentId);
public T localOrShuffleGrouping(String componentId, String streamId);
// 无分组
public T noneGrouping(String componentId);
public T noneGrouping(String componentId, String streamId);
// 广播分组
public T allGrouping(String componentId);
public T allGrouping(String componentId, String streamId);
// 直接分组
public T directGrouping(String componentId);
public T directGrouping(String componentId, String streamId);
// 自定义分组
public T customGrouping(String componentId, CustomStreamGrouping grouping);
public T customGrouping(String componentId, String streamId, CustomStreamGrouping grouping);
public T grouping(GlobalStreamId id, Grouping grouping);
}
从InputDeclarer接口中可以看出,流分组的方式主要有fieldsGrouping(字段分组)、globalGrouping(全局分组)、shuffleGrouping(随机分组)、localOrShuffleGrouping(本地或者随机分组)和noneGrouping(无分组)、allGrouping(广播分组)、directGrouping(直接分组)、customGrouping(自定义分组)这8种不同的流分组方式。每个InputDeclarer实例可以有不止一个源,每个源可以用不同的流分组方式来分组。
1. 随机分组
随机分组(Shuffle Grouping)是最常用的流分组方式,它随机地分发元组到Bolt上的任务,这样能保证每个任务得到相同数量的元组。
随机分组执行原子操作,这是非常有用的,例如数学运算。但是,如果操作不能被随机分发的话,应该考虑使用其他的分组方式,例如,在单词统计(WordCount)例子中,需要计算单词,就不适合使用随机分组。
2. 字段分组
字段分组(Fields Grouping)根据指定字段对流进行分组。例如,如果流是按user-id字段进行分组,具有相同user-id的元组总是被分发到相同的任务,具有不同user-id的元组可能被分发到不同的任务。
字段分组是实现流连接和关联,以及大量其他的用例的基础。在实现上,字段分组使用取模散列来实现。
3. 广播分组
广播分组(All Grouping)是指流被发送到所有Bolt的任务中。使用这个分组方式时要小心。
4. 全局分组
全局分组(Global Grouping)是指全部流都发送到Bolt的同一个任务中,再具体一点,是发送给ID最小的任务。
5. 无分组
假定你不关心流是如何分组的,则可以使用这种分组方式。目前这种分组和随机分组是一样的效果,有一点不同的是Storm会把这个Bolt放到Bolt的订阅者的同一个线程中执行。
6. 直接分组
直接分组(Direct Grouping)是一种特殊的分组。这种方式的流分组意味着由元组的生产者决定元组的消费者的接收元组的任务。直接分组只能在已经声明为直接流(Direct Stream)的流中使用,并且元组必须使用emitDirect方法来发射。Bolt通过TopologyContext对象或者OutputCollector类的emit方法的返回值,可以得到其消费者的任务id列表(List<Integer>)。
7. 本地或者随机分组
如果目标Bolt在同一工作进程存在一个或多个任务,元组会随机分配给这些任务。否则,该分组方式与随机分组方式是一样的。
8. 自定义分组
可以自定义流分组的方式,通过实现CustomStreamGrouping接口来创建自定义的流分组。
CustomStreamGrouping接口的定义如下:
public interface CustomStreamGrouping extends Serializable {
void prepare(WorkerTopologyContext context, GlobalStreamId stream, List<Integer> targetTasks);
List<Integer> chooseTasks(int taskId, List<Object> values);
}
CustomStreamGrouping接口主要有两个方法:prepare和chooseTasks。CustomStreamGrouping接口的具体实现,可以参考如下的代码类:
storm.trident.partition.GlobalGrouping
storm.trident.partition.IdentityGrouping
storm.trident.partition.IndexHashGrouping
backtype.storm.testing.NGrouping
让我们来看一个简单的自定义流分组的实现,它来自storm.trident.partition. GlobalGrouping。GlobalGrouping是Trident中实现全局分组功能的自定义流分组类。 GlobalGrouping的类定义代码如下:
public class GlobalGrouping implements CustomStreamGrouping {
List<Integer> target;
@Override
public void prepare(WorkerTopologyContext context, GlobalStreamId stream, List<Integer> targets) {
List<Integer> sorted = new ArrayList<Integer>(targets);
Collections.sort(sorted);
target = Arrays.asList(sorted.get(0));
}
@Override
public List<Integer> chooseTasks(int i, List<Object> list) {
return target;
}
}
自定义流分组的使用是很简单的。假设对ExclamationTopology使用自定义流分组。ExclamationTopology的“exclaim2”Bolt原来是对“exclaim1”Bolt使用随机分组,代码如下:
builder.setBolt("exclaim2", new ExclamationBolt(), 2)
.shuffleGrouping("exclaim1");
现在,修改为“exclaim2”Bolt对“exclaim1”Bolt使用自定义流分组GlobalGrouping,代码如下:
builder.setBolt("exclaim2", new ExclamationBolt(), 2)
.customGrouping("exclaim1", new GlobalGrouping());