涛声依旧在 发表于 2022-8-20 18:37:27

Flink-基于 DataStream API 实现欺诈检测

案例来源于 https://nightlies.apache.org/flink/flink-docs-release-1.14/docs/try-flink/datastream/
案例背景

在当今数字时代,信用卡欺诈行为越来越被重视。 罪犯可以通过诈骗或者入侵安全级别较低系统来盗窃信用卡卡号。 用盗得的信用卡进行很小额度的例如一美元或者更小额度的消费进行测试。 如果测试消费成功,那么他们就会用这个信用卡进行大笔消费,来购买一些他们希望得到的,或者可以倒卖的财物。
在这个教程中,你将会建立一个针对可疑信用卡交易行为的反欺诈检测系统。 通过使用一组简单的规则,你将了解到 Flink 如何为我们实现复杂业务逻辑并实时执行。
欺诈检测规则


[*]对于一个账户,如果出现一笔小于1元的交易后, 紧跟着在1分钟内又出现一笔大于500元的交易,则认为该账户属于欺诈,就输出一个报警消息。
[*]图说明如下
https://img2022.cnblogs.com/blog/408749/202206/408749-20220629175650743-537167948.jpg
对原有案例进行改造

1. 数据源使用Kafka,发送json格式字符串
消息格式:{"accountId":1001, "timestamp":1656490723171, "amount":0.12}

2. 自定义 DeserializationSchema, 直接将kafka的json字符串转成POJO对象流程图

https://img2022.cnblogs.com/blog/408749/202206/408749-20220629180522076-1276333741.jpg
核心代码


[*]自定义DeserializationSchema
public class TransactionDeserialization implements DeserializationSchema<Transaction> {
    @Override
    public Transaction deserialize(byte[] bytes) throws IOException {
      ByteBuffer buffer = ByteBuffer.wrap(bytes);
      String message = byteBufferToString(buffer);
      if (StringUtils.isBlank(message)) {
            return null;
      }
      Transaction transaction = JsonUtils.fromJson(message, Transaction.class);
      return transaction;
    }

    @Override
    public boolean isEndOfStream(Transaction transaction) {
      return false;
    }

    @Override
    public TypeInformation<Transaction> getProducedType() {
      return TypeInformation.of(Transaction.class);
    }



    /**
   * ByteBuffer 转换 String
   * @param buffer
   * @return
   */
    private String byteBufferToString(ByteBuffer buffer) {
      String ret = "";
      try{
            CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
            CharBuffer charBuffer = decoder.decode(buffer.asReadOnlyBuffer());;
            ret = charBuffer.toString();
      }catch (Exception e) {
            e.printStackTrace();
      }
      return ret;
    }
}
[*]欺诈检测核心代码
public class FraudDetector extends KeyedProcessFunction<Long, Transaction, Alert> {

    /**
   * 定义小金额边界
   */
    private static final double SMALL_AMOUNT = 1.00;

    /**
   * 定义大金额边界
   */
    private static final double LARGE_AMOUNT = 500.00;

    /**
   * 1分钟时间
   */
    private static final long ONE_MINUTE = 60 * 1000;

    /**
   * 保存是否有消费小金额的状态
   */
    private transient ValueState<Boolean> smallAmountState;

    /**
   * 定时器状态
   */
    private transient ValueState<Long> timerState;

    @Override
    public void open(Configuration parameters) throws Exception {
      // 初始化ValueState

      ValueStateDescriptor<Boolean> smallAmountStateDescriptor = new ValueStateDescriptor<Boolean>("small-amount-state", Types.BOOLEAN);
      smallAmountState = getRuntimeContext().getState(smallAmountStateDescriptor);

      ValueStateDescriptor<Long> timerStateDescriptor = new ValueStateDescriptor<Long>("timer-state", Types.LONG);
      timerState = getRuntimeContext().getState(timerStateDescriptor);

    }

    @Override
    public void processElement(Transaction transaction, Context context, Collector<Alert> collector) throws Exception {
      if (Objects.isNull(transaction)) {
            return;
      }
      // Get the current state for the current key
      Boolean lastTransactionWasSmall = smallAmountState.value();

      // Check if the flag is set
      if (Objects.nonNull(lastTransactionWasSmall)) {
            if (transaction.getAmount() > LARGE_AMOUNT) {
                Alert alert = new Alert();
                alert.setAccountId(transaction.getAccountId());
                alert.setAmount(transaction.getAmount());

                collector.collect(alert);
            }
            clearUp(context);
      }

      if (transaction.getAmount() < SMALL_AMOUNT) {
            // set the flag to true
            smallAmountState.update(true);

            // 注册定时器,设置一个当前时间一分钟后触发的定时器,同时,将触发时间保存到 timerState 状态中。
            long timer = context.timerService().currentProcessingTime() + ONE_MINUTE;
            context.timerService().registerProcessingTimeTimer(timer);
            timerState.update(timer);
      }

    }

    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<Alert> out) throws Exception {
      // remove flag after 1 minute
      timerState.clear();
      smallAmountState.clear();
    }

    private void clearUp(Context ctx) {
      try {
            // delete timer
            Long timer = timerState.value();
            ctx.timerService().deleteProcessingTimeTimer(timer);

            timerState.clear();
            smallAmountState.clear();

      } catch (IOException e) {
            e.printStackTrace();
      }
    }
}
[*]FLink Job 启动类
public class FraudDetectionJob {
    public static void main(String[] args) throws Exception {
      // 初始化环境
      StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

      // kafka消息格式: {"accountId":1001, "timestamp":1656490723171, "amount":0.12}

      // 定义Kafka数据源
      KafkaSource<Transaction> source = KafkaSource.<Transaction>builder()
                .setBootstrapServers("192.168.0.192:9092")
                .setTopics("TOPIC_FRAUD_DETECTION")
                .setGroupId("TEST_GROUP")
                .setStartingOffsets(OffsetsInitializer.latest())
                .setValueOnlyDeserializer(new TransactionDeserialization())
                .build();

      // 加载数据源
      DataStreamSource<Transaction> fraudDetectionSource
                = env.fromSource(source, WatermarkStrategy.noWatermarks(), "FraudDetection-Source");

      // 处理数据
      SingleOutputStreamOperator<Alert> alertStreamOperator = fraudDetectionSource.keyBy(Transaction::getAccountId)
                .process(new FraudDetector())
                .name("Fraud-Detector");

      // 输出告警结果
      alertStreamOperator.addSink(new AlertSink())
                .name("Send-Alerts");

      env.execute("Fraud Detection");

    }
}执行效果


[*]kafka输入
https://img2022.cnblogs.com/blog/408749/202206/408749-20220629181456090-640228819.jpg
[*]告警结果
https://img2022.cnblogs.com/blog/408749/202206/408749-20220629181512413-1878619973.jpg
完整代码

https://github.com/Mr-LuXiaoHua/study-flink
代码入口: com.example.datastream.frauddetection.FraudDetectionJob
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Flink-基于 DataStream API 实现欺诈检测