抛出问题
通义千问2.5发布后,我发现准确率有了很大的提升,但是处理任务的时长并没有明显的提升,不知道用过通义千问的小伙伴们发现没有,通义千问响应的内容是持续性输出的,而不是一次全部返回。
发现问题
在监听网络请求之后,我发现一个有意思的请求dialog/conversation
,这个接口在返回200状态码之后持续输出了一分多钟。 请求头
那么这个EventStream是个啥东西呢?
SSE 流式传输
这个时候,我们就不得不说SSE了,SSE 全称为 Server-sent events , 是一种基于 HTTP 协议的通信技术,允许服务端主动推送数据到客户端。客户端一旦和服务端建立连接之后,服务端就有源源不断的像客户端发送数据,无需客户端再次发送新的请求。
主要特点:
- 单向通信:SSE主要用于服务器向客户端推送数据,不支持客户端向服务器发送消息,WebSocket在连接建立时借用了HTTP的机制,但它本身是一个独立的协议,运行在TCP之上,并且在握手之后的操作与HTTP完全不同,提供了低延迟、双向通信的能力,适用于实时数据传输的场景。这与WebSocket的双向通信有所不同。
- 简单易用:相对于WebSocket等复杂协议,SSE在实现上更为简单,易于集成到现- 有的Web应用程序中。
- 兼容性好:作为HTML5标准的一部分,现代浏览器大多支持SSE,无需额外插件。 轻量级:SSE建立在HTTP协议之上,使用简单的文本格式进行数据传输,对网络带宽要求较低。 自动重连:当连接意外中断时,大多数浏览器会自动尝试重新建立与服务器的连接。 事件驱动:数据以事件的形式发送,客户端可以通过JavaScript的EventSource API订阅这些事件并作出响应。
工作原理
- 客户端通过发送一个HTTP GET请求到一个支持SSE的URL端点。
- 服务器响应这个请求,并保持这个连接打开状态,而不是像常规HTTP请求那样完成响应后关闭连接。
- 当服务器有新的数据需要推送时,它通过这个已建立的连接发送一个事件,该事件包含一个类型(event字段)、数据(data字段)以及其他可选字段如id和retry等。
- 客户端接收到事件后,可以通过EventSource对象的事件处理器(如onmessage, onopen, onerror)来处理这些数据。
应用场景
我们说了,SSE和websocket最大的区别是基于http的单向通信,那就说在维护,传输,宽带占用方面有明显的优势,所以SSE成为了很多实时Web功能实现的首选技术之一,尤其是在那些对实时性要求不是极高,且主要由服务器主导信息流动的场景下。
- 数据监控,比如股票价格,日志分析,平台监控等
- 内容推送 比如新闻,赛事播报 实时天气等
实时监控cpu的负载
接下来我们实现一个小应用,实时监控我的mac的cpu负载
- 前端代码jsx
javascript
import React from 'react';
class App extends React.Component {
//创建sse客户端
eventSource = null;
state = {
buttonText: "暂停", status: true,//是否继续接收数据
data: []
}
componentDidMount() {
this.handleData(true)
}
handleData = (status) => {
console.log(status);
const buttonText = status ? "暂停" : "继续"
this.setState({
status: status, buttonText: buttonText
})
if (status === false) {
this.eventSource.close()
}
if (status === true) {
this.eventSource = new EventSource('/api/sse/data')
this.eventSource.onmessage = (event) => {
const sseData = JSON.parse(event.data)
//设置时间
sseData.time = new Date().toLocaleString()
const historyData = this.state.data
let newData = [sseData]
if (historyData.length > 0) {
newData = [...historyData, sseData]
}
this.setState({
data: newData
})
};
}
}
render() {
const {data, status, buttonText} = this.state
return (<div>
<button onClick={() => this.handleData(!status)}>{buttonText}</button>
<table>
<thead>
<tr>
<th>时间</th>
<th>一分钟内cpu负载</th>
<th>五分钟内cpu负载</th>
<th>十五分钟内cpu负载</th>
</tr>
</thead>
<tbody>
{data ? data.map((item, index) =>
<tr key={index}>
<th>{item.time}</th>
<th>{item.load1}</th>
<th>{item.load5}</th>
<th>{item.load15}</th>
</tr>
) : ""}
</tbody>
</table>
</div>)
}
}
export default App;
- 用go起了一个后端服务,代码如下
javascript
func SseDataHandler(c *gin.Context) {
// 设置头部信息,表明这是一个事件流
c.Writer.Header().Set("Content-Type", "text/event-stream")
c.Writer.Header().Set("Cache-Control", "no-cache")
c.Writer.Header().Set("Connection", "keep-alive")
// 创建一个通道,并不断发送时间信息
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for range ticker.C {
//获取cpu负载信息
stat, err := load.Avg()
// 打印或进一步处理输出
data := fmt.Sprintf("data: %s\n\n", stat)
// 写数据到客户端
_, err = c.Writer.WriteString(data)
c.Writer.Flush() // 刷新数据,确保发送到客户端
if err != nil {
return // 如果写入有错误,结束循环
}
select {
case <-c.Request.Context().Done(): // 检查客户端是否还连接着
return
default:
continue
}
}
}
- 下面是页面输出情况