Storm 从入门到精通 第十六讲 Storm 可靠性 - ack 与 fail 及 数据拆分(多Bolt处理),事务不一致

Spout是 Storm数据流的入口,在设计拓扑时,一件很重要的事情就是需要考虑消息的可靠性,如果消息不能被处理而丢失是很严重的问题。

以一个传递消息并且实时处理的例子,来说明这个问题。

Message Spout: 发送一批数据, 格式使用逗号分隔, 共计4条Subject,每个Subject 包含两个单词。
private static final String[] Subjects = new String[] { "Groovy,Oracle", "Springboot,Java", "C++,VB","Ruby,Scala" }; 

SplitSubjectBolt:接收Spout 每一个 Subject ,按照逗号分词,通过Global Grouping 发送给下一个Writer bot。

WriteSubjectBolt: 将SplitSubjectBolt分词每个Word 记录到日志中。



1. MessageReliabilityTopology

package com.john.learn.storm.reliability;

import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;

import com.john.learn.storm.reliability.bolt.SplitSubjectBolt;
import com.john.learn.storm.reliability.bolt.WriteSubjectBolt;
import com.john.learn.storm.reliability.spout.MessageSpout;

public class MessageReliabilityTopology {

	public static void main(String[] args) throws InterruptedException {

		Config config = new Config();
		config.setNumWorkers(2);
		config.setDebug(false);

		TopologyBuilder topologyBuilder = new TopologyBuilder();

		topologyBuilder.setSpout("MessageSpout", new MessageSpout());

		topologyBuilder.setBolt("SplitSubjectBolt", new SplitSubjectBolt()).shuffleGrouping("MessageSpout");

		topologyBuilder.setBolt("WriteSubjectBolt", new WriteSubjectBolt()).globalGrouping("SplitSubjectBolt");

		LocalCluster localCluster = new LocalCluster();
		localCluster.submitTopology("MessageReliabilityTopology", config, topologyBuilder.createTopology());

		Thread.sleep(20000);

		localCluster.killTopology("MessageReliabilityTopology");
		localCluster.shutdown();
	}
}

2. MessageSpout

package com.john.learn.storm.reliability.spout;

import java.util.Map;

import org.apache.storm.spout.SpoutOutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseRichSpout;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;

public class MessageSpout extends BaseRichSpout {

	public void nextTuple() {

		if (index >= Subjects.length) {
			return;
		}
		// 第一个参数Message, 第二个 message Id
		collector.emit(new Values(index, Subjects[index]), index);

		System.out.println("[" + Thread.currentThread().getName() + "] " + Subjects[index] + " 消息发送完毕,消息ID:" + index);

		index++;

	}

	public void open(Map config, TopologyContext contexxt, SpoutOutputCollector collector) {

		this.collector = collector;

	}

	public void declareOutputFields(OutputFieldsDeclarer fieldsDeclarer) {

		fieldsDeclarer.declare(new Fields("Index", "Subject"));

	}

	@Override
	public void ack(Object msgId) {

		System.out.println("[" + Thread.currentThread().getName() + "] 消息成功处理,消息ID:" + msgId);

	}

	@Override
	public void fail(Object msgId) {

		System.out.println("[" + Thread.currentThread().getName() + "] 消息处理失败,消息ID:" + msgId);

		System.out.println("[" + Thread.currentThread().getName() + "] 消息重发, 消息ID:" + msgId);
		collector.emit(new Values(msgId, Subjects[(Integer) msgId]), msgId);

		System.out.println("[" + Thread.currentThread().getName() + "] 消息重发完毕, 消息ID:" + msgId);
	}

	@Override
	public void close() {

		// Release Resource

		// 例如: Close File 、 Close database\ Close socket etc.

	}

	private SpoutOutputCollector collector;

	private static final String[] Subjects = new String[] { "Groovy,Oracle", "Springboot,Java", "C++,VB",
			"Ruby,Scala" };

	private int index = 0;

	private static final long serialVersionUID = 1L;
}

通过ack 和 fail 方法 完成异常数据重复发送,增加系统可靠性

3. SplitSubjectBolt

package com.john.learn.storm.reliability.bolt;

import java.util.Map;
import java.util.Random;

import org.apache.storm.shade.org.apache.commons.lang.StringUtils;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.BasicOutputCollector;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseBasicBolt;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;

import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;

import com.john.learn.storm.reliability.dice.DiceUtils;

public class SplitSubjectBolt extends BaseRichBolt {

	public void execute(Tuple tuple) {

		String subject = tuple.getStringByField("Subject");
		Integer index = tuple.getIntegerByField("Index");

		try {

			String[] words = StringUtils.splitByWholeSeparator(subject, ",");

			for (String word : words) {

				// 故意导致系统异常,测试Storm 容错性
				if (failed && "Java".equals(word)) {

					throw new RuntimeException("Failed Message Word:" + word);
				}

				// 发送消息必须携带 Tuple,用于异常处理
				collector.emit(tuple, new Values(index, word));

				System.out.println("[" + Thread.currentThread().getName() + "] 成功发送单词到下一个Strom Bolt:" + word + " 第"
						+ index + "行数据!");

			}

			// 手工标记成功
			collector.ack(tuple);

		} catch (Throwable e) {

			System.out.println(
					"[" + Thread.currentThread().getName() + "] 失败发送单词到下一个Strom Bolt:Java第" + index + "行数据!开启重新发送!");
			if (DiceUtils.isSmall()) {  // DiceUtils.isSmall() 随机模拟Bug修复

				// 失败已经处理完毕
				failed = false;
			}
			// 手工标记失败
			collector.fail(tuple);
		}
	}

	public void prepare(Map config, TopologyContext context, OutputCollector collector) {

		this.collector = collector;
	}

	public void declareOutputFields(OutputFieldsDeclarer fieldsDeclarer) {

		fieldsDeclarer.declare(new Fields("Index", "Word"));
	}

	private OutputCollector collector;

	private boolean failed = true;

	private static final long serialVersionUID = 1L;
}

4. WriteSubjectBolt

package com.john.learn.storm.reliability.bolt;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import java.util.Random;

import org.apache.storm.shade.org.apache.commons.lang.StringUtils;
import org.apache.storm.task.OutputCollector;
import org.apache.storm.task.TopologyContext;
import org.apache.storm.topology.BasicOutputCollector;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.topology.base.BaseBasicBolt;
import org.apache.storm.topology.base.BaseRichBolt;
import org.apache.storm.tuple.Fields;

import org.apache.storm.tuple.Tuple;
import org.apache.storm.tuple.Values;

import com.john.learn.storm.reliability.dice.DiceUtils;
import com.john.learn.storm.reliability.storage.DatabaseSimulatorDataStorage;

public class WriteSubjectBolt extends BaseRichBolt {

	public void prepare(Map config, TopologyContext context, OutputCollector collector) {

		this.collector = collector;

		File file = new File("C:\\Storm\\MessageReliabilityBolt\\message.txt");

		file.getParentFile().mkdirs();

		try {

			writer = new FileWriter(file);

		} catch (IOException e) {

			e.printStackTrace();
		}
	}

	public void declareOutputFields(OutputFieldsDeclarer fieldsDeclarer) {

	}

	public void execute(Tuple tuple) {

		String word = tuple.getStringByField("Word");
		Integer index = tuple.getIntegerByField("Index");

		String marker = "WriteSubjectBolt.Success." + index + "." + word;

		try {

//			if (DatabaseSimulatorDataStorage.getInstance().isSuccess(marker)) {
//				// 手工标记成功
//				collector.ack(tuple);
//				return;
//			}

			// 故意导致系统异常,测试Storm 容错性
			if (failed && "Java".equals(word)) {

				int i = 1 / 0;
			}

			writer.write(word);
			writer.write("\r\n");
			writer.flush();

			System.out.println("[" + Thread.currentThread().getName() + "] 成功写入文件:" + word + " 第" + index + "行数据!");

			// 手工标记成功
			collector.ack(tuple);

			//记录成功标志
			//DatabaseSimulatorDataStorage.getInstance().markSuccess(marker);

		} catch (Throwable e) {

			System.out.println("[" + Thread.currentThread().getName() + "] 写入文件失败:" + word + " 第" + index + "行数据!");

			// 失败已经处理完毕

			if (DiceUtils.isSmall()) {
				failed = false;
			}
			// 手工标记失败
			collector.fail(tuple);
		}
	}

	@Override
	public void cleanup() {

		try {
			writer.close();
		} catch (Exception e) {

		}
	}

	private OutputCollector collector;

	private boolean failed = true;

	private Writer writer = null;

	private static final long serialVersionUID = 1L;
}

5. DiceUtils

package com.john.learn.storm.reliability.dice;

import java.util.Random;

import clojure.main;

public class DiceUtils {

	public static int dice() {

		return random.nextInt(6) + 1;
	}

	public static boolean isSmall() {

		return dice() < 4;
	}

	private static Random random = new Random();
	
	public static void main(String[] args) {
		System.out.println(DiceUtils.isSmall());
	}
}

运行结果说明:

1.  4组Subject 待发送, 每个Subject 由两个词组成。

2. 发送4个Subject

 

程序片段:MessageSpout


3. Split bolt 中 Java 分词导致系统异常,需要重新发送。但注意:

"Springboot,Java" 中 Springboot 成功发送个WriterBolt。 Java 导致 整个"Springboot,Java"  subject 重新发送,即Springboot必将出现重复处理,没有实现弱事务一致性



程序片段:SplitSubjectBolt


3. WriteSubjectBolt  中仅仅成功写入 Springboot 没有 Java, 但随后 Subject (Springboot,Java) 由于 Failed, 将重新从Spout发送。








4. 重发流程:


    Subject数据 共计 4个 其中 2,3, 0 索引的数据成功发送完毕。 对于1号索引 "Springboot, Java" 由于系统异常,重复发送
    第一次error:[Thread-19-SplitSubjectBolt-executor[2 2]] 失败发送单词到下一个Strom Bolt:Java第1行数据!开启重新发送!
    出现 Spout (1)的重复, 然后 Split 成功接收到数据,发送给 Writer Bolt (2)
    但由于Writer 写入 Java 异常 (3),导致数据"Springboot, Java" 再次从 Spout 重新发送(4)。
    然后 Split 成功接收到数据,发送给 Writer Bolt (5), 由于通过Dice随机修复失败 (6)!DiceUtils.isSmall() 导致Spout重发(7)
     然后 Split 成功接收到数据,发送给 Writer Bolt (8),这次 WriterBolt 成功写入 Java (9), 最终任务处理成功!(10)


    由于 "Springboot,Java" 数据在Split Bolt 拆分成2个独立事务,Springboot必将出现重复处理,没有实现弱事务一致性。 即文件中写入多个Sprintboot
   


C:\Storm\MessageReliabilityBolt\message.txt


随后讲解如下确保数据一致性,避免重复处理。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值