Java Spark自定义累加器的实现(需要注意的细节!!!)

本文详细介绍了如何在Apache Spark中实现自定义累加器,通过具体代码示例展示了AccumulatorParam接口的实现方法,包括addAccumulator、addInPlace和zero方法的具体逻辑。

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

Spark自定义累加器需要实现 AccumulatorParam

!!!!!!

需要注意的是 ,源码中给出

 

也就是说两个方法的实现是不一样的。

下面是我的实现

 TimeAccumulator.java


import constant.Constants;
import org.apache.spark.AccumulatorParam;
import util.StringUtils;

public class TimeAccumulator implements AccumulatorParam<String> {

    @Override
    public String addAccumulator(String s1, String s2) {
        //校验:s2位空的话,直接返回s1
        if(StringUtils.isEmpty(s2)) {
            return s1;
        }
        // 使用StringUtils工具类,从s1中,提取s2对应的值,并累加
        String oldValue = StringUtils.getFieldFromConcatString(s1, "\\|", s2);
        if(oldValue != null) {
            int newValue = Integer.valueOf(oldValue) + 1;
            return StringUtils.setFieldInConcatString(s1, "\\|", s2, String.valueOf(newValue));
        }
        return s1;
    }

    @Override
    public String addInPlace(String r1, String r2) {
        //校验:r1位空的话,直接返回r2
        if(StringUtils.isEmpty(r1)) {
            return r2;
        }
        // 使用StringUtils工具类,从r1 r2对应的值,并累加
        int oldValue1 = Integer.parseInt(StringUtils.getFieldFromConcatString(r1,"\\|",Constants.S_1s_3s));
        int oldValue2 = Integer.parseInt(StringUtils.getFieldFromConcatString(r1,"\\|",Constants.S_4s_6s));
        int oldValue3 = Integer.parseInt(StringUtils.getFieldFromConcatString(r1,"\\|",Constants.S_7s_9s));
        int oldValue4 = Integer.parseInt(StringUtils.getFieldFromConcatString(r1,"\\|",Constants.S_10s_30s));
        int oldValue5 = Integer.parseInt(StringUtils.getFieldFromConcatString(r1,"\\|",Constants.S_30s_60s));
        int oldValue6 = Integer.parseInt(StringUtils.getFieldFromConcatString(r1,"\\|",Constants.S_1m_3m));
        int oldValue7 = Integer.parseInt(StringUtils.getFieldFromConcatString(r1,"\\|",Constants.S_3m_10m));
        int oldValue8 = Integer.parseInt(StringUtils.getFieldFromConcatString(r1,"\\|",Constants.S_10m_30m));
        int oldValue9 = Integer.parseInt(StringUtils.getFieldFromConcatString(r1,"\\|",Constants.S_30m));

        int newValue1 = Integer.parseInt(StringUtils.getFieldFromConcatString(r2,"\\|",Constants.S_1s_3s));
        int newValue2 = Integer.parseInt(StringUtils.getFieldFromConcatString(r2,"\\|",Constants.S_4s_6s));
        int newValue3 = Integer.parseInt(StringUtils.getFieldFromConcatString(r2,"\\|",Constants.S_7s_9s));
        int newValue4 = Integer.parseInt(StringUtils.getFieldFromConcatString(r2,"\\|",Constants.S_10s_30s));
        int newValue5 = Integer.parseInt(StringUtils.getFieldFromConcatString(r2,"\\|",Constants.S_30s_60s));
        int newValue6 = Integer.parseInt(StringUtils.getFieldFromConcatString(r2,"\\|",Constants.S_1m_3m));
        int newValue7 = Integer.parseInt(StringUtils.getFieldFromConcatString(r2,"\\|",Constants.S_3m_10m));
        int newValue8 = Integer.parseInt(StringUtils.getFieldFromConcatString(r2,"\\|",Constants.S_10m_30m));
        int newValue9 = Integer.parseInt(StringUtils.getFieldFromConcatString(r2,"\\|",Constants.S_30m));

        return Constants.S_1s_3s+"="+(oldValue1+newValue1)+"|"+Constants.S_4s_6s+"="+(oldValue2+newValue2)+"|"+Constants.S_7s_9s+"="+(oldValue3+newValue3)+"|"+
                Constants.S_10s_30s+"="+(oldValue4+newValue4)+"|"+Constants.S_30s_60s+"="+(oldValue5+newValue5)+"|"+Constants.S_1m_3m+"="+(oldValue6+newValue6)+"|"+
                Constants.S_3m_10m+"="+(oldValue7+newValue7)+"|"+Constants.S_10m_30m+"="+(oldValue8+newValue8)+"|"+Constants.S_30m+"="+(oldValue9+newValue9);
    }

    @Override
    public String zero(String initialValue) {
        return Constants.S_1s_3s+"=0|"+Constants.S_4s_6s+"=0|"+Constants.S_7s_9s+"=0|"+
                Constants.S_10s_30s+"=0|"+Constants.S_30s_60s+"=0|"+Constants.S_1m_3m+"=0|"+
                Constants.S_3m_10m+"=0|"+Constants.S_10m_30m+"=0|"+Constants.S_30m+"=0";
    }
}

 StringUtils.java

package util;

public class StringUtils {
    /**
     * 判断字符串是否为空
     *
     * @param str 字符串
     * @return 是否为空
     */
    public static boolean isEmpty(String str) {
        return "".equals(str) || str == null;
    }

    /**
     * 判断字符串是否不为空
     *
     * @param str 字符串
     * @return 是否不为空
     */
    public static boolean isNotEmpty(String str) {
        return str != null && str.length() > 0;
    }

    /**
     * 从拼接的字符串中提取字段
     * searchKeywords=abc|clickCategoryIds=1,2,3
     *
     * @param str       字符串   searchKeywords=abc|clickCategoryIds=1,2,3
     * @param delimiter 分隔符   |
     * @param field     字段   clickCategoryIds
     * @return 字段值   1,2,3
     */
    public static String getFieldFromConcatString(String str, String delimiter, String field) {
        String[] split = str.split(delimiter);
        String value = null;
        for (String s : split) {
            String[] split1 = s.split("=");
            if(field.equals(split1[0])){
                value = split1[1];
            }
        }
        return value;
    }

    /**
     * 从拼接的字符串中给字段设置值
     * searchKeywords=abc|clickCategoryIds=1,2,3
     *
     * @param str           字符串  searchKeywords=abc|clickCategoryIds=1,2,3
     * @param delimiter     分隔符  |
     * @param field         字段名 searchKeywords
     * @param newFieldValue 新的field值  efg
     * @return 字段值  searchKeywords=efg|clickCategoryIds=1,2,3
     */
    public static String setFieldInConcatString(String str, String delimiter, String field, String newFieldValue) {
        String[] split = str.split(delimiter);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < split.length; i++) {
            String[] split1 = split[i].split("=");
            if(field.equals(split1[0])){
                sb.append(split1[0]);
                sb.append("=");
                sb.append(newFieldValue);
                sb.append("|");
                continue;
            }
            sb.append(split[i]);sb.append("|");
        }
        return sb.substring(0,sb.lastIndexOf("|"));
    }
}

 

### Spark 累加器的使用方法 #### 什么是累加器累加器Spark 提供的一种只读共享变量,能够在分布式环境中安全地执行跨任务的累积操作。它们通常被用来实现求和、计数等功能,并且只能通过 `add` 方法增加其值[^4]。 #### 创建数值型累加器 可以通过调用 `SparkContext.longAccumulator()` 或者 `SparkContext.doubleAccumulator()` 来创建 Long 类型或者 Double 类型的累加器实例。这些累加器主要用于简单的数值累计操作。 ```java // Java 示例代码 import org.apache.spark.api.java.JavaRDD; import org.apache.spark.SparkConf; import org.apache.spark.api.java.JavaSparkContext; public class AccumulatorExample { public static void main(String[] args) { SparkConf conf = new SparkConf().setAppName("accumulator-example").setMaster("local"); JavaSparkContext sc = new JavaSparkContext(conf); // 创建一个 long 型的累加器 long accumulatorValue = sc.sc().longAccumulator(0); JavaRDD<Integer> rdd = sc.parallelize(Arrays.asList(1, 2, 3, 4)); // 对 RDD 的每个元素应用 add 操作更新累加器 rdd.foreach(x -> accumulatorValue.add(x)); System.out.println("累加器最终值:" + accumulatorValue.value()); sc.close(); } } ``` 这段代码展示了如何初始化一个长整型累加器并将 RDD 中的数据逐项加入其中。 #### 自定义累加器 除了内置支持的基础数据类型外,还可以开发自己的累加器以适应更复杂的应用场景。比如针对电商数据分析中的商品热度统计问题,可以设计专门用于记录不同用户行为(如点击、下单、付款)频次的对象作为累加单元[^5]。 下面是一个基于 Scala 实现的例子: ```scala case class HotCategory(var clickCount: Int, var orderCount: Int, var payCount: Int) class CategoryAccumulator extends AccumulatorParam[Map[String, HotCategory]] { override def zero(initialValue: Map[String, HotCategory]): Map[String, HotCategory] = initialValue.mapValues(h => h.copy(clickCount = 0, orderCount = 0, payCount = 0)) override def addInPlace(t1: Map[String, HotCategory], t2: Map[String, HotCategory]): Map[String, HotCategory] = { (t1.keySet ++ t2.keySet).map { key => val hc1 = t1.getOrElse(key, HotCategory(0, 0, 0)) val hc2 = t2.getOrElse(key, HotCategory(0, 0, 0)) key -> HotCategory( hc1.clickCount + hc2.clickCount, hc1.orderCount + hc2.orderCount, hc1.payCount + hc2.payCount ) }.toMap } } val categoryAccumulator = sc.accumulableCollection(new mutable.HashMap[String, HotCategory])() categoryAccumulator.register() rdd.foreachPartition(partition => partition.foreach(record => { record.behaviorType match { case "click" => categoryAccumulator += ((record.categoryId, HotCategory(1, 0, 0))) case "order" => categoryAccumulator += ((record.categoryId, HotCategory(0, 1, 0))) case "pay" => categoryAccumulator += ((record.categoryId, HotCategory(0, 0, 1))) } })) println(categoryAccumulator.value.mkString("\n")) ``` 此脚本构建了一个名为 `HotCategory` 的样例类来保存各类别的三种主要活动指标;接着定义了继承自 `AccumulatorParam[T]` 抽象类的新类 `CategoryAccumulator` ,从而实现了对多个类别下各种交互动作次数的有效汇总[^3]。 #### 注意事项 - **线程安全性**:由于累加器的设计初衷就是在多线程环境下工作良好,因此无需担心同步问题。 - **访问权限限制**:仅驱动程序能够获取累加器当前状态而工作者节点上的进程则无权查询该信息。 - **性能考量**:尽管累加器有助于减少网络传输开销,但在某些情况下仍需注意可能引发过多的小规模通信请求影响整体效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值