ES 两次group by,先按照每天的时间力度,在统计每天的指定字段的值出现的次数

文章介绍了如何在Elasticsearch中使用DateHistogram聚合器按天对数据进行分组,并获取agg_tw_id字段的子聚合结果。
    public void addPubtimeAgg(){

        DateHistogramBuilder dateHistogramBuilder = AggregationBuilders.dateHistogram("pubtime_histogram")
                .field("pubtime").interval(DateHistogram.Interval.days(1))
                .minDocCount(0L);
        dateHistogramBuilder.format("yyyy-MM-dd");

        TermsBuilder field = AggregationBuilders.terms("agg_tw_id").field("tw_id").size(30);
        dateHistogramBuilder.subAggregation(field);

        searchbuilder.addAggregation(dateHistogramBuilder);
        if (this.query != null) {
            this.searchbuilder.setQuery(this.query);
        }
        SearchResponse sr = searchbuilder.execute().actionGet();

        Map<String, Aggregation> aggMap = sr.getAggregations().asMap();
        DateHistogram classTerms = (DateHistogram)aggMap.get("pubtime_histogram");
        Iterator<org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram.Bucket> classBucketIt = (Iterator<Bucket>) cla
<think>我们面对的需求是:使用Flink处理Kafka中的爬虫访问数据,按照多个不同维度各自聚合统计一段时间范围内的访问次数。 分析: 1. 数据源:Kafka中的爬虫访问数据(每条数据包含多个字段,如时间戳、URL、IP、用户代理等) 2. 维度:多个不同维度(例如:按IP统计、按URL统计、按用户代理统计、按IP+URL组合统计等) 3. 时间范围:滚动窗口(例如:每5分钟统计一次) 4. 统计指标:每个维度在窗口内的访问次数(即计数,不需要去重) 注意:这里要求的是“多个不同维度各自聚合”,即同时输出多个维度的统计结果。例如,同一个事件,我们可能同时要按IP、按URL、按用户代理等多个维度分别计数。 方案: 我们可以使用Flink的KeyedStream和窗口函数,但是要注意,同一个数据需要被复制到多个不同的维度流中进行处理。我们可以使用两种方法: 方法1:使用`flatMap`将每条数据复制成多条,每条对应一个维度,然后按照不同的维度分组(keyBy)并开窗聚合。 方法2:使用Flink的侧输出流(Side Output)为每个维度生成一个流,然后分别进行窗口聚合。 考虑到维度数量可能较多(比如几十个),且每个维度都需要独立统计,这里使用方法1更为直观和易于管理。 步骤: 1. 从Kafka读取数据,解析为POJO(包含所需的字段) 2. 定义需要统计的维度列表(如:["ip", "url", "user_agent", ...]) 3. 使用flatMap将每条数据根据维度列表拆分成多条数据,每条数据包含一个维度的(作为key)和原始数据(用于计数) 4. 按照维度进行keyBy,然后开窗(如5分钟的滚动窗口) 5. 使用窗口函数进行计数(count) 6. 将结果输出到外部存储(如控制台、文件、数据库等) 注意:由于每个维度独立,所以每个维度都会有自己的计数,互不影响。 代码结构: 1. Kafka Source配置 2. 数据解析(将JSON字符串解析为对象) 3. 定义维度列表 4. 复制数据并打上维度标记 5. 按维度分组开窗计数 6. 结果输出 示例代码: 假设爬虫访问数据格式如下(JSON): { "timestamp": 1620000000000, "ip": "192.168.1.1", "url": "https://example.com/page1", "userAgent": "Mozilla/5.0" } 我们定义维度:ip, url, userAgent, 以及组合维度(如ip+url) 注意:组合维度可以用连接字符串的方式,例如:ip+"|"+url 具体实现:</think>以下是基于 Flink 实现 Kafka 爬虫数据多维度聚合统计的完整解决方案,结合引用内容进行优化设计: ### 一、架构设计(参考引用[2]) ```mermaid graph LR A[Kafka爬虫数据源] --> B[数据解析] B --> C[多维数据分发] C --> D[维度1窗口聚合] C --> E[维度2窗口聚合] C --> F[维度N窗口聚合] D --> G[ClickHouse存储] E --> G F --> G ``` ### 二、核心代码实现 #### 1. Kafka 数据源配置 ```java Properties props = new Properties(); props.setProperty("bootstrap.servers", "kafka:9092"); props.setProperty("group.id", "crawler-aggregation"); FlinkKafkaConsumer<CrawlerRecord> consumer = new FlinkKafkaConsumer<>( "crawler_topic", new CrawlerRecordDeserializer(), // 自定义爬虫数据解析器 props ); DataStream<CrawlerRecord> source = env .addSource(consumer) .assignTimestampsAndWatermarks( WatermarkStrategy.<CrawlerRecord>forBoundedOutOfOrderness(Duration.ofSeconds(5)) .withTimestampAssigner((event, ts) -> event.getTimestamp()) ); ``` #### 2. 多维度聚合处理 ```java // 定义维度枚举(参考引用[2]数据结构) public enum Dimension { IP, URL, USER_AGENT, DEVICE_TYPE, REGION } // 主处理流程 source.flatMap(new DimensionDispatcher()) // 数据分发到各维度流 .keyBy(record -> Tuple2.of(record.f0, record.f1)) // (维度, 维度) .window(TumblingEventTimeWindows.of(Time.minutes(5))) .aggregate(new CountAggregator()) .addSink(new ClickHouseSink()); // 维度分发器(将每条数据复制到多个维度流) public static class DimensionDispatcher implements FlatMapFunction<CrawlerRecord, Tuple2<Dimension, String>> { @Override public void flatMap(CrawlerRecord record, Collector<Tuple2<Dimension, String>> out) { // 发送到各维度流 out.collect(Tuple2.of(Dimension.IP, record.getIp())); out.collect(Tuple2.of(Dimension.URL, record.getUrl())); out.collect(Tuple2.of(Dimension.USER_AGENT, record.getUserAgent())); // 添加组合维度 out.collect(Tuple2.of(Dimension.REGION, record.getCountry() + "|" + record.getCity())); } } // 计数聚合器 public static class CountAggregator implements AggregateFunction< Tuple2<Dimension, String>, // 输入 Long, // 累加器 Tuple3<Dimension, String, Long> // 输出(维度, 维度, 计数) > { @Override public Long createAccumulator() { return 0L; } @Override public Long add(Tuple2<Dimension, String> value, Long accumulator) { return accumulator + 1; } @Override public Tuple3<Dimension, String, Long> getResult(Long accumulator) { // 实际实现需携带维度信息 return Tuple3.of(currentDimension, currentValue, accumulator); } @Override public Long merge(Long a, Long b) { return a + b; } } ``` #### 3. ClickHouse 存储优化(参考引用[3]) ```java public class ClickHouseSink extends RichSinkFunction<Tuple3<Dimension, String, Long>> { private transient PreparedStatement stmt; @Override public void open(Configuration parameters) { Connection conn = DriverManager.getConnection("jdbc:clickhouse://ch:8123/crawler"); stmt = conn.prepareStatement( "INSERT INTO agg_results (window_end, dimension, dimension_value, count) " + "VALUES (?, ?, ?, ?)"); } @Override public void invoke(Tuple3<Dimension, String, Long> value, Context context) { stmt.setLong(1, System.currentTimeMillis()); // 窗口结束时间 stmt.setString(2, value.f0.name()); // 维度类型 stmt.setString(3, value.f1); // 维度 stmt.setLong(4, value.f2); // 计数 stmt.executeUpdate(); } } ``` #### 4. 动态维度配置(参考引用[2]架构) ```java // 通过广播流动态更新维度配置 DataStream<DimensionConfig> configStream = env.addSource(new ConfigSource()); MapStateDescriptor<Void, DimensionConfig> configDescriptor = new MapStateDescriptor<>("config", Void.class, DimensionConfig.class); BroadcastStream<DimensionConfig> broadcastConfig = configStream.broadcast(configDescriptor); source.connect(broadcastConfig) .process(new DynamicDimensionProcessor()) .keyBy(...) // 后续处理同上 ``` ### 三、性能优化策略 1. **状态管理优化**(参考引用[1]) ```java // 启用增量检查点 env.enableCheckpointing(60_000, CheckpointingMode.EXACTLY_ONCE); env.getCheckpointConfig().enableUnalignedCheckpoints(); // 配置RocksDB状态后端 env.setStateBackend(new EmbeddedRocksDBStateBackend()); ``` 2. **窗口优化** ```java .window(TumblingEventTimeWindows.of(Time.minutes(5))) .trigger(ContinuousProcessingTimeTrigger.of(Time.minutes(1))) // 每分钟输出中间结果 .allowedLateness(Time.seconds(30)) .sideOutputLateData(lateDataTag) // 迟到的数据侧输出 ``` 3. **资源优化** ```java // 每个TaskManager配置 containerized.taskmanager.memory.process.size: 4096m taskmanager.memory.managed.fraction: 0.4 taskmanager.numberOfTaskSlots: 4 ``` ### 四、监控与诊断(参考引用[3]) ```java // 注册自定义指标 getRuntimeContext().getMetricGroup() .addGroup("crawler") .counter("lateRecords") .inc(lateDataCount); // 异常数据诊断流 lateDataStream.process(new DiagnosticSink()) .addSink(new ElasticsearchSink()); // 入ES供实时诊断 ``` ### 五、部署建议 1. **资源配比**(每10万条/秒): - TaskManager: 4核8GB &times; 3节点 - Kafka分区数: 与Flink并行度一致 - ClickHouse: SSD存储 + 16GB内存 2. **监控指标**: - 窗口延迟:`eventsLate` - 吞吐量:`numRecordsInPerSecond` - 状态大小:`rocksdb.estimated-num-keys` --- ### 相关问题 1. 如何动态调整 Flink 作业中的聚合维度而不重启作业?[^2] 2. 当维度基数过高时(如百万级URL),如何优化存储和计算性能?[^1] 3. 如何在 Exactly-Once 语义下保证跨窗口的计数准确性?[^1] 4. 如何实现基于地理位置的动态区域维度聚合?[^2] 5. 当 Kafka 数据出现延迟时,如何保证窗口结果的完整性?[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值