研发背景

公司安全部目前针对内部系统的网络访问日志的安全审计,大部分都是T+1时效,每日当天,启动Python编写的定时任务,完成昨日的日志审计和检测,定时任务运行完成后,统一进行企业微信告警推送。这种方案在目前的网络环境和人员规模下,呈现两个痛点,一是面对日益频繁的网络攻击、钓鱼链接,T+1的定时任务,难以及时进行告警,因此也难以有效避免如关键信息泄露等问题,二是目前以Python为主的单机定时任务,针对不同场景的处理时效,从一小时到十几小时不等,效率低下。为解决以上问题,本人协助公司安全部同时对告警采集平台进行改造,由之前的python单机任务处理,切换到基于Flink集群的并行处理,且告警推送时效,由之前的T+1天,提升到秒级实时告警。本次改造涉及网络日志审计的多个常见场景,如端口扫描、黑名单统计、异常流量、连续恶意登录等。本次以一段时间内连续登录失败20次后,下一次登录成功场景来进行介绍。

场景描述

针对一个内部系统,如邮件系统,公司员工的访问行为日志,存放于kafka,我们希望对于一个用户账号在同一个IP下,任意的3分钟时间内,连续登录邮件系统20次失败,下一次登录成功,这种场景能够及时获取并推送到企业微信某个指定的安全接口人。kafka中的数据,能够通过某个关键字,区分当前网络访问是否一次登录事件,且有访问时间(也就是事件时间)。在解析到符合需求的用户账号之后,第一时间进行企业微信告警推送,并将其这段时间内的访问行为,写入下游ElasticSearch。

组件版本

  • Flink-1.14.4
  • Java8
  • ElasticSearch-7.3.2
  • Kafka-2.12_2.8.1

日志结构

IP和账号皆为测试使用。

{
"user": "wangxm",
"client_ip": "110.68.6.182",
"source": "login",
"loginname": "wangxm@test.com",
"IP": "110.8.148.58",
"timestamp": "17:58:12",
"@timestamp": "2022-04-20T09:58:13.647Z",
"ip": "110.7.231.25",
"clienttype": "POP3",
"result": "success",
"@version": "1"
}

技术方案

上述场景,可考虑使用FlinkCEP及Flink的滑动窗口进行实现。由于本人在采用FlinkCEP的方案进行代码编写调试后,发现并不能满足,因此改用滑动窗口进行实现。

关键代码

主入口类

主入口类,创建了flink环境、设置了基础参数,创建了kafkaSource,接入消息后,进行了映射、过滤,并设置了水位线,进行了分组,之后设置了滑动窗口,在窗口内进行了事件统计,将复合条件的事件收集返回并写入ElasticSearch。

针对map、filter、keyBy、window等算子,都单独进行了编写,后面会一一列出来。

package com.data.dev.flink.mailTopic.main;

import com.data.dev.common.javabean.BaseBean;
import com.data.dev.common.javabean.kafkaMailTopic.MailMsgAlarm;
import com.data.dev.elasticsearch.ElasticSearchInfo;
import com.data.dev.elasticsearch.SinkToEs;
import com.data.dev.flink.FlinkEnv;
import com.data.dev.flink.mailTopic.OperationForLoginFailCheck.*;
import com.data.dev.kafka.KafkaSourceBuilder;
import com.data.dev.key.ConfigurationKey;
import com.data.dev.utils.TimeUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.connector.kafka.source.KafkaSource;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.KeyedStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.datastream.WindowedStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.windowing.assigners.SlidingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow; import java.time.Duration; /**
* Flink处理在3分钟内连续登录失败20次后登录成功的场景
* 采用滑动窗口来实现
* @author wangxiaomin 2022-06-01
*/ @Slf4j
public class MailMsg extends BaseBean { /**
* Flink作业名称
*/
public static final String JobName = "告警采集平台——连续登录失败后登录成功告警";
/**
* Kafka消息名
*/
public static final String KafkaSourceName = "Kafka Source for AlarmPlatform About Mail Topic"; public MailMsg(){
log.info("初始化滑动窗口场景告警程序");
} /**
* 执行逻辑统计场景,实现告警推送
*/
public static void execute(){ //① 创建Flink执行环境并设置checkpoint等必要的参数
StreamExecutionEnvironment env = FlinkEnv.getFlinkEnv();
KafkaSource<String> kafkaSource = KafkaSourceBuilder.getKafkaSource(ConfigurationKey.KAFKA_MAIL_TOPIC_NAME,ConfigurationKey.KAFKA_MAIL_CONSUMER_GROUP_ID) ;
DataStreamSource<String> kafkaMailMsg = env.fromSource(kafkaSource, WatermarkStrategy.forBoundedOutOfOrderness(Duration.ofMillis(10)), KafkaSourceName); //② 筛选登录消息,创建初始登录事件流
SingleOutputStreamOperator<com.data.dev.common.javabean.kafkaMailTopic.MailMsg> loginMapDs = kafkaMailMsg.map(new MsgToBeanMapper()).name("Map算子加工");
SingleOutputStreamOperator<com.data.dev.common.javabean.kafkaMailTopic.MailMsg> loginFilterDs = loginMapDs.filter(new MailMsgForLoginFilter()).name("Filter算子加工"); //③ 设置水位线
WatermarkStrategy<com.data.dev.common.javabean.kafkaMailTopic.MailMsg> watermarkStrategy = WatermarkStrategy.<com.data.dev.common.javabean.kafkaMailTopic.MailMsg>forBoundedOutOfOrderness(Duration.ofMinutes(1))
.withTimestampAssigner((mailMsg, timestamp) -> TimeUtils.switchUTCToBeijingTimestamp(mailMsg.getTimestamp_datetime()));
SingleOutputStreamOperator<com.data.dev.common.javabean.kafkaMailTopic.MailMsg> loginWmDs = loginFilterDs.assignTimestampsAndWatermarks(watermarkStrategy.withIdleness(Duration.ofMinutes(3))).name("增加水位线"); //④ 设置主键
KeyedStream<com.data.dev.common.javabean.kafkaMailTopic.MailMsg, String> loginKeyedDs = loginWmDs.keyBy(new LoginKeySelector()); //⑥ 转化为滑动窗口
WindowedStream<com.data.dev.common.javabean.kafkaMailTopic.MailMsg, String, TimeWindow> loginWindowDs = loginKeyedDs.window(SlidingEventTimeWindows.of(Time.seconds(180L),Time.seconds(90L))); //⑦ 在窗口内进行逻辑统计
SingleOutputStreamOperator<MailMsgAlarm> loginWindowsDealDs = loginWindowDs.process(new WindowProcessFuncImpl()).name("窗口处理逻辑"); //⑧ 将结果转化为通用DataStream<String>格式
SingleOutputStreamOperator<String> resultDs = loginWindowsDealDs.map(new AlarmMsgToStringMapper()).name("窗口结果转化为标准格式"); //⑨ 将最终结果写入ES
resultDs.addSink(SinkToEs.getEsSinkBuilder(ElasticSearchInfo.ES_LOGIN_FAIL_INDEX_NAME,ElasticSearchInfo.ES_INDEX_TYPE_DEFAULT).build()); //⑩ 提交Flink集群进行执行
FlinkEnv.envExec(env,JobName); }
}

mapper算子

package com.data.dev.flink.mailTopic.OperationForLoginFailCheck;

import com.alibaba.fastjson.JSON;
import com.data.dev.common.javabean.BaseBean;
import com.data.dev.common.javabean.kafkaMailTopic.MailMsgAlarm;
import lombok.extern.slf4j.Slf4j;
import org.apache.flink.api.common.functions.MapFunction; /**
* 逻辑统计场景告警推送ES消息体
* @author wangxiaoming-ghq 2022-06-01
*/
@Slf4j
public class AlarmMsgToStringMapper extends BaseBean implements MapFunction<MailMsgAlarm, String> { @Override
public String map(MailMsgAlarm mailMsgAlarm) throws Exception {
return JSON.toJSONString(mailMsgAlarm);
}
}

filter算子

package com.data.dev.flink.mailTopic.OperationForLoginFailCheck;

import com.data.dev.common.javabean.BaseBean;
import com.data.dev.common.javabean.kafkaMailTopic.MailMsg;
import lombok.extern.slf4j.Slf4j;
import org.apache.flink.api.common.functions.FilterFunction; /**
* ② 消费mail主题的消息,过滤其中login的事件
* @author wangxiaoming-ghq 2022-06-01
*/
@Slf4j
public class MailMsgForLoginFilter extends BaseBean implements FilterFunction<MailMsg> {
@Override
public boolean filter(MailMsg mailMsg) {
if("login".equals(mailMsg.getSource())) {
log.info("筛选原始的login事件:【" + mailMsg + "】");
}
return "login".equals(mailMsg.getSource());
}
}

keyBy算子

package com.data.dev.flink.mailTopic.OperationForLoginFailCheck;

import com.data.dev.common.javabean.BaseBean;
import com.data.dev.common.javabean.kafkaMailTopic.MailMsg;
import lombok.extern.slf4j.Slf4j;
import org.apache.flink.api.java.functions.KeySelector; /**
* CEP 编程,需要进行key选取
*/
@Slf4j
public class LoginKeySelector extends BaseBean implements KeySelector<MailMsg, String> {
@Override
public String getKey(MailMsg mailMsg) {
return mailMsg.getUser() + "@" + mailMsg.getClient_ip();
}
}

窗口函数(核心代码)

package com.data.dev.flink.mailTopic.OperationForLoginFailCheck;

import com.data.dev.common.javabean.kafkaMailTopic.MailMsg;
import com.data.dev.common.javabean.kafkaMailTopic.MailMsgAlarm;
import com.data.dev.utils.HttpUtils;
import com.data.dev.utils.IPUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List; /**
* 滑动窗口内复杂事件解析逻辑实现
* @author wangxiaoming-ghq 2022-06-01
*/
@Slf4j
public class WindowProcessFuncImpl extends ProcessWindowFunction<MailMsg, MailMsgAlarm, String, TimeWindow> implements Serializable {
@Override
public void process(String key, ProcessWindowFunction<MailMsg, MailMsgAlarm, String, TimeWindow>.Context context, Iterable<MailMsg> iterable, Collector<MailMsgAlarm> collector) { List<MailMsg> loginEventList = new ArrayList<>();
MailMsgAlarm mailMsgAlarm;
for (MailMsg mailMsg : iterable) { log.info("收集到的登录事件【" + mailMsg + "】"); if (mailMsg.getResult().equals("fail")) { //开始检测当前窗口内的事件,并将失败的事件收集到loginEventList
log.info("开始检测当前窗口内的事件,并将失败的事件收集到loginEventList");
loginEventList.add(mailMsg);
} else if (mailMsg.getResult().equals("success") && loginEventList.size() < 20) {//如果检测到登录成功事件,但此时登录失败的次数不足20次,则清空loginEventList,等待下一次检测
log.info("检测到登录成功事件,但此时登录失败的次数为【" + loginEventList.size() + "】不足20次,清空loginEventList,等待下一次检测");
loginEventList.clear();
} else if (mailMsg.getResult().equals("success") && loginEventList.size() >= 20) {
mailMsgAlarm = getMailMsgAlarm(loginEventList,mailMsg);
log.info("检测到登录成功的事件,此时窗口内连续登录失败的次数为【" + mailMsgAlarm.getFailTimes() + "】"); //一旦符合窗口内连续登录失败超过20次且下一次登录成功这个事件,则清空此时的loginEventList并将当前登录成功的事件进行告警推送;
loginEventList.clear();
doAlarmPush(mailMsgAlarm); collector.collect(mailMsgAlarm);//将当前登录成功的事件进行收集上报
} else {
log.info(mailMsg.getUser() + "当前已连续:【" + loginEventList.size() + "】 次登录失败");
}
}
} /**
* 2022年6月17日15:03:06
* @param eventList:当前窗口内的事件列表
* @param eventCurrent:当前登录成功的事件
* @return mailMsgAlarm:告警消息体
*/
public static MailMsgAlarm getMailMsgAlarm(List<MailMsg> eventList,MailMsg eventCurrent){ String alarmKey = eventCurrent.getUser() + "@" + eventCurrent.getClient_ip();
String loginFailStartTime = eventList.get(0).getTimestamp_datetime();
String loginSuccessTime = eventCurrent.getTimestamp_datetime();
int loginFailTimes = eventList.size(); MailMsgAlarm mailMsgAlarm = new MailMsgAlarm();
mailMsgAlarm.setMailMsg(eventCurrent);
mailMsgAlarm.setAlarmKey(alarmKey);
mailMsgAlarm.setStartTime(loginFailStartTime);
mailMsgAlarm.setEndTime(loginSuccessTime);
mailMsgAlarm.setFailTimes(loginFailTimes); return mailMsgAlarm;
} /**
* 2022年6月17日14:47:53
* @param mailMsgAlarm :当前构建的需要告警的事件
*/
public void doAlarmPush(MailMsgAlarm mailMsgAlarm){
String userKey = mailMsgAlarm.getAlarmKey();
String clientIp = mailMsgAlarm.mailMsg.getClient_ip();
boolean isWhiteListIp = IPUtils.isWhiteListIp(clientIp);
if(isWhiteListIp){//如果是白名单IP,不告警
log.info("当前登录用户【" + userKey + "】属于白名单IP");
}else {
//IP归属查询结果、企业微信推送告警
String user = HttpUtils.getUserByClientIp(clientIp);
HttpUtils.pushAlarmMsgToWechatWork(user,mailMsgAlarm.toString());
}
}
}

最后一次map算子

package com.data.dev.flink.mailTopic.OperationForLoginFailCheck;

import com.alibaba.fastjson.JSON;
import com.data.dev.common.javabean.BaseBean;
import com.data.dev.common.javabean.kafkaMailTopic.MailMsgAlarm;
import lombok.extern.slf4j.Slf4j;
import org.apache.flink.api.common.functions.MapFunction; /**
* 逻辑统计场景告警推送ES消息体
* @author wangxiaoming-ghq 2022-06-01
*/
@Slf4j
public class AlarmMsgToStringMapper extends BaseBean implements MapFunction<MailMsgAlarm, String> { @Override
public String map(MailMsgAlarm mailMsgAlarm) throws Exception {
return JSON.toJSONString(mailMsgAlarm);
}
}

ElasticSearch工具类

package com.data.dev.elasticsearch;

import com.data.dev.common.javabean.BaseBean;
import com.data.dev.key.ConfigurationKey;
import com.data.dev.key.ElasticSearchKey;
import lombok.extern.slf4j.Slf4j;
import org.apache.flink.api.common.functions.RuntimeContext;
import org.apache.flink.streaming.connectors.elasticsearch.ElasticsearchSinkFunction;
import org.apache.flink.streaming.connectors.elasticsearch.RequestIndexer;
import org.apache.flink.streaming.connectors.elasticsearch7.ElasticsearchSink;
import org.apache.flink.streaming.connectors.elasticsearch7.RestClientFactory;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.Requests; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; /**
* 2022年6月17日15:15:06
* @author wangxiaoming-ghq
* Flink流计算结果写入ES公共方法
*/
@Slf4j
public class SinkToEs extends BaseBean {
public static final long serialVersionUID = 2L;
private static final HashMap<String,String> ES_PROPS_MAP = ConfigurationKey.getApplicationProps();
private static final String HOST = ES_PROPS_MAP.get(ConfigurationKey.ES_HOST);
private static final String PASSWORD = ES_PROPS_MAP.get(ConfigurationKey.ES_PASSWORD);
private static final String USERNAME = ES_PROPS_MAP.get(ConfigurationKey.ES_USERNAME);
private static final String PORT = ES_PROPS_MAP.get(ConfigurationKey.ES_PORT); /**
* 2022年6月17日15:17:55
* 获取ES连接信息
* @return esInfoMap:ES连接信息持久化
*/
public static HashMap<String,String > getElasticSearchInfo(){
log.info("获取ES连接信息:【 " + "HOST="+HOST + "PORT="+PORT+"USERNAME="+USERNAME+"PASSWORD=********" + " 】");
HashMap<String,String> esInfoMap = new HashMap<>();
esInfoMap.put(ElasticSearchKey.HOST,HOST);
esInfoMap.put(ElasticSearchKey.PASSWORD,PASSWORD);
esInfoMap.put(ElasticSearchKey.USERNAME,USERNAME);
esInfoMap.put(ElasticSearchKey.PORT,PORT); return esInfoMap;
} /**
* @param esIndexName:写入索引名称
* @param esType:写入索引类型
* @return ElasticsearchSink.Builder<String>:构建器
*/
public static ElasticsearchSink.Builder<String> getEsSinkBuilder(String esIndexName,String esType){
HashMap<String, String> esInfoMap = getElasticSearchInfo();
List<HttpHost> httpHosts = new ArrayList<>();
httpHosts.add(new HttpHost(String.valueOf(esInfoMap.get(ElasticSearchKey.HOST)), Integer.parseInt(esInfoMap.get(ElasticSearchKey.PORT)), "http")); ElasticsearchSink.Builder<String> esSinkBuilder = new ElasticsearchSink.Builder<>(
httpHosts,
new ElasticsearchSinkFunction<String>() { public IndexRequest createIndexRequest() {
Map<String, String> json = new HashMap<>();
//log.info("写入ES的data:【"+json+"】");
IndexRequest index = Requests.indexRequest()
.index(esIndexName)
.type(esType)
.source(json);
return index;
} @Override
public void process(String element, RuntimeContext ctx, RequestIndexer indexer) {
indexer.add(createIndexRequest());
}
}
); //定义es的连接配置 带用户名密码
RestClientFactory restClientFactory = restClientBuilder -> {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(
String.valueOf(esInfoMap.get(ElasticSearchKey.USERNAME)),
String.valueOf(esInfoMap.get(ElasticSearchKey.PASSWORD))
)
);
restClientBuilder.setHttpClientConfigCallback(httpAsyncClientBuilder -> {
httpAsyncClientBuilder.disableAuthCaching();
return httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
});
}; esSinkBuilder.setRestClientFactory(restClientFactory);
return esSinkBuilder;
} }

事件实体类

package com.data.dev.common.javabean.kafkaMailTopic;

import com.data.dev.common.javabean.BaseBean;
import lombok.Data; import java.util.Objects; /**
* @author wangxiaoming-ghq 2022-05-15
* 逻辑统计场景告警事件
*/
@Data
public class MailMsgAlarm extends BaseBean { /**
* 当前登录成功的事件
*/
public MailMsg mailMsg; /**
* 当前捕获的告警主键:username@client_ip
*/
public String alarmKey; /**
* 第一次登录失败的事件时间
*/
public String startTime; /**
* 连续登录失败后下一次登录成功的事件时间
*/
public String endTime; /**
* 连续登录失败的次数
*/
public int failTimes; @Override
public String toString() {
return "{" +
" 'mailMsg_login_success':'" + mailMsg + "'" +
", 'alarmKey':'" + alarmKey + "'" +
", 'start_login_time_in3min':'" +startTime + "'" +
", 'end_login_time_in3min':'" +endTime + "'" +
", 'login_fail_times':'" +failTimes + "'" +
"}";
} public MailMsgAlarm() {
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MailMsgAlarm)) return false;
MailMsgAlarm that = (MailMsgAlarm) o;
return getFailTimes() == that.getFailTimes() && getMailMsg().equals(that.getMailMsg()) && getAlarmKey().equals(that.getAlarmKey()) && getStartTime().equals(that.getStartTime()) && getEndTime().equals(that.getEndTime());
} @Override
public int hashCode() {
return Objects.hash(getMailMsg(), getAlarmKey(), getStartTime(), getEndTime(), getFailTimes());
}
}

消息实体类

package com.data.dev.common.javabean.kafkaMailTopic;

import com.data.dev.common.javabean.BaseBean;
import lombok.Data; import java.util.Objects; /**
* {
* "user": "wangxm",
* "client_ip": "110.68.6.182",
* "source": "login",
* "loginname": "wangxm@test.com",
* "IP": "110.8.148.58",
* "timestamp": "17:58:12",
* "@timestamp": "2022-04-20T09:58:13.647Z",
* "ip": "110.7.231.25",
* "clienttype": "POP3",
* "result": "success",
* "@version": "1"
* }
*
* user登录用户
* client_ip 来源ip
* source 类型
* loginname 登录用户邮箱地址
* ip 目标前端ip
* timestamp 发送时间
* @timestamp 发送日期时间
* IP 邮件日志发送来源IP
* clienttype 客户端登录类型
* result 登录状态
*/ @Data
public class MailMsg extends BaseBean {
public String user;
public String client_ip;
public String source;
public String loginName;
public String mailSenderSourceIp;
public String timestamp_time;
public String timestamp_datetime;
public String ip;
public String clientType;
public String result;
public String version; public MailMsg() {
} public MailMsg(String user, String client_ip, String source, String loginName, String mailSenderSourceIp, String timestamp_time, String timestamp_datetime, String ip, String clientType, String result, String version) {
this.user = user;
this.client_ip = client_ip;
this.source = source;
this.loginName = loginName;
this.mailSenderSourceIp = mailSenderSourceIp;
this.timestamp_time = timestamp_time;
this.timestamp_datetime = timestamp_datetime;
this.ip = ip;
this.clientType = clientType;
this.result = result;
this.version = version;
} @Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MailMsg)) return false;
MailMsg mailMsg = (MailMsg) o;
return getUser().equals(mailMsg.getUser()) && getClient_ip().equals(mailMsg.getClient_ip()) && getSource().equals(mailMsg.getSource()) && getLoginName().equals(mailMsg.getLoginName()) && getMailSenderSourceIp().equals(mailMsg.getMailSenderSourceIp()) && getTimestamp_time().equals(mailMsg.getTimestamp_time()) && getTimestamp_datetime().equals(mailMsg.getTimestamp_datetime()) && getIp().equals(mailMsg.getIp()) && getClientType().equals(mailMsg.getClientType()) && getResult().equals(mailMsg.getResult()) && getVersion().equals(mailMsg.getVersion());
} @Override
public int hashCode() {
return Objects.hash(getUser(), getClient_ip(), getSource(), getLoginName(), getMailSenderSourceIp(), getTimestamp_time(), getTimestamp_datetime(), getIp(), getClientType(), getResult(), getVersion());
} @Override
public String toString() {
return "{" +
" 'user':'" + user + "'" +
", 'client_ip':'" + client_ip + "'" +
", 'source':'" + source + "'" +
", 'loginName':'" + loginName + "'" +
", 'IP':'" + mailSenderSourceIp + "'" +
", 'timestamp':'" + timestamp_time + "'" +
", '@timestamp':'" + timestamp_datetime + "'" +
", 'ip':'" + "'" +
", 'clientType':'" + clientType + "'" +
", 'result':'" + result + "'" +
", 'version':'" + version + "'" +
"}";
} }

基于FLink实现的实时安全检测(一段时间内连续登录失败20次后,下一次登录成功场景)的更多相关文章

  1. 基于flink快速开发实时TopN程序

    TopN 是统计报表和大屏非常常见的功能,主要用来实时计算排行榜.流式的TopN可以使业务方在内存中按照某个统计指标(如出现次数)计算排名并快速出发出更新后的排行榜. 我们以统计词频为例展示一下如何快 ...

  2. Lyft 基于 Flink 的大规模准实时数据分析平台(附FFA大会视频)

    摘要:如何基于 Flink 搭建大规模准实时数据分析平台?在 Flink Forward Asia 2019 上,来自 Lyft 公司实时数据平台的徐赢博士和计算数据平台的高立博士分享了 Lyft 基 ...

  3. 字节跳动流式数据集成基于Flink Checkpoint两阶段提交的实践和优化

    背景 字节跳动开发套件数据集成团队(DTS ,Data Transmission Service)在字节跳动内基于 Flink 实现了流批一体的数据集成服务.其中一个典型场景是 Kafka/ByteM ...

  4. 趣头条基于 Flink 的实时平台建设实践

    本文由趣头条实时平台负责人席建刚分享趣头条实时平台的建设,整理者叶里君.文章将从平台的架构.Flink 现状,Flink 应用以及未来计划四部分分享. 一.平台架构 1.Flink 应用时间线 首先是 ...

  5. 基于Flink和规则引擎的实时风控解决方案

    案例与解决方案汇总页:阿里云实时计算产品案例&解决方案汇总 对一个互联网产品来说,典型的风控场景包括:注册风控.登陆风控.交易风控.活动风控等,而风控的最佳效果是防患于未然,所以事前事中和事后 ...

  6. DataPipeline王睿:业务异常实时自动化检测 — 基于人工智能的系统实战

    大家好,先自我介绍一下,我是王睿.之前在Facebook/Instagram担任AI技术负责人,现在DataPipeline任Head of AI,负责研发企业级业务异常检测产品,旨在帮助企业一站式解 ...

  7. 轻装上阵Flink--在IDEA上开发基于Flink的实时数据流程序

    前言 本文介绍如何在IDEA上快速开发基于Flink框架的DataStream程序.先直接上手! 环境清单 案例是在win7运行.安装VirtualBox,在VirtualBox上安装Centos操作 ...

  8. 基于flink和drools的实时日志处理

    1.背景 日志系统接入的日志种类多.格式复杂多样,主流的有以下几种日志: filebeat采集到的文本日志,格式多样 winbeat采集到的操作系统日志 设备上报到logstash的syslog日志 ...

  9. [OpenCV]基于特征匹配的实时平面目标检测算法

    一直想基于传统图像匹配方式做一个融合Demo,也算是对上个阶段学习的一个总结. 由此,便采购了一个摄像头,在此基础上做了实时检测平面目标的特征匹配算法. 代码如下: # coding: utf-8 ' ...

  10. 美团点评基于 Flink 的实时数仓建设实践

    https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651749037&idx=1&sn=4a448647b3dae5 ...

随机推荐

  1. 包管理器pacman常用方法

    详见[pacman(简体中文) - ArchWiki]:https://wiki.archlinux.org/title/Pacman_(简体中文) 更新系统: pacman -Syu 对整个系统进行 ...

  2. 深入浅出学习透析Nginx服务器的基本原理和配置指南「Keepalive性能分析实战篇」

    Linux系统:Centos 7 x64 Nginx版本:1.11.5 Nginx 是一款面向性能设计的 HTTP 服务器,能反向代理 HTTP,HTTPS 和邮件相关(SMTP,POP3,IMAP) ...

  3. thinkphp6文件上传自定义命名规则

    think官方自带的命名规则有三种 规则 描述 date 根据日期和微秒数生成 md5 对文件使用md5_file散列生成 sha1 对文件使用sha1_file散列生成 其中md5和sha1规则会自 ...

  4. 安装mySql 出现 one more product requirements have not been satisified

    安装mySql 出现 one more product requirements have not been satisified 原因是缺少一些依赖环境. 在弹出的对话框中点击 否. 然后点击执行, ...

  5. day32 6 请求转发与重定向的区别、session会话对象 & cookie & 8 应用程序上下文对象ServletContext & 5 请求转发与jsp页面内置对象

    1 请求转发与重定向的区别 2 session与cookie的区别 3 过滤器与监听器的区别 4 web-inf目录 web-inf目录是安全目录,无法从客户端访问,只能通过(服务端的)servlet ...

  6. Windows 10 读取bitlocker加密的硬盘出现参数错误怎么解决?

    我为了数据安全,用windows专业版的bitlocker加密了一个固态硬盘SSD做的移动硬盘(u盘同理),在家里电脑(windows10 家庭版)打开的时候出现了参数错误 即使密码输入正确还是这个错 ...

  7. 【Spring系列】- Spring循环依赖

    Spring循环依赖 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 目录 Spring循 ...

  8. vue设计与实现 第6章 ref 响应原理 笔记

    ref 函数实现代码 const a = ref(1); function ref(value){ const wrapper = {value} Object.defineProperty(wrap ...

  9. mysql基础问题三问(底层逻辑;正在执行;日志观察)

    背景:经常面试会遇到且实际工作中也会应用到的三个场景: 目录: 一.mysql查询时的底层原理是什么? 二.如何查看正在执行的mysql语句? 三.如何观察mysql运行过程中的日志信息? - - - ...

  10. 实施 GitOps 的三个关键步骤

    GitOps 是一种自动化和管理基础架构和应用程序的模型,通过许多团队已经使用的相同 DevOps 最佳实践来形成的模型,例如版本控制.代码审查和 CI/CD 流水线.在实施 DevOps 时,我们找 ...