flink ProcessionFunction 的使用 以及踩到的一些坑

笔者最近新需求需要在日志后面加入用户每个页面浏览的时间,由于日志中本身只有时间这个字段,没有浏览计时,最简单粗暴的方法就是后一条日志的时间减去前一条的时间,然后再设定一个超时阈值作为用户的超时时间,当一个用户长时间未操作时写回一个固定时间
第一个想到的是用window来做,由于flink也是在摸索之中,踩坑未果,然后使用ProcessionFunction完成(写完ProcessFunction又回头写了一下window,也解决了。汗。。。。。)
代码如下

package operator;

import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.util.Collector;
import pojo.LogBean;

import java.sql.Time;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Date;

/**
 * Created by IntelliJ IDEA.
 * User: fr
 * Time: 2020/2/28 14:26
 */

public class AddTimeProcessFunction extends KeyedProcessFunction<String,Tuple2<String, LogBean>, Tuple2<String, LogBean>> {

    /**
     * process function维持的状态
     */
    private ValueState<CountWithTimestamp> state;

    /**
     * 设定的日志延迟时间
     */
    private final long delay = 500;

    @Override
    public void open(Configuration parameters) throws Exception {
        state = getRuntimeContext().getState(new ValueStateDescriptor<>("myState", CountWithTimestamp.class));
    }

    @Override
    public void processElement(Tuple2<String, LogBean> value, Context ctx, Collector<Tuple2<String, LogBean>> out) throws Exception {
// retrieve the current count
        // 获取当前key的状态
        CountWithTimestamp current = state.value();

        //判断状态是否存在
        if (current == null || current.flag == 0) {
            //将时间转换成long型
            LocalDateTime parse = LocalDateTime.parse(value.f1.getOperTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            long nowEventTime = parse.toEpochSecond(ZoneOffset.of("+8"));
            nowEventTime *= 1000 ;

            current = new CountWithTimestamp();
            current.key = value.f0;
            current.logbean=value.f1;
            current.flag=1;
            current.lastModified = nowEventTime;

            //更新状态到state中
            state.update(current);

        }else {
            // set the state's timestamp to the record's assigned event time timestamp
            LocalDateTime parse = LocalDateTime.parse(value.f1.getOperTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
            long nowEventTime = parse.toEpochSecond(ZoneOffset.of("+8"));
            nowEventTime *= 1000 ;

            current.logbean.setGlobal1(String.valueOf(nowEventTime - current.lastModified));
            out.collect(new Tuple2<>(current.key,current.logbean));
            //System.err.println(current);
            current.logbean=value.f1;
            current.flag=1;

            // schedule the next timer 60 seconds from the current event time
            // 从当前事件时间开始计划下一个delay秒的定时器
            ctx.timerService().registerEventTimeTimer(current.lastModified + delay);
            // 将状态写回
            state.update(current);

        }

    }

    @Override
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<Tuple2<String, LogBean>> out) throws Exception {
        // get the state for the key that scheduled the timer

        //获取计划定时器的key的状态
        CountWithTimestamp result = state.value();

        // 检查是否是过时的定时器或最新的定时器
        if (timestamp >= result.lastModified + delay) {
            state.value().flag=0;
            result.logbean.setGlobal1(String.valueOf(delay));
            
            System.err.println(result);
            // emit the state on timeout
            out.collect(new Tuple2<String, LogBean>(result.key, result.logbean));
            //清除此状态
            state.clear();

        }
    }

}
class CountWithTimestamp {

    public String key;
    public LogBean logbean;
    public long lastModified;
    public int flag = 0;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public LogBean getLogbean() {
        return logbean;
    }

    public void setLogbean(LogBean logbean) {
        this.logbean = logbean;
    }

    public long getLastModified() {
        return lastModified;
    }

    public void setLastModified(long lastModified) {
        this.lastModified = lastModified;
    }

    public int getFlag() {
        return flag;
    }

    public void setFlag(int flag) {
        this.flag = flag;
    }

    public CountWithTimestamp() {
    }

    public CountWithTimestamp(String key, LogBean logbean, long lastModified, int flag) {
        this.key = key;
        this.logbean = logbean;
        this.lastModified = lastModified;
        this.flag = flag;
    }

    @Override
    public String toString() {
        return "CountWithTimestamp{" +
                "key='" + key + '\'' +
                ", logbean=" + logbean +
                ", lastModified=" + lastModified +
                ", flag=" + flag +
                '}';
    }
}

值得一说的是
由于使用的是ctx.timerService().registerEventTimeTimer,是eventTime,所以在主代码中要加上

//声明使用的是eventTime
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
//获取eventTime,转化为Long值
data.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<LogBean>() {
            @Override
            public long extractAscendingTimestamp(LogBean element) {
                LocalDateTime parse = LocalDateTime.parse(element.getOperTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                long eventTime = parse.toEpochSecond(ZoneOffset.of("+8"));
                //flink时间是精确到毫秒,日志中只转化到了秒,所以要乘一下
                return eventTime * 1000;
            }
        })
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 要使用Flink将数据索引到Elasticsearch,你需要使用Flink的Elasticsearch connector,该connector可以在Flink的官方文档找到。 以下是实现索引数据到Elasticsearch的步骤: 1. 首先,你需要创建一个Flink程序,这个程序可以连接到数据源,例如Kafka或者其他的数据源。你需要使用Flink的DataStream API来处理数据。 2. 在程序使用Elasticsearch connector将数据写入Elasticsearch。要使用Elasticsearch connector,你需要在pom.xml文件添加以下依赖项: ``` <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-elasticsearch7_2.12</artifactId> <version>1.13.0</version> </dependency> ``` 3. 在程序使用Elasticsearch connector将数据写入Elasticsearch。以下是使用Elasticsearch connector将数据写入Elasticsearch的示例代码: ``` DataStream<Tuple2<String, Integer>> dataStream = ... //从数据源获取数据 //将数据转换为Elasticsearch需要的格式 DataStream<JSONObject> esDataStream = dataStream.map(new MapFunction<Tuple2<String, Integer>, JSONObject>() { @Override public JSONObject map(Tuple2<String, Integer> value) throws Exception { JSONObject jsonObject = new JSONObject(); jsonObject.put("word", value.f0); jsonObject.put("count", value.f1); return jsonObject; } }); //将数据写入Elasticsearch List<HttpHost> httpHosts = new ArrayList<>(); httpHosts.add(new HttpHost("localhost", 9200, "http")); esDataStream.addSink( new ElasticsearchSink.Builder<JSONObject>(httpHosts, new ElasticsearchSinkFunction<JSONObject>() { public IndexRequest createIndexRequest(JSONObject element) { return Requests.indexRequest() .index("my-index") .type("_doc") .source(element.toJSONString(), XContentType.JSON); } @Override public void process(JSONObject element, RuntimeContext ctx, RequestIndexer indexer) { indexer.add(createIndexRequest(element)); } }).build() ); ``` 在这个例子,我们将每个单词的计数写入Elasticsearch。要将数据写入Elasticsearch,我们需要将数据转换为JSON格式,并使用ElasticsearchSinkFunction将数据写入Elasticsearch。在ElasticsearchSinkFunction,我们需要实现createIndexRequest方法,它将数据转换为IndexRequest对象,然后使用RequestIndexer将IndexRequest发送到Elasticsearch。 4. 启动Flink程序,并等待数据被索引到Elasticsearch。 这就是使用Flink将数据索引到Elasticsearch的步骤。注意,在实际生产环境,你可能需要处理更复杂的数据并在Elasticsearch建立更复杂的索引。 ### 回答2: Flink是一个开源的流处理框架,具有高效、可扩展和容错等特性。使用Flink可以将索引数据实时发送到Elasticsearch。 为了实现索引数据到Elasticsearch,我们需要进行以下步骤: 1. 连接到数据源:首先,我们需要从数据源获取索引数据。可以是日志文件、消息队列或其他流式数据源。借助Flink的连接器,我们可以轻松地从这些数据源读取数据。 2. 数据转换和处理:接下来,我们需要对获取的数据进行转换和处理。可以使用Flink的转换操作对数据进行清洗、过滤、格式化等操作,以使其适合索引到Elasticsearch。 3. 将数据发送到Elasticsearch:一旦数据转换和处理完成,我们就可以使用Flink提供的Elasticsearch连接器将数据发送到Elasticsearch。连接器自动将数据批量发送到Elasticsearch集群的相应索引。 4. 容错和恢复:在数据处理过程,可能出现故障或网络断等情况。Flink提供了容错机制,可以保证数据处理的高可用性和可靠性。如果出现故障,Flink自动恢复并重新处理丢失的数据。 使用Flink实现索引数据到Elasticsearch具有以下优势: 1. 实时性:Flink作为一个流处理框架,可以使索引数据几乎实时地传输到Elasticsearch,确保数据的最新性。 2. 可扩展性:Flink具有良好的扩展性,可以处理大规模的数据,并且可以根据需要动态地扩展集群规模。 3. 容错性:Flink的容错机制可以保证在发生故障时数据的安全性和可恢复性,避免数据丢失或损坏。 总结而言,使用Flink可以轻松地将索引数据实时发送到Elasticsearch,并享受其高效、可扩展和容错的优势。 ### 回答3: 使用Flink实现索引数据到Elasticsearch是一个相对简单且高效的过程。Flink是一个实时流处理框架,可以通过连接到数据源,并以流式方式处理和转换数据。 首先,我们需要连接到数据源。可以通过Flink提供的API或者适配器来连接到不同类型的数据源,如Kafka、RabbitMQ等。一旦连接到数据源,我们可以使用Flink的DataStream API将数据流转换为可供索引的格式。 接下来,我们需要将转换后的数据流发送到Elasticsearch进行索引。可以使用Flink的Elasticsearch连接器来实现此功能。该连接器提供了一种将数据流的记录自动索引到Elasticsearch的方式。 为了使用Elasticsearch连接器,我们需要在Flink作业添加相应的依赖。然后,在代码配置Elasticsearch连接和索引的相关信息,如主机地址、索引名称等。一旦配置完成,我们可以使用DataStream的addSink()方法将数据流发送到Elasticsearch。 在将数据流发送到Elasticsearch之前,可以进行一些额外的转换和处理。例如,可以对数据流进行过滤、映射或聚合操作,以便索引的数据满足特定的需求。 最后,运行Flink作业并监控其运行状态。一旦作业开始运行,Flink将自动将数据流的记录发送到Elasticsearch进行索引。 使用Flink实现索引数据到Elasticsearch的好处是它提供了流式处理的能力,能够实时处理和索引数据。另外,Flink还提供了容错和恢复机制,以确保数据的准确性和可靠性。 总之,通过Flink实现索引数据到Elasticsearch是一种快速、简单且高效的方法,可以帮助我们充分利用实时流数据并实时索引到Elasticsearch
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值