大数据——WebSocket埋点实现离线+实时数据处理

一、WebSocket概述

        WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

        WebSocket使得客户端和服务器之间的数据缓缓变得更加简单,允许服务端主动向客户端推送数据。

        在WebSocketAPI中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

        浏览器通过JavaScript向服务器发出建立WebSocket连接的请求,连接建立以后,客户端和服务端就可以改通过TCP连接直接交换数据。

        相对于HTTP这种非持久性的协议来说,WebSocket是一个持久化的协议。

二、WebSocket 工程的创建

选择Spring Initializr工程,点击下一步

 填写工程的组名,并选择所使用的java版本号

 选择Spring Web 和WebSocket工具

 

 

 填写工程名及所在路径

 

 这时,pom.xml文件中自动打入了如下两个依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

三、WebSocket配置

WebSocket配置可以使用@Configuration注解来代替xml文件配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

//@Configuration注解等同于xml文件
@Configuration
public class WebSocketConfig {
    //这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
    //endpoint 端口的意思
    @Bean
    public ServerEndpointExporter getServerEndPointExporter(){

        return new ServerEndpointExporter();
    }
}

四、核心代码

import cn.bigdata.shop12.websoc.websocserver.config.WebSocketConfig;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.*;

//@ConditionalOnClass表示当有条件中的类时,自动创建组件,配置类里面内容才会生效
@ConditionalOnClass(value= WebSocketConfig.class)
//@Component 通用,当不明确属于哪一层时使用
//@Controller 控制层注解
//@Service 服务层注解
//@Repository 数据访问层注解
@Component
//当满足条件时,创建WebSocket端点
@ServerEndpoint(value="/shop12_ws/user_behavior_event")
public class ShopWebSocket {
    //类型属性: 所有ShopWebSocket对象共享
    private static ConcurrentMap<String,ShopWebSocket> clients = null;
    //类型属性: 负责向kafka中按条写入日志
    private static KafkaProducer<Integer,String> kafka = null;
    //输入kafka主题
    private static final String INPUT_TOPIC = "input_user_behavior_event_topic";
    //控制kafka消息写入的分区号
    private static int partition = 0;
    //kafka最大分区数
    private static final int MAX_PARTITION = 3;
    //失败次数
    private static final int MAX_RETRY_TIMES = 3;

    //HDFS
    private static SimpleDateFormat dateFormat = null;
    private static final String LOCAL_DIR = "C:\\Users\\Administrator\\Desktop\\local_dir";
    private static final int LOCAL_BUFFER = 1024;   //1024*1024
    private static final int FLUSH_BUFFER = LOCAL_BUFFER;
    private static PrintWriter localWriter = null;
    private static FileSystem hdfsWriter = null;

    //定时线程调度执行hdfs写入
    private static ScheduledExecutorService scheduler = null;
    private static File localFile = null;
    private static final String HDFS_ROOT = "hdfs://192.168.131.200:9820";
    private static final String HDFS_DIR = "/shop12/user_act_log";

    //静态代码块初始化: 自动调用
    static{
        clients = new ConcurrentHashMap<>();
        Properties kafkaConfig = new Properties();

        kafkaConfig.setProperty("bootstrap.servers","192.168.131.200:9092");
        kafkaConfig.setProperty("key.serializer","org.apache.kafka.common.serialization.IntegerSerializer");
        kafkaConfig.setProperty("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
        kafkaConfig.setProperty("retries","3");
        kafkaConfig.setProperty("acks","-1");
        kafkaConfig.setProperty("batch.size","8192");
        kafkaConfig.setProperty("linger.ms","30000");
        kafka = new KafkaProducer<Integer, String>(kafkaConfig);

        dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");

        scheduler = Executors.newScheduledThreadPool(2);
        Configuration hdfsConfig = new Configuration();
        hdfsConfig.set("fs.defaultFS",HDFS_ROOT);
        initHadoopFileSystem(hdfsConfig,0);

        scheduler.scheduleAtFixedRate(()->{
            writeToHdfs();
        },1,1, TimeUnit.MINUTES);
    }

    private static void writeToHdfs(){
        //将local_DIR目录下非空,不以.COMPLETED结尾的文件过滤出来
        File[] files = new File(LOCAL_DIR).listFiles(
                file->file.isFile() &&
                        !file.getName().endsWith(".COMPLETED") &&
                        file.length()>FLUSH_BUFFER);
        if (files.length>0){
            for (File file : files) {
               String[] ns = file.getName().split("_");
               String dir = ns[0];
               String name = ns[1];
                try {
                    hdfsWriter.copyFromLocalFile(
                            new Path(file.getAbsolutePath()),
                            new Path(HDFS_DIR+"/"+dir+"/"+name));
                    file.renameTo(new File(file.getAbsolutePath()+".COMPLETED"));
                    System.out.println("succeed to copy "+file.getName()+" to hdfs");
                } catch (IOException e) {
                    System.err.println("fail to copy "+file.getName()+" to hdfs : "+e.getMessage());
                }

            }
        }

    }
    private static void initHadoopFileSystem(Configuration hdfsConfig,int retryCount){
        try {
            hdfsWriter = FileSystem.get(hdfsConfig);
            return;
        } catch (IOException e){
            if(++retryCount<MAX_RETRY_TIMES){
                //递归
                initHadoopFileSystem(hdfsConfig,retryCount);
            }else{
                //没有启动hadoop服务,安全模式
                System.err.println("fail to initialize hadoop file system for 3 times");
                System.exit(-1);
            }
        }
    }

    private static boolean needInitLocalWriter(){
        if (null==localWriter){
            return true;
        }
        localWriter.flush();
        if (localFile.length()>=FLUSH_BUFFER){
            return true;
        }
        return false;
    }


    //static synchronized 的锁对象为shopWebSocket.class
    private static synchronized boolean initLocalWriter(){
        if (needInitLocalWriter()){
            if(null!=localWriter){
                localWriter.close();
            }
            for (int i = 0;i<MAX_RETRY_TIMES;){
                try{
                    localFile = new File(LOCAL_DIR+"/"+dateFormat.format(new Date()));
                    if (localFile.exists() && localFile.length()>=FLUSH_BUFFER){
                        Thread.sleep(1000);
                        continue;
                    }
                    localWriter = new PrintWriter(
                            new BufferedWriter(
                                    new FileWriter(localFile,true),LOCAL_BUFFER));
                    return true;
                }catch (Exception e){
                    i++;
                    continue;
                }
            }
            System.err.println("fail to initialize local file writer for 3 times");
            return false;
        }
        return true;
    }

    private static void localWriter(String log){
        boolean needInit = needInitLocalWriter();
        if(needInit ? initLocalWriter() : true){
            localWriter.println(log);
        }
    }

    private static void kafkaWrite(String log){
        kafka.send(new ProducerRecord(INPUT_TOPIC,partition++%MAX_PARTITION,partition,log));
    }

    //对象级属性: 构造方法或setter初始化
    private Session session = null;

    //客户端首次连接服务器
    @OnOpen
    public void Open(Session session){
        this.session = session;
        //每个会话都有唯一的十六进制id
        clients.put(session.getId(),this);
        System.out.println(session.getId()+" connected");
    }

    //客户端断开连接
    @OnClose
    public void Close(){
        clients.remove(session.getId());
        System.out.println(session.getId()+" disconnected");
    }

    //连接异常
    @OnError
    public void onError(Throwable e){
        //clients.remove(session.getId());
        System.out.println(session.getId()+" disconnected");
        e.printStackTrace();
    }

    //收到消息
    @OnMessage
    public void onMessage(String msg){
        //kafka写入: 实时
        kafkaWrite(msg);
        //HDFS写入: 离线
        localWriter(msg);

    }
}

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
实现实时聊天功能,可以使用WebSocket技术。WebSocket是一种在单个TCP连接上进行全双工通信的协议。在客户端和服务器之间建立WebSocket连接后,双方可以通过该连接进行实时通信。 在uniapp+vue中实现WebSocket通信,可以使用uni-app提供的uni-ws组件。uni-ws是用于在uni-app中进行WebSocket通信的组件。使用uni-ws组件,可以轻松地在uni-app中实现实时聊天功能。 以下是实现WebSocket实时聊天功能的步骤: 1. 在vue组件中引入uni-ws组件,并在data中定义WebSocket连接对象: ``` import uniWS from '@/components/uni-ws/uni-ws.vue' export default { components: { uniWS }, data() { return { ws: null } }, } ``` 2. 在模板中使用uni-ws组件,并绑定事件处理函数: ``` <uni-ws url="ws://localhost:8080/ws" @open="onOpen" @message="onMessage" @close="onClose" @error="onError"></uni-ws> ``` 3. 在事件处理函数中处理WebSocket连接的各种事件: ``` methods: { onOpen() { console.log('WebSocket连接已打开') }, onMessage(event) { console.log('接收到消息:', event.data) }, onClose() { console.log('WebSocket连接已关闭') }, onError(event) { console.error('WebSocket连接发生错误', event) } } ``` 4. 使用WebSocket连接对象发送和接收消息: ``` methods: { sendMessage() { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send('Hello, WebSocket!') } } } ``` 在以上代码中,sendMessage()方法用于向WebSocket服务器发送消息。如果WebSocket连接已打开,就可以通过WebSocket连接对象的send()方法向服务器发送消息。 通过以上步骤,就可以在uniapp+vue中使用WebSocket实现实时聊天功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vicky_Tang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值