Flink广播变量和分布式缓存

Flink广播变量和分布式缓存

一:Flink广播变量

Flink支持广播变量,就是将数据广播到具体的taskmanager上,数据存储在内存中,这样可以减缓大量的shuffle操作;

比如在数据join阶段,不可避免的就是大量的shuffle操作,我们可以把其中一个dataSet广播出去,一直加载到taskManager的内存中,可以直接在内存中拿数据,避免了大量的shuffle,导致集群性能下降;

广播变量创建后,它可以运行在集群中的任何function上,而不需要多次传递给集群节点。另外需要记住,不应该修改广播变量,这样才能确保每个节点获取到的值都是一致的。

一句话解释,可以理解为是一个公共的共享变量,我们可以把一个dataset 数据集广播出去,然后不同的task在节点上都能够获取到,这个数据在每个节点上只会存在一份。如果不使用broadcast,则在每个节点中的每个task中都需要拷贝一份dataset数据集,比较浪费内存(也就是一个节点中可能会存在多份dataset数据)。

注意:因为广播变量是要把dataset广播到内存中,所以广播的数据量不能太大,否则会出现OOM这样的问题

Flink广播变量和分布式缓存_第1张图片

实例:

需求:从内存中拿到data2的广播数据,再与data1数据根据第二列元素组合成(Int, Long, String, String)

val data1 = new mutable.MutableList[(Int, Long, String)]

​ data1 .+=((1, 1L, “xiaoming”))

​ data1 .+=((2, 2L, “xiaoli”))

​ data1 .+=((3, 2L, “xiaoqiang”))

val ds1 = env.fromCollection(data1)

​ val data2 = new mutable.MutableList[(Int, Long, Int, String, Long)]

​ data2 .+=((1, 1L, 0, “Hallo”, 1L))

​ data2 .+=((2, 2L, 1, “Hallo Welt”, 2L))

data2 .+=((2, 3L, 2, “Hallo Welt wie”, 1L))

val ds2 = env.fromCollection(data2)

开发步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 加载创建数据源

  3. 数据转换(使用广播)

  4. 打印测试

import java.util

import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.configuration.Configuration
//进行隐式参数的转换
import org.apache.flink.api.scala._
import scala.collection.mutable


object 广播变量 {
  def main(args: Array[String]): Unit = {
    //样例数据
    val env = ExecutionEnvironment.getExecutionEnvironment
    val data1 = new mutable.MutableList[(Int, Long, String)]
    data1.+=((1, 1L, "xiaoming"))
    data1.+=((2, 2L, "xiaoli"))
    data1.+=((3, 2L, "xiaoqiang"))
    val ds1 = env.fromCollection(data1)

    val data2 = new mutable.MutableList[(Int, Long, Int, String, Long)]
    data2.+=((1, 1L, 0, "Hallo", 1L))
    data2.+=((2, 2L, 1, "Hallo Welt", 2L))
    data2.+=((2, 3L, 2, "Hallo Welt wie", 1L))
    val ds2 = env.fromCollection(data2)

    //注意这两个参数一个是输入类型,一个是输出类型
    ds1.map(new RichMapFunction[(Int, Long, String), (Int, Long, String, String, Long)] {
      var ds: util.List[(Int, Long, Int, String, Long)] = null

      //在一个方法用来获取变量
      override def open(parameters: Configuration): Unit = {
        //获取广播变量
        ds = getRuntimeContext.getBroadcastVariable[(Int, Long, Int, String, Long)]("ds2")
      }


      import collection.JavaConverters._

      //这个方法用来处理数据的
      override def map(value: (Int, Long, String)): (Int, Long, String, String, Long) = {
        //拿到广播变量以后因为它是list,所以要先遍历
        var tuple: (Int, Long, String, String, Long) = null
        for (line <- ds.asScala) {
          //首先判断一下第二个索引的是不是相同,如果相同就进行数据合并
          if (value._2 == line._2) {
            tuple = (value._1, value._2, value._3, line._4, line._5)
          }
        }
        tuple
      }
    }).withBroadcastSet(ds2, "ds2")
      .print()   //触发执行打印

  }
}

二.Flink分布式缓存DistributedCache

Flink提供了一个分布式缓存,类似于hadoop,可以使用户在并行函数中很方便的读取本地文件

缓存的工作机制
程序注册一个文件或者目录(本地或者远程文件系统,例如hdfs或者s3),通过ExecutionEnvironment注册缓存文件并为它起一个名称。当程序执行,Flink自动将文件或者目录复制到所有taskmanager节点的本地文件系统,用户可以通过这个指定的名称查找文件或者目录,然后从taskmanager节点的本地文件系统访问它

也就是说当我们在运行并行函数的时候如果会用到本地或者hdfs上的文件的话,可以使用分布式缓存,将这个文件缓存到每个task节点,当函数在使用功能的时候就可以从本地系统访问他了

注意:flink分布式缓存是基于磁盘的,并不是内存的,他可以看做是spark的广播,只不过这里广播的是文件

实例:

需求:从hdfs上拿到subject.txt数据,再与Clazz数据组合成新的数据集,组成:(学号 , 班级 , 学科 , 分数)

样例数据

hdfs上的

subject.txt : (学号, 学科, 分数)

1,wuli,76

2,yuwen,80

3,yingwu,75

4,shuju,65

5,huaxue,87

6,shuju,95.5

7,yuwen,96.5

8,huaxue,95.5

Clazz : (学号, 班级 )

val clazz:DataSet[Clazz] = env.fromElements(
Clazz(1,“class_1”),
Clazz(2,“class_1”),
Clazz(3,“class_2”),
Clazz(4,“class_2”),
Clazz(5,“class_3”),
Clazz(6,“class_3”),
Clazz(7,“class_4”),
Clazz(8,“class_1”)
)

步骤

  1. 获取 ExecutionEnvironment 运行环境

  2. 开启分布式缓存

  3. 加载本地数据

  4. 数据转换(使用分布式缓存)

  5. 打印测试

参考代码

import org.apache.flink.api.common.functions.RichMapFunction
import org.apache.flink.api.scala.{DataSet, ExecutionEnvironment}
import org.apache.flink.api.scala._
import org.apache.flink.configuration.Configuration
import scala.collection.mutable.ListBuffer
import scala.io.Source

object DistributeCache {
  def main(args: Array[String]): Unit = {
    //flink分布式缓存
    /**首先定义两个类,一个Clazz和一个我们要输出的合并后的数据的类
      * 1.获取 ExecutionEnvironment 运行环境
      * 2.开启分布式缓存
      * 2. 加载本地数据
      * 3. 数据转换(使用分布式缓存)
      * 4. 打印测试
      */

    //获取执行环境
    val env = ExecutionEnvironment.getExecutionEnvironment
    //设置分布式缓存地址
    val path = "hdfs://node01:9000/score.txt"
    //然后注册分布式缓存
    env.registerCachedFile(path, "DistributeCache")

    //然后加载本地数据
    val clazz: DataSet[Clazz] = env.fromElements(
      Clazz(1, "class_1"),
      Clazz(2, "class_1"),
      Clazz(3, "class_2"),
      Clazz(4, "class_2"),
      Clazz(5, "class_3"),
      Clazz(6, "class_3"),
      Clazz(7, "class_4"),
      Clazz(8, "class_1")
    )

    clazz.map(new RichMapFunction[Clazz, info] {

      var myline = new ListBuffer[String]

      //在老一结果open实现获取分布式缓存的方法
      override def open(parameters: Configuration): Unit = {
        //获取分布式缓存
        val file = getRuntimeContext.getDistributedCache.getFile("DistributeCache")

        //解析文件,获取每一行,io流
        val str: Iterator[String] = Source.fromFile(file.getAbsoluteFile).getLines()
        str.foreach(line => {
          myline.append(line)
        })
      }


      //这是处理数据的方法
      override def map(value: Clazz): info = {
        var info: info = null
        //在这里进行数据转换
        for (line <- myline) {
          val splits_words = line.split(",")
          //然后再判断学号是否相同,学号相同的话就进行数据的合并
          if (
            splits_words(0).toInt == value.id) {
            //判断之后进行数据的合并
            info(value.id, value.clazz, splits_words(1), splits_words(2))
          }
        }
        //在这里进行返回
        info
      }
    }).print() //最后触发执行打印一下
  }

  //在有一个输出的类
  case class info(in: Int, clazz: String, subject: String, score: Double)
  case class Clazz(id: Int, clazz: String)

}

区别:

1.广播变量是基于内存的,是将变量分发到各个worker节点的内存上

2.分布式缓存是基于磁盘的,将文件copy到各个节点上,当函数运行时可以在本地文件系统检索该文件

声明:文章仅记录自己平时学习的过程的记录,如果有不对的或者侵权的地方请联系我改正,避免误导更多的人

你可能感兴趣的:(Flink广播变量和分布式缓存)