Bolt

本文介绍了Apache Storm中Bolt接口的不同类型及其功能,包括IBolt、IRichBolt、IBasicBolt和IBatchBolt等。文章还详细解释了Bolt组件的工作原理和生命周期,以及如何使用Bolt进行消息处理。

转载自:https://blog.youkuaiyun.com/lulongzhou_llz/article/details/46399457

Bolt 接口

  Storm中定义的Bolt接口主要有IBolt 、IRichBolt 、IBasicBolt和IBatchBolt,

IBolt

  IBolt定义了Bolt的功能集合。
  Bolt是Storm中的基础运行单位,当其启动并有消息输人时,将调用execute方法来进行处理。与ISpout类似, IBolt对象在提交时也会被序列化为字节数组,具体的执行节点通过反序列化的方法得到该对象,并调用prepare回调方法。用户应将复杂对象的初始化放在prepare回调方法中实现,以保证每个具体对象都可以正确初始化。
  对象被销毁时,将调用cleanup回调方法,但是Storm并不保证该方法一定被执行。
  通常,在execute方法的实现中会对输人消息进行处理,这有可能产生新消息需要发送到下游节点,最后还要对输入的消息进行ack操作。如果消息处理失败,则需对输入的消息进行fail操作 这是保证Ack消息系统可以正常工作的基础。

IRichBolt

  IRichBolt需要同时实现IComponent以及IBolt接口。在实际使用中,IRichBolt是实现Topology组件的主要接口。

IBasicBolt

  IBasicBolt接口的定义与IBolt基本一致,具体的实现要求也与IBolt相同,它与IBolt的区别在于以下两点:

  1. 它的输出收集器使用的是BasicOutputCollector,并且该参数被放在了execute方法中而不是prepare中。
  2. 它实现了IComponent接口,这表明它可以用来定义Topology组件。

  IBasicBolt的主要作用是为用户提供一种更简单的Bolt编写方式。基于IBasicBolt编写的好处是Storm框架本身帮你处理了所发出消息的ackfailanchor操作,这是由执行器BasicBoltExecutor实现的。

IBatchBolt

  区别于IBasicBolt接口,IBatchBolt主要用于Storm中的批处理。 目前,Storm主要用该接口来实现可靠的消息传输,在这种情况下,批处理会比单一消息处理更为高效。Storm的事务Topology以及Trident主要是基于IBatchBolt的。相比前面的IBolt、IBasicBolt和IRichBolt, IBatchBolt中多了一个finishBatch方法, 它在一个批处理结束时被调用。此外, IBatchBolt还去除了cleanup方法。


Bolt生命周期

  Bolt是这样一种组件,它把元组作为输入,然后产生新的元组作为输出。实现一个bolt时,通常需要实现IRichBolt接口。Bolts对象由客户端机器创建,序列化为拓扑,并提交给集群中的主机。然后集群启动工人进程反序列化bolt,调用prepare,最后开始处理元组。
  要创建一个bolt对象,它通过构造器参数初始化成员属性,bolt被提交到集群时,这些属性值会随着一起序列化。

  拓扑是一个树型结构,消息(元组)穿过其中一条或多条分支。树上的每个节点都会调用ack(tuple)fail(tuple),Storm因此知道一条消息是否失败了,并通知那个/那些制造了这些消息的spout(s)。既然一个Storm拓扑运行在高度并行化的环境里,跟踪始发spout实例的最好方法就是在消息元组内包含一个始发spout引用。这一技巧称做锚定(Anchoring)。

class SplitSentence implenents IRichBolt {
    private OutputCollector collector;

    public void prepare(Map conf, TopologyContext context, OutputCollector collector) {
        this.collector = collector;
    }

    public void execute(Tuple tuple) {
        String sentence = tuple.getString(0);
        for(String word : sentence.split(" ")) {
            collector.emit(tuple, new Values(word));
        }
        collector.ack(tuple);
    }

    public void cleanup(){}

    public void declareOutputFields(OutputFieldsDeclarer declarer){
        declar.declare(new Fields("word"));
    }
}

  锚定发生在调用collector.emit()时。正如前面提到的,Storm可以沿着元组追踪到始发spout。collector.ack(tuple)collector.fail(tuple)会告知spout每条消息都发生了什么。当树上的每条消息都已被处理了,Storm就认为来自spout的元组被全面的处理了。如果一个元组没有在设置的超时时间内完成对消息树的处理,就认为这个元组处理失败。默认超时时间为30秒。可以通过修改Config.TOPOLOGY_MESSAGE_TIMEOUT修改拓扑的超时时间。
  spout需要考虑消息的失败情况,并相应的重试或丢弃消息。处理的每条消息要么是确认的(collector.ack())要么是失败的(collector.fail())。Storm使用内存跟踪每个元组,所以如果不调用这两个方法,该任务最终将耗尽内存。

多锚定

  为了用bolt连接或聚合数据流,需要借助内存缓冲元组。为了在这一场景下确保消息完成,需要把流锚定到多个元组上。可以向emit方法传入一个元组列表来达成目的。

...
List anchors = new ArrayList();
anchors.add(tuple1);
anchors.add(tuple2);
collector.emit(anchors, values);
...

  通过这种方式,bolt在任意时刻调用ackfail方法,都会通知消息树,而且由于流锚定了多个元组,所有相关的spout都会收到通知。

使用IBasicBolt自动确认

  在许多情况下都需要消息确认。简单起见,Storm提供了另一个用来实现bolt的接口IBasicBolt。对于该接口的实现类的对象,会在执行execute方法之后自动调用ack方法。

class SplitSentence extends BaseBasicBolt {
    public void execute(Tuple tuple, BasicOutputCollector collector) {
        String sentence = tuple.getString(0);
        for(String word : sentence.split(" ")) {
            collector.emit(new Values(word));
        }
}

    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("word"));
    }
}

  分发消息的BasicOutputCollector自动锚定到作为参数传入的元组。

06-17
### Bolt Database 和 Message Queue 的区别与应用 Bolt 是一种嵌入式数据库,使用键值对存储数据[^1]。它在文件级别进行锁定,这意味着在同一时间只能有一个进程访问该文件。因此,如果应用程序需要高并发的读写操作,Bolt 数据库可能不是最佳选择。此外,Bolt 适用于简单的数据存储场景,而不适合复杂的聚合或连接操作[^2]。 相比之下,消息队列(Message Queue)如 Redis 或 Apache Kafka 更适合处理异步消息传递和高吞吐量的需求。消息队列的主要功能是保证消息的可靠传递,并支持复杂的处理逻辑,例如聚合、连接和延迟确认(acking)[^2]。对于需要处理大量消息并确保每个消息都被正确处理的应用程序,消息队列通常是更好的选择。 以下是一个基于 Python 的简单消息队列实现示例,使用了 Redis 作为后端: ```python import redis def process_message(queue_name, message): print(f"Processing message: {message} from queue: {queue_name}") def consume_messages(redis_client, queue_name): while True: message = redis_client.lpop(queue_name) if message: process_message(queue_name, message.decode('utf-8')) # 初始化 Redis 客户端 redis_client = redis.StrictRedis(host='localhost', port=6379, db=0) # 开始消费消息 consume_messages(redis_client, 'my_queue') ``` 在某些情况下,可能需要结合使用 Bolt 和消息队列。例如,可以使用消息队列来处理实时消息传递,同时使用 Bolt 数据库来存储持久化数据[^1]。这种架构能够充分利用两者的优点,满足复杂应用场景的需求。 ### 性能优化与后台线程实现 为了确保性能不受到影响,可以引入后台线程来处理耗时任务。例如,在 Unity 游戏开发中,可以通过实现一个后台线程来定期调用 Photon 的 `SendOutgoingCommands` 方法[^3]。下面是一个简单的实现示例: ```csharp using System.Threading; public class BackgroundTask : MonoBehaviour { private Thread backgroundThread; private bool isRunning = true; void Start() { backgroundThread = new Thread(new ThreadStart(ThreadFunction)); backgroundThread.Start(); } void ThreadFunction() { while (isRunning) { // 调用 Photon 的 SendOutgoingCommands 方法 PhotonNetwork.SendOutgoingCommands(); // 暂停 200 毫秒以避免影响性能 Thread.Sleep(200); } } void OnDestroy() { isRunning = false; backgroundThread.Join(); // 等待线程结束 } } ``` 此代码片段展示了如何通过后台线程定期调用方法,从而避免阻塞主线程并保持游戏流畅运行。 ### 学习策略与数学模型 学习系统的实现也可以借鉴类似的设计思路。例如,基于艾宾浩斯遗忘曲线的间隔重复算法可以用于量化学习成长的核心指标[^4]。通过定义技能复习的时间间隔,系统可以动态调整复习计划,帮助用户更高效地掌握知识。 ```python def spaced_repetition(learning_records, current_time): review_list = [] for record in learning_records: skill, last, interval = record if current_time - last > interval * 86400: # 转换为秒 new_interval = interval * 2 if interval < 15 else 30 review_list.append((skill, new_interval)) return review_list ``` 此函数根据用户的复习记录生成需要复习的技能列表,并动态调整下次复习的时间间隔。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值