子任务2描述:
2、使用Flink 消费kafka 中fact_order_detail 主题的数据,统计商城每分钟的GMV(结果四舍五入保留两位小数),将结果存入redis 中(value 为字符串格式,仅存GMV),key 为store_gmv,使用redis cli 以get key 方式获取store_gmv 值,将每次截图粘贴至客户端桌面【Release\模块C 提交结果.docx】中对应的任务序号下(每分钟查询一次,至少查询3 次)。
(GMV:所有订单金额,购买商品单价*购买商品数量,包括已下单未付款)
赛题分析:
通过gpt分析GMV的意思:
GMV是Gross Merchandise Volume的缩写,中文翻译为“总交易额”或“总商品交易额”。它是电子商务领域中常用的一个指标,用于表示特定平台或市场在一定时期内销售的商品的总价值。
GMV和实际交易额的区别:
GMV:那些只要下单付款的订单金额,就会被计入到GMV当中,管你取消不取消订单、拒不拒收货物; 实际交易额:只有买家收到货并确认收货,事后也不t款的订单金额,才能被计入到实际交易额中。 思路分析:
(1) 统计“每分钟”的GMV,意味着使用1分钟大小的窗口(赛题要求为处理时间,处理时间还更简单一点,由此可见这省赛样题还是比较简单,因为小编制作去年国赛样题的时候使用的是事件时间,懂的朋友们应该知道事件时间可以实现更精确的操作,但是相应的需要涉及到时间与时间戳的转化)。 (2) 需要取出订单详细表(order_detail)数据的商品单价以及商品数量(可以创建java pojo类然后使用Flink的map转换操作实现拿出需要的数据) (3)需要掌握一种四舍五入保留两位小数的方法(java或者Flinkapi都可行)
各位小伙伴还对其他题感兴趣可以添加小编🐧:2815619722(收费)
kafka 中fact_order_detail 主题的数据以json格式存储,大概如下:
{ "order_detail_id":1, "order_sn":"2022111496083548", "product_id":5380, "product_name":"无线5.0蓝牙耳机双耳入耳式迷你跑步运动", "product_cnt":3, "product_price":702.53, "average_cost":0.00, "weight":4.89577, "fee_money":25.80, "w_id":1291, "create_time":"20221109153354", "modified_time":"2022-11-10 04:55:54" }
java版本代码:
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.functions.MapFunction;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.java.functions.KeySelector;
import org.apache.flink.connector.kafka.source.KafkaSource;
import org.apache.flink.connector.kafka.source.enumerator.initializer.OffsetsInitializer;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.connectors.redis.RedisSink;
import org.apache.flink.streaming.connectors.redis.common.config.FlinkJedisPoolConfig;
import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommand;
import org.apache.flink.streaming.connectors.redis.common.mapper.RedisCommandDescription;
import org.apache.flink.streaming.connectors.redis.common.mapper.RedisMapper;
public class Task05Job {
// 注意:转换取出数据可以使用pojo 也可以使用FLink自带的元组
// 取出订单详细表的商品数量和商品单价
public static class OrderDetailEvent{
int productCnt; // 购买商品数量
double productPrice; // 购买商品单价
public OrderDetailEvent() {}
public OrderDetailEvent(int productCnt,double productPrice) {
this.productCnt = productCnt;
this.productPrice = productPrice;
}
@Override
public String toString() {
return "OrderDetailEvent{" +
"productCnt=" + productCnt +
", productPrice=" + productPrice +
'}';
}
}
public static void main(String[] args) throws Exception {
// 获取流执行环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 读取kafka主题中fact_order_detail的数据
KafkaSource<String> source = KafkaSource.<String>builder()
.setBootstrapServers("192.168.21.12:9092")// 需要修改自己的ip地址
.setTopics("fact_order_detail") // kafka中的主题
.setGroupId("group-test") // 设置消费者组id :随便设置
.setStartingOffsets(OffsetsInitializer.earliest()) // 设置偏移量为从最开始的位置读取
.setValueOnlyDeserializer(new SimpleStringSchema())
.build();
// 创建redis sink的配置 (默认端口号6379)
FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig.Builder()
.setMaxTotal(1000)
.setMaxIdle(32)
.setTimeout(10*1000)
.setHost("192.168.21.12")
.setPort(6379)
.build();
// 流处理管道
DataStream<Double> orderStream = env
// 取出kafka数据
.fromSource(source, WatermarkStrategy.<String>noWatermarks(), "Kafka Source")
.map(new MapFunction<String, OrderDetailEvent>() {
@Override
public OrderDetailEvent map(String line) throws Exception {
// 使用google的gson转换方式
JsonObject jsonObj = JsonParser.parseString(line).getAsJsonObject();
int productCnt = jsonObj.get("product_cnt").getAsInt(); // 商品数量
double productPrice = jsonObj.get("product_price").getAsDouble(); // 商品金额
// System.out.println("product_cnt:" + productCnt + ", product_price:" + productPrice);
return new OrderDetailEvent(productCnt,productPrice);
}
})
// 因为只有开窗才能实现.windows 转换为keyed stream 全部分组到0所有和开窗前是区别不大的
.keyBy(new KeySelector<OrderDetailEvent, Integer>() {
@Override
public Integer getKey(OrderDetailEvent value) throws Exception {
return 0;
}
})
// 指定窗口
.window(TumblingProcessingTimeWindows.of(Time.minutes(1)))
// 执行窗口聚合函数
.aggregate(new AggGmvTemp());
orderStream.print();
// data sink
orderStream.addSink(new RedisSink<Double>(conf, new RedisSinkMapper()));
// execute program
env.execute("Flink Streaming");
}
// 窗口增量聚合函数: IN, ACC, OUT
static class AggGmvTemp implements AggregateFunction<OrderDetailEvent, Double, Double> {
// 创建初始ACC
@Override
public Double createAccumulator() {
return 0.00;
}
// 累加每个订单项的支付金额
@Override
public Double add(OrderDetailEvent value, Double accumulator) {
return accumulator + value.productCnt * value.productPrice;
}
// 分区合并
@Override
public Double getResult(Double accumulator) {
return accumulator;
}
// 返回已下单订单的总支付金额
@Override
public Double merge(Double acc1, Double acc2) {
return acc1 + acc2;
}
}
// redisMap接口,设置key和value
// Redis Sink 核心类是 RedisMappe 接口,使用时我们要编写自己的redis操作类实现这个接口中的三个方法
static class RedisSinkMapper implements RedisMapper<Double> {
// getCommandDescription:设置数据使用的数据结构 HashSet 并设置key的名称
@Override
public RedisCommandDescription getCommandDescription() {
// RedisCommand.SET 指定存储类型
return new RedisCommandDescription(RedisCommand.SET);
}
// 设置写入到redis中的 key值
@Override
public String getKeyFromData(Double event) {
return "store_gmv";
}
// 指定value
@Override
public String getValueFromData(Double event) {
return String.valueOf(event);
}
}
}