基于flink,kafka实时计算订单topN,存到redis

 主要难点是计算一定时间内订单总数topN

val result = kafkaSource
  .filter(_.startsWith("L")) // 只保留O开头的数据
  .map(item => item.substring(2)) // 去掉字符串最开始O:
  .map(item => item.split(",")) // 将数据切割成数组
  .map(item => (item(1), item(4).toInt)) // 构建元组,key要统计的ke
  .windowAll(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(2)))
  .aggregate(new ItemHashMapCountAgg(), new ItemAllWindowResult())
 result.print()

使用aggregate算子,重写方法自定义聚合函数

ItemHashMapCountAgg

// 决定了时间窗口内的数据,该怎么聚合
// 泛型: 1 输入的数据类型。 2. 我们一个分区的数据会聚合成什么样子 3. 最终输出的结果是什么类型
class ItemHashMapCountAgg extends AggregateFunction[(String, Int), util.HashMap[String, Int], List[(String, Int)]] {
  // 初始化操作,这里初始化需要创建一个hashMap
  override def createAccumulator = new util.HashMap[String, Int]

  /**
   * 每一项数据来了之后怎么聚合
   *
   * @param value       来的数据每一项
   * @param accumulator 集合
   * @return 合并好的集合
   */
  override def add(value: (String, Int), accumulator: util.HashMap[String, Int]): util.HashMap[String, Int] = {
    // 判断这个数据是不是第一次来
    if (accumulator.containsKey(value._1)) {
      //之前已经存在了,获取这个数据
      val count = accumulator.get(value._1)
      // 更新数据,他的值就是两次数的累和
      accumulator.put(value._1, count + value._2)
    } else {
      // 第一次来,直接设置
      accumulator.put(value._1, value._2)
    }
    accumulator
  }

  /**
   * 获取对应结果
   *
   * @param accumulator 处理好的hashMap
   * @return 排好序的list
   */
  override def getResult(accumulator: util.HashMap[String, Int]): List[(String, Int)] = {
    //    // 创建java的arrayList
    //    val result = new util.ArrayList[(String, Long)]
    //    // 遍历所有的key
    //    for (key <- accumulator.keySet) {
    //      // 添加list里面
    //      result.add((key, accumulator.get(key)))
    //    }
    //    // 调用java的api进行排序
    //    // Java集合的工具类
    //    result.sort(new Comparator[(String, Long)]() {
    //      override def compare(o1: (String, Long), o2: (String, Long)): Int = o2._2.intValue - o1._2.intValue
    //    })
    //    // toList就是把java的list转成scala的list
    //    result.toList
    var result = List[(String, Int)]()
    for (key <- accumulator.keySet()) {
      result = (key, accumulator.get(key)) :: result
    }
    result.sortWith(_._2 > _._2)
  }

  /**
   * 分区之间怎么合并
   *
   * @param stringLongHashMap 第一个分区
   * @param acc1              第二个分区
   * @return
   */
  override def merge(stringLongHashMap: util.HashMap[String, Int], acc1: util.HashMap[String, Int]): util.HashMap[String, Int] = {
    // 把第二个放到第一个里面
    stringLongHashMap.putAll(acc1)
    // 返回第一个
    stringLongHashMap
  }
}

ItemAllWindowResult

每个窗口的结果怎么处理
* 泛型:1. 输入聚合结果的类型 2. 最终输出的类型 3. 固定的时间窗口
class ItemAllWindowResult extends ProcessAllWindowFunction[List[(String, Int)], (String,(String,Int)), TimeWindow] {
  /**
   * 每个分区的数据怎么处理
   *
   * @param context  上下文
   * @param elements 每一项
   * @param out      输出的内容
   */
  override def process(context: Context, elements: Iterable[List[(String, Int)]], out: Collector[(String,(String,Int))]): Unit = {
    // 拿到了第一项的列表
    val list = elements.iterator.next

// ("窗口结束时间: "new Timestamp(context.window.getEnd) 
    //取List前两个包装信息输出
    // 取出了top5的数据
val time= new Timestamp(context.window.getEnd).toString
    for (i <- 0 until 5) {
      val currTuple = list(i)
      out.collect(time,currTuple)
    }
    // 输出
  }
}

自定义RedisMapper,存入redis

 我使用的方式是set,时间窗结束时间作为key,id和num拼接成value。根据源码可以判断hset并不能实现流式处理,赋值additionalKey,除非改源码。

class TopNRedisMapper extends RedisMapper[(String,(String,Int))]{
  /**
   * 数据过来之后,通过什么操作放到redis⾥
   * @return
   */
  override def getCommandDescription: RedisCommandDescription = new RedisCommandDescription(RedisCommand.SET)

     def getAdditionalKey(t: (String,(String,Int))): String =  getCommandDescription.getAdditionalKey
  /**
   * 数据过来之后,哪⼀项是redis的key
   * @param t 数据每⼀项
   * @return
   */
  override def getKeyFromData(t: (String,(String,Int))): String = {
    t._1
  }
  /**
   * 数据过来之后,哪⼀项是value
   * @param t
   * @return
   */
  override
  def getValueFromData(t: (String,(String,Int))): String = {
    t._2._1+"_"+t._2._2.toString
  }
}

最后新建 RedisSink传入自定义的RedisMapper,env.execute()执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值