Java版本的SSE服务端实现样例

简单记录一下使用netty方式实现SSE的服务端功能

目录

  • 简要说明
  • 基于Netty
  • 功能需求
  • 后端代码
    • 1. 创建一个SpringBoot 应用
    • 2. 创建服务端功能
    • 3. 创建前端功能
    • 4. 测试SSE
  • 封装为组件

简要说明

Server-Sent Events (SSE) 是一种用于在客户端和服务器之间建立单向通信的技术。
它允许服务器主动向客户端推送实时更新,而不需要客户端不断地请求数据。
Server-Sent Events (SSE) 的流行可以追溯到 HTML5 的引入,

最大特点:

  • 前端JS原生支持
  • 只接受服务端数据,单向通讯
  • 原生支持断开重连

他和我们现在经常接触的 websocket,mqtt,类rabbitmq 有说明区别,
同样是客户端服务端的数据访问,同样用于取代客户端轮询访问方式,他们有审美不一样或者说使用场景是什么,下面表格简要说明一下:

技术SSE (Server-Sent Events)WebSocketMQTT类RabbitMQ
类型单向通信双向通信发布/订阅模式消息队列
协议基于 HTTP独立于 HTTP轻量级消息传递协议支持多种协议(如 AMQP)
使用场景实时更新(如新闻/股票信息推送)实时双向通信(如聊天)物联网设备通信,例如硬件设备主动上报给服务端信号信息可靠消息传递和任务队列,用于服务端系统之间通讯
优点- 简单易用- 低延迟- 轻量级- 强大的消息路由功能
- 自动重连- 支持双向通信- 支持 QoS 级别- 提供持久化消息存储
- 支持文本数据推送- 支持二进制数据传输- 发布/订阅解耦- 支持多种消息模式
缺点- 仅支持单向通信- 实现相对复杂- 需要 MQTT 代理- 设置和管理相对复杂
- 不支持二进制数据- 需要额外的安全措施- 对简单实时应用复杂- 可能需要更多资源

基于Netty

关于java版本的SSE服务端实现,网上大多举例不正确或者说并没有实现SSE的技术特性 (例如网上举例说 创建一个 servlet,你会发现基本上是http轮询,因为一次service请求后,IO通讯就断开了,前端只会不断重连请求)。

Netty 强大健壮的异步IO通讯框架。

功能需求

  • 在SpringBoot项目中创建SSE服务端功能
  • 基于Netty框架
  • 前端样例可自由断开或连接
  • 支持携带Get请求的参数
  • 前端样例支持断开重连,连接状态展示

后端代码

1. 创建一个SpringBoot 应用

这里测试的SpringBoot 版本是 2.6.14
使用的JDK版本是 17

2. 创建服务端功能

引入netty Maven依赖

        <!-- https://mvnrepository.com/artifact/io.netty/netty-all --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.113.Final</version></dependency>

创建 SSE 服务端的EventLoopGroup,假设绑定端口 8849


import com.middol.yfagv.model.oms.properties.SseProperties;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;/*** SSE服务 server sent events** @author admin*/
@Slf4j
@Service
public class SseServer {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();private boolean started = false;@PostConstructpublic void init() {log.debug("SSE服务初始化完毕");}public void shutdown() {log.debug("SSE服务 Shutting down server...");// 优雅关闭 workerGroupif (!workerGroup.isShutdown()) {workerGroup.shutdownGracefully(5, 10, TimeUnit.SECONDS);}// 优雅关闭 bossGroupif (!bossGroup.isShutdown()) {bossGroup.shutdownGracefully(5, 10, TimeUnit.SECONDS);}log.debug("SSE服务 Server shut down gracefully.");}@Asyncpublic void start() throws Exception {if (started) {return;}try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new HttpServerCodec());ch.pipeline().addLast(new HttpObjectAggregator( 1024 * 1024));ch.pipeline().addLast(new ChunkedWriteHandler());ch.pipeline().addLast(new SseHandler());}}).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);// 绑定端口并同步ChannelFuture f = b.bind(8849).sync();log.debug("SSE服务启动完成,绑定端口:{}", 8849);started = true;// 添加关闭钩子Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));// 等待服务器通道关闭f.channel().closeFuture().sync();} finally {shutdown();}}
}

创建处理前端请求的 ChannelHandler,这里我们假设只处理url 是 /events的前端请求。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;/*** SSE处理器** @author admin*/
@Slf4j
@ChannelHandler.Sharable
public class SseHandler extends SimpleChannelInboundHandler<FullHttpRequest> {private static final String PREFIX = "events";// 定义一个 AttributeKey 用于存储 ScheduledFutureprivate static final AttributeKey<ScheduledFuture<?>> SCHEDULED_FUTURE_KEY = AttributeKey.valueOf("scheduledFuture");@Overridepublic void channelActive(io.netty.channel.ChannelHandlerContext ctx) throws Exception {// 获取远程地址String remoteAddress = ctx.channel().remoteAddress().toString();log.debug(">>>>>>>>>>>>>>>>>>>>>>>>SseHandler: channelActive, remoteAddress={}", remoteAddress);super.channelActive(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {// 获取远程地址String remoteAddress = ctx.channel().remoteAddress().toString();log.debug(">>>>>>>>>>>>>>>>>>>>>>>>SseHandler: channelInactive, remoteAddress={}", remoteAddress);// 从 ChannelHandlerContext 中获取定时任务并取消ScheduledFuture<?> scheduledFuture = ctx.channel().attr(SCHEDULED_FUTURE_KEY).get();if (scheduledFuture != null) {// 显式取消定时任务scheduledFuture.cancel(false);}super.channelInactive(ctx);}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {if (request.method() == HttpMethod.OPTIONS) {// 处理预检请求FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "GET, OPTIONS");response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type");ctx.writeAndFlush(response);return;}if (HttpUtil.is100ContinueExpected(request)) {send100Continue(ctx);}// 检查请求的 URI 是否以指定的前缀开始String uri = request.uri();// 解析 GET 参数QueryStringDecoder queryStringDecoder = new QueryStringDecoder(uri);Map<String, List<String>> parameters = queryStringDecoder.parameters();if (parameters != null && !parameters.isEmpty()) {log.debug(">>>>>>>>>>>>>>>>>>>>>>>>SseHandler: parameters={}", parameters);}if (!uri.startsWith("/" + PREFIX)) {ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND));return;}// 设置 CORS 头HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/event-stream");response.headers().set(HttpHeaderNames.CACHE_CONTROL, "no-cache");response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);// CORS 头response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); // 允许所有域response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS, "GET, OPTIONS"); // 允许的请求方法response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type"); // 允许的请求头ctx.write(response);// 发送初始 SSE 事件sendSseEvent(ctx, "Connected to SSE server");// 定期发送 SSE 事件long initialDelay = 0L;long period = 5L;ctx.executor().scheduleAtFixedRate(() -> sendSseEvent(ctx, "CurrentTimeMillis: " + System.currentTimeMillis()),initialDelay, period, java.util.concurrent.TimeUnit.SECONDS);// 将定时任务的引用存储在 ChannelHandlerContext 的属性中ctx.channel().attr(SCHEDULED_FUTURE_KEY).set(scheduledFuture);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// 获取远程地址String remoteAddress = ctx.channel().remoteAddress().toString();log.error(">>>>>>>>>>>>>>>>>>>>>>>>SseHandler: exceptionCaught, remoteAddress={}", remoteAddress, cause);// 关闭连接,自动释放相关资源ctx.close();}protected static void send100Continue(ChannelHandlerContext ctx) {FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);ctx.write(response);}protected void sendSseEvent(ChannelHandlerContext ctx, String data) {ByteBuf buffer = ctx.alloc().buffer();buffer.writeBytes(("data: " + data + "\n\n").getBytes(StandardCharsets.UTF_8));ctx.writeAndFlush(new DefaultHttpContent(buffer));}}

以上核心业务方法是 channelRead0, 里面设置了可以跨越,
为什么要跨越?原因是netty服务端绑定的端口和本身 SpringBoot的应用端口不是一样,前端页面可能即要请求SpringBoot的业务接口也需要SSE服务接口。

以上是简单模拟向前端页面推送时间戳信息,每隔5秒一次,如果我要推送Bean方法里面的业务数据该如何做?

最简单方法是修改sendSseEvent里面的业务逻辑,使用SpringUtil获得Bean

    protected void sendSseEvent(ChannelHandlerContext ctx, String data) {ByteBuf buffer = ctx.alloc().buffer();buffer.writeBytes(("data: " + JSONObject.toJSONString(SpringUtil.getBean(YourService.class).querySome() +  "\n\n")).getBytes(StandardCharsets.UTF_8));ctx.writeAndFlush(new DefaultHttpContent(buffer));}

最后写一个手动启动Netty服务的Controller方法,这个主要用于测试,可以设置SpringBoot启动时自动启动Netty服务。

    @Lazy@Resourceprivate SseServer sseServer;@ApiOperation(value = "启动SSE服务")@PostMapping("startSseServer")public ResponseVO<String> startSseServer() {try {sseServer.start();} catch (Exception e) {return ResponseVO.fail(e.getMessage(), DateUtil.now());}return ResponseVO.success(ResponseVO.SUCCESS_MSG, DateUtil.now());}

3. 创建前端功能

前端代码,我直接ChatGPT帮我生成一个用于测试SSE功能的页面:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>SSE Example</title><script>let eventSource; // 声明 eventSource 变量let isConnected = false; // 连接状态标志function toggleEventSource() {const button = document.getElementById('toggleButton');const inputUrl = document.getElementById('urlInput').value.trim(); // 获取输入框中的 URLif (!inputUrl) {alert('请输入有效的 URL');return;}if (isConnected) {eventSource.close(); // 关闭连接button.innerText = "开启 EventSource"; // 更新按钮文本button.classList.remove('close'); // 移除关闭状态的样式button.classList.add('open'); // 添加开启状态的样式document.getElementById('status').innerText = "Disconnected from SSE server.";document.getElementById('status').style.color = "red";isConnected = false; // 更新连接状态} else {eventSource = new EventSource(inputUrl); // 使用输入框中的 URL 创建新的 EventSource 实例eventSource.onopen = function() {console.log("Connection to server opened.");const status = document.getElementById('status');status.innerText = "Connected to SSE server.";status.style.color = "green";status.style.fontWeight = "bold";button.innerText = "关闭 EventSource"; // 更新按钮文本button.classList.remove('open'); // 移除开启状态的样式button.classList.add('close'); // 添加关闭状态的样式isConnected = true; // 更新连接状态};eventSource.onmessage = function(event) {console.log("Received message: " + event.data);const messagesDiv = document.getElementById('messages');messagesDiv.innerHTML += `<p>${event.data}</p>`;// 检查行数并在超过100行时清空内容const lines = messagesDiv.getElementsByTagName('p').length;if (lines > 100) {messagesDiv.innerHTML = ''; // 清空内容console.log("Messages cleared after exceeding 100 lines.");}};eventSource.onerror = function() {console.error("EventSource failed.");const status = document.getElementById('status');status.innerText = "Exception: Connection to SSE server lost.";status.style.color = "red";status.style.fontWeight = "bold";button.innerText = "开启 EventSource"; // 更新按钮文本button.classList.remove('close'); // 移除关闭状态的样式button.classList.add('open'); // 添加开启状态的样式isConnected = false; // 更新连接状态};}}</script><style>body {font-family: Arial, sans-serif;margin: 20px;padding: 20px;background-color: #f4f4f4;border-radius: 8px;}#toggleButton {padding: 10px 20px; /* 增加内边距 */font-size: 16px; /* 增大字体 */color: white; /* 字体颜色 */border: none; /* 去掉边框 */border-radius: 5px; /* 圆角 */cursor: pointer; /* 鼠标悬停时显示手型 */transition: background-color 0.3s; /* 背景颜色过渡效果 */}#toggleButton.open {background-color: #007bff; /* 开启状态按钮背景颜色 */}#toggleButton.open:hover {background-color: #0056b3; /* 开启状态悬停时的背景颜色 */}#toggleButton.open:active {background-color: #004080; /* 开启状态点击时的背景颜色 */}#toggleButton.close {background-color: #f17b87; /* 关闭状态按钮背景颜色 */}#toggleButton.close:hover {background-color: #d9534f; /* 关闭状态悬停时的背景颜色 */}#toggleButton.close:active {background-color: #c9302c; /* 关闭状态点击时的背景颜色 */}#urlInput {width: 400px; /* 输入框宽度 */padding: 8px; /* 增加内边距 */font-size: 16px; /* 增大字体 */margin-right: 10px; /* 添加与按钮的间距 */}#messages {margin-top: 20px;padding: 10px;background-color: #fff;border: 1px solid #ccc;border-radius: 4px;max-height: 300px;overflow-y: auto;}p {margin: 5px 0;font-size: 18px; /* 增大内容字体大小 */}#status {font-size: 20px; /* 增大状态字体大小 */}</style>
</head>
<body>
<h1>SSE Example</h1>
<label for="urlInput"></label><input type="text" id="urlInput" placeholder="Enter SSE URL" value="http://localhost:8849/events"/> <!-- URL 输入框 -->
<button id="toggleButton" class="open" onclick="toggleEventSource()">开启 EventSource</button>
<br/><br/><br/>
<div id="status">请点击 [开启 EventSource] 按钮开启 EventSource。</div>
<br/>
<div id="messages"></div>
</body>
</html>

4. 测试SSE

启动SpringBoot服务,启动Netty服务

谷歌浏览器输入 http://localhost:8088/test1.html
这里的8088是SpringBoot应用端口,test1.html 是以上创建的页面,放在SpringBoot 的静态资源文件目录下的页面文件。
在这里插入图片描述
点击 【开启 EventSource】
在这里插入图片描述
点击 关闭 按钮
在这里插入图片描述
再次开启,然后关闭后台服务,然后再次开启后台服务测试 SSE自动重连
在这里插入图片描述

在这里插入图片描述

封装为组件

依据Netty的高性能实现的SSE服务端功能,基本上实现了SSE的所有技术特点,在理想情况下,一台 普通的 32GB 内存的服务器可以支持数几十万个前端连接,基本满足中小企业业务需求量。

使用SSE其实针对中小企业来说最大的优点其实是部署的便捷性,无需引入其他消息队列中间件等服务。

以上是测试样例,以下是封装成 Spring-boot-starter 组件,代码仓库如下:

https://github.com/dwhgygzt/sse-spring-boot-starter

https://gitee.com/banana6/sse-spring-boot-starter

欢迎下载测试交流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/881346.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

通信工程学习:什么是RFID射频识别

RFID&#xff1a;射频识别 RFID射频识别&#xff08;Radio Frequency Identification&#xff09;&#xff0c;又称为无线射频识别&#xff0c;是一种非接触式的自动识别技术。它通过无线电信号来识别特定目标并读写相关数据&#xff0c;而无需在识别系统与特定目标之间建立机械…

任务【浦语提示词工程实践】

0.1 环境配置 首先点击左上角图标&#xff0c;打开Terminal&#xff0c;运行如下脚本创建虚拟环境&#xff1a; # 创建虚拟环境 conda create -n langgpt python3.10 -y 运行下面的命令&#xff0c;激活虚拟环境&#xff1a; conda activate langgpt 之后的操作都要在这个环境…

【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。使用的软件&#…

【华为OD机试真题】95、最少面试官数

package mainimport ("fmt""sort" )type s struct {start intend intworkCount int }type duration struct {start intend int }// 查询时间段内是否有可用的面试官 func getFreeS(sList []*s, d *duration, workCountLimit int) (sIndex int)…

案例:问题处理与原因分析报告的模板

系统上线后暴露的问题也是一种财富&#xff0c;我们需要从中吸收经验教训&#xff0c;规避其他类似的问题。对于上线后的问题如何进行原因分析&#xff0c;我提供两个分析报告的模板&#xff0c;供大家参考。 模板案例1&#xff1a;共性现象的原因分析报告 模板案例二&#xf…

Java后端面试很水的,7天就能搞定!

随着Java的越来越卷&#xff0c;面试也直接上难度了&#xff0c;从以前的八股文到场景题了&#xff0c;尤其是有经验的去面试&#xff0c;场景题都是会问的&#xff0c;近期面试过的应该都深有体会&#xff01; 场景题230道&#xff1a; 1.分布式锁加锁失败后的等待逻辑是如何…

人脸表情行为识别系统源码分享

人脸表情行为识别系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

Webstorm 中对 Node.js 后端项目进行断点调试

首先&#xff0c;肯定需要有一个启动服务器的命令脚本。 然后&#xff0c;写一个 debug 的配置&#xff1a; 然后&#xff0c;debug 模式 启动项目和 启动调试服务&#xff1a; 最后&#xff0c;发送请求&#xff0c;即可调试&#xff1a; 这几个关键按钮含义&#xff1a; 重启…

基于单片机的智能浇花系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采样DHT11温湿度传感器检测温湿度&#xff0c;通过LCD1602显示 4*4按键矩阵可以设置温度湿度阈值&#xff0c;温度大于阈值则开启水泵&#xff0c;湿度大于阈值则开启风扇…

关于PPT生成的开源大模型总结

目前需要开源的PPT生成模型&#xff0c;在这里对github上的一些模型进行筛选 搜索关键词&#xff1a;ppt generate&#xff08;more starts&#xff09; williamfzc/chat-gpt-ppt: 支持直接生成PPT支持中英文需要调用ChatGPT&#xff08;Add your token (official openai api k…

【Matlab】Matlab 导入数据.csv或者.xlsx文件,然后使用这些数据来绘制图表

Matlab 导入数据.csv或者.xlsx文件&#xff0c;然后使用这些数据来绘制图表 初始数据 filename C:\Users\jia\Desktop\yadian\data\1Hz 2024_09_12 17_10_06.csv; 代码&#xff1a; clc;clear close all; % 读取Excel文件 filename C:\Users\jia\Desktop\yadian\data\1Hz …

智能手表(Smart Watch)项目

文章目录 前言一、智能手表&#xff08;Smart Watch&#xff09;简介二、系统组成三、软件框架四、IAP_F411 App4.1 MDK工程结构4.2 设计思路 五、Smart Watch App5.1 MDK工程结构5.2 片上外设5.3 板载驱动BSP5.4 硬件访问机制-HWDataAccess5.4.1 LVGL仿真和MDK工程的互相移植5…

注意,学会解决路由问题!(未完)

文章目录 Abstract1 Introduction2 相关工作3 注意力模型3.1 编码器3.2 解码器Abstract 最近提出的为组合优化问题学习启发式方法的想法很有前景,因为它可以节省昂贵的开发成本。然而,要将这一想法推向实际应用,我们需要更好的模型和更好的训练方法。我们在这两个方向都做出…

Java_Se 容器2(Set 接口)

Set接口继承自Collection接口&#xff0c;Set接口中没有新增方法&#xff0c;它和Collection接口保持完全一致。我们在前面学习List接口的使用方式&#xff0c;在Set中仍然适用。因此&#xff0c;学习Set的使用将没有任何难度。Set接口特点Set特点&#xff1a;无序、不可重复。…

国庆作业

day1 1.开发环境 Linux系统GCCFDBmakefilesqlite3 2.功能描述 项目功能: 服务器&#xff1a;处理客户端的请求&#xff0c;并将数据存入数据库中&#xff0c;客户端请求的数据从数据库进行获取&#xff0c;服务器转发给客户端。 用户客户端&#xff1a;实现账号的注册、登…

C++:string (用法篇)

文章目录 前言一、string 是什么&#xff1f;二、C语法补充1. auto2. 范围for 三、string类对象的常见构造1. Construct string object2. String destructor3. operator 四、string迭代器相关1. begin与end1&#xff09;begin2&#xff09;end3&#xff09;使用 2. rbegin 与 r…

计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

数据处理方式,线程与进程,多任务,Spark与MR的区别

目录 数据处理的方式有哪些 单机数据处理 集群数据处理 分布式计算框架 MapReduce ApplicationMaster Spark分布式计算类别 进程与线程的区别 进程是计算时分配资源的最小单位 线程是执行计算任务的最小任务 多进程的执行效率没有多线程的执行效率高 多任务 Spark和M…

厂商资源分享网站

新华三&#xff08;H3C&#xff09;是一家中国知名的网络设备供应商&#xff0c;提供网络设备、网络解决方案和云计算服务。公司成立于2003年&#xff0c;是华为公司和惠普公司合资的企业&#xff0c;总部位于中国深圳。 华为&#xff08;Huawei&#xff09;是一家全球知名的电…

一个技巧实现在SharePoint中使用Copilot

前几天写了在onedrive中使用copilot对单个文件进行提问汇总分析与对多个文件进行比较汇总提问等&#xff1a; Copilot重磅更新&#xff01;OneDrive全新功能炸裂 很多小伙伴表示特别受用。 于是他们在纷纷尝试了一段时间后&#xff0c;开始把目光转向SharePoint和teams文件&a…