一、大数据场景下一份测试数据可能要几十G、几百G,用单机生成即浪费空间时间还长。可以用mapreduce或者spark来并行生成。

需求:使用spark生成1份几百G的测试数据,根据一年12个月均匀分布。

  1. 一开始没拐过弯来的是:spark要先有rdd,这个rdd怎么建,建个hdfs上空目录的rdd?此处利用rdd的惰性,先把整个大数据在内存中建好,然后在各个分区执行。
  2. 如果上面行不通(因为在创建rdd之前就要在内存创建好数据,这个数据很大,此时还没有牵扯到rdd,可能会内存溢出),就先在driver用parallelize创建1个以12个月份为key的rdd,这个rdd一共就12条数据,然后给这个rdd用HashPartitioner(12)来分成12个区,然后在rdd.mapPartitionsWithIndex中分别创建,这个方法100%会成功。
    这个思路普遍适用于用分布式程序(不一定是分布式分析和计算)

二、使用spark

1.利用rdd的惰性,先在driver中用parallelize创建数据的rdd,然后分区,然后用mapPartition或者mapPartitionsWithIndex在各个分区将数据写入到同一个hdfs目录下。

2.须知:

  1. 分区号是从0开始计数的
  2. partitionBy(Partitioner)中的org.apache.spark.HashPartitioner(12)是根据key分区的。

3.example1

package cn.dc.mockdata

import java.io.{BufferedWriter, FileOutputStream, OutputStreamWriter}

import org.apache.spark.sql.SparkSession

import scala.collection.mutable.ListBuffer

object SparkMockData {
  def main(args: Array[String]): Unit = {

    val ss = SparkSession.builder().master("local[*]").appName("mock").getOrCreate()

    case class Test(date: Int, name: String, id: String)

    val list = ListBuffer(Test(1, "1", "1"), Test(2, "2", "2"))

    list += Test(3, "3", "3")
    list += Test(3, "4", "4")
    list.append(Test(3, "4", "4"))
    list :+ Test(3, "4", "4")

    val rdd = ss.sparkContext.parallelize(list).map(test => (test.date -> test.name)).partitionBy(new org.apache.spark.HashPartitioner(12))

    rdd.mapPartitionsWithIndex(
      (partIdx, iter) => {
        val writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("test/",true)))
        val part_map = scala.collection.mutable.Map[Int, Test]()
        while (iter.hasNext) {
          val next = iter.next
          println(s"$partIdx : $next")
          writer.append(s"$partIdx").write(next.toString())
        }
        writer.flush()
        writer.close()
        part_map.iterator
      }).take(1)
  }
}

4.example2

package cn.dc.mockdata

import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{FileSystem, Path}
import org.apache.spark.Partitioner
import org.apache.spark.sql.SparkSession
import org.slf4j.LoggerFactory

object SparkMockData2 {
  val log = LoggerFactory.getLogger(classOf[SparkMockData2])
  def main(args: Array[String]): Unit = {

    val ss = SparkSession.builder().appName("mock-tencent-data").getOrCreate()
    val sc = ss.sparkContext

    val seq = Seq(1 to 12)

    /*
    构建rdd
      将RDD转成pairRDD
      如果想要更多的分区,比如24个分区,可以设置24个数,然后MyPartitioner中对12取余即可。
     */
    val rdd = sc.parallelize(seq).map(num => num -> num).partitionBy(new MyPartitioner(12))
    /*
    广播fs,12减少为3
    亲测,会报错,并发相关的错,只能一个partition创建1个fs
    val broadcast = sc.broadcast(FileSystem.get(new Configuration()))
     */

    rdd.mapPartitionsWithIndex(

      (partIdx, iter) => {
        val fs = FileSystem.get(new Configuration())

        val dirPath = new Path(f"/user/hive/dctest/tencent_big_all")
        if (!fs.exists(dirPath)) fs.mkdirs(dirPath)
        val filePath = new Path(f"$dirPath/2018${partIdx + 1}%02d")
        if (fs.exists(filePath)) fs.delete(filePath,false)
        val writer = fs.create(filePath)
        //这里必须创建1个无关的map返回,直接返回iter会报错,原因未知
        val part_map = scala.collection.mutable.Map[Int, Int]()

        for (i <- 0 until 1000) {
          val template = s"2018${partIdx + 1}" + "1"+"2"
          if (i == 999) writer.write(template.getBytes("UTF-8"))
          else writer.write((template + "\n").getBytes("UTF-8"))
        }

        part_map.iterator
      }).take(1)
  }
}

class MyPartitioner(numParts: Int) extends Partitioner {
  //设置总分区数,使用设置的总分区数覆盖默认的分区数
  override def numPartitions: Int = numParts

  //根据key返回分区号,从0开始计数,比如12个分区,就是0~11
  override def getPartition(key: Any): Int = key.toString.toInt - 1

}

class SparkMockData2{}

分布式场景下生成唯一id

  1. 尽量不用UUID,占空间,join时浪费性能。
  2. 一开始用的是时间戳+随机数+分区号,即使是5位随机数的情况下,并行度50的情况下,1亿条会有2.5万的重复,这是不可接受的,时间戳精确到毫秒,1毫秒会生成260多条数据。而且这个策略占用很多空间,时间戳的前半部分但部分是重复的,总长度也在20以上。
  3. 实际上亿只有9位,理论上10位就足够了。这里借鉴了zipWithUniqueId里的分布式递增生成主键的算法。每个分区初始值为分区号,然后每个分区递增的步长为分区数,假设总数为1亿,分区数为20,这种算法相当于把1亿分成了20份,每个分区间隔占有自己的部分。完美的分布式唯一id生成算法。

example1

一开始想在driver创建1个(o until 100000000)的rdd,然后用zipWithUniqueId,但运行时发现因为资源不够,直接卡住,给driver增加到7G内存和5个core都不行,而且这种方式本身就违反了spark的设计初衷。但小数据量下是正常运行的。

example2

创建0 until 20的rdd,然后mapPartitionWithIndex,在里面用for(i <- Range(partIdx,sum,partNum))的方式创建id,完美解决问题