Storm Component多语言支持剖析

Storm Component的多语言支持是通过父子线程实现通信,通信格式为JSON。


下面看一个小例子,父子进程都是Java语言:

// 父进程,语言为Java
public class ParentProcess {
	
	// 子进程
	Process subProcess;
	
	public void run() throws IOException, InterruptedException {
		
		// 运行子进程的命令,实际与直接在控制台下运行java SubProcess效果等同
		String[] command = new String[] { "java", "SubProcess" };
		
		// 通过ProcessBuilder构建子进程
		ProcessBuilder builder = new ProcessBuilder(command);
		subProcess = builder.start();
        
		// 单独启动一个线程负责向子进程中写入数据,这里只写入一句问候语
		// subProcess.getOutputStream()绑定于子进程的标准输入
		Thread writeThread = new Thread( new Runnable() {
			public void run() {
			        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(subProcess.getOutputStream()));
			        try {
						writer.write("hello son!");
						writer.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
        		}
		);
		writeThread.start();

		// 单独启动一个线程负责接收子进程中发送给父进程的数据
		// subProcess.getInputStream()绑定于子进程的标准输出
		Thread readThread = new Thread( new Runnable() {
			public void run() {
			        BufferedReader reader = new BufferedReader(new InputStreamReader(subProcess.getInputStream()));
			        String line = null;
			        try {
						while((line = reader.readLine()) != null) {
							System.out.println(line);
						}
						reader.close();
					} 
			        catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		);
		readThread.start();

		// TODO 实际中还需要启动一个线程负责读取子进程的错误流subProcess.getErrorStream(),否则由可能造成IO阻塞,在此忽略
        
		// 等待父子进程完成通信交互
		readThread.join();
		writeThread.join();
        
		// 销毁子进程
		subProcess.destroy();
	}

	public static void main(String[] args) {
		ParentProcess process = new ParentProcess();
		try {
			process.run();
		} 
		catch (IOException e) {
			e.printStackTrace();
		} 
		catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

// 子进程,语言同样为Java
public class SubProcess {
	
	public void run() throws IOException {
		
		// 单独启动一个线程负责接收父进程发来的数据
		Thread readThread = new Thread( new Runnable() {
				public void run() {
					BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
			        String line = null;
			        try {
						while((line = reader.readLine()) != null) {
							System.out.println(line);
						}
						reader.close();
					} 
			        catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		);
		readThread.start();
		
		// 单独启动一个线程负责向父进程发送数据
		Thread writeThread = new Thread( new Runnable() {
			public void run() {
			        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(System.out));
			        try {
						writer.write("hello parent!");
						writer.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
        		}
		);
		writeThread.start();
	}

	public static void main(String[] args) {
		SubProcess process = new SubProcess();
		try {
			process.run();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

编译两个Java文件,将ParentProcess.class与SubProcess.class放在同一个目录下,运行java ParentProcess,可以看到运行结果为:
hello son!
hell parent!
至此完成了一个简单的父子进程通信。
为了实现多语言,可以将父子进程的实现替换为任意支持标准IO的编程语言,shell, python, scala, clojure等等。。


下面来看看Storm支持多语言Component的实现。

// shell子进程的代理,通过java提供了一层封装
public class ShellProcess {
	
	// 子进程的标准输入,标准输出和标准错误流
	private DataOutputStream processIn;
	private BufferedReader processOut;
	private InputStream processErrorStream;

	// Java标准进程对象,用于运行Shell脚本
	private Process _subprocess;

	// 具体要执行的Shell命令
	private String[] command;

	public ShellProcess(String[] command) {
		this.command = command;
	}

	// 初始化Shell子进程。
	public Number launch(Map conf, TopologyContext context) throws IOException {
		ProcessBuilder builder = new ProcessBuilder(command);

		// 设置子进程工作目录
	        builder.directory(new File(context.getCodeDir()));
		_subprocess = builder.start();

		// 接收子进程的标准IO流
		processIn = new DataOutputStream(_subprocess.getOutputStream());
		processOut = new BufferedReader(new InputStreamReader(_subprocess.getInputStream()));
		processErrorStream = _subprocess.getErrorStream();

		// 读取子进程的PID, 此逻辑在Shell脚本中实现
		return (Number)readMessage().get("pid");
	}

	// 向子进程中写入对象,序列化为JSON格式数据
	public void writeMessage(Object msg) throws IOException {
		writeString(JSONValue.toJSONString(msg));
	}

	// 具体写入IO
	private void writeString(String str) throws IOException {
		byte[] strBytes = str.getBytes("UTF-8");
		processIn.write(strBytes, 0, strBytes.length);
		processIn.writeBytes("\nend\n");
		processIn.flush();
	}

	// 从子进程中读取数据,将数据反序列化为JSONObject对象
	public JSONObject readMessage() throws IOException {
		String string = readString();
		JSONObject msg = (JSONObject)JSONValue.parse(string);
		if (msg != null) {
			return msg;
		} else {
			throw new IOException("unable to parse: " + string);
		}
	}

	// 清空子进程标准错误流,防止阻塞
	public String getErrorsString() {
		if(processErrorStream!=null) {
		try {
			return IOUtils.toString(processErrorStream);
		} catch(IOException e) {
			return "(Unable to capture error stream)";
		}
		} else {
			return "";
		}
	}

    // 具体业务方法,从子进程中读取数据,直到读入"end"标识结束
    private String readString() throws IOException {
        StringBuilder line = new StringBuilder();

        synchronized (processOut) {
            while (true) {
                String subline = processOut.readLine();
                if(subline==null) {
                    StringBuilder errorMessage = new StringBuilder();
                    errorMessage.append("Pipe to subprocess seems to be broken!");
                    if (line.length() == 0) {
                        errorMessage.append(" No output read.\n");
                    }
                    else {
                        errorMessage.append(" Currently read output: " + line.toString() + "\n");
                    }
                    errorMessage.append("Shell Process Exception:\n");
                    errorMessage.append(getErrorsString() + "\n");
                    throw new RuntimeException(errorMessage.toString());
                }
                if(subline.equals("end")) {
                    break;
                }
                if(line.length()!=0) {
                    line.append("\n");
                }
                line.append(subline);
            }
        }

        return line.toString();
    }
}

// 驱动Shell子进程的父进程,所有需要用Shell实现的Bolt组件需要继承此类
public class ShellBolt implements IBolt {

	// 子进程代理对象
	private ShellProcess _process;

	// 子进程执行的命令
	private String[] _command;

	// 与子进程标准IO通信的线程
	private Thread _readerThread;
	private Thread _writerThread;

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

		// 生成子进程代理对象
		_process = new ShellProcess(_command);

		// 启动子线程
		try {
		    Number subpid = _process.launch(stormConf, context);
		} catch (IOException e) {
		    throw new RuntimeException("Error when launching multilang subprocess\n" + _process.getErrorsString(), e);
		}

		// 读取子进程数据,根据具体命令做相应上层业务处理
		_readerThread = new Thread(new Runnable() {
		    public void run() {
			while (_running) {
			    try {
				JSONObject action = _process.readMessage();
				if (action == null) {
				    // ignore sync
				}

				String command = (String) action.get("command");
				if(command.equals("ack")) {
				    handleAck(action);
				} else if (command.equals("fail")) {
				    handleFail(action);
				} else if (command.equals("error")) {
				    handleError(action);
				} else if (command.equals("log")) {
				    String msg = (String) action.get("msg");
				    LOG.info("Shell msg: " + msg);
				} else if (command.equals("emit")) {
				    handleEmit(action);
				}
			    } catch (InterruptedException e) {
			    } catch (Throwable t) {
				die(t);
			    }
			}
		    }
		});
		
		_readerThread.start();

		// 向子进程写入数据
		_writerThread = new Thread(new Runnable() {
		    public void run() {
			while (_running) {
			    try {
				Object write = _pendingWrites.poll(1, SECONDS);
				if (write != null) {
				    _process.writeMessage(write);
				}
			    } catch (InterruptedException e) {
			    } catch (Throwable t) {
				die(t);
			    }
			}
		    }
		});
		
		_writerThread.start();
	}
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值