从storm-jdbc谈谈component的生命周期

storm中的spout,bolt的生命周期,都是storm帮你管理好,你只要新建一个实例,指定该component的并行数,就可以。那么topology中所用到的工具类的生命周期该如何处理,例如数据库连接池、redis的连接池,这些对象该放在哪里初始化?本文以jdbc连接池为例,讨论此问题。阅读本文前请先自行了解storm topology的运行流程。


Storm Component的生命周期

storm component泛指topoloty中的spout、bolt,是spout和bolt的一个统称。而component生命周期,我结合storm作者nathanmarz大神的回答和自己的理解,描述一下:

1,当提交了topology后,spout、bolt的实例会在本地机器(执行storm submit所在的机器)上被创建,并且在本地序列化另外,所有的component的构造函数、declareOutputFields方法都本地会运行一次。

2,整个topology被上传到nimbus上。

3,对应supervisor会向nimbus获取序列化后的topology代码,交给对应的worker执行。

4,每个worker执行如下:反序列化代码,运行对应component的prepare/open(初始化对应的component)。举个例子,某spout在topology中设置了3个并行度,且topology设置了3个worker,该open方法就会在3个不同的worker上分别调用1次,共调用3次。

5,worker通知executor不断循环去跑nextTuple/execute方法。

需要注意一点:component的初始化相关的操作应放在prepare/open方法中执行, 而不是在实例化component的时候进行或者topology的main函数。


Jdbc Pool 初始化方案

参考component的生命周期,能想到有两种方案:

1,每个component都维持一个与数据库的连接,此种方案适合于写操作非常频繁,component数目比较少,且中间不会有一段时间没有任何写、读操作,否则会发生mysql connection timeout;另外可省去从连接池获取连接,用完连接后返回给连接池这两步操作。如果确定这种方案,则只要将数据库的配置放到config中,在对应的component中初始化数据库连接即可。

2,在每worker中维持一个连接池,worker中所有executor需要用到连接时,从连接池中获取,用后归还。一般还是推荐这种方案。

下面说一下第二种方案的连接池,应该在什么时候初始化。

首先明确一点,在topology运行中,一个worker相当于jvm进程,相当于有N个worker,就会有N个连接池。初始化连接池的动作需要在worker端进行,一个worker中只有一个连接池的实例,适合用单例模式。

初始化思路:每个component接收数据库配置信息,在其prepare/open时,初始化连接池,这里需要保证在单个worker中只需要初始化一次就可以,即对初始化的代码加锁处理,保证只能有一个component初始化。


在storm源码包中,提供了storm-jdbc的实现,其中用了JDBC 连接池组件,具体实现思路就是上面讨论的第二种方案。以下为初始化时加锁的部分。

public class HikariCPConnectionProvider implements ConnectionProvider {

    private Map<String, Object> configMap;
    private transient HikariDataSource dataSource;

    public HikariCPConnectionProvider(Map<String, Object> hikariCPConfigMap) {
        this.configMap = hikariCPConfigMap;
    }

    @Override
    public synchronized void prepare() {
        if(dataSource == null) {
            Properties properties = new Properties();
            properties.putAll(configMap);
            HikariConfig config = new HikariConfig(properties);
            this.dataSource = new HikariDataSource(config);
            this.dataSource.setAutoCommit(false);
        }
    }

    @Override
    public Connection getConnection() {
        try {
            return this.dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void cleanup() {
        if(dataSource != null) {
            dataSource.shutdown();
        }
    }
}

看到,初始化prepare被上锁了,以保证只初始化一次。其实这里可以再优化一下:不要对方法加锁,而是对具体初始化连接的代码加锁,减少锁影响的范围。

你可能感兴趣的:(storm,component,storm-jdbc,component生命周期)