主要难点是计算一定时间内订单总数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()执行