Storm

参考文献

https://www.cnblogs.com/intsmaze/p/5918087.html

基本概念

  • 一个worker就是一个JVM进程。一个worker中可以运行多个bolt和spout,但是这些bolt和spolt都必须属于一个topology。一台物理机器可以运行多个worker。所有同一个worker里的bolt和spout可以共享该JVM中的资源。为了防止资源重复,建议每一个bolt和spout用单例模式获取资源。一个woker拥有一个端口用于通信。一台机器上可以同时启动多个worker,每个woker占用一个端口。executor是实际执行的线程,可以执行一个或者多个task,但是这些task必须是相同的bolt或者spout。executor数量可以动态调整,task则在代码中指定,所以,你可以把task设置高点(提高bolt和spout的并发度),不然,即使你以后增加并发跳高executor数量,多于task的executor也没有任何意义。

  • storm使用tuple来作为它的数据模型。每个tuple是一堆值,每个值有一个名字,并且每个值可以是任何类型。一个没有边界的、源源不断的、连续的Tuple序列就组成了Stream。

  • storm的每一个executor都会创建自己的执行的bolt和spout的实例。线程之间不共享实例,所以实例(bolt spout)中的字段没有并发问题。

  • 主控节点运行Nimbus守护进程,类似于Hadoop中的jobtracker,负责在集群中分发代码,对节点分配任务,并监视主机故障。每个工作节点运行Supervisor守护进程,负责监听工作节点上已经分配的主机作业,启动和停止Nimbus已经分配的工作进程。supervisor会定时从zookeeper获取拓补信息topologies、任务分配信息assignments及各类心跳信息,以此为依据进行任务分配。在supervisor同步时,会根据新的任务分配情况来启动新的worker或者关闭旧的worker并进行负载均衡

  • storm底层采取netty通信框架,kryo进行序列化。

结构

每个Worker进程配置了一个NettyServer,有一个线程专门负责监听在分配给自己这个worker端口。这个线程把从网络接收到的消息放在对应的task的反序列化队列里。又一个反序列化线程专门负责反序列化,然后放到对应task的执行队列里。每个task执行队列中的事件,最终由该task的executor线程负责执行。
这里写图片描述

Grouping

  • Shuffle Grouping 随机分组
  • Fields Grouping 按照字段分组
  • All Grouping 广播分组(所有都会收到)
  • Global Grouping:全局分组。这种分组会将所有的tuple都发到一个taskid最小的task上。由于所有的tuple都发到唯一一个task上,势必在数据量大的时候会造成资源不够用的情况。
  • Non Grouping
  • Direct Grouping:直接分组,这是一种比较特别的分组方法,用这种分组意味着消息的发送者指定由消息接受者的哪个task处理这个消息。
  • Local or shuffle grouping:如果目标bolt有一个或者多个task在同一个工作进程中,tuple将会被随机发送给这些tasks。否则,和普通的Shuffle Grouping行为一致。

Storm消息机制

为了保证数据能正确的被处理, 对于spout产生的每一个tuple, storm都会进行跟踪。

Storm中有个特殊的task名叫acker,他们负责跟踪spout发出的每一个Tuple的Tuple树(因为一个tuple通过spout发出了,经过每一个bolt处理后,会生成一个新的tuple发送出去)。当acker(框架自启动的task)发现一个Tuple树已经处理完成了,它会发送一个消息给产生这个Tuple的那个task。
Acker的跟踪算法是Storm的主要突破之一,对任意大的一个Tuple树,它只需要恒定的20字节就可以进行跟踪。

实例

定义bolt的输出。下面的输出tuple包含两个字段。

 @Override
    publicvoiddeclareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(newFields("double","triple"));
    }

Storm调优

##日志
不同于一般的JAVA应用,storm的日志配置文件需要手动指定。可以使用logback也可以使用log4j。

ConfigExtension.setUserDefinedLog4jConf(conf, "log4j.properties");

JAVA API

BaseRichBolt和BaseBasicBolt

在BaseBasicBolt中,BasicOutputCollector在emit数据的时候,会自动和输入的tuple相关联,而在execute方法结束的时候那个输入tuple会被自动ack。

//Basicbolt所实现的接口
public interface IBasicBolt extends IComponent {
    void prepare(Map var1, TopologyContext var2);

    void execute(Tuple var1, BasicOutputCollector var2);

    void cleanup();
}

//Richbolt所实现的接口
public interface IBolt extends Serializable {
    void prepare(Map var1, TopologyContext var2, OutputCollector var3);

    void execute(Tuple var1);

    void cleanup();
}

BaseBasicBolt中带了BasicOutputCollector。可以直接使用该BasicOutputCollector向下游发送。BaseRichBolt显然没有这个功能。

   //BasicBoltExecutor类中代码
    public void execute(Tuple input) {
        this._collector.setContext(input);

        try {
            this._bolt.execute(input, this._collector);
            this._collector.getOutputter().ack(input);
        } catch (FailedException var3) {
            if (var3 instanceof ReportedFailedException) {
                this._collector.reportError(var3);
            }

            this._collector.getOutputter().fail(input);
        }

    }

BaseBasicBolt如果抛出异常,那么自动调用fail。否则调用ack。而BaseRichBolt必须自己手动调用ack。

性能指标

在Storm UI上,可以看到如下性能指标

MemoryUsed:使用的物理内存
HeapMemory:VM使用到的堆内存
CpuUsedRatio: cpu利用率
NettyCliSendSpeed:当前发送流量,单位字节/每秒
NettySrvRecvSpeed:当前接收流量,单位字节/每秒
FullGc:当前1分钟 full gc 次数
RecvTps: 接收到的tuple的tps
SendTps: 发送tuple的tps
Emitted:当前1分钟发送的消息数,包括业务消息和acker消息。
Acked: 当前1分钟被ack的消息数,注意这个和Emitted的区别:如果打开了acker机制, emitted的消息里面含有acker消息, 经常emitted 消息数量是acker消息数量的2倍
Failed:当前1分钟 被ack失败的消息数(可能是没有完全处理,也可能是超时)

如果你在一个spout coponent里发现下面的的指标,不用担心,因为emited的事件包含acked的事件(相当于原事件和ack事件),所以会保持差不多1:2的比例关系。
这里写图片描述

详细请参考阿里的文档
http://www.jstorm.io/Maintenance_cn/JStormMetrics.html

##T opo启动过程

  • 使用Jstorm命令启动,Jstorm会将命令行加上自己的库和配置,然后执行以上传jar包的main方法为入口执行
  • 在代码中调用 StormSubmitter.submitTopology(args[0], conf, builder.createTopology()); 启动nimbus client,把当前的topo提交给Nimbus
  • 现在可以在控制台上终止命令。
  • nimbus首先调用各个bolt和spout的构造函数,初始化spout和bolt对象。然后序列化,传给各个supervisor。
  • supervisor接收到序列化对象后,启动ProcessLauncher进程,该进程负责启动woker进程,并将对象传给worker。理论上ProcessLauncher进程会在启动worker成功或失败后自动退出。
    //第一步,构造命令行
    public String getLauncherParameter(
            LocalAssignment assignment, Map totalConf, String stormHome, String topologyId, int port) throws IOException {
        boolean isEnable = ConfigExtension.isProcessLauncherEnable(totalConf);
        if (!isEnable) {
            return "";
        }

        // STORM-LOCAL-DIR/supervisor/stormdist/topologyId
        String stormRoot = StormConfig.supervisor_stormdist_root(conf, topologyId);
        // STORM-LOCAL-DIR/supervisor/stormdist/topologyId/stormjar.jar
        String stormJar = StormConfig.stormjar_path(stormRoot);

        StringBuilder sb = new StringBuilder();
        sb.append(" java ");
        sb.append(ConfigExtension.getProcessLauncherChildOpts(totalConf));
        sb.append(getLogParameter(totalConf, stormHome, assignment.getTopologyName(), port));
        sb.append(" -cp ");
        sb.append(getClassPath(stormHome, totalConf));
        if (ConfigExtension.isEnableTopologyClassLoader(totalConf)) {
            // don't append storm jar when classloader is enabled
        } else {
            // user defined log configuration is likely to be bundled in the storm jar
            sb.append(":").append(stormJar);
        }
        //启动的JVM的主类是ProcessLauncher
        sb.append(" ").append(ProcessLauncher.class.getName()).append(" ");

        String launcherCmd = sb.toString();
        if (ConfigExtension.getWorkerRedirectOutput(totalConf)) {
            String outFile = getWorkerRedirectOutput(totalConf, assignment, port);
            outFile = "-Dlogfile.name=" + outFile + " ";
            launcherCmd = launcherCmd.replaceAll("-Dlogfile\\.name=.*?\\s", outFile);
        }

        return launcherCmd;
    }
    
    //第二步,准备envirment
    
        Map<String, String> environment = new HashMap<>();
        if (ConfigExtension.getWorkerRedirectOutput(totalConf)) {
            environment.put("REDIRECT", "true");
        } else {
            environment.put("REDIRECT", "false");//默认是False
        }
        environment.put("LD_LIBRARY_PATH", (String) totalConf.get(Config.JAVA_LIBRARY_PATH));
        environment.put("jstorm.home", stormHome);
        environment.put("jstorm.workerId", workerId);
        environment.put("jstorm.on.yarn", isJstormOnYarn ? "1" : "0");

        String launcherCmd = getLauncherParameter(assignment, totalConf, stormHome, topologyId, port);
        String workerCmd = getWorkerParameter(assignment,
                totalConf,
                stormHome,
                topologyId,
                supervisorId,
                workerId,
                port);
        String cmd = launcherCmd + " " + workerCmd;
        cmd = cmd.replace("%JSTORM_HOME%", stormHome);
        //日志会被打印到supervisor.log中,可以在UI看到
        LOG.info("Launching worker with command: " + cmd);
        LOG.info("Environment:" + environment.toString());

        /**
         * if run on yarn, set backend false, otherwise set true
         * 注意,2.1.1版本没有yarn支持
         */
        boolean backend = !isJstormOnYarn;
        LOG.info("backend mode is " + backend);
        JStormUtils.launchProcess(cmd, environment, backend);

  //第三步,使用ProcessBuilder新建进程
      protected static java.lang.Process launchProcess(final List<String> cmdlist,
                                                     final Map<String, String> environment) throws IOException {
        ProcessBuilder builder = new ProcessBuilder(cmdlist);
        //设置将标准错误输出合并到标准输出
        builder.redirectErrorStream(true);
        Map<String, String> process_evn = builder.environment();
        for (Entry<String, String> entry : environment.entrySet()) {
            process_evn.put(entry.getKey(), entry.getValue());
        }

        return builder.start();
    }      

查阅JAVA API可知,Process所有标准 io(即 stdin,stdout,stderr)操作都将通过三个流 (getOutputStream(),getInputStream(),getErrorStream()) 重定向到父进程。注意这点行为和C语言中的fork并不一致。

在完成后,要检查启动的Launcher线程的返回状态。


                Process process = launchProcess(cmdlist, environment);


                StringBuilder sb = new StringBuilder();
                String output = JStormUtils.getOutput(process.getInputStream());
                String errorOutput = JStormUtils.getOutput(process.getErrorStream());
                sb.append(output);
                sb.append("\n");
                sb.append(errorOutput);

                int ret = process.waitFor();
                if (ret != 0) {
                    LOG.warn(command + " is terminated abnormally. ret={}, str={}", ret, sb.toString());
                }
                return sb.toString();
  • worker将对象进行反序列化,根据自己被分配的executor的数目,决定具体反序列化多少个bolt/spout对象
  • 调用bolt/spout中的初始化方法,准备完毕。开始接收事件。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值