• Spark菜鸟学习营Day5 分布式程序开发


    Spark菜鸟学习营Day5

    分布式程序开发

    这一章会和我们前面进行的需求分析进行呼应,完成程序的开发。

    开发步骤

    分布式系统开发是一个复杂的过程,对于复杂过程,我们需要分解为简单步骤的组合。

    • 针对每个简单步骤,难度会降低,学习成本降低

    • 每个步骤都可以作为里程碑,可以反馈进展,同时,有助树立目标感。

    • Step1:需求分析

      • 1.1 拆分程序,形成步骤
        • 以语句为单位拆分,一般一个语句就是一个步骤
      • 1.2 对步骤进行分类
        • 根据需求分析指南,分入A1、A2、B1等规则小类
      • 1.3 分析每个步骤的输入输出
    • Step2:建表

      • 使用PojoMaker工具
    • Step3:新建测试程序

      • 具体可以看测试案例编写的指南
    • Step4:测试数据准备

      • 4.1 数据文件建立
        • 使用DataPrepareUtil工具
      • 4.2 将数据传入程序
        • 使用mock方法

    开发准备

    • Step1:首先在测试类中,新建对处理类的调用。
        public void runTest(ComputeBatchNode cbn, JavaSparkContext sc, Hashtable params, ComputeResult result) {
            prepareData(cbn, sc, params, result);
            new QtslProcessor(cbn, sc, params, result).process();
        }
    
    • Step2:新建处理类

    在标红处Alt+Enter,新建处理类
    输入参数:

    • cbn 运行环境,会存公共对象,比如数据库连接、redis连接等
    • sc Spark实例,调用各种Spark命令
    • params 参数,可以保存内部参数,也可以保存外部参数
    • result 输出,程序的对外输出
      ![2016-07-27 13-43-31](http://o6jujlzry.bkt.clouddn.com/2016-09-03-2016-07-27 13-43-31.jpg)

    建立如下的初始代码:

    public class QtslProcessor extends SplitProcessor {
        public QtslProcessor(ComputeBatchNode cbn, JavaSparkContext sc, Hashtable params, ComputeResult result) {
            super(cbn, sc, params, result);
        }
        @Override
        public String process() {
            return null;
        }
    }
    

    开发开发开发

    A.数据清理

    • 因为不同的数据库的删除逻辑不一致,所以采用统一api的方式调用,可以将不同数据库的写法统一
      • 语句入口方法是delete()
      • 语句需要通过appendDeleteCondition来进行输出

    B.批量数据转换

    B1.Dataframe方式

    分为几个步骤:

    • Step1 对SQL语句进行规范化
    • Step1.1 列名对准
            insert into tab1(a1,a2,a3)
                select b1,b2,b3 from tab2
    

    替换为

            insert into tab1(a1,a2,a3)
                select b1 a1,b2 a2,b3 a3 from tab2
    
    • Step1.2 替换逻辑
    • IN语句
               Select a from tab1 t 
                    where t.a in (select a from tab1)         
    

    替换为

               Select a from tab1 t , (select distinct a from tab1) t2
                    where t.a = t1.a            
    
    • Step1.3 替换函数

      • 待补充
    • Step1.4 替换变量

      • 在变量外面增加#{}
        select a from tab1 t where a = v_a
    

    替换为

       select a from tab1 t where a = #{v_a}    
    
    • Step2 初始化变量

      • 调用putSqlParam
    • Step3 执行Sql

      • 调用runSparkSql方法
    • Step4 输出结果

      • 调用appendResultDataframe方法

    C.逐笔循环数据转换

    • C1.Cursor转RDD
      这是最为复杂的一个步骤,需要掌握RDD的开发基础

    • C2.单行数据过滤
      采用fiter方法,内部采用where方法来定义条件

                JavaRDD<QtslTempPojo> perparedQtslRDD = filtedQtslRDD.filter(
                    v1 -> where(() -> v1.getZQZH().equals("0"))
                            .or(() -> v1.getZQZH().equals(""))
                            .get());
    

    对于in,exists,not in , not exists 这样的单行过滤条件,我们需要采用anyMatch方法来进行判断
    可以使用comparePojo方法对两个pojo进行比较
    如下是一个not exists逻辑

                    perparedQtslRDD
                            .filter(v1 -> where().and_not(
                                    () -> oracleData.stream().anyMatch(
                                            record -> comparePojo(record, v1)
                                    ))
                                    .get()
                            )
    
    • C3.过滤重复数据
      采用groupBy方法,对每个分组只返回一条记录
        .groupBy(
                            v1 -> new Tuple2<>(v1.getFundCode(), v1.getSecurityId()))
        .map(
                            v1 -> toList(v1._2).get(0));    
    
    • C4.单行数据删除
      待补充

    • C5.单行数据输出
      实际就是从一个pojo转换成另外一个pojo,考虑到可能出现数据异常的情况,推荐采用flatMap方法实现
      可以使用clonePojo方法,将两个pojo中相同的字段自动转换,差异字段需要额外赋值

            .flatMap(new FlatMapFunction<QtslTempPojoExtend, OutTrdQtslSubPojo>() {
                @Override
                public Iterable<OutTrdQtslSubPojo> call(QtslTempPojoExtend v1) {
                    OutTrdQtslSubPojo outTrdQtslSubPojo = clonePojo(v1, OutTrdQtslSubPojo.class).orElseGet(null);
                    if (outTrdQtslSubPojo != null) {
                        outTrdQtslSubPojo.setDEAL_FLAG("0");
                        outTrdQtslSubPojo.setSEQ_NO(Long.toString(v1.getSeqNo()));
                        outTrdQtslSubPojo.setSUB_NO("1");
                        outTrdQtslSubPojo.setSUB_NO_PRE("0");
                    }
                    return result(outTrdQtslSubPojo);
                }
    

    D. 优化处理

    • D1.从Oracle取数
      第一步,需要在sqlmap文件中配置sql语句
        <select id="selectFundCjqsTmp" resultType="java.util.Map" parameterType="HashMap">
            <![CDATA[
    			SELECT t.*
                              FROM dat_fund_cjqs_tmp t
                             WHERE t.bcrq = #{businessDate}
                               AND t.comfirm_status = '1')
    		]]>
        </select>
    

    第二步,通过getPojoListFromMybatis方法获取数据

            List<OutTrdQtslHisPojo> oracleData = this
                    .getPojoListFromMybatis("splitSqlMapper.getQtslHis", OutTrdQtslHisPojo.class);
    

    样例代码1

    • 步骤1:清理中间表+结果数据表(A3+A4) *
            appendDeleteCondition(
                    delete("out_trd_qtsl").where(field("rq").eq(lastDate))
            );
    
    • 步骤2:输出数据表,清理Oracle(A4) *
    appendDeleteCondition(
                    delete("out_trd_qtsl_sub").where(field("rq").eq(splitDate)));
    
    • 步骤3:输出数据表,清理Oracle(A4) *
            appendDeleteCondition(
                    delete("out_trd_qtsl_his").where(field("rq").eq(splitDate)));
    
    • 步骤4:使用Dataframe的select语句来进行处理(B1) *
            putSqlParam("v_scdm", "001");
            putSqlParam("v_last_date", lastDate);
            DataFrame df2 = runSparkSql("  SELECT scdm, hydm, sjlx, zqzh, xwh, zqdm, zqlb, ltlx, qylb, gpnf, sl1, sl2,
    " +
                    "         bh1, bh2, fzdm, rq, bcsm, byn
    " +
                    "           FROM qtsl_temp a, (SELECT distinct partner_code
    " +
                    "            FROM par_fund_partner
    " +
                    "           WHERE market_code =  #{v_scdm}
    " +
                    "             AND sub_partner_code = '000000'
    " +
                    "             AND #{v_last_date} BETWEEN inure_begin_date AND inure_end_date) b
    " +
                    "           WHERE a.rq =  #{v_last_date}
    " +
                    "               AND a.zqzh = b.partner_code
    " +
                    "        ");
            appendResultDataframe(df2, OutTrdQtslPojo.class);
    

    待续...

    练习3

    题目

    进行RDD去重操作的训练

    1. 读取交易记录
    2. 按照fundCode+SecurityId进行分组
    3. 取出quantity最小的那条记录
    4. 输出结果
    • 步骤1:对RDD数据进行分组,groupBy方法传入的是分组条件,请注意这里是对两个字段分组,所以我们输出一个Tuple2。
    return this.getInputRDD(PracticePojo.class)
                    .groupBy(
                            v1 -> new Tuple2<>(v1.getFundCode(), v1.getSecurityId()))
    

    这里需要注意的是,groupBy方法的返回是:
    JavaPairRDD<Tuple2<String, String>, Iterable>
    这是一个key-value结果,其中key是分组的键值,而value是一个数组。
    比如下:输入[ (1 2 3); (1 2 4); (2 3 4) ]
    如果按照字段1进行groupBy
    结果为: (1, [(1 2 3 ) , (1,2,4)])
    (2, [(2 3 4)])

    • 步骤2:对数据的迭代数据排序,并返回第一条记录。比较算法,采用if逻辑可以防止变量溢出。
     .map(
                            v1 -> toOrderedList(v1._2,
                                    (t1, t2) -> {
                                        //t1比t2大,返回正数
                                        //t1比t2小,返回负数
                                        //t1和t2一样大,返回0
                                        if (t1.getQuantity() > t2.getQuantity()) return 1;
                                        if (t1.getQuantity() < t2.getQuantity()) return -1;
                                        return 0;
                                    }
                            ).get(0));
    

    练习4

    题目:数据关联过滤练习

    1. 读取交易记录
    2. 按照PracticeSecurity对交易数据进行过滤
    3. 输出结果

    思路1:采用join操作

    • 步骤1:获取输入RDD
            JavaRDD<PracticePojo> inputRDD = this.getInputRDD(PracticePojo.class);
            JavaRDD<PracticeSecurityPojo> securityRDD = this.getInputRDD(PracticeSecurityPojo.class);
    
    • 步骤2:将两个RDD转换为PairRDD,因为仅有PairRDD支持join操作
            JavaPairRDD<String, PracticePojo> pairInputRDD = inputRDD.mapToPair(
                    practicePojo -> new Tuple2(practicePojo.getSecurityId(), practicePojo));
    
            JavaPairRDD<String, PracticeSecurityPojo> pairSecurityRDD = securityRDD.mapToPair(
                    practiceSecurityPojo -> new Tuple2<>(practiceSecurityPojo.getSecurityId(), practiceSecurityPojo));
    
    • 步骤3:执行join操作,并返回结果
    return pairInputRDD
                    .join(pairSecurityRDD)
                    .map(new Function<Tuple2<String, Tuple2<PracticePojo, PracticeSecurityPojo>>, PracticePojo>() {
                        @Override
                        public PracticePojo call(Tuple2<String, Tuple2<PracticePojo, PracticeSecurityPojo>> v1) throws Exception {
                            return v1._2._1;
                        }
                    });
    

    这里没有采用lambda表达式方式,因为类型提示比较有用。
    我们可以看到join方法的返回是Tuple2<String, Tuple2<PracticePojo, PracticeSecurityPojo>>
    举个例子说明:
    数据集1:[(1,3,4) ; (1,5,6) ]
    数据集2:[(1,2)]
    如果按照第一个字段进行join
    结果为:[ (1, ( (1,3,4) , (1,2) ) ,
    (1, ( (1,5,6) , (1,2) ) ]
    在练习中,需要返回的是[(1,3,4);(1,5,6)],所以需要获得 v1._2._1

  • 相关阅读:
    sitecore系统教程之体验编辑器
    Sitecore安装(手动方式)
    Sitecore详细安装(包含sitecore安装过程截图)
    logstash快速入门实战指南-Logstash简介
    Elasticsearch从入门到精通之Elasticsearch基本概念
    arcgis api 3.x for js 解决 textSymbol 文本换行显示(附源码下载)
    openlayers4 入门开发系列之前端动态渲染克里金插值 kriging 篇(附源码下载)
    arcgis api 4.x for js 结合 react 入门开发系列react全家桶实现加载天地图(附源码下载)
    arcgis api 4.x for js 结合 react 入门开发系列"esri-loader"篇(附源码下载)
    arcgis api 4.x for js 结合 react 入门开发系列初探篇(附源码下载)
  • 原文地址:https://www.cnblogs.com/dt-zhw/p/5837321.html
Copyright © 2020-2023  润新知