JdbcSink 简析


1、JdbcSink

  用于DataStream增加Jdbc的Sink输出,主要两个接口:sink()和exactlyOnceSink()。其中exactlyOnceSink()是13版本新增的支持事务性的接口,本次主要介绍sink()接口。

public static <T> SinkFunction<T> sink(
        String sql,
        JdbcStatementBuilder<T> statementBuilder,
        JdbcExecutionOptions executionOptions,
        JdbcConnectionOptions connectionOptions) {
    return new GenericJdbcSinkFunction<>(
            new JdbcBatchingOutputFormat<>(
                    new SimpleJdbcConnectionProvider(connectionOptions),
                    executionOptions,
                    context -> {
                        Preconditions.checkState(
                                !context.getExecutionConfig().isObjectReuseEnabled(),
                                "objects can not be reused with JDBC sink function");
                        return JdbcBatchStatementExecutor.simple(
                                sql, statementBuilder, Function.identity());
                    },
                    JdbcBatchingOutputFormat.RecordExtractor.identity()));
}

1.1、参数

  接口有四个参数,其中第三个参数executionOptions可以省略使用默认值,具体样例参看1、JdbcSink方式

  • sql

    String类型,一个SQL语句模板,就是通常使用的PreparedStatement那种形式,例如:insert into wordcount (wordcl, countcl) values (?,?)

  • statementBuilder

    JdbcStatementBuilder类型,作用是完成流数据与SQL具体列的对应,基于上一个参数的PreparedStatement形式,完成对应关系

  • executionOptions

    Flink Jdbc输出的执行规则,主要设置执行触发机制,主要设置三个参数:数据量触发阈值、时间触发阈值、最大重试次数。其中,数据量触发默认为5000,时间触发默认为0,即关闭时间触发。注意触发阈值不要设置的过低,否则可能造成数据库的阻塞。

  • connectionOptions

    JdbcConnectionOptions类型,用于设置数据库连接属性,包括Url、Driver、Username、Password等

1.2、返回

  接口返回的是一个基于SinkFunction实现的GenericJdbcSinkFunction类,其核心是参数JdbcBatchingOutputFormat。

  GenericJdbcSinkFunction的结果核心方法如下,都是基于JdbcBatchingOutputFormat的操作。

@Override
public void open(Configuration parameters) throws Exception {
    super.open(parameters);
    RuntimeContext ctx = getRuntimeContext();
    outputFormat.setRuntimeContext(ctx);
    outputFormat.open(ctx.getIndexOfThisSubtask(), ctx.getNumberOfParallelSubtasks());
}

@Override
public void invoke(T value, Context context) throws IOException {
    outputFormat.writeRecord(value);
}

@Override
public void snapshotState(FunctionSnapshotContext context) throws Exception {
    outputFormat.flush();
}

2、JdbcBatchingOutputFormat

  JdbcBatchingOutputFormat是进行Jdbc交互的实现类,在向Jdbc输出前进行数据聚合

2.1、参数

  接口有四个参数

  • JdbcConnectionProvider

    提供Jdbc连接

  • JdbcExecutionOptions

    执行参数

  • StatementExecutorFactory

    Statement执行工厂,也就是流数据与数据库字段对应关系的处理

  • RecordExtractor

    数据提取的执行类

2.2、open方法

  Open方法是进行数据库连接初始化及前期准备的接口,存在调用关系

Task.doRun()
    ->invokable.invoke()->DataSinkTask.invoke()
        ->format.open()->JdbcBatchingOutputFormat.open()

2.2.1、连接数据库

  Open方法的第一步是连接数据库,调用上层方法AbstractJdbcOutputFormat的open方法,之后调用JdbcConnectionProvider的实现类SimpleJdbcConnectionProvider的getOrEstablishConnection()方法建立连接,getOrEstablishConnection的具体操作如下

public Connection getOrEstablishConnection() throws SQLException, ClassNotFoundException {
    if (connection != null) {
        return connection;
    }
    if (jdbcOptions.getDriverName() == null) {
        connection =
                DriverManager.getConnection(
                        jdbcOptions.getDbURL(),
                        jdbcOptions.getUsername().orElse(null),
                        jdbcOptions.getPassword().orElse(null));
    } else {
        Driver driver = getLoadedDriver();
        Properties info = new Properties();
        jdbcOptions.getUsername().ifPresent(user -> info.setProperty("user", user));
        jdbcOptions.getPassword().ifPresent(password -> info.setProperty("password", password));
        connection = driver.connect(jdbcOptions.getDbURL(), info);
        if (connection == null) {
            // Throw same exception as DriverManager.getConnection when no driver found to match
            // caller expectation.
            throw new SQLException(
                    "No suitable driver found for " + jdbcOptions.getDbURL(), "08001");
        }
    }
    return connection;
}

  此处根据有没有设置Drive有两种处理。如果没有设置,会根据设置的URL自动解析,用到了Java的DriverManager,这个类用于管理Jdbc驱动。DriverManager会自动识别classpath里的Driver,然后可以根据URL自动解析配对。如果设置了Driver,那就直接加载这个Driver来进行连接处理。

2.2.2、JdbcExec

  这个是基于StatementExecutorFactory创建的,此处最后使用的实现类是JdbcBatchStatementExecutor,在sink()接口中设定。这一步实际的操作就是做一个prepareStatements

@Override
public void prepareStatements(Connection connection) throws SQLException {
    this.st = connection.prepareStatement(sql);
}

2.2.3、scheduler

  数据库性能有限,所以Flink写数据库通常采用批的方式,此处就是设置时间调度的,具体参数可以参看第一章。需要注意的是两个特殊配置值:时间为0或者条数为1则不创建这个调度器。

if (executionOptions.getBatchIntervalMs() != 0 && executionOptions.getBatchSize() != 1) {

  此处创建的调度线程池只包含一个线程

this.scheduler =
        Executors.newScheduledThreadPool(
                1, new ExecutorThreadFactory("jdbc-upsert-output-format"))

  调度器最终执行的操作就是整个类最大的一点,flush数据到数据库

synchronized (JdbcBatchingOutputFormat.this) {
    if (!closed) {
        try {
            flush();
        } catch (Exception e) {
            flushException = e;
        }
    }
}

2.3、writeRecord方法

  writeRecord是类的核心方法,进行数据的写入。主要进行两个操作,将数据加入列表,达到条件时flush到数据库中。

try {
    addToBatch(record, jdbcRecordExtractor.apply(record));
    batchCount++;
    if (executionOptions.getBatchSize() > 0
            && batchCount >= executionOptions.getBatchSize()) {
        flush();
    }
} catch (Exception e) {
    throw new IOException("Writing records to JDBC failed.", e);
}

2.3.1、缓存数据

  缓存数据使用的就是一个简单的ArrayList,其定义在SimpleBatchStatementExecutor

SimpleBatchStatementExecutor(
        String sql, JdbcStatementBuilder<V> statementBuilder, Function<T, V> valueTransformer) {
    this.sql = sql;
    this.parameterSetter = statementBuilder;
    this.valueTransformer = valueTransformer;
    this.batch = new ArrayList<>();
}

  如上,batch就是用于缓存数据的,添加数据操作如下。

@Override
public void addToBatch(T record) {
    batch.add(valueTransformer.apply(record));
}

  其中valueTransformer的作用就是返回输入,在sink初始时定义:

return JdbcBatchStatementExecutor.simple(
        sql, statementBuilder, Function.identity());

/**
 * Returns a function that always returns its input argument.
 *
 * @param <T> the type of the input and output objects to the function
 * @return a function that always returns its input argument
 */
static <T> Function<T, T> identity() {
    return t -> t;
}

2.3.2、flush

  flush就是把缓存数据向数据库刷出,最终调用的是SimpleBatchStatementExecutor的executeBatch方法

@Override
public void executeBatch() throws SQLException {
    if (!batch.isEmpty()) {
        for (V r : batch) {
            parameterSetter.accept(st, r);
            st.addBatch();
        }
        st.executeBatch();
        batch.clear();
    }
}
<think>好的,用户现在问的是关于“disk数据结构简析”。我需要先确定用户指的是哪种上下文中的“disk数据结构”。因为“disk”这个词在计算机科学中可以指硬盘、磁盘的数据结构,也可能是指数据库中的数据结构,或者是某种特定的磁盘管理结构,比如文件系统中的结构。 首先回顾一下之前的对话历史。用户之前问过“四级物理地址”,涉及到内存管理和页表结构。现在的问题可能延续存储管理的主题,但转向磁盘的数据结构。因此,用户可能是在学习操作系统的存储管理,或者是数据库的物理存储结构。 接下来,我需要分析用户的具体需求。用户可能想了解磁盘上数据的组织方式,例如文件系统如何管理磁盘空间,或者数据库如何在磁盘上存储数据。也有可能用户指的是磁盘本身的物理结构,比如磁道、扇区等。 考虑到用户之前的问题是关于内存管理的,现在转向磁盘,可能是在学习存储系统的层次结构,从内存到磁盘的过渡。因此,用户可能需要了解文件系统的数据结构,如超级块、inode、目录结构等,或者是磁盘分区和格式化的相关知识。 另外,用户可能对磁盘的数据结构有更具体的兴趣,比如磁盘调度算法中用到的数据结构,或者是RAID中的磁盘数据分布结构。但根据问题中的“简析”,用户可能希望得到一个概述性的介绍,而不是深入某个特定方面。 需要确认是否应该涵盖物理结构和逻辑结构。物理结构包括磁道、扇区、柱面等,而逻辑结构涉及文件系统的组织方式,如FAT、EXT4、NTFS等不同的文件系统结构。此外,可能还需要提到磁盘分区表,如MBR或GPT。 可能用户是计算机专业的学生,正在准备考试或做项目,需要了解磁盘数据的基础知识。或者是一位开发者,在优化存储性能时需要了解磁盘数据结构。因此,回答需要兼顾基础概念和实际应用,同时保持结构清晰,层次分明。 需要确保回答中使用正确的术语,并且解释清楚每个数据结构的作用和相互关系。例如,文件系统中的超级块存储文件系统的元数据,inode存储文件元数据,数据块存储实际内容等。同时,可能需要提到磁盘的块(block)和簇(cluster)的概念,以及它们在空间分配中的作用。 还要注意用户可能对现代文件系统如EXT4、Btrfs或ZFS的特性感兴趣,但根据问题中的“简析”,可能需要保持基础,不深入具体文件系统的细节。此外,可能需要简要提及磁盘缓存、缓冲区管理相关的数据结构,但这可能属于操作系统层面的内容,而非磁盘本身的数据结构。 总结,回答应该包括磁盘的物理结构、逻辑结构(如文件系统的基本组成)、分区表,以及可能的访问方法相关的数据结构。需要分点说明,结构清晰,使用例子或比喻帮助理解。同时,确保技术术语准确,符合中文表达习惯,避免过于晦涩。</think>以下是磁盘(存储设备)数据结构的核心解析,重点涵盖**物理结构**与**逻辑结构**两个层面: --- ### **一、物理结构** 磁盘(如机械硬盘)的物理数据结构由硬件设计决定: 1. **磁道(Track)** - 盘片表面同心圆轨道,数据存储的物理路径。 2. **扇区(Sector)** - 磁道的最小存储单元,传统硬盘每扇区$512$字节,现代支持$4K$(高级格式化)。 3. **柱面(Cylinder)** - 多个盘片同一半径磁道的集合,优化磁头移动效率。 4. **块(Block)** - 操作系统对扇区的逻辑抽象,通常由多个扇区组成(如$4K$块=8个$512$B扇区)。 --- ### **二、逻辑结构(文件系统层)** 文件系统通过数据结构管理磁盘空间与文件存储,以**EXT4**(Linux)为例: 1. **超级块(Superblock)** - 存储文件系统元数据:块大小、总块数、inode数量等。 - 备份超级块用于故障恢复。 2. **块组描述符(Block Group Descriptor)** - 划分磁盘为多个块组,记录每个块组的空闲块和inode信息。 3. **inode表(inode Table)** - 存储文件元数据:权限、大小、时间戳、数据块指针等。 - **EXT4 inode结构**: ```c struct ext4_inode { __le16 i_mode; // 文件类型与权限 __le64 i_size; // 文件大小 __le32 i_block[EXT4_N_BLOCKS]; // 直接/间接/双间接块指针 // ... 其他字段 }; ``` 4. **目录项(Directory Entry)** - 记录文件名与对应inode号的映射,支持哈希树加速查找。 5. **数据块(Data Block)** - 实际存储文件内容或目录结构。 --- ### **三、磁盘访问相关数据结构** 1. **分区表** - **MBR(主引导记录)**:传统方案,支持4个主分区(最大$2TB$)。 - **GPT(GUID分区表)**:现代方案,支持更多分区与更大容量。 2. **I/O调度队列** - 内核通过**电梯算法**(如CFQ、Deadline)组织磁盘请求队列,优化寻道时间。 --- ### **四、示例:文件读取流程** 1. 应用程序调用`read()` → 内核通过文件路径查找目录项获取inode号。 2. 从inode中解析数据块地址 → 转换为物理扇区号(LBA)。 3. 磁盘控制器将LBA转换为(柱面, 磁头, 扇区)物理坐标。 4. 磁头移动到指定位置读取数据 → 返回至应用程序。 --- ### **五、高级存储结构** 1. **RAID**:多磁盘组合的冗余阵列(如RAID-0/1/5),通过条带化/镜像提升性能与可靠性。 2. **LVM(逻辑卷管理)**:抽象物理磁盘为可动态调整的逻辑卷。 3. **日志(Journaling)**:文件系统通过写入日志保证崩溃一致性(EXT4的`journal`块)。 --- 如需深入特定文件系统(如NTFS、ZFS)或SSD的FTL(闪存转换层)设计,可进一步说明需求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值