Spout 并行重复读取问题

来源:互联网 发布:java将int转换成string 编辑:程序博客网 时间:2024/05/22 18:57

Spout如果单单设置executor的并行个数,那么其输出可能是有重复的,这样的并行策略是有问题的。

比如下面的Spout:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package wc.redis.spout;  
  2.   
  3. import java.util.Map;  
  4.   
  5. import redis.clients.jedis.Jedis;  
  6. import wc.redis.util.RedisUtils;  
  7. import backtype.storm.spout.SpoutOutputCollector;  
  8. import backtype.storm.task.TopologyContext;  
  9. import backtype.storm.topology.OutputFieldsDeclarer;  
  10. import backtype.storm.topology.base.BaseRichSpout;  
  11. import backtype.storm.tuple.Fields;  
  12. import backtype.storm.tuple.Values;  
  13.   
  14. public class WCSpout extends BaseRichSpout {  
  15.   
  16.     /** 
  17.      *  
  18.      */  
  19.     private static final long serialVersionUID = 1L;  
  20.     private SpoutOutputCollector collector;  
  21.     private Jedis jedis;  
  22.     Integer taskId;  
  23.     String conponentId;  
  24.     String slow_fast;  
  25.     @Override  
  26.     public void open(@SuppressWarnings("rawtypes") Map conf, TopologyContext context,  
  27.             SpoutOutputCollector collector) {  
  28.         this.collector = collector;  
  29.         slow_fast = (String)conf.get("slow_fast");  
  30.         jedis = RedisUtils.connect(RedisUtils.HOSTNAME, RedisUtils.PORT, RedisUtils.INSERT_DB);  
  31.         taskId = context.getThisTaskId();  
  32.         conponentId = context.getThisComponentId();  
  33.         context.getThisTaskIndex();  
  34.         System.out.println(RedisUtils.getCurrDateWithInfo(conponentId, taskId, " WCSpout初始化完成!"));  
  35.     }  
  36.   
  37.     @Override  
  38.     public void nextTuple() {  
  39.         long interval =0;  
  40.         while(true){// 获取数据  
  41.             interval++;  
  42.             String zero = getItem("0");  
  43.             String one = getItem("1");  
  44.             String two =  getItem("2");  
  45.               
  46.             try {  
  47.                 Thread.sleep(200);// 每200毫秒发送一次数据  
  48.             } catch (InterruptedException e) {  
  49.                 e.printStackTrace();  
  50.             }   
  51.             if(zero==null||one==null||two==null){  
  52.                 // do nothing  
  53.                 // 没有数据  
  54.                 if(interval%15==0){  
  55. //                  System.out.println(new java.util.Date()+":ConponentId:"+conponentId+",taskID:"+taskId+  
  56. //                      ",spout:No Data...");  
  57. //                  System.out.println(RedisUtils.getCurrDateWithInfo(conponentId, taskId, "spout:No data..."));  
  58.                 }  
  59.             }else{  
  60.                 this.collector.emit(new Values(zero+","+one+","+two));  
  61.                 if(interval%15==0&&"fast".equals(slow_fast)){  
  62. //                  System.out.println(new java.util.Date()+":ConponentId:"+conponentId+",taskID:"+taskId+  
  63. //                      ",spout:["+zero+","+one+","+two+"]");  
  64.                     System.out.println(RedisUtils.getCurrDateWithInfo(conponentId, taskId, "Spout:["+zero+","+one+","+two+"]"));  
  65.                 }else if("slow".equals(slow_fast)){  
  66.                     System.out.println(RedisUtils.getCurrDateWithInfo(conponentId, taskId, "Spout:["+zero+","+one+","+two+"]"));  
  67.                 }else{  
  68.                     new RuntimeException("Wrong argument!");  
  69.                 }  
  70.             }  
  71.               
  72.         }     
  73.     }  
  74.   
  75.     @Override  
  76.     public void declareOutputFields(OutputFieldsDeclarer declarer) {  
  77.         declarer.declare(new Fields("line"));  
  78.     }  
  79.       
  80.     /** 
  81.      * Redis中获取键值并删除对应的键 
  82.      * @param index 
  83.      */  
  84.     private String getItem(String index){  
  85.         if(!jedis.exists(index)){  
  86.             return null;  
  87.         }  
  88.         String val = jedis.get(index);  
  89. //      if(val==null||"null".equals("null")){  
  90. //          return ;  
  91. //      }  
  92.           
  93.         jedis.del(index);  
  94.         return val;  
  95.     }  
  96.   
  97. }  

这个Spout从Redis服务器中获取数据,获取后把对应的数据删除。两个Spout都同时读取了数据,然后进行了输出,同时只能有一个Spout删除了Redis中的数据,这样就会有重复数据输出了,类似图1:


图1

从图1红色区域可以看到Spout的输出,从时间可以看出两个输出只相差了1毫秒;从蓝色的框也可以看出Spout的下一个Bolt获取了两条相同的数据,这就说明Spout输出了重复的数据;

所以Spout的并行策略应该是获取taskid,根据数据的特征来选择(可以随机)需要处理的executor,代码如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package wc.redis.spout;  
  2.   
  3. import java.util.Map;  
  4.   
  5. import redis.clients.jedis.Jedis;  
  6. import wc.redis.util.RedisUtils;  
  7. import backtype.storm.spout.SpoutOutputCollector;  
  8. import backtype.storm.task.TopologyContext;  
  9. import backtype.storm.topology.OutputFieldsDeclarer;  
  10. import backtype.storm.topology.base.BaseRichSpout;  
  11. import backtype.storm.tuple.Fields;  
  12. import backtype.storm.tuple.Values;  
  13.   
  14. public class WCSpout extends BaseRichSpout {  
  15.   
  16.     /** 
  17.      *  
  18.      */  
  19.     private static final long serialVersionUID = 1L;  
  20.     private SpoutOutputCollector collector;  
  21.     private Jedis jedis;  
  22.     Integer taskId;  
  23.     String componentId;  
  24.     String slow_fast;  
  25.     int numTasks ;  
  26.     int thisTaskId;  
  27.     @Override  
  28.     public void open(@SuppressWarnings("rawtypes") Map conf, TopologyContext context,  
  29.             SpoutOutputCollector collector) {  
  30.         this.collector = collector;  
  31.         slow_fast = (String)conf.get("slow_fast");  
  32.         jedis = RedisUtils.connect(RedisUtils.HOSTNAME, RedisUtils.PORT, RedisUtils.INSERT_DB);  
  33.         taskId = context.getThisTaskId();  
  34.         componentId = context.getThisComponentId();  
  35.         numTasks = context.getComponentTasks(componentId).size();  
  36.         thisTaskId = context.getThisTaskIndex();  
  37.         System.out.println(RedisUtils.getCurrDateWithInfo(componentId, taskId, " WCSpout初始化完成!"));  
  38.     }  
  39.   
  40.     @Override  
  41.     public void nextTuple() {  
  42.         long interval =0;  
  43.         while(true){// 获取数据  
  44.             interval++;  
  45.             String zero = getItem("0");  
  46.             String one = getItem("1");  
  47.             String two =  getItem("2");  
  48.               
  49.             try {  
  50.                 Thread.sleep(200);// 每200毫秒发送一次数据  
  51.             } catch (InterruptedException e) {  
  52.                 e.printStackTrace();  
  53.             }   
  54.             if(zero==null||one==null||two==null){  
  55.                 // do nothing  
  56.                 // 没有数据  
  57. //              if(interval%15==0){  
  58. //              }  
  59.             }else{  
  60.                 String tmpStr =zero+","+one+","+two;  
  61.                 if(thisTaskId==tmpStr.hashCode()%numTasks){ // spout负载均衡  
  62.                     this.collector.emit(new Values(tmpStr));  
  63.                   
  64.                     if(interval%15==0&&"fast".equals(slow_fast)){  
  65.                         System.out.println(RedisUtils.getCurrDateWithInfo(String.valueOf(thisTaskId),  
  66.                                 taskId, "Spout:["+zero+","+one+","+two+"]"));  
  67.                     }else if("slow".equals(slow_fast)){  
  68.                         System.out.println(RedisUtils.getCurrDateWithInfo(String.valueOf(thisTaskId),  
  69.                                 taskId, "Spout:["+zero+","+one+","+two+"]"));  
  70.                     }else{  
  71.                         new RuntimeException("Wrong argument!");  
  72.                     }  
  73.                 }  
  74.             }  
  75.               
  76.         }     
  77.     }  
  78.   
  79.     @Override  
  80.     public void declareOutputFields(OutputFieldsDeclarer declarer) {  
  81.         declarer.declare(new Fields("line"));  
  82.     }  
  83.       
  84.     /** 
  85.      * Redis中获取键值并删除对应的键 
  86.      * @param index 
  87.      */  
  88.     private String getItem(String index){  
  89.         if(!jedis.exists(index)){  
  90.             return null;  
  91.         }  
  92.         String val = jedis.get(index);  
  93. //      if(val==null||"null".equals("null")){  
  94. //          return ;  
  95. //      }  
  96.           
  97.         jedis.del(index);  
  98.         return val;  
  99.     }  
  100.   
  101. }  
使用上面的代码后,Spout的输出就不会重复了,同时也达到了distribution的目的,如图2


图2

从图2中红色框中可以看到从时间21:58 883 taskId5 Spout输出后,接着到了taskID6 21:59 494 Spout输出,然后又到taskID5 21:59 905 的Spout输出,并没有重复记录;

0 0