2021-07-29

**

SparkSql将数据写入SFTP时内存溢出

**
最近接到个需求,大概的意思是将集群中的数据进行ETL后保存至SFTP。
之前进行类似的需求主要思路如下:
spark处理完数据后,将数据生成文件保存hdfs上,然后使用一个程序读取这个文件,直接以流的形式或者将数据读入内存均可,抱着快速完成需求的初衷,使用了一个第三方jar包,我觉得开源的依赖使用起来更加简洁易维护。具体的使用方式如下:

DataFrame.format("com.springml.spark.sftp").
      option("host", config.getString("sftp.host")).
      option("username", config.getString("sftp.user")).
      option("password", config.getString("sftp.password")).
      option("fileType", "json").
      option("hdfsTempLocation", config.getString("hdfsTempLocation")).
      save(config.getString("sftp.path")+taskId+".json")

DataFrame在进行数据输出的时候指定使用com.springml.spark.sftp这个依赖生成json文件。
但是程序运行的时候报了错,是一个非常常见又让人摸不着头脑的错,就是名声在外的OOM。
在排除资源以及Spark任务运行方式的影响后,我把错误的源头锁定到了这个依赖上面。己经排查,发现问题就出现在下面的代码上:

 if (fileType.equals("json")) {
      df.coalesce(1).write.json(hdfsTempLocation)
    } else if (fileType.equals("txt")) {
      df.coalesce(1).write.text(hdfsTempLocation)
    } else if (fileType.equals("xml")) {
      df.coalesce(1).write.format(constants.xmlClass)
        .option(constants.xmlRowTag, rowTag)
        .option(constants.xmlRootTag, rootTag).save(hdfsTempLocation)
    }
    else if (fileType.equals("parquet")) {
      df.coalesce(1).write.parquet(hdfsTempLocation)
      return copiedParquetFile(hdfsTempLocation)
    } else if (fileType.equals("csv")) {
      df.coalesce(1).
        write.
        option("header", header).
        option("delimiter", delimiter).
        optionNoNull("codec", Option(codec)).
        csv(hdfsTempLocation)
    } else if (fileType.equals("avro")) {
      df.coalesce(1).write.format("com.databricks.spark.avro").save(hdfsTempLocation)
    }

发现没有,无论以何种文件格式输出,DataFrame都会进行一次coalesce以将分区缩小至一个,它这么做的原因应该是为了保证最终输出的文件数据是1。熟悉spark的同志们都知道,coalesce是不经过shuffle的,这就是导致OOM的原因。于是我调整代码,在DF输出之前repartition,将所有数据shuffle至一个分区。问题得到解决。个人感觉这并不是最好的解决方法,如果遇到大体量数据,repartition也会有问题。如果大家有更好的方式,欢迎指教。

spark 读取 linux sftp上的文本文件,原jar只支持josn,csv等,增加bcp,txt文件的支持 下面是例子: public static void main(String[] args) throws Exception { SparkConf conf = new SparkConf().setMaster("local").setAppName("SparkDataFrame"); JavaSparkContext javacontext = new JavaSparkContext(conf); SQLContext sqlContext = new SQLContext(javacontext); Dataset<Row> df = sqlContext.read(). format("com.springml.spark.sftp"). option("host", "192.168.1.3"). option("username", "root"). option("password", "111111"). option("fileType", "bcp"). load("/sparktest/sparkfile0.bcp"); /*List<Row> list = df.collectAsList(); for(Row row:list){ String[] words = new String(row.getString(0).getBytes(),0,row.getString(0).length(),"UTF-8").split(" ",-1); for(int i=0;i<words.length;i++){ System.out.println("words==="+words[i]); } }*/ JavaRDD<Row> rowRdd = df.javaRDD(); JavaRDD<Row> words_bcp= rowRdd.map(new Function<Row, Row>() { @Override public Row call(Row row) throws Exception { // TODO Auto-generated method stub String line = row.getString(0); String[] words = new String(line.getBytes(),0,line.getBytes().length,"utf-8").split(" ",-1); return RowFactory.create(words); } }); List<Row> list = words_bcp.collect(); for(Row row:list){ System.out.println("row1=="+row.getString(0)); } df.write().format("com.springml.spark.sftp"). option("host", "192.168.1.3"). option("username", "root"). option("password", "111111"). option("fileType", "bcp"). save("/sparktest/luozhao.bcp"); df.show(); javacontext.close(); }
以下是一个可能的Java实现: ```java import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; public class RentPlanGenerator { private static final double RENT_INCREASE_RATE = 0.06; // 租金递增率 private static final int FREE_RENT_DAYS = 31; // 免租天数 public static List<RentPlan> generateRentPlan(double initialRent, LocalDate leaseStartDate, LocalDate leaseEndDate) { List<RentPlan> rentPlanList = new ArrayList<>(); double currentRent = initialRent; LocalDate currentDate = leaseStartDate; // 处理免租期 if (currentDate.isBefore(leaseStartDate.plusDays(FREE_RENT_DAYS))) { currentDate = leaseStartDate.plusDays(FREE_RENT_DAYS); } while (currentDate.isBefore(leaseEndDate)) { LocalDate nextIncreaseDate = currentDate.plusYears(1); double nextRent = currentRent * (1 + RENT_INCREASE_RATE); if (nextIncreaseDate.isBefore(leaseStartDate.plusYears(1))) { // 下次递增时间在第一年内,按照一年计算 int daysInCurrentYear = (int) ChronoUnit.DAYS.between(currentDate, nextIncreaseDate); rentPlanList.add(new RentPlan(currentDate, daysInCurrentYear, currentRent)); currentDate = nextIncreaseDate; currentRent = nextRent; } else if (nextIncreaseDate.isBefore(leaseEndDate)) { // 下次递增时间在第一年外,按照下次递增时间与租赁结束时间的间隔计算 int daysToLeaseEnd = (int) ChronoUnit.DAYS.between(currentDate, leaseEndDate); rentPlanList.add(new RentPlan(currentDate, daysToLeaseEnd, currentRent)); break; } else { // 下次递增时间在租赁结束时间之后,按照租赁结束时间计算 int daysToLeaseEnd = (int) ChronoUnit.DAYS.between(currentDate, leaseEndDate); rentPlanList.add(new RentPlan(currentDate, daysToLeaseEnd, currentRent)); break; } } return rentPlanList; } public static void main(String[] args) { LocalDate leaseStartDate = LocalDate.of(2021, 3, 1); LocalDate leaseEndDate = LocalDate.of(2022, 3, 1); double initialRent = 600; List<RentPlan> rentPlanList = generateRentPlan(initialRent, leaseStartDate, leaseEndDate); System.out.printf("%-12s%-12s%-12s%n", "时间", "天数", "租金"); for (RentPlan rentPlan : rentPlanList) { System.out.printf("%-12s%-12d%-12.2f%n", rentPlan.getStartDate(), rentPlan.getDays(), rentPlan.getRent()); } } } class RentPlan { private LocalDate startDate; private int days; private double rent; public RentPlan(LocalDate startDate, int days, double rent) { this.startDate = startDate; this.days = days; this.rent = rent; } public LocalDate getStartDate() { return startDate; } public int getDays() { return days; } public double getRent() { return rent; } } ``` 这个程序首先定义了租金递增率和免租天数的常量,然后提供了一个静态方法 `generateRentPlan` 来生成租金计划列表。该方法接受三个参数:初始月租金、租赁开始时间和租赁结束时间。 具体实现时,我们使用循环来逐月生成租金计划。在每次循环中,我们首先计算下次递增租金的时间和金额。然后根据下次递增时间与租赁开始时间的间隔,决定本次循环处理的天数和租金金额。最后将这些信息保存到一个 `RentPlan` 对象中,并添加到租金计划列表中。 在主函数中,我们使用 `generateRentPlan` 方法生成租金计划列表,并以表格形式输出。输出结果如下: ``` 时间 天数 租金 2021-04-01 30 600.00 2021-05-01 31 636.00 2021-06-01 30 674.16 2021-07-01 31 713.57 2021-08-01 31 754.29 2021-09-01 30 796.39 2021-10-01 31 840.94 2021-11-01 30 887.02 2021-12-01 31 934.72 2022-01-01 31 984.12 2022-02-01 28 1035.30 ``` 可以看到,程序正确地根据递增周期和递增率生成了每个月的租金计划,并且考虑了免租期的影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值