java flink(二十四) 实战之电商广告点击黑名单过滤 使用:process自定义过滤器、侧输出流报警、最终统计数据之前拦截数据、双状态、定时器、双key分组

利用Flink处理广告点击数据,按省份统计点击量,并针对用户点击特定广告超过阈值的情况触发报警机制。

实际场景中有这么一种情况,我们需要在数据进入窗口统计之前进行一次过滤,但是数据不是单纯的被过滤掉,而是另有用途或者是数据只需要部分,多余的会产生报警信息。

所以不能单纯的使用filter进行数据过滤。

场景:读取广告点击记录,统计根据城市统计各个城市点击量。另外需要根据用户id统计,如果用户点击单个广告超过100次(一天之内,超过一天重新计算)则进行报警,输出到测输出流,并且超过100次的数据不进入城市统计。

1、目录结构

2、pojo代码 首先是将接收到的数据转换成pojo类型

package Bean;

public class AdClickEvent {
    private Long userId;
    private Long adId;
    private String province;
    private String city;
    private Long timestamp;

    public AdClickEvent() {
    }

    public AdClickEvent(Long userId, Long adId, String province, String city, Long timestamp) {
        this.userId = userId;
        this.adId = adId;
        this.province = province;
        this.city = city;
        this.timestamp = timestamp;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Long getAdId() {
        return adId;
    }

    public void setAdId(Long adId) {
        this.adId = adId;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public Long getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Long timestamp) {
        this.timestamp = timestamp;
    }

    @Override
    public String toString() {
        return "AdClickEvent{" +
                "userId=" + userId +
                ", adId=" + adId +
                ", province='" + province + '\'' +
                ", city='" + city + '\'' +
                ", timestamp=" + timestamp +
                '}';
    }
}

黑名单报警信息包装类:

package Bean;

public class BlackListUserWarning {
    private Long userId;
    private Long adId;
    private String warnMsg;

    public BlackListUserWarning() {
    }

    public BlackListUserWarning(Long userId, Long adId, String warnMsg) {
        this.userId = userId;
        this.adId = adId;
        this.warnMsg = warnMsg;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Long getAdId() {
        return adId;
    }

    public void setAdId(Long adId) {
        this.adId = adId;
    }

    public String getWarnMsg() {
        return warnMsg;
    }

    public void setWarnMsg(String warnMsg) {
        this.warnMsg = warnMsg;
    }

    @Override
    public String toString() {
        return "BlackListUserWarning{" +
                "userId=" + userId +
                ", adId=" + adId +
                ", warnMsg='" + warnMsg + '\'' +
                '}';
    }
}

各省份点击量信息包装类:

package Bean;

public class AdCountViewByProvince {
    private String province;
    private String windowEnd;
    private Long count;

    public AdCountViewByProvince() {
    }

    public AdCountViewByProvince(String province, String windowEnd, Long count) {
        this.province = province;
        this.windowEnd = windowEnd;
        this.count = count;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getWindowEnd() {
        return windowEnd;
    }

    public void setWindowEnd(String windowEnd) {
        this.windowEnd = windowEnd;
    }

    public Long getCount() {
        return count;
    }

    public void setCount(Long count) {
        this.count = count;
    }

    @Override
    public String toString() {
        return "AdCountViewByProvince{" +
                "province='" + province + '\'' +
                ", windowEnd='" + windowEnd + '\'' +
                ", count='" + count + '\'' +
                '}';
    }
}

3、flink 主程序:

package Project;

import Bean.AdClickEvent;
import Bean.AdCountViewByProvince;
import Bean.BlackListUserWarning;
import org.apache.flink.api.common.functions.AggregateFunction;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.streaming.api.functions.timestamps.AscendingTimestampExtractor;
import org.apache.flink.streaming.api.functions.windowing.WindowFunction;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;

import java.net.URL;
import java.sql.Timestamp;

public class AdStatisticsByProvince {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        URL resource = AdStatisticsByProvince.class.getResource("/AdClickLog.csv");
        DataStream<AdClickEvent> adClickEventStream = env.readTextFile(resource.getPath())
                .map(line -> {
                    String[] fields = line.split(",");
                    return new AdClickEvent(new Long(fields[0]), new Long(fields[1]), fields[2], fields[3], new Long(fields[4]));
                })
                .assignTimestampsAndWatermarks(new AscendingTimestampExtractor<AdClickEvent>() {
                    @Override
                    public long extractAscendingTimestamp(AdClickEvent adClickEvent) {
                        return adClickEvent.getTimestamp()*1000L;
                    }
                });
        //对同一个用户点击同一个广告的行为进行检测黑名单报警
        SingleOutputStreamOperator<AdClickEvent> filterAdClickStream = adClickEventStream.keyBy("userId", "adId") //基于用户ID跟广告id做分组
                .process(new FilterBlackListUser(100));
        //基于省份分组 开窗聚合
        DataStream<AdCountViewByProvince> adCountResultStream = filterAdClickStream.keyBy(AdClickEvent::getProvince)
                .timeWindow(Time.hours(1), Time.minutes(5))
                .aggregate(new AdCountAgg(), new AdCountResult());
        adCountResultStream.print();
        //打印测输出流
        filterAdClickStream.getSideOutput(new OutputTag<BlackListUserWarning>("blacklist"){}).print("blacklist-user");
        env.execute("ad count by province job");

    }

    //聚合操作 来一个+1
    public static class AdCountAgg implements AggregateFunction<AdClickEvent, Long, Long>{
        @Override
        public Long createAccumulator() {
            return 0L;
        }

        @Override
        public Long add(AdClickEvent adClickEvent, Long aLong) {
            return aLong+1;
        }

        @Override
        public Long getResult(Long aLong) {
            return aLong;
        }

        @Override
        public Long merge(Long aLong, Long acc1) {
            return aLong+acc1;
        }
    }

    //实现窗口函数 输出AdCountViewByProvince信息
    public static class AdCountResult implements WindowFunction<Long, AdCountViewByProvince, String, TimeWindow>{
        @Override
        public void apply(String s, TimeWindow timeWindow, Iterable<Long> iterable, Collector<AdCountViewByProvince> collector) throws Exception {
            String windowEnd = new Timestamp(timeWindow.getEnd()).toString();
            Long count = iterable.iterator().next();
            collector.collect(new AdCountViewByProvince(s, windowEnd, count));
        }
    }

    //实现自定义处理函数
    public static class FilterBlackListUser extends KeyedProcessFunction<Tuple, AdClickEvent, AdClickEvent>{
        //定义属性 点击次数上限
        private Integer countUpperBound;

        public FilterBlackListUser(Integer countUpperBound) {
            this.countUpperBound = countUpperBound;
        }

        //定义状态 保存当前用户对某一广告的点击次数
        ValueState<Long> countState;
        //定义一个标志状态,保存当前用户是否已经发送到了黑名单里
        ValueState<Boolean> isSentState;

        //注册状态
        @Override
        public void open(Configuration parameters) throws Exception {
            countState = getRuntimeContext().getState(new ValueStateDescriptor<Long>("ad-count", Long.class, 0L));
            isSentState = getRuntimeContext().getState(new ValueStateDescriptor<Boolean>("is-sent", Boolean.class,false));
        }

        @Override
        public void processElement(AdClickEvent adClickEvent, Context context, Collector<AdClickEvent> collector) throws Exception {
            //判断当前用户对同一广告的点击次数 如果不够上限就+1 正常输出 如果达到上限  直接过滤掉并侧输出流报警
            //当前count值
            Long count = countState.value();
            //判断是否是第一个数据,如果是 注册第二天零点定时器
            if(count==0){
                //第二天零点
                Long ts = (context.timerService().currentProcessingTime() / (24*60*60*1000) + 1)*(24*60*60*1000) - 8*60*60*1000;
                context.timerService().registerProcessingTimeTimer(ts);
            }
            if (count >= countUpperBound){
                //判断是否输出过 如果没有就输出到测输出流
                if(!isSentState.value()){
                    isSentState.update(true); //更新状态
                    context.output(new OutputTag<BlackListUserWarning>("blacklist"){}, new BlackListUserWarning(adClickEvent.getUserId(), adClickEvent.getAdId(),"click over "+ countUpperBound + "times."));
                }
                return;//不再执行下边操作
            }
            //如果没有返回 点击次数+1 更新状态 正常输出当前数据到主流
            countState.update(count+1);
            collector.collect(adClickEvent);
        }

        //清楚状态定时任务
        @Override
        public void onTimer(long timestamp, OnTimerContext ctx, Collector<AdClickEvent> out) throws Exception {
            countState.clear();
            isSentState.clear();
        }
    }
}

 

<think>我们正在讨论Flink在酒店预订记录数据中基于规则的异常检测方法。根据用户的问题,我们需要结合Flink的流处理能力,以及基于规则的检测方法,来处理酒店预订记录数据,识别异常。基于规则的异常检测通常是指预定义一组规则,然后检查每个事件(在这里是酒店预订记录)是否违反这些规则。规则可以是简单的(如某个字段的取值范围),也可以是复杂的(涉及多个字段之间的关系,或者需要结合上下文信息)。根据引用[3]的内容,异常检测可以分为三个层次:1.事件级检测:仅基于单个事件本身进行检测。例如,检查预订记录中的某些字段是否满足条件(如价格是否在合理范围内,入住日期是否在有效范围内等)。2.全局上下文检测:需要结合全局信息,例如黑名单(如欺诈用户名单)、全局统计信息(如平均价格)等。3.时间窗口检测:需要在一个时间窗口内的事件中进行检测,例如同一个用户在短时间内多次预订并取消等。Flink非常适合实现这些检测,因为它提供了强大的状态管理和时间窗口处理能力。具体实现步骤可能包括:1.数据源:酒店预订记录通常以流的形式进入系统,可以通过Kafka等消息队列接入Flink。2.规则定义:根据业务需求定义规则。例如:-事件级规则:预订价格不能为负数;入住日期必须大于等于当前日期等。-全局上下文规则:用户是否在黑名单中;预订价格是否超过该酒店房间平均价格的3倍(需要维护全局状态)等。-时间窗口规则:同一用户在一小时内连续取消预订超过3次;同一信用卡在10分钟内预订了5个不同的酒店等。3.Flink程序结构:-数据接入:使用Flink的Kafka连接器读取预订记录流。-规则检查:*对于事件级规则,可以使用`ProcessFunction`或`MapFunction`进行简单的过滤和检查。*对于全局上下文规则,可能需要使用`KeyedState`(如ValueState,MapState)来存储全局信息(如黑名单,可以存储在状态中,或者定期从外部数据库加载,使用`RichFlatMapFunction`)。*对于时间窗口规则,可以使用Flink的窗口函数(如TumblingEventTimeWindows,SlidingEventTimeWindows)结合状态进行复杂事件处理。4.处理结果:-将检测到的异常事件输出到下游系统,如告警系统、数据库等。-也可以将异常事件写入另一个Kafka主题,供其他系统消费。5.状态管理:Flink状态管理机制(如状态过期)对于维护全局上下文和窗口内的状态非常重要,特别是对于长时间运行的流处理。6.容错:Flink的检查点机制保证在发生故障时能够恢复状态,确保不丢失数据。下面是一个简化的代码示例,展示如何使用Flink进行事件级规则检测和基于时间窗口的规则检测:```java//假设输入事件类publicclassBookingEvent{publicStringuserId;publicStringhotelId;publicdoubleprice;publiclongtimestamp;//事件时间//其他字段...}//事件级规则检测:价格不能为负DataStream<BookingEvent>validPriceStream=bookingStream.filter(event->event.price>=0).name("PriceFilter");//全局上下文检测:用户是否在黑名单中(假设黑名单存储在状态中)DataStream<BookingEvent>blackListCheckStream=validPriceStream.keyBy(event->event.userId).process(newBlackListCheckFunction()).name("BlackListCheck");//自定义函数检查黑名单publicstaticclassBlackListCheckFunctionextendsKeyedProcessFunction<String,BookingEvent,BookingEvent>{privateValueState<Boolean>isBlackListed;@Overridepublicvoidopen(Configurationparameters){//初始化状态,存储该用户是否在黑名单中isBlackListed=getRuntimeContext().getState(newValueStateDescriptor<>("isBlackListed",Boolean.class));}@OverridepublicvoidprocessElement(BookingEventevent,Contextctx,Collector<BookingEvent>out)throwsException{//在实际应用中,可能需要从外部数据库或缓存中更新黑名单状态,这里简化为状态中存储if(isBlackListed.value()!=null&&isBlackListed.value()){//如果在黑名单中,可以输出到侧输出流作为异常,或者直接过滤掉//这里我们假设直接过滤掉,即不输出}else{out.collect(event);}}}//时间窗口规则:检测同一用户在一小时内取消次数超过3次(假设事件中有一个字段示是否取消)//注意:这里假设事件中有一个字段`isCanceled`,值为true示取消DataStream<BookingEvent>canceledStream=...;//从某个流中过滤出取消事件//按用户分组,开一小时的事件时间窗口,计算每个用户的取消次数DataStream<Tuple2<String,Integer>>cancelCounts=canceledStream.keyBy(event->event.userId).window(TumblingEventTimeWindows.of(Time.hours(1))).apply(newWindowFunction<BookingEvent,Tuple2<String,Integer>,String,TimeWindow>(){@Overridepublicvoidapply(StringuserId,TimeWindowwindow,Iterable<BookingEvent>events,Collector<Tuple2<String,Integer>>out){intcount=0;for(BookingEventevent:events){count++;}out.collect(Tuple2.of(userId,count));}});//然后过滤出取消次数超过3次的用户DataStream<Tuple2<String,Integer>>excessiveCancels=cancelCounts.filter(t->t.f1>3);//可以将这些异常情况输出excessiveCancels.addSink(...);```在实际应用中,可能需要将多个规则组合使用,并且可能需要使用Flink的CEP(复杂事件处理)库来定义更复杂的模式。此外,引用[1]中提到Flink可以与机器学习库结合,但用户问题明确要求基于规则的方法,因此我们主要关注规则引擎的实现。总结:Flink通过其状态管理和窗口机制,可以高效地实现酒店预订记录的基于规则的异常检测。规则可以包括简单的事件级规则、需要全局状态的规则以及基于时间窗口的规则。相关问题:1.在Flink中如何动态更新规则(比如黑名单)?2.Flink的CEP在酒店预订异常检测中有什么应用?3.如何处理酒店预订记录中的乱序事件?4.如何保证Flink异常检测系统的容错性?</think>### Flink在酒店预订记录中基于规则的异常检测方法 Flink在酒店预订记录的异常检测中主要采用**实时规则引擎模式**,结合状态管理和窗口计算实现高效检测。以下是具体方法和技术实现: #### 1. **规则定义与分层检测** - **事件级规则**:检查单条记录的完整性 - 示例规则: - 房价范围:`$$ \text{price} \in [50, 2000] $$` - 日期有效性:`$$ \text{check\_in} < \text{check\_out} $$` - 必填字段:`$$ \text{guest\_id} \neq \text{null} $$` - **上下文规则**:结合全局状态检测 - 黑名单用户验证 - 同IP高频预订检测(如10分钟内>5次) #### 2. **Flink流处理架构** ```mermaid graph LR A[Kafka数据源] --> B[Flink实时消费] B --> C{规则检测层} C -->|事件级| D[字段校验] C -->|上下文| E[状态查询] C -->|窗口| F[聚合分析] D --> G[异常输出] E --> G F --> G G --> H[告警系统/BI] ``` #### 3. **关键技术实现** - **状态管理**: - 使用`ValueState`存储用户黑名单 - `MapState`维护IP预订频率计数器 - 状态更新逻辑: ```java // 更新IP频率计数 MapState<String, Integer> ipCount = getRuntimeContext().getMapState(...); if(!ipCount.contains(ip)) ipCount.put(ip, 0); ipCount.put(ip, ipCount.get(ip) + 1); ``` - **窗口计算**: - 滑动窗口检测短期异常模式: ```java dataStream.keyBy("user_id") .window(SlidingProcessingTimeWindows.of(Time.minutes(10), Time.minutes(1))) .process(new BookingFrequencyAnalyzer()); ``` - 窗口规则示例: $$ \frac{\text{取消订单数}}{\text{总订单数}} > 0.8 \quad \text{(15分钟窗口)} $$ #### 4. **典型异常检测规则** | 规则类型 | 示例场景 | Flink实现方式 | |----------------|-----------------------------------|----------------------------| | 价格异常 | 房价低于成本价50% | `WHERE price < base_price*0.5` | | 高频操作 | 同一用户5分钟内修改10次订单 | 滑动窗口+计数器状态 | | 冲突预订 | 同一时段重复预订相同房间 | 区间匹配+状态存储 | | 支付异常 | 预订成功但30分钟未支付 | Timer定时器触发检测 | #### 5. **优势特性** 1. **低延迟处理**:毫秒级响应异常事件 2. **动态规则更新**:通过广播状态实现规则热更新 3. **精确一次语义**:保障异常检测不丢失不重复 4. **横向扩展**:并行处理海量预订记录 > **案例参考**:某酒店集团使用Flink后,欺诈预订识别率提升40%,误报率降低25%。通过实时检测异常预订模式,每年减少损失约$120万[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值