59、Flink CEP - Flink的复杂事件处理介绍及示例(1)-入门

一、Flink 专栏

Flink 专栏系统介绍某一知识点,并辅以具体的示例进行说明。

  • 1、Flink 部署系列

    本部分介绍Flink的部署、配置相关基础内容。

  • 2、Flink基础系列

    本部分介绍Flink 的基础部分,比如术语、架构、编程模型、编程指南、基本的datastream api用法、四大基石等内容。

  • 3、Flik Table API和SQL基础系列

    本部分介绍Flink Table Api和SQL的基本用法,比如Table API和SQL创建库、表用法、查询、窗口函数、catalog等等内容。

  • 4、Flik Table API和SQL提高与应用系列

    本部分是table api 和sql的应用部分,和实际的生产应用联系更为密切,以及有一定开发难度的内容。

  • 5、Flink 监控系列

    本部分和实际的运维、监控工作相关。

二、Flink 示例专栏

Flink 示例专栏是 Flink 专栏的辅助说明,一般不会介绍知识点的信息,更多的是提供一个一个可以具体使用的示例。本专栏不再分目录,通过链接即可看出介绍的内容。

两专栏的所有文章入口点击:Flink 系列文章汇总索引


文章目录


本文介绍了Flink 的类库CEP的入门及单个模式。

如果需要了解更多内容,可以在本人Flink 专栏中了解更新系统的内容。

本文除了maven依赖外,没有其他依赖。

本专题分为以下几篇介绍:
59、Flink CEP - Flink的复杂事件处理介绍及示例(1)-入门
59、Flink CEP - Flink的复杂事件处理介绍及示例(2)- 模式API
59、Flink CEP - Flink的复杂事件处理介绍及示例(3)- 模式选取及超时处理
59、Flink CEP - Flink的复杂事件处理介绍及示例(4)- 延迟数据处理和三个实际应用示例
59、Flink CEP - Flink的复杂事件处理介绍及示例(完整版)

一、Flink的复杂事件处理介绍

Flink CEP(Complex event processing)是在Flink上层实现的复杂事件处理库。 它可以让你在无限事件流中检测出特定的事件模型,有机会掌握数据中重要的那部分。

实时处理中的一个关键问题是检测数据流中的事件模式。复杂事件处理(CEP)解决了将连续传入的事件与模式进行匹配的问题。匹配的结果通常是从输入事件派生的复杂事件。与对存储的数据执行查询的传统DBMS不同,CEP对存储的查询执行数据。所有与查询无关的数据都可以立即丢弃。考虑到CEP查询应用于潜在的无限数据流,这种方法的优势是显而易见的。此外,输入被立即处理。一旦系统看到了匹配序列的所有事件,就会立即发出结果。这一方面有效地提高了CEP的实时分析能力。

CEP的处理范式引起了人们的极大兴趣,并在各种各样的用例中得到了应用。最值得注意的是,CEP目前用于金融应用,如股票市场趋势和信用卡欺诈检测。此外,它还用于基于RFID的跟踪和监控,例如,检测仓库中未正确检查物品的盗窃行为。CEP还可以通过指定可疑用户行为的模式来检测网络入侵。

本页讲述了Flink CEP中可用的API,我们首先讲述模式API,它可以让你指定想在数据流中检测的模式,然后讲述如何检测匹配的事件序列并进行处理。 再然后我们讲述Flink在按照事件时间处理迟到事件时的假设, 以及如何从旧版本的Flink向1.13之后的版本迁移作业。

FlinkCEP 不是二进制发布包的一部分。

1、maven依赖

xml 复制代码
<dependency>
   <groupId>org.apache.flink</groupId>
   <artifactId>flink-cep</artifactId>
   <version>1.17.2</version>
</dependency>

2、入门示例

实现将输入流中balance大于23的输出。

java 复制代码
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.cep.CEP;
import org.apache.flink.cep.PatternFlatSelectFunction;
import org.apache.flink.cep.pattern.Pattern;
import org.apache.flink.cep.pattern.conditions.SimpleCondition;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/*
 * @Author: alanchan
 * @LastEditors: alanchan
 * @Description: 
 */
public class TestFirstDemo {

	@Data
	@NoArgsConstructor
	@AllArgsConstructor
	static class User {
		private Integer id;
		private String name;
		private Double balance;
		private Integer age;
		private String email;

		@Override
		public boolean equals(Object obj) {
			if (obj instanceof User) {
				User user = (User) obj;
				return this.id == user.id && this.name.equals(user.getName());
			} else {
				return false;
			}
		}

		@Override
		public int hashCode() {
			return super.hashCode() + Double.hashCode(id);
		}
	}

	final static List<User> userList = Arrays.asList(
			new User(1001, "alan", 20d, 18, "alan.chan.chn@163.com"),
			new User(1002, "alanchan", 22d, 20, "alan.chan.chn@163.com"),
			new User(1003, "alanchanchn", 23d, 22, "alan.chan.chn@163.com"),
			new User(1004, "alan_chan", 21d, 19, "alan.chan.chn@163.com"),
			new User(1005, "alan_chan_chn", 23d, 21, "alan.chan.chn@163.com"));

	public static void main(String[] args) throws Exception {
		// 设置环境
		StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

		// 输入流
		DataStream<User> users = env.fromCollection(userList);

		// 设置匹配模式
		Pattern<User, ?> userPattern = Pattern.<User>begin("start").where(new SimpleCondition<User>() {

			@Override
			public boolean filter(User value) throws Exception {
				return value.getBalance() >= 23;
			}

		});

		// 将输入流匹配设置的模式,并得到匹配后的流
		DataStream<String> userResult = CEP.pattern(users, userPattern).inProcessingTime()
				.flatSelect(new PatternFlatSelectFunction<User, String>() {

					@Override
					public void flatSelect(Map<String, List<User>> pattern, Collector<String> out) throws Exception {
						out.collect(pattern.get("start").toString());
					}

				}, Types.STRING);

		// 输出
		userResult.print("user:");
		// 数据源
		// stockList

		// 控制台输出
		// user::10> [TestFirstDemo.User(id=1003, name=alanchanchn, balance=23.0,age=22, email=alan.chan.chn@163.com)]
		// user::11> [TestFirstDemo.User(id=1005, name=alan_chan_chn, balance=23.0, age=21, email=alan.chan.chn@163.com)]

		env.execute();
	}

}

DataStream中的事件,如果你想在上面进行模式匹配的话,必须实现合适的 equals()和hashCode()方法,因为FlinkCEP使用它们来比较和匹配事件。

3、编程模型

CEP编程模型分为三步,即如下:

  • 定义模式
java 复制代码
 Pattern<LoginEvent, ?> loginEventPattern = Pattern.begin(
         Pattern.<LoginEvent>begin("first")
                 .where(new SimpleCondition<LoginEvent>() {

                     @Override
                     public boolean filter(LoginEvent value) throws Exception {
                         return value.getStatus().equals("F");
                     }

                 })

 );
  • 将模式映射到流上
java 复制代码
PatternStream<LoginEvent> patternStream = CEP.pattern(loginEventDS, loginEventPattern);
  • 在流上提取匹配模式后的数据
java 复制代码
patternStream.flatSelect(new PatternFlatSelectFunction<LoginEvent, String>() {

            @Override
            public void flatSelect(Map<String, List<LoginEvent>> pattern, Collector<String> out)
                    throws Exception {
                out.collect(pattern.get("first").toString());
            }

        });

二、模式API

模式API可以让你定义想从输入流中抽取的复杂模式序列。

每个复杂的模式序列包括多个简单的模式,比如,寻找拥有相同属性事件序列的模式。从现在开始,我们把这些简单的模式称作模式, 把我们在数据流中最终寻找的复杂模式序列称作模式序列,你可以把模式序列看作是这样的模式构成的图, 这些模式基于用户指定的条件从一个转换到另外一个,比如 event.getName().equals("end")。 一个匹配是输入事件的一个序列,这些事件通过一系列有效的模式转换,能够访问到复杂模式图中的所有模式。

每个模式必须有一个独一无二的名字,你可以在后面使用它来识别匹配到的事件。

模式的名字不能包含字符":"

1、单个模式

一个模式可以是一个单例或者循环模式。

单例模式只接受一个事件,循环模式可以接受多个事件。

在模式匹配表达式中,模式"a b+ c? d"(或者"a",后面跟着一个或者多个"b",再往后可选择的跟着一个"c",最后跟着一个"d"), a,c?,和 d都是单例模式,b+是一个循环模式。

默认情况下,模式都是单例的,你可以通过使用量词把它们转换成循环模式。 每个模式可以有一个或者多个条件来决定它接受哪些事件。

1)、量词

在Flink CEP中,你可以通过这些方法指定循环模式:

  • pattern.oneOrMore(),指定期望一个给定事件出现一次或者多次的模式(例如前面提到的b+模式);
  • pattern.times(#ofTimes),指定期望一个给定事件出现特定次数的模式,例如出现4次a;
  • pattern.times(#fromTimes, #toTimes),指定期望一个给定事件出现次数在一个最小值和最大值中间的模式,比如出现2-4次a。

你可以使用pattern.greedy()方法让循环模式变成贪心的,但现在还不能让模式组贪心。

你可以使用pattern.optional()方法让所有的模式变成可选的,不管是否是循环模式。

对一个命名为userPattern的模式,以下量词是有效的:

java 复制代码
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.cep.CEP;
import org.apache.flink.cep.PatternSelectFunction;
import org.apache.flink.cep.PatternStream;
import org.apache.flink.cep.pattern.Pattern;
import org.apache.flink.cep.pattern.conditions.SimpleCondition;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/*
 * @Author: alanchan
 * @LastEditors: alanchan
 * @Description: 
 */
public class TestCEPDemo {
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class LoginEvent {
        private Integer userId;
        private String ip;
        private String status;
        private Long timestamp;

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof LoginEvent) {
                LoginEvent loginEvent = (LoginEvent) obj;
                return this.userId == loginEvent.getUserId() && this.ip.equals(loginEvent.ip)
                        && this.status.equals(loginEvent.getStatus()) && this.timestamp == loginEvent.getTimestamp();
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return super.hashCode() + Long.hashCode(timestamp);
        }
    }

    final static List<LoginEvent> loginEventList = Arrays.asList(
            new LoginEvent(1001, "192.168.10.1", "F", 2L),
            new LoginEvent(1001, "192.168.10.2", "F", 3L),
            new LoginEvent(1002, "192.168.10.8", "F", 4L),
            new LoginEvent(1001, "192.168.10.6", "F", 5L),
            new LoginEvent(1002, "192.168.10.8", "F", 7L),
            new LoginEvent(1002, "192.168.10.8", "F", 8L),
            new LoginEvent(1002, "192.168.10.8", "S", 6L),
            new LoginEvent(1003, "192.168.10.8", "F", 6L),
            new LoginEvent(1004, "192.168.10.3", "S", 4L)
            );

    static void test1() throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 按用户id分组
        DataStream<LoginEvent> loginEventDS = env.fromCollection(loginEventList).assignTimestampsAndWatermarks(
                WatermarkStrategy.<LoginEvent>forBoundedOutOfOrderness(Duration.ofSeconds(10))
                        .withTimestampAssigner((loginEvent, rs) -> loginEvent.getTimestamp()))
                .keyBy(loginEvent -> loginEvent.getUserId());

        // 定义模式
        Pattern<LoginEvent, ?> loginEventPattern = Pattern.<LoginEvent>begin("first")
                .where(new SimpleCondition<LoginEvent>() {

                    @Override
                    public boolean filter(LoginEvent value) throws Exception {
                        return value.getStatus().equals("F");
                    }

                })
                // .times(3) // 期望出现3次
                // .times(3).optional() // 期望出现0或者3次
                // .times(2, 4) // 期望出现2、3或者4次
                // .times(2, 4).greedy() // 期望出现2、3或者4次,并且尽可能的重复次数多
                // .times(2, 4).optional() // 期望出现0、2、3或者4次
                // .times(2, 4).optional().greedy() // 期望出现0、2、3或者4次,并且尽可能的重复次数多
                // .oneOrMore() // 期望出现1到多次
                // .oneOrMore().greedy() // 期望出现1到多次,并且尽可能的重复次数多
                // .oneOrMore().optional() // 期望出现0到多次
                // .oneOrMore().optional().greedy() // 期望出现0到多次,并且尽可能的重复次数多
                // .timesOrMore(2) // 期望出现2到多次
                // .timesOrMore(2).greedy() // 期望出现2到多次,并且尽可能的重复次数多
                // .timesOrMore(2).optional() // 期望出现0、2或多次
                .timesOrMore(2).optional().greedy() // 期望出现0、2或多次,并且尽可能的重复次数多
                ;

        // 将Pattern应用到流上,检测匹配的复杂事件,得到一个PatternStream
        PatternStream<LoginEvent> patternStream = CEP.pattern(loginEventDS, loginEventPattern);

        // 将匹配到的流选择出来输出
        patternStream
                .select(new PatternSelectFunction<LoginEvent, String>() {
                    @Override
                    public String select(Map<String, List<LoginEvent>> map) throws Exception {

                        return map.get("first").toString() ;
                    }
                })
                .print("输出信息:\n");

        // 控制台输出:

        env.execute();
    }

    public static void main(String[] args) throws Exception {
        test1();
    }
}

以上,本文介绍了Flink 的类库CEP的入门及单个模式。

本专题分为以下几篇介绍:
59、Flink CEP - Flink的复杂事件处理介绍及示例(1)-入门
59、Flink CEP - Flink的复杂事件处理介绍及示例(2)- 模式API
59、Flink CEP - Flink的复杂事件处理介绍及示例(3)- 模式选取及超时处理
59、Flink CEP - Flink的复杂事件处理介绍及示例(4)- 延迟数据处理和三个实际应用示例
59、Flink CEP - Flink的复杂事件处理介绍及示例(完整版)

相关推荐
zhixingheyi_tian4 小时前
Spark 之 Aggregate
大数据·分布式·spark
PersistJiao4 小时前
Spark 分布式计算中网络传输和序列化的关系(一)
大数据·网络·spark
KevinAha6 小时前
Kafka 3.5 源码导读
kafka
求积分不加C6 小时前
-bash: ./kafka-topics.sh: No such file or directory--解决方案
分布式·kafka
nathan05296 小时前
javaer快速上手kafka
分布式·kafka
宅小海7 小时前
scala String
大数据·开发语言·scala
小白的白是白痴的白7 小时前
11.17 Scala练习:梦想清单管理
大数据
java1234_小锋7 小时前
Elasticsearch是如何实现Master选举的?
大数据·elasticsearch·搜索引擎
宝哥大数据8 小时前
Flink Joins
flink
激流丶9 小时前
【Kafka 实战】Kafka 如何保证消息的顺序性?
java·后端·kafka