Spark中SimpleDateFormat的安全使用

日常开发常用时间格式化工具,其在单线程环境可放心使用,但非线程安全,Spark开发处理时间相关数据时需谨慎。分享在Spark开发中安全使用该工具的方法,JDK 7及以下适用,JDK 8及以上优先用线程安全且性能更好的工具。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

日常开发中,我们经常使用 SimpleDateFormat 来对时间进行格式化/转换等操作。我们可以放心得使用在单线程环境中,但是需要时刻谨记 SimpleDateFormat 是非线程安全的,在 Spark 开发中同样需要注意。尤其碰到时间相关数据的处理时,不管是 UDF 还是 匿名函数 ,都需要谨慎使用非线程安全的 SimpleDateFormat

在这里和大家分享笔者如何在 Spark 开发中安全的使用 SimpleDateFormat

  • 以下方案仅在使用 JDK 7及以下 的情况中推荐,如果使用 JDK 8及以上 ,请优先使用线程安全且性能更好的 DateTimeFormatter 来处理时间和日期 (性能测试参考链接);
  • 以下演示代码为 Scala 版本,Java 版本可自行修改;
  1. 声明线程安全的 ThreadSafeFormat 对象(也可以在类/对象中声明为变量),继承自 ThreadLocal ,并覆写 initialValue() 方法初始化 SimpleDateFormat 实例;
     import java.text.SimpleDateFormat
     import java.util.SimpleTimeZone
    
     /**
      * Thread Safe SimpleDateFormat for Spark.
      */
     object ThreadSafeFormat extends ThreadLocal[SimpleDateFormat] {
    
       override def initialValue(): SimpleDateFormat = {
         val dateFormat = new SimpleDateFormat("yyyy-MM-dd:H")
         // 假如需要使用特定的时区,也可以指定
         val utcTimeZone = new SimpleTimeZone(SimpleTimeZone.UTC_TIME, "UTC")
         dateFormat.setTimeZone(utcTimeZone)
         dateFormat
       }
    
     }
    
  2. 使用 ThreadSafeFormat.get() 就可以获取到线程安全的 SimpleDateFormat 对象来处理日期和时间相关数据;
import org.apache.log4j.{Level, Logger} import org.apache.spark.SparkConf import org.apache.spark.sql.functions._ import org.apache.spark.sql.{DataFrame, SparkSession} import java.sql.Timestamp import java.text.SimpleDateFormat import java.util.Calendar object prac1 { def main(args: Array[String]): Unit = { System.setProperty("HADOOP_USER_NAME","root") Logger.getLogger("org").setLevel(Level.ERROR) val sparkConf = new SparkConf().setMaster("local[*]").setAppName("task1") val spark = new SparkSession.Builder().config(sparkConf) .config("spark.sql.warehouse.dir", "hdfs://node1:8020/user/hive/warehouse") .config("spark.sql.shuffle.partitions", "1500") .config("dfs.replications", "3") .config("hive.exec.dynamic.partition.mode", "nonstrict") .config("hive.exec.max.dynamic.partitions", "3000") .enableHiveSupport() .getOrCreate() val table=Array("customer_inf","product_info","order_master","order_detail") table.foreach(x=>{ println(s"===================抽取数据:$x=====================") val mysql = new Tools().loadMysql(spark, x).cache() mysql.show() println("====================获取增量字段=====================") var incre: Any = null val simple = new SimpleDateFormat("yyyyMMdd") val calendar=Calendar.getInstance() calendar.add(Calendar.DATE,-1) val etl_date = simple.format(calendar.getTime) val simple1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") if (x=="customer_inf"){ incre = spark.table(s"ods.customer_inf").select(max("modified_time")).collect()(0).get(0) if (incre==null){ incre=Timestamp.valueOf(simple1.parse("0").getTime.toString) } } else { incre = spark.table(s"ods.$x").select(max("modified_time")).collect()(0).get(0) if (incre==null){ incre=Timestamp.valueOf(simple1.parse("0").getTime.toString) } } println(s"======================Final incre:$incre=======================") mysql.where(col("modified_time")>incre) .withColumn("etl_date",lit(etl_date).cast("string")) // .show() .write.format("hive").mode("append").partitionBy("etl_date").saveAsTable(s"ods.$x") }) spark.close() } } class Tools { def loadMysql(spark:SparkSession,table:String): DataFrame ={ val prop=Map( "url"->"jdbc:mysql://node1/ds_db01?useSSL=false", "driver"->"com.mysql.jdbc.Driver", "user"->"root", "password"->"8888", "dbtable"->table ) spark.read.format("jdbc") .options(prop) .load() } }用这里小白我该怎么写这个代码,还有怎么记
最新发布
03-16
<think>嗯,用户让我解释这段Scala代码,并且作为一个小白该怎么写和记住它。首先,我需要理解代码的整体结构。看起来这是一个Spark程序,用于从MySQL抽取数据到Hive的ODS层。用户可能对Spark和数据处理流程不太熟悉,所以得从基础开始解释。 代码开头导入了很多库,比如Log4j、SparkConf、SparkSession等。这部分可能需要解释每个导入的作用,但用户是小白,可能需要先概述这些库的用途,而不是逐个详细说明。然后定义了一个对象prac1,里面有main方法,这是Scala程序的入口点。 接下来,System.setProperty设置了Hadoop用户,Logger.getLogger用来调整日志级别,避免过多的日志干扰。然后配置SparkConf,设置本地模式和应用名称,接着创建SparkSession,配置Hive相关的参数,比如warehouse目录、分区数、副本数等。这部分涉及到Spark和Hive的集成,用户可能需要了解SparkSession的作用以及Hive的配置项。 然后定义了一个表名数组,循环处理每个表。在循环里,用Tools类从MySQL读取数据,缓存并展示。接着处理增量字段,获取前一天的日期作为ETL日期。这里使用SimpleDateFormat和Calendar,可能需要解释时间处理的部分。然后根据表名不同,处理增量时间戳,如果是customer_inf表,则从ODS层取最大modified_time,否则从其他表取。如果incre是null,就设置为初始时间。这可能涉及到增量抽取的逻辑,用户需要明白如何获取增量数据。 之后用where过滤出modified_time大于incre的数据,添加etl_date分区字段,写入Hive表。这里需要解释DataFrame的写入操作,分区的作用,以及mode("append")的意义。最后关闭SparkSession。 Tools类中的loadMysql方法,使用JDBC连接MySQL,读取数据。需要解释JDBC配置参数,比如URL、驱动、用户密码等。 用户可能需要知道整个流程的步骤:配置环境、读取MySQL数据、处理增量、写入Hive。以及如何逐步构建这个代码,比如先配置Spark,再处理数据抽取,最后处理增量逻辑。可能需要分步骤讲解,每个部分的作用和编写方法。 关于记忆方法,可以建议用户将代码分为几个模块:配置部分、数据抽取循环、增量逻辑、写入操作。每个模块单独理解,再组合起来。同时,强调增量同步的关键点,比如获取最大时间戳,过滤新增数据,分区写入。可能还需要提醒常见问题,比如时间格式处理、空值处理、分区字段添加等。 用户可能对Spark和Hive的配置不太熟悉,需要解释这些配置项的意义,比如shuffle分区数、副本数、动态分区设置。此外,可能需要提到依赖管理,比如确保正确的JDBC驱动和Spark版本兼容。 最后,建议用户通过实际运行和调试来加深理解,比如打印中间结果,查看日志,逐步验证每个部分的功能。同时,可以推荐学习资源,如Spark SQL文档、Scala基础、ETL最佳实践等,帮助用户巩固知识。</think>好的,我会分步骤帮你理解这个代码的结构和功能,并提供记忆方法: 一、代码结构分解(按执行顺序) 1. 环境准备部分 ```scala // 设置Hadoop用户身份(对接HDFS时需要) System.setProperty("HADOOP_USER_NAME","root") // 设置日志级别(避免过多日志干扰) Logger.getLogger("org").setLevel(Level.ERROR) // 创建Spark配置 val sparkConf = new SparkConf() .setMaster("local[*]") // 本地模式运行 .setAppName("task1") // 应用名称 // 创建SparkSession(访问Hive的核心入口) val spark = SparkSession.builder() .config(sparkConf) .config("spark.sql.warehouse.dir", "hdfs://node1:8020/user/hive/warehouse") .config("spark.sql.shuffle.partitions", "1500") // shuffle分区数 .config("dfs.replications", "3") // HDFS副本数 .config("hive.exec.dynamic.partition.mode", "nonstrict") // 动态分区模式 .enableHiveSupport() // 启用Hive支持 .getOrCreate() ``` 2. 表定义与循环处理 ```scala // 需要处理的表列表 val table = Array("customer_inf","product_info","order_master","order_detail") // 遍历每张表进行数据同步 table.foreach { x => // 这里处理每张表的逻辑... } ``` 3. 增量同步核心逻辑 ```scala // 获取当前日期(用于分区字段) val calendar = Calendar.getInstance() calendar.add(Calendar.DATE, -1) // 取昨天日期 val etl_date = new SimpleDateFormat("yyyyMMdd").format(calendar.getTime) // 获取最大修改时间(增量标记) var incre = spark.table(s"ods.$x") .select(max("modified_time")) // 从Hive表获取最新时间 .collect()(0).get(0) // 处理首次同步的情况(没有历史数据时) if (incre == null) { incre = new Timestamp(0) // 1970-01-01 00:00:00 } // 过滤增量数据并写入Hive mysql.where(col("modified_time") > incre) .withColumn("etl_date", lit(etl_date)) // 添加分区字段 .write .format("hive") .mode("append") // 追加模式 .partitionBy("etl_date") // 按日期分区 .saveAsTable(s"ods.$x") ``` 二、代码功能总结 这是一个基于Spark的增量数据同步程序,主要完成: 1. 从MySQL数据库抽取数据 2. 按modified_time字段识别增量数据 3. 将增量数据写入Hive的ODS层(按日期分区) 4. 支持多表批量处理 三、记忆技巧 1. 分层记忆法: - 环境配置层(Spark+Hive配置) - 数据抽取层(MySQL连接) - 增量处理层(时间戳比对) - 数据存储层(Hive写入) 2. 关键配置记忆点: - `enableHiveSupport`:启用Hive集成 - `dynamic.partition.mode=nonstrict`:动态分区模式 - `shuffle.partitions=1500`:调整并行度 3. 增量逻辑口诀: - "查Hive取最新,比时间筛增量" - "首次全量用零点,后续增量比时间" 四、小白学习路径建议 1. 先理解基础组件: - Spark SQL:`spark.read.format("jdbc")` - Hive集成:`saveAsTable`写入 - 时间处理:`SimpleDateFormat`用法 2. 再掌握增量逻辑: - 通过`modified_time`字段判断新增/更新 - 使用`max()`函数获取最新时间点 - `etl_date`分区字段的作用 3. 最后理解配置参数: - `local[*]`:本地模式运行 - `shuffle.partitions`:调整任务并行度 - `dfs.replications`:HDFS副本数量 五、常见问题提醒 1. 驱动问题:确保mysql-connector-java.jar在classpath中 2. 时间格式:SimpleDateFormat的线程安全问题(当前写法在Spark中是安全的) 3. 空值处理:`incre == null`的判断必须放在Driver端(collect后判断) 4. 分区写入:Hive表必须预先创建好分区字段对应的表结构 建议通过画流程图来辅助记忆(数据流向:MySQL -> Spark -> Hive),并实际运行调试观察每个阶段的输出结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值