springboot websocket 持续打印 pod 日志

springboot 整合 websocket 和 连接 k8s 集群的方式参考历史 Java 专栏文章

  1. 修改前端页面
hetml 复制代码
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Java后端WebSocket的Tomcat实现</title>
    <script type="text/javascript"></script>
</head>

<body>
<div></div>
Welcome<br/>
<div id="message"></div>
</body>
<script type="text/javascript">
    var websocket = null;
    //判断当前浏览器是否支持WebSocket
    if('WebSocket' in window) {
        //改成你的地址
        // websocket = new WebSocket("ws://localhost:8080/api/websocket/100");
        websocket = new WebSocket("ws://localhost:8080/api/websocket/pod/mm-httpbin-b549bdf48-l2fjp/container/mm-httpbin");
    } else {
        alert('当前浏览器 Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function() {
        setMessageInnerHTML("WebSocket连接发生错误");
    };

    //连接成功建立的回调方法
    websocket.onopen = function() {
        setMessageInnerHTML("WebSocket连接成功");
    }
    var U01data, Uidata, Usdata
    //接收到消息的回调方法
    websocket.onmessage = function(event) {
        console.log(event.data);
        setMessageInnerHTML(event.data);
    }

    //连接关闭的回调方法
    websocket.onclose = function() {
        setMessageInnerHTML("WebSocket连接关闭");
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function() {
        closeWebSocket();
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send('{"msg":"' + message + '"}');
        setMessageInnerHTML(message + "&#13;");
    }
</script>

</html>
  1. 修改 websocket 类

命名空间测试写死了,需要可以调整

java 复制代码
package com.vazquez.k8sclient.websocket;

import com.vazquez.k8sclient.util.K8sClient;
import io.kubernetes.client.PodLogs;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.Configuration;
import io.kubernetes.client.util.Config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.*;

/**
 * @Description
 * @Author vazquez
 * @DATE 2024/4/10 8:50
 */

@Slf4j
@Service
@ServerEndpoint("/api/websocket/pod/{podName}/container/{containerName}")
public class PodLogWebsocket {
    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<PodLogWebsocket> podLogWebsocket = new CopyOnWriteArraySet<PodLogWebsocket>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    private static final ExecutorService executorService = Executors.newCachedThreadPool();

    private static final int RECENT_LOG_LINE = 1000;

    private boolean flag = true;

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("podName") String podName, @PathParam("containerName") String containerName) {
        this.session = session;
        podLogWebsocket.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        getPodLog(podName, containerName);
        try {
            sendMessage("conn_success");
            log.info("当前在线人数为:" + getOnlineCount());
        } catch (IOException e) {
            log.error("websocket IO Exception");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        podLogWebsocket.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        //断开连接情况下,更新主板占用情况为释放
        //这里写你 释放的时候,要处理的业务
        log.info("有一连接关闭!当前在线人数为" + getOnlineCount());

    }

    /**
     * 收到客户端消息后调用的方法
     * @ Param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        //群发消息
        for (PodLogWebsocket item : podLogWebsocket) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @ Param session
     * @ Param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        PodLogWebsocket.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        PodLogWebsocket.onlineCount--;
    }

    public static CopyOnWriteArraySet<PodLogWebsocket> getWebSocketSet() {
        return podLogWebsocket;
    }

    private void getPodLog(String podName, String containerName) {
        executorService.execute(() -> {
            BufferedReader reader = null;
            InputStream is = null;
            try {
                //使用单例创建ApiClient
                String token = "ey...cJ29w";
                String url = "https://10.xxx6:6443";
                ApiClient client = Config.fromToken(url, token, false);
                Configuration.setDefaultApiClient(client);

                PodLogs logs = new PodLogs(client);
                //按时间过滤stream流会有堵塞延迟
                is = logs.streamNamespacedPodLog("default", podName, containerName, null, RECENT_LOG_LINE, false);
                reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));

                String line;
                //若ws连接已经断开,则不再读取数据输出到前端
                while (this.flag && this.session != null && this.session.isOpen()) {
                    while ((line = readLineWithTimeout(podName, reader)) != null) {
                        if (this.flag) {
                            System.out.println(line);
                            sendMessage(line);
                        } else {
                            break;
                        }
                    }
                }
            } catch (Exception e) {
                log.error("get container log Exception", e);
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (IOException e) {
                    log.error("reader IOException:{}", e.getMessage());
                }
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    log.error("InputStreamReader IOException:{}", e.getMessage());
                }
            }
        });
    }

    /**
     * 读取一行bufferedReader数据并返回 堵塞超时返回null
     *
     * @param bufferedReader
     * @return
     */
    private static String readLineWithTimeout(String podName, BufferedReader bufferedReader) {
        Future<String> future;
        try {
            future = executorService.submit(() -> {
                try {
                    return bufferedReader.readLine();
                } catch (IOException e) {
                    return null;
                }
            });

            String message = future.get(10, TimeUnit.SECONDS);
            //当pod重启或删除时 由于连接会断开 future会直接返回数据为null 此时会不断请求 业务暂时添加10s休眠
            if (message == null) {
                Thread.sleep(10 * 1000);
            }
            return message;
        } catch (TimeoutException e) {
            // Timeout occurred
            log.error(podName + " readLineWithTimeout " + Thread.currentThread().getId(), e);
            return null;
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            return null;
        }
    }
}
  1. 效果展示
相关推荐
AH_HH4 分钟前
Spring Boot 3 集成 Spring Security(1)认证
spring boot·spring security
paterWang5 分钟前
小程序-基于java+SpringBoot+Vue的美食推荐系统设计与实现
java·spring boot·小程序
《源码好优多》5 分钟前
基于Java Springboot餐饮美食分享平台
java·spring boot·美食
说书客啊9 分钟前
计算机毕业设计 | SpringBoot+vue美食推荐商城 食品零食购物平台(附源码+论文)
java·spring boot·node.js·vue·毕业设计·课程设计·美食
小学鸡!18 分钟前
Bean的生命周期详解保姆级教程,结合spring boot和spring.xml两种方式讲解,5/7/10大小阶段详细分析
xml·spring boot·spring
Smilejudy26 分钟前
三行五行的 SQL 只存在于教科书和培训班
后端·github
爱上语文30 分钟前
Http 请求协议
网络·后端·网络协议·http
贝克街的天才41 分钟前
据说在代码里拼接查询条件不够优雅?Magic-1.0.2 发布
java·后端·开源
monkey_meng1 小时前
【Rust Iterator 之 fold,map,filter,for_each】
开发语言·后端·rust
运维&陈同学1 小时前
【kafka01】消息队列与微服务之Kafka详解
运维·分布式·后端·微服务·云原生·容器·架构·kafka