SparkStreaming实时数仓——金额分摊

本文介绍了一种解决订单详情分摊金额问题的思路和实现代码,通过判断是否为最后一个详情来消除精度误差。同时,详细阐述了如何将处理后的数据写入Clickhouse进行OLAP查询,涉及Hadoop、Hive、Kylin和Elasticsearch等OLAP工具。
摘要由CSDN通过智能技术生成

一、实现思路

1.按比例求分摊: 分摊金额/实际付款金额 = 个数*单价/该单原始总金额
2.所以: 分摊金额 = 个数*单价*实际付款金额/该单原始总金额
3.由于有除法的存在, 结果我们需要做四舍五入, 会导致精度的丢失
4.一个订单对应多个详情, 每个详情均做四舍五入, 他们的和可能与该订单的实际支付总金额不等
            订单              详情 1   详情 2    详情 3
       原始: 120            	    40		40         40
       分摊: 100				    33.33    33.33	   33.33			
5.需要消除这个bug
	如果有 3 个详情:
        前面的详情使用乘除:    分摊金额 = 个数*单价*实际付款金额/该单原始总金额
        最后一个详情使用减法:  分摊金额 = 实际付款总金额 -- ∑前面分摊总金额
               
6.难点: 如何知道最后一个详情
         当前详情
         	个数*成单价 == 该单原始总金额 -- ∑前面详情的个数*单价
                    
7.需要记录的状态:
        ∑前面分摊总金额
        ∑前面详情的个数*单价
OLAP在线分析处理查询:
hive,hbase+phoenix
kylin
es
Clickhourse

微信图片_20201123200131

二、实现代码

/**
  -- 接双流join代码
*/ 
    orderWideDetail
        .mapPartitions((it: Iterator[OrderWide]) => { // // 计算分摊金额
          val client: Jedis = MyRedisUtil.getClient
          val r = it.map(orderWide => {
            // 保存前面所有详情的总金额 key
            val preTotalKey = s"pre_total:${orderWide.order_id}"
            // 保存前面所有详情的总分担 key
            val preShareKey = s"pre_share:${orderWide.order_id}"

            // 1. 获取前面的详情的总金额  如果是该单的第一个详情, 则是空
            val preTotalTemp: String = client.get(preTotalKey)
            val preTotal: Double = if (preTotalTemp != null) preTotalTemp.toDouble else 0D

            // 2. 获取前面的详情的总分摊
            val preShareTemp: String = client.get(preShareKey)
            val preShare: Double = if (preShareTemp != null) preShareTemp.toDouble else 0D

            // 3. 判断是否为最后一单
            val current = orderWide.sku_num * orderWide.sku_price
            if (current == orderWide.original_total_amount - preTotal) { // 是最后一单
              val share = orderWide.final_total_amount - preShare
              orderWide.final_detail_amount = share
              // 删除相关的key
              client.del(preTotalKey)
              client.del(preShareKey)
              println("最后一单...")
            } else {
              val shareTemp = current * orderWide.final_total_amount / orderWide.original_total_amount
              val share = math.round(shareTemp * 100).toDouble / 100 // 10.888  => 10.888*100 => 1088.8 => 1089 => 1089/100 => 10.89
              orderWide.final_detail_amount = share
              // redis 保存总的分摊, 必须使用redis提供的自增功能
              client.incrByFloat(preShareKey, share)
              // 前面详情的原始总金额
              client.incrByFloat(preTotalKey, current)
              println("不是最后一单...")
            }
            orderWide
          })
          client.close()
          r
        })

三、将数据写入clickhouser

val clickHouseUrl = "jdbc:clickhouse://hadoop162:8123/gmall"
orderWideDetail
	.foreachRDD(rdd => {  //把数据写入到clickhouser中
          rdd
              .toDS
              .write
              .option("batchsize", "100")
              .option("isolationLevel", "NONE") // 设置没有事务
              .option("numPartitions", "2") // 设置并发
              .mode("append")
              .jdbc(clickHouseUrl,"order_wide",new Properties())
        OffsetManager.saveOffsets(offsetRanges.values.reduce(_ ++ _), groupId, topics)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值