在distributedRPC (DRPC)中的思想是对Storm上许多Function计算的并行化。Stormtopology以function参数流作为输入,emits这些Function调用的结果作为输出。DRPC并不是Storm的特征,而是由Storm的primitive,即由streams,spouts, bolts, and topologies表达的pattern。DRPC被打包为独立于Storm的library,但非常有用以至于被Storm绑定。
概览
Distributed RPC由"DRPC server"协调,DRPC server协调接收一个RPC request,分发request给Storm topology,接收Storm topology的结果并发送结果给等待的client。DistributedRPC调用如同普通的RPCcall,比如,how a client如何计算带有参数"http://twitter.com"的function“reach”的结果:
DRPCClient client = new DRPCClient("drpc-host", 3772);
String result = client.execute("reach", "http://twitter.com");
distributed RPC 工作流如下:
client向DRPCserver发送需要执行的function和arguments。topology使用DRPCSpout
从
DRPCserver
接收一个
Function
调用流,
每一个function调用被DRPC server打上unique id作为标签,topology计算结果,最后由topology最后一个名为ReturnResults
的
bolt连接DRPC server并返回Function调用id的结果。DRPCserver用此id匹配client正在等待的结果,unblocks等待的client并发送结果给它。
LinearDRPCTopologyBuilder
Storm中名为LinearDRPCTopologyBuilder的topology builder 自动完成DRPC调用几乎全部步骤:
-
设置spout
-
返回结果给DRPCserver
-
赋予bolts功能,为每组tuple实现最终的汇总操作
如下例,实现一个DRPCtopology,为输入参数添加"!":
public static class ExclaimBolt extends BaseBasicBolt { public void execute(Tuple tuple, BasicOutputCollector collector) { String input = tuple.getString(1); collector.emit(new Values(tuple.getValue(0), input + "!")); } public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(new Fields("id", "result")); } } public static void main(String[] args) throws Exception { LinearDRPCTopologyBuilder builder = new LinearDRPCTopologyBuilder("exclamation"); //告诉topology需要执行的DRPC function名,DRPC server利用此名与其他Function调用相区分加以协调。builder.addBolt(new ExclaimBolt(), 3); //首个bolt带有2-tuple参数,request id和此request的参数;而最后一个bolt emit一个形如[id,result] 2-tuple的输出流;最后,所有中间tuple必须以request id作为第一个Field。 // ... }此例,ExclaimBolt为tuple的第二个field添加
"!",LinearDRPCTopologyBuilder处理
连接DRPC server和返回结果的协调工作。
Local mode DRPC
以上实例的在Local mode下:
LocalDRPC drpc = new LocalDRPC(); LocalCluster cluster = new LocalCluster(); cluster.submitTopology("drpc-demo", conf, builder.createLocalTopology(drpc)); System.out.println("Results for 'hello':" + drpc.execute("exclamation", "hello")); cluster.shutdown(); drpc.shutdown();
首先创建一个LocalDRPC
对象,在流程中它仿真DRPC server,正如LocalCluster
仿真Stormcluster。然后创建LocalCluster
在local mode下运行topology,
LinearDRPCTopologyBuilder
分别拥有创建localtopologies和remote topologies的方法。在localmode下,LocalDRPC
对象并不绑定任何port,topology知道它通信的对象。这是为什么createLocalTopology
以
LocalDRPC
对象作为输入。发起
topology
之后,
DRPC调用在LocalDRPC
上的
execute
方法。
Remote mode DRPC
在真实的Storm集群上使用DRPC:
-
发起DRPC server(s)
-
配置DRPCservers地址
-
在Storm集群上提交DRPC topologies
发起一个DRPCserver可由storm脚本完成,正如发起Nimbus或者UI:
bin/storm drpc
接下来,在Stormcluster中配置DRPCserver(s)的地址,由此DRPCSpout
知道在哪里读取
function invocations。这可由storm.yaml
文件或
topology configurations完成,storm.yaml
完成的配置如下:
drpc.servers: - "drpc1.foo.com" - "drpc2.foo.com"
最后,StormSubmitter
发起DRPCtopologies,以remote mode运行上例:
StormSubmitter.submitTopology("exclamation-drpc", conf, builder.createRemoteTopology());
createRemoteTopology
用于创建适于Storm clusters的topologies。
A more complex example
此例需要在Stormcluster 上并行计算DRPCfunction。它计算Twitter上URL的reach,reach就是一个人在Twitter上打开此URL的次数,为此:
-
获取所有tweeted到此URL的人
-
获取这些人的所有followers
-
followers集合去重
-
计算followers集合唯一计数
计算过程中,需要调用上千次database calls并访问千万级的followerrecords,属于密集计算。在单机上,此计算需要花费数分钟;在Storm集群上,可在秒级完成最难的URLreach计算。定义reach topology:
LinearDRPCTopologyBuilder builder = new LinearDRPCTopologyBuilder("reach"); builder.addBolt(new GetTweeters(), 3); builder.addBolt(new GetFollowers(), 12) .shuffleGrouping(); builder.addBolt(new PartialUniquer(), 6) .fieldsGrouping(new Fields("id", "follower")); builder.addBolt(new CountAggregator(), 2) .fieldsGrouping(new Fields("id"));
执行如下4steps:
-
GetTweeters
获取所有tweeted URL 的用户,它转换输入流[id,url]
为输出流[id,tweeter]
,每一
url
tuple将匹配多个tweeter
tuples. -
GetFollowers
获取tweeters的followers。它转换输入流[id,tweeter]
为输出流[id,follower]
。
跨越所有的task,这必然存在重复的followertuples ,因为一个人可以follow多个人去tweeted相同的URL。 -
PartialUniquer
由followerid 为followers流分组,有效的将相同的follower化入同一个task,PartialUniquer
的每一个task将接受彼此独立的followers集合,OncePartialUniquer
一旦为
requestid
直接接受所有的
followertuples ,则emit此followers子集合的去重计数。 -
最后,
CountAggregator
接受每个
PartialUniquer
部分计数并作总计。
PartialUniquer
bolt代码:
public class PartialUniquer extends BaseBatchBolt { BatchOutputCollector _collector; Object _id; Set<String> _followers = new HashSet<String>(); @Override public void prepare(Map conf, TopologyContext context, BatchOutputCollector collector, Object id) { _collector = collector; _id = id; } @Override public void execute(Tuple tuple) { _followers.add(tuple.getString(1)); } @Override public void finishBatch() { _collector.emit(new Values(_id, _followers.size())); } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(new Fields("id", "partial-count")); } }
PartialUniquer
通过扩展
BaseBatchBolt
实现接口IBatchBolt
,
batchbolt 提供API将批量tuples作为一个实际单位进行处理,为每一个requestid 创建一个batchbolt 的新实例,Storm 在适当的时候负责清理实例。
PartialUniquer
的
execute
函数接受一
follower tuple并将之加入到为此requestid设置的 HashSet
。
finishBatch
函数在本task批处理完成后调用,PartialUniquer
emits一单个tuple,其中包含follower ids的子集和它的去重计数。
在底层,CoordinatedBolt
用于知道一个给定的
bolt
何时接受到一
requestid
对应的所有
tuples
,
CoordinatedBolt
使用
directstreams
来管理此协调。
Non-linear DRPC topologies
LinearDRPCTopologyBuilder
仅处理线性
DRPCtopologies,其计算为一序列步骤,直接利用CoordinatedBolt
,
因此不能胜任解决bolt之间的分支、合并等复杂计算。