flink實戰--基於Kafka+Flink(窗口+窗口函數)+Redis電商大屏實時計算PV,UV

背景

           阿里的雙11銷量大屏可以說是每年雙十一的一道特殊的風景線。實時大屏(real-time dashboard)正在被越來越多的企業採用,用來及時呈現關鍵的數據指標。並且在實際操作中,肯定也不會僅僅計算一兩個維度。由於Flink的“真·流式計算”這一特點,它比Spark Streaming要更適合大屏應用。本文將結合實際工作經驗抽象出簡單的模型,並簡要敘述計算流程(當然大部分都是源碼)。

由於大屏的最大訴求是實時性,等待遲到數據顯然不太現實,因此我們採用處理時間作爲時間特徵,接下來以計算PV爲例,給大家展示具體的計算流程

需求:計算每一天的PV,並且每秒更新一次PV值

注意:爲了展示,計算一天數據沒法展示測試結果,這裏的demo是計算一分鐘內的pv,每秒更新一次

數據準備:

{
    "userId": 234567,
    "orderId": 2902306918400,
    "subOrderId": 2902306918401,
    "siteId": 10219,
    "siteName": "site_blabla",
    "cityId": 101,
    "cityName": "北京市",
    "warehouseId": 636,
    "merchandiseId": 187699,
    "price": 299,
    "quantity": 2,
    "orderStatus": 1,
    "isNewOrder": 0,
    "timestamp": 1572963672217
}

使用用戶的行爲日誌作爲我們計算的數據,線上日誌數據存在kafka中,這裏爲了演示。本地使用nc -lp 模擬用戶數據  userId +其他行爲日誌(這裏用1代替)

邏輯代碼:

               使用userId進行分組,開10s中的處理時間窗口(模擬線上1天的窗口),並同時設定ContinuousProcessingTimeTrigger觸發器,以1秒週期觸發計算去更新最新的PV值

public class PVTest {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
        DataStreamSource<String> source = env.socketTextStream("127.0.0.1", 10080);
        SingleOutputStreamOperator<UserInfo> userStream = source.map(new MapFunction<String, UserInfo>() {
            @Override
            public UserInfo map(String s) throws Exception {
                String[] split = s.split(",");
                UserInfo subOrderDetail = new UserInfo();
                subOrderDetail.setOrderId(split[1]);
                subOrderDetail.setUserId(split[0]);
                return subOrderDetail;
            }
        });
        userStream
                .keyBy("userId")
                .window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
                .trigger(ContinuousProcessingTimeTrigger.of(Time.seconds(1)))
                .aggregate(new RsesultAggregateFunc())
                .print("每秒執行一次---------");
        env.execute("test");
 
    }

計算PV的窗口函數

  public static class RsesultAggregateFunc implements AggregateFunction<UserInfo,ResultInfo,ResultInfo>{
        //初始化計數器:就是給你要計算指標賦予初始值
        @Override
        public ResultInfo createAccumulator() {
            ResultInfo resultInfo = new ResultInfo();
            return resultInfo;
        }
        //指標計算:指標增長的邏輯 比如pv userID一樣加一
        @Override
        public ResultInfo add(UserInfo userInfo, ResultInfo resultInfo) {
            if(userInfo.getUserId().equals(resultInfo.getUserId())){
                resultInfo.setOrderId( resultInfo.count+=1);
            }else {
                resultInfo.setUserId(userInfo.getUserId());
                resultInfo.setOrderId(1);
            }
            return resultInfo;
        }
 
        @Override
        public ResultInfo getResult(ResultInfo resultInfo) {
            return resultInfo;
        }
        //指標結果合併
        @Override
        public ResultInfo merge(ResultInfo resultInfo1, ResultInfo resultInfo2) {
            resultInfo2.setOrderId(resultInfo2.getOrderId()+resultInfo1.getOrderId());
            return resultInfo2;
        }
 
    }
 
    public static class UserInfo implements Serializable {
        private static final long serialVersionUID = 1L;
        private String userId;
        private String orderId;
        public UserInfo() {
        }
        public UserInfo(String userId, String orderId) {
            this.userId = userId;
            this.orderId = orderId;
        }
        public String getUserId() {
            return userId;
        }
        public void setUserId(String userId) {
            this.userId = userId;
        }
        public String getOrderId() {
            return orderId;
        }
        public void setOrderId(String orderId) {
            this.orderId = orderId;
        }
    }
    public static class ResultInfo implements Serializable {
        private static final long serialVersionUID = 1L;
        private String userId;
        private int count;
        public ResultInfo() {
        }
        public ResultInfo(String userId, int count) {
            this.userId = userId;
            this.count = count;
        }
        public String getUserId() {
            return userId;
        }
        public void setUserId(String userId) {
            this.userId = userId;
        }
        public int getOrderId() {
            return count;
        }
        public void setOrderId(int count) {
            this.count = count;
        }
        @Override
        public String toString() {
            return   "ResultInfo{" +
                    "userId='" + userId + '\'' +
                    ", count=" + count +
                    '}';
        }
    }
}

注意事項:

               1秒內有數據變化的站點並不多,而ContinuousProcessingTimeTrigger每次觸發都會輸出窗口裏全部的聚合數據,對於線上大數據量的情況下,這樣做了很多無用功,並且還會增大Redis的壓力。所以,我們可以在聚合結果後再接一個ProcessFunction,ProcessFuntcion的使用以及其他窗口函數的使用可以參考我的上篇文章https://blog.csdn.net/aA518189/article/details/85250713

結果展示:

           通過結果我們看到10s內(線上是1天)的pv,每秒更新一次。計算uv,實時訂單也是和pv一樣的流程,只是具體邏輯改改而已,還看什麼自己實現一下試試!!!

掃一掃加入大數據公衆號,瞭解更多大數據技術,還有免費資料等你哦

掃一掃加入大數據公衆號,瞭解更多大數據技術,還有免費資料等你哦

掃一掃加入大數據公衆號,瞭解更多大數據技術,還有免費資料等你哦

 
 

發佈了92 篇原創文章 · 獲贊 115 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章