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、付费专栏及课程。

余额充值