Storm 可靠的与不可靠的消息

设计拓扑时,一件很重要的事情就是要考虑消息的可靠性。如果消息不能被处理而丢失是很严重的问题,我们需要决定如何处理丢失的消息,如何与拓扑作为一个整体处理。例如,处理银行存款的时候,事物一致性是很重要的,不能失去任何消息,任何消息都要被处理。在Storm中,根据每个拓扑的需要,保证消息可靠性,这涉及一个平衡:一个可靠的拓扑必须处理丢失的消息,这就需要更多的资源;一个不可靠的拓扑可能会丢失一些消息,但不占用资源。不管你选择哪一种可靠性策略,Storm都可以提供工具来实现它。

为了管理Spout的可靠性,可以在发射元组的时候,在元组里面包含一个消息ID,如下:

collector.emit(new Values(...),tupleID);
当元组处理成功时调用ack()方法,当元组处理失败时调用fail()方法。当元组被所有的目标Bolt和所有的锚定Bolt所处理时,认为元组处理成功。当如下情况发生时,元组处理会失败:

1.collector.fail(tuple)方法被目标Spout调用。

2.处理时间超过配置的超时时间。


我们以银行业务来深入了解,消息的可靠性,有一下要求:

1.如果一个事物失败,则重发消息。

2.如果事物失败了多次,则终止拓扑。


在拓扑中,有1个Spout和1个Bolt。Spout会发送100个随机的事物ID,Bolt在接收一个元组时有80%的可能性会失败。Bolt使用Map来发射事物消息元组,因此很容易对消息进行重发。

Spout的主要成员变量定义如下:

//最大失败次数
	private static final Integer MAX_FAILS = 2;
	//全部的消息Map
	private Map<Integer, String> messages;
	//消息的失败次数计数Map
	private Map<Integer, Integer> transactionFailureCount;
	//发送的消息Map
	private Map<Integer, String> toSend;
	//SpoutOutputCollector对象
	private SpoutOutputCollector collector;


在open()方法中进行一系列的初始化:

@Override
	public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
		
		
		//创建随机数生成器
		Random random = new Random();
		//出事化相关对象
		messages = new HashMap<Integer, String>();
		toSend = new HashMap<Integer, String>();
		transactionFailureCount = new HashMap<Integer, Integer>();
		
		//遍历100此,模拟100条消息
		for (int i=100; i<100; i++){
			//添加消息到"全部的消息Map集合"
			messages.put(i, "transaction_" + random.nextInt());
			//每条消息的错误次数都置为0
			transactionFailureCount.put(i, 0);
		}
		
		//把"全部的消息Map集合" 添加到"发送的消息Map集合"
		toSend.putAll(messages);
		
		//初始化发射器
		this.collector = collector;
	}

nextTuple()方法定义如下:

@Override
	public void nextTuple() {

		if (!toSend.isEmpty()) {// 如果发射消息的Map集合不为空
			// 遍历发射消息的Map集合进行消息的发射
			for (Map.Entry<Integer, String> transactionEntry : toSend.entrySet()) {
				// 获取消息ID
				Integer transactionId = transactionEntry.getKey();
				// 获取消息内容
				String transactionMessage = transactionEntry.getValue();
				// 把消息发射出去(带上ID号)
				collector.emit(new Values(transactionMessage), transactionId);
			}
			// 把发射消息的Map集合清空,以便存储要重新发射的消息
			toSend.clear();
			
			//休眠1秒钟
			Utils.sleep(1000);
		}
	}
如果toSend集合不为空的话,就说明存在消息等待发送,则把每个消息的ID和消息的内容作为一个元组发送,然后清空toSend集合(方便存储需要重发的消息),这里要注意的是在nextTuple中调用Map的clear()方法是安全的,因为nextTuple()、fail()、ack()方法是修改Map的方法,他们都运行在相同的线程中。


messages和failCounterMessages两个Map用来跟踪等待发送的事物消息以及每笔交易已经失败的次数。

ack()方法表示操作成功时的相应,这里通过msgId来删除每个列表中的事物消息:

@Override
	public void ack(Object msgId) {

		//如果消息发送成功,则从"所有消息Map集合"中删除一条消息
		messages.remove(msgId);
		//错误计数Map也要相应的删除一条记录
		transactionFailureCount.remove(msgId);
	}


fail()方法决定是否重发一个事物消息,或者事物消息已经失败了太多次而最终是完全失败(不能被重发),如果在拓扑中使用广播分组,那么任何Bolt实例失败,Spout的fail()方法都会被调用。

@Override
	public void fail(Object msgId) {

		//获取事物ID
		Integer transactionId = (Integer) msgId;
		//通过事物ID获取事物失败的次数
		Integer failures = transactionFailureCount.get(transactionId) + 1;
		
		//判断事物失败的次数是否大于最大允许失败次数
		if (failures >= MAX_FAILS){
			//如果失败次数大于或者等于最大允许失败次数,终止拓扑并抛出异常
			throw new RuntimeException("该事物失败过多,已经不能再被发送");
		} else {
			//如果失败的次数小于最大允许失败的次数,我们保存失败次数,并把该消息放入"发送消息集合Map"进行重新发送
			transactionFailureCount.put(transactionId, failures);
			//放入toSend集合从新发送
			toSend.put(transactionId, messages.get(transactionId));
		}
	}
fail()方法会检查已经失败的事物的次数。如果一个事物失败了很多次,超过最大允许失败次数,会抛出一个RuntimeException异常并终止运行中的Worker进程。否则,保存失败技术,把事物消息放到toSend集合进行重发。

Storm节点不维护状态。如果在内存中存储消息,并且节点又宕机了,将会失去所有的信息。所以,Storm节点的状态由外部的Zookeeper集群所维护。Storm是一种快速失败的系统。如果抛出一个异常,拓扑将会失败。但Storm会在一个一致性状态中重启进程,然后正常恢复进程的执行。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值