Flink 自定义Sink 之 写入HDFS

一、pom.xml添加依赖。

<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-connector-filesystem_${scala.binary.version}</artifactId>
  <version>${flink.version}</version>
</dependency>
<!-- 访问hdfs -->
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>${hadoop.version}</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>${hadoop.version}</version>
    <scope>provided</scope>
    <exclusions>
        <exclusion>
            <groupId>xml-apis</groupId>
            <artifactId>xml-apis</artifactId>
        </exclusion>
    </exclusions>
</dependency>

二、HDFS帮助类。

object HdfsUtil {
  private val LOGGER: Logger = LoggerFactory.getLogger(HdfsUtil.getClass)

  /**
    * 是否目录
    * */
  def isDir(hdfs: FileSystem, path: String): Boolean = {
    if(StringUtils.isEmpty(path)) {
      return false
    }

    hdfs.isDirectory(new Path(path))
  }

  /**
    * 是否目录
    * */
  def isDir(hdfs: FileSystem, path: Path): Boolean = {
    hdfs.isDirectory(path)
  }

  /**
    * 是否文件
    * */
  def isFile(hdfs: FileSystem, path: String): Boolean = {
    if(StringUtils.isEmpty(path)) {
      return false
    }

    hdfs.isFile(new Path(path))
  }

  /**
    * 是否文件
    * */
  def isFile(hdfs: FileSystem, path: Path): Boolean = {
    hdfs.isFile(path)
  }

  /**
    * 创建文件
    * */
  def createFile(hdfs: FileSystem, path: String): Boolean = {
    if(StringUtils.isEmpty(path)) {
      return false
    }

    hdfs.createNewFile(new Path(path))
  }

  /**
    * 创建文件
    * */
  def createFile(hdfs: FileSystem, path: Path): Boolean = {
    hdfs.createNewFile(path)
  }

  /**
    * 创建目录
    * */
  def createDir(hdfs: FileSystem, path: String): Boolean = {
    if(StringUtils.isEmpty(path)) {
      return false
    }

    hdfs.mkdirs(new Path(path))
  }

  /**
    * 创建目录
    * */
  def createDir(hdfs: FileSystem, path: Path): Boolean = {
    hdfs.mkdirs(path)
  }

  /**
    * 文件是否存在
    * */
  def exists(hdfs: FileSystem, path: String): Boolean = {
    if(StringUtils.isEmpty(path)) {
      return false
    }

    hdfs.exists(new Path(path))
  }

  /**
    * 文件是否存在
    * */
  def exists(hdfs: FileSystem, path: Path): Boolean = {
    hdfs.exists(path)
  }

  /**
    * 删除
    * */
  def delete(hdfs: FileSystem, path: String): Boolean = {
    if(StringUtils.isEmpty(path)) {
      return false
    }

    hdfs.delete(new Path(path), true)
  }

  /**
    * 删除
    * */
  def delete(hdfs: FileSystem, path: Path): Boolean = {
    hdfs.delete(path, true)
  }

  /**
    * 追加写入
    * */
  def append(hdfs: FileSystem, path: String, content: String): Boolean = {
    if(StringUtils.isEmpty(path) || content == null) {
      return false
    }

    if(!exists(hdfs, path)) {
      createFile(hdfs, path)
    }
    hdfs.getConf.setBoolean("dfs.support.append", true)

    try {
      val append = hdfs.append(new Path(path))
      append.write(content.getBytes("UTF-8"))
      append.write(10) // 换行
      append.flush()
      append.close()
      true
    } catch {
      case e: Exception => {
        LOGGER.error(s"append file exception, path{$path}, content{$content}", e)
        false
      }
    }
  }

  /**
    * 读取文件
    * */
  def read(hdfs: FileSystem, file: String): Array[Byte] = {
    val result = new Array[Byte](0)
    if(StringUtils.isEmpty(file)) {
      return result
    }
    if(exists(hdfs, file)) {
      return result
    }

    var isr: InputStreamReader = null
    var br: BufferedReader = null
    try {
      val path = new Path(file)
      val inputStream = hdfs.open(path)
      isr = new InputStreamReader(inputStream)
      br = new BufferedReader(isr)

      var content = new StringBuilder
      var line: String = br.readLine()
      while (line != null) {
        content ++= line
        line = br.readLine()
      }

      br.close()
      isr.close()

      content.toString().getBytes("UTF-8")
    } catch {
      case e: Exception => {
        LOGGER.error(s"read file exception, file{$file}", e)
        result
      }
    } finally {
      try {
        isr.close()
      } catch {
        case _: Exception => {}
      }
      try {
        br.close()
      } catch {
        case _: Exception => {}
      }
    }
  }

  /**
    * 上传本地文件
    * */
  def uploadLocalFile(hdfs: FileSystem, localPath: String, hdfsPath: String): Boolean = {
    if(StringUtils.isEmpty(localPath) || StringUtils.isEmpty(hdfsPath)) {
      return false
    }

    val src = new Path(localPath)
    val dst = new Path(hdfsPath)
    hdfs.copyFromLocalFile(src, dst)
    true
  }

  /**
    * 列出目录下所有文件
    * */
  def list(hdfs: FileSystem, path: String): List[String] = {
    val list: List[String] = new ArrayList[String]()
    if(StringUtils.isEmpty(path)) {
      return list
    }

    val stats = hdfs.listStatus(new Path(path))
    for(i <- 0 to stats.length-1) {
      if(stats(i).isFile) {
        // path.getName,只是文件名,不包括路径
        // path.getParent,只是父文件的文件名,不包括路径
        // path.toString,完整的路径名
        list.add(stats(i).getPath.toString)
      } else if(stats(i).isDirectory) {
        list.add(stats(i).getPath.toString)
      } else if(stats(i).isSymlink) {
        list.add(stats(i).getPath.toString)
      }
    }

    list
  }

三、自定义Sink。

import java.util.{Date, Properties}

import com.igg.flink.tool.common.Constant
import com.igg.flink.tool.member.bean.MemberLogInfo
import com.igg.flink.tool.utils.{DateUtil, FileUtil, HdfsUtil}
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.sink.{RichSinkFunction, SinkFunction}
import org.apache.hadoop.fs.FileSystem

class HdfsSink extends RichSinkFunction[MemberLogInfo] {
  private val serialVersionUID = 10000L

  var default_fs: String = null
  var conf: org.apache.hadoop.conf.Configuration = null
  var fs: FileSystem = null
  var rabbitmqProps: Properties = null

  override def open(parameters: Configuration): Unit = {
    // 加载配置文件
    rabbitmqProps = FileUtil.loadProperties(Constant.CONFIG_NAME_RABBITMQ)
    default_fs = rabbitmqProps.getProperty("hdfs.fs.default.fs")

    conf = new org.apache.hadoop.conf.Configuration()
    // 指定使用 hdfs 系统, namenode 所在节点
    // 9000端口用于Filesystem metadata operations
    conf.set("fs.defaultFS", default_fs)
    // 禁用 hdfs 的fs等缓存
    // conf.setBoolean("fs.hdfs.impl.disable.cache", true)
    fs = FileSystem.get(conf)

    // 初始化目录
    HdfsUtil.createDir(fs, rabbitmqProps.getProperty("hdfs.save.path.login"))
  }

  override def invoke(value: MemberLogInfo, context: SinkFunction.Context[_]): Unit = {
    // 文件名
    val fileName = DateUtil.format(new Date(value.getTimestamp), DateUtil.yyyyMMddSpt) + ".log"
    // 输出内容
    val content = value.getIggid + "\t" + value.getType

    HdfsUtil.append(fs, rabbitmqProps.getProperty("hdfs.save.path.login") + fileName, content)
  }

  override def close(): Unit = {
    if (fs != null) {
      try {
        fs.close()
      } catch {
        case e: Exception => {}
      }
    }
  }
}

四、总结。

(1)自定义sink,只需继承 SinkFunction 或 RichSinkFunction 即可。区别在于 RichSinkFunction 提供了更加丰富的方法,如open(), close()等方法。

(2)写入hdfs,使用了原生的hdfs接口。只适合单线程,不适合多线程写入。

(3)如果要多线程写入hdfs,推荐使用 StreamFileSink 或 BucketingSink 写入HDFS。如何使用,可查阅我之前写的文章。

一起学习

要将文件写入HDFS并指定Gzip压缩格式,可以使用Flink提供的`org.apache.flink.core.fs.FileSystem`和`org.apache.flink.core.fs.Path`类来实现。具体步骤如下: 1. 创建一个`org.apache.flink.core.fs.FileSystem`对象,指定HDFS的URI和配置信息。 2. 创建一个`org.apache.flink.core.fs.Path`对象,指定写入HDFS文件路径。 3. 调用`FileSystem.create()`方法创建一个输出流。 4. 将数据写入输出流,这里可以使用`org.apache.flink.api.common.io.FileOutputFormat`类来实现Gzip压缩。 5. 关闭输出流。 下面是一个示例程序,它将数据写入HDFS并使用Gzip压缩: ```java import org.apache.flink.api.common.io.FileOutputFormat; import org.apache.flink.core.fs.FileSystem; import org.apache.flink.core.fs.Path; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; public class WriteToHdfsExample { public static void main(String[] args) throws Exception { // 创建执行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // 准备要写入HDFS的数据 DataStream<String> data = ... // 写入HDFS String outputPath = "hdfs://hadoop-master:9000/path/to/output"; FileSystem hdfs = FileSystem.get(new URI(outputPath), new Configuration()); Path path = new Path(outputPath); FSDataOutputStream outputStream = hdfs.create(path); GzipCompressor gzipCompressor = new GzipCompressor(); FileOutputFormat<String> fileOutputFormat = new TextOutputFormat<>(path, gzipCompressor); fileOutputFormat.setOutputFilePath(path); fileOutputFormat.setWriteMode(FileSystem.WriteMode.OVERWRITE); fileOutputFormat.open(outputStream); data.writeUsingOutputFormat(fileOutputFormat); fileOutputFormat.close(); // 启动任务执行 env.execute("Write to HDFS Example"); } } ``` 在以上示例程序中,`hadoop-master:9000`是HDFS的URI,`/path/to/output`是要写入文件路径。`TextOutputFormat`是Flink提供的一个文本输出格式,它支持Gzip压缩。在`FileOutputFormat`的构造函数中,将`TextOutputFormat`作为参数传入,即可实现Gzip压缩。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

magic_kid_2010

你的支持将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值