这里补充一下 Spark 与 Storm的区别,1.Storm 可以实现1s以下的实时查询,Spark却做不到,Storm更加自由风格 计算是在bolt节点上,Spark是在链上,Spark stream 与 storm 同样可以实现即时 有一定延迟的 比如CEP的查询 Spark 有ML graphx 机器学习 图计算 SQL 类似于hive的实时查询 Storm 没有,Storm 更加适合实时流 的方式,Spark更加适合有一定缓冲 存储的 批次来源,无论是来自消息中间件还是来自其他来源。Storm 有DRPC 可以用来做实时查询并返回结果的类型.
1.Storm 基本API (非Trident)
首先我来讲一下我提的一些重点
1.BasicRichBolt /BasicBasicBolt
2. Stream Join 的方式
3.Group的种类
4.BaseBatchBolt 批次处理
5.Drpc的使用
6.图像搜索的实战
1.首先BasicRichBolt 与 BasicBasicBolt的主要区别是一个是需要确认 一个不需要确认
也就是说前者需要调用ack方法 ,后者 自动调用。当失败 其实会最终到源spout
如果确认会在spout中移除 可以覆盖自己实现。
public void ack(Object msgId) { System.out.println("OK:"+msgId); } public void close() {} public void fail(Object msgId) { System.out.println("FAIL:"+msgId); }
下面看看 我们怎么使用
首先
prepare 用于初始化 一些变量以及元素 cleanup用于销毁 释放资源,比如jdbc连接池 其中execute为bolt执行内容,同spark不同的是spark执行的是在链上,而storm执行的是在bolt节点上 下面讲一下 tuple 为原子对象 可以获取上一个spout 或者 bolt emit提交的数据,可以通过索引id 来接受 也可以通过 fieldName来接受 byte[] datas=input.getBinary(3); input.getBinaryByField("datas"); 下面我们来看看emit collector.emit(new Values(uuid,imageid,1,1.0)); emit 为提交数据,可以向下一个bolt 或者 向某个stream id提交,想stream id提交时候 有时候结合TimeCacheMap可以实现简单的join哦 这也是一种join的方式 。其实最简单的join的方式就是提交多个 spout 在下一个bolt时候进行提交源的field字段的判断 处理。 上面 我们提交uuid 与 imageid 我们需要声明提交字段的声明表示 declarer.declare(new Fields("id","imageid","type","score")); 分别对应提交的数据 下面看看我们在图像搜索中的一些使用。
public class ImageSurfCompareBolt extends BaseBasicBolt { private QueryRunner qr; private Set<String> ids = Sets.newConcurrentHashSet(); //LockFreeOrderedList<String> ids=new LockFreeOrderedList<String>(); @Override public void prepare(Map stormConf, TopologyContext context) { qr=new QueryRunner(DBHelper.getDateSource()); context.getSharedExecutor().execute(new Runnable() { @Override public void run() { ImageSurf surf=ImageSurf.surf; } }); } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(new Fields("id","imageid","type","score")); } @Override public void execute(Tuple input, BasicOutputCollector collector) { String imageid=input.getString(1); Integer type=input.getInteger(2); byte[] datas=input.getBinary(3); final Object uuid=input.getValue(0); String key=uuid.toString()+"-"+imageid; if(!ids.contains(key)){ //没有被处理 //imagecache.put(key, imageid); ids.add(key); String sql="select path from image_path where id=?"; try { String imagepath=qr.query(sql, new ScalarHandler<String>(1),imageid); String fileName=imagepath; if(imagepath!=null){ BufferedImage imageA=ImageIO.read(new File(fileName)); BufferedImage imageB=ImageIO.read(new ByteArrayInputStream(datas)); double score=ImageSurf.compare(imageA, imageB); if(type==4){ MatchModel matchModel=DetectUtility.matchImage(imageA, imageB); double mscore=matchModel.getScore(); Integer sum=matchModel.getSum(); if(sum>200&&mscore>0.6){ collector.emit(new Values(uuid,imageid,3,String.valueOf(score))); } }else{ collector.emit(new Values(uuid,imageid,type,String.valueOf(score))); } //MatchModel imatchscore=DetectUtility.matchImage(imageA, imageB); //System.out.println(fileName+"########################"+score); } } catch (Exception e) { System.out.println("=========imageSurfCompare=========="+e.getMessage()); } if(ids.size()>90){ ids.removeIf(new java.util.function.Predicate<String>() { @Override public boolean test(String t) { if(t.startsWith(uuid.toString())){ return false; }else{ return true; } } }); } } }
2.上面说道的join方式,这里指出来有几种 1.提交多个spout 并在bolt上进行判断 2.emit时候置顶stream id 想stream id提交数据 emit(String streamId, List<Object> tuple) emit(String streamId, List<Object> tuple,String messageId),然后设置在bolt中接受相应stream 3.通过basicBatchBolt方式进行 通过提交多个spout 然后在finishBatch 方法中 集合TimeCacheMap 进行join
3.group方式
// 1. Shuffle Grouping: 随机分组, 随机派发stream里面的tuple,保证每个bolt接收到的tuple数目相同.
// 2. Fields Grouping:按字段分组,比如按userid来分组,具有同样userid的tuple会被分到相同的Bolts,而不同的userid则会被分配到不同的Bolts.
// 3. All Grouping:广播发送, 对于每一个tuple,所有的Bolts都会收到.
// 4. Global Grouping: 全局分组,这个tuple被分配到storm中的一个bolt的其中一个task.再具体一点就是分配给id值最低的那个task.
// 5. Non Grouping: 不分组,意思是说stream不关心到底谁会收到它的tuple.目前他和Shuffle grouping是一样的效果,有点不同的是storm会把这个bolt放到这个bolt的订阅者同一个线程去执行.
// 6. Direct Grouping: 直接分组,这是一种比较特别的分组方法,用这种分组意味着消息的发送者举鼎由消息接收者的哪个task处理这个消息.只有被声明为Direct Stream的消息流可以声明这种分组方法.而且这种消息tuple必须使用emitDirect方法来发射.消息处理者可以通过TopologyContext来或者处理它的消息的taskid (OutputCollector.emit方法也会返回taskid)
//拓扑是一个树型结构,消息(元组)穿过其中一条或多条分支。树上的每个节点都会调用ack(tuple)或fail(tuple),Storm因此知道一条消息是否失败了,并通知那个/那些制造了这些消息的spout(s)。既然一个Storm拓扑运行在高度并行化的环境里,跟踪始发spout实例的最好方法就是在消息元组内包含一个始发spout引用。这一技巧称做锚定(译者注:原文为Anchoring)
4.BaseBatchBolt 批次处理,会等待所有的相应的fieldGroup/其他group 完成 最后一次调用并调用finishBatch方法 ,所以可以用来求TopN问题,比如redis zsortSet的跳表 结构。
下面是一个实例
/** * 进行vladdistance 聚合 并 发起从索引中提取数据 * @author zhuyuping * 2016年3月5日 */ public class ImageVladIndexSearchBolt extends BaseBatchBolt{ Object uuid; BatchOutputCollector collector; private double[][] distances; Map<Integer,double[][]> knns; QueryRunner runner; byte[] imgdatas=null; @Override public void prepare(Map conf, TopologyContext context, BatchOutputCollector collector, Object id) { this.uuid=id; this.collector=collector; this.runner=new QueryRunner(DBHelper.getDateSource()); this.distances=new double[16][256]; this.knns=AliyunOSSUtils.getKNNDatas(); } @Override public void execute(Tuple tuple) { //declarer.declare(new Fields("id","key","query","k")); Integer key=tuple.getInteger(1); //System.out.println("======================"+key); if(imgdatas==null) imgdatas=tuple.getBinary(4); double[] qus=(double[]) tuple.getValue(2); //System.out.println("qu is $$$$$$ "+qus.length); Integer k=tuple.getInteger(3); final BoundedPriorityQueue<IntDoublePair> queue = new BoundedPriorityQueue<IntDoublePair>( k, IntDoublePair.SECOND_ITEM_ASCENDING_COMPARATOR); double[][] nn=knns.get(key); for (int j = 0; j < k; j++) { double score = DoubleFVComparison.SUM_SQUARE.compare(qus,nn[j]); int index = key; IntDoublePair wp = new IntDoublePair(index, score); wp = queue.offerItem(wp); } List<IntDoublePair> pair = queue.toOrderedListDestructive();// double[] double[] sort = new double[pair.size()]; for (int j = 0; j < pair.size(); j++) { sort[j] = pair.get(j).second; //System.out.println("vlad sort is ********************"+sort[j]); } this.distances[key]=sort; System.out.println("=================="+this.distances[key][70]); } @Override public void finishBatch() { //得到所有的距离 开始进行索引搜索 mysql 表中获取count 以及分成10页 System.out.println("=================="+this.distances[0][70]); try{ List<VladIndex> vladIndexs=runner.query("select * from vladindex", new ResultSetHandler<List<VladIndex>>() { @Override public List<VladIndex> handle(ResultSet rs) throws SQLException { List<VladIndex> vladindexs=Lists.newArrayList(); while(rs.next()){ String id=rs.getString("id"); String vlad=rs.getString("vlad"); vladindexs.add(new VladIndex(id,vlad)); } return vladindexs; } }); // ByteArrayDataOutput out=ByteStreams.newDataOutput(); // BinIO.storeDoubles(this.distances,out); // byte[] distancesdatas=out.toByteArray(); AliyunOCSService.put(uuid+"distance", distances); for (VladIndex vladIndex : vladIndexs) { //System.out.println("!!!!!!!!!!!!!!!!!!!!!!!!"+distancesdatas.length/1024); collector.emit(new Values(uuid.toString(),vladIndex.getId(),vladIndex.getDatas(),imgdatas)); } }catch(Exception e){ e.printStackTrace(); } } @Override public void declareOutputFields(OutputFieldsDeclarer declarer) { declarer.declare(new Fields("id","imageid","datas","imagedatas")); } }
5.Drpc 同 实时外部调用,接受来自外部的drpc请求,并传入调用,默认有个drpc Spout 他是等待外部链接 如果没有 就return 也可以自己写 下面我们使用的是一个自定义好的方式。不过注意一点 就是每次必须提交回话id
在Triendle中 一样也有drpc 。
就如同上文 这个id 就是。
declarer.declare(new Fields("id"," LinearDRPCTopologyBuilder builder = new LinearDRPCTopologyBuilder("search"); builder.addBolt(new IndexSearchTaskBolt()); builder.addBolt(new ImageSearchBolt(), 10).fieldsGrouping(new Fields("type")); builder.addBolt(new ImageSurfCompareBolt(), 30).fieldsGrouping(new Fields("imageid")); //builder.addBolt(new ImageTopScoreBolt()).globalGrouping(); builder.addBolt(new ImageTopScoreBolt()).fieldsGrouping(new Fields("id")); return builder; main方法中 LinearDRPCTopologyBuilder builder = construct(); Config conf = new Config(); if (args == null || args.length == 0) { conf.setMaxTaskParallelism(5); LocalDRPC drpc = new LocalDRPC(); LocalCluster cluster = new LocalCluster(); cluster.submitTopology("search", conf, builder.createLocalTopology(drpc)); try { for (int i = 0; i < 4; i++) { String result=drpc.execute("search", Base64.encodeBase64String(Files.toByteArray(new File("c:/baidu/test3.jpg")))); System.out.println(i+" ------------------------------------- "+result); } } catch (IOException e) { } cluster.shutdown(); drpc.shutdown(); }else { conf.put(Config.TOPOLOGY_NAME, "search"); conf.put(Config.DRPC_PORT, 3772); conf.put(Config.DRPC_SERVERS,Lists.newArrayList("10.47.50.235","10.47.49.206")); conf.put(Config.NIMBUS_HOST, "10.47.50.235"); conf.put(Config.NIMBUS_THRIFT_PORT, 6627); conf.setMaxSpoutPending(5000); conf.setNumWorkers(10); conf.setNumAckers(5); conf.put("topology.spout.max.batch.size", 1000 /* x1000 i.e. every tuple has 1000 feature vectors*/); conf.put("topology.trident.batch.emit.interval.millis", 1000); conf.put(Config.STORM_CLUSTER_MODE, "distributed"); conf.put(Config.NIMBUS_TASK_TIMEOUT_SECS, 10); StormSubmitter.submitTopologyWithProgressBar(args[0], conf, builder.createRemoteTopology()); } }
本地运行
2.Storm 更高层次链式API Trident
字数好像提醒超过最大限制了 这里解释一下 就截图了
1.each 类似于上面一片文章的spark 中的map 可以自定义转换处理 就是基本的处理
下面代码 我抽出一部分代码 ,比如下面一行代码,第一个参数表示置顶要使用的流数据的field
中间是被处理的方法,最后面的一行表示生成length 加到前面的 已经有的field 后面
each(new Fields("word"), new StringLength(), new Fields("length"))
2.project 这个其实就是each 每一次可以生成新的field ,但是以前的field数据并没有删除 project就是 保留project里面需要的field
3.aggregate 另外by xx 可以另行观看源码 聚合 也就是类似spark reduce aggreagete
4.partitionBy 分区 其实 就是上面storm基本类型的group 是一样的 ,重定向流
5.Join trident join很简单, topology.join(hashtags, new Fields("tweetId"), urls, new Fields("tweetId"), new Fields("tweetId", "hashtag", "url"))
分别为流的join的字段 比如 id - age id-name 通过id join 就是 id name age
5.测试
首先我贴出来一张实例
private static StormTopology buildTopology() { FixedBatchSpout spout = new FixedBatchSpout(new Fields("sentence"), 3, new Values("the cow jumped over the moon"), new Values("the man went to the store and bought some candy"), new Values("four score and seven years ago"), new Values("how many apples can you eat")); spout.setCycle(true); TridentTopology topology = new TridentTopology(); topology.newStream("spout", spout) //no name .each(new Fields("sentence"), new Split(), new Fields("word")) .partitionBy(new Fields("word")) .name("abc") .each(new Fields("word"), new StringLength(), new Fields("length")) .partitionBy(new Fields("length")) .name("def") .aggregate(new Fields("length"), new Count(), new Fields("count")) .partitionBy(new Fields("count")) .name("ghi") .aggregate(new Fields("count"), new Sum(), new Fields("sum")); return topology.build(); } public static void main(String[] args) throws Exception { StormTopology topology = buildTopology(); Config conf = new Config(); LocalCluster cluster = new LocalCluster(); cluster.submitTopology("search", conf, topology); //StormSubmitter.submitTopology("search", conf, topology); }
本地运行结果