【Golang】Go 语言实现高可用、用户态感知与多端广播的服务端 SSE 架构

Go 语言实现高可用、用户态感知与多端广播的服务端 SSE 架构

在大模型、实时消息通知等业务场景中,SSE (Server-Sent Events) 是一种极其轻量且高效的单向实时通信技术。相比于 WebSocket,它基于标准的 HTTP 协议,具备天然的断线重连机制,非常适合实现服务端的流式推送。

本文将带领大家从零开始,使用 Go 语言实现一个具备用户态管理(支持一号多端/多窗口)精准定向推送 以及防重机制的工程级 SSE 后端架构。


一、 核心架构设计

在引入"用户态"时,我们需要解决两个核心问题:

  1. 多端/多窗口登录 :同一个 UserID 可能在多个标签页或设备上同时建立长连接。
  2. 连接生命周期管理:客户端断开时必须安全注销,防止内存泄漏和 Goroutine 阻塞。

为此,我们在后端设计了一个线程安全的连接管理器 (SSEManager)

  • 维护两级映射关系:map[string]map[*Client]boolUserID -> 客户端连接集合)。
  • 使用读写锁 (sync.RWMutex) 保证多协程并发操作的安全。
  • 配合 r.Context().Done() 监听客户端断开,及时回收资源。

二、 完整后端源码 (Go)

创建一个 main.go 文件,将以下完整的工程化代码写入其中:

go 复制代码
package main

import (
	"fmt"
	"net/http"
	"sync"
)

// Client 代表单个设备的 SSE 连接实例
type Client struct {
	Message chan string
}

// SSEManager 管理所有用户的连接状态
type SSEManager struct {
	clients map[string]map[*Client]bool
	mutex   sync.RWMutex
}

func NewSSEManager() *SSEManager {
	return &SSEManager{
		clients: make(map[string]map[*Client]bool),
	}
}

// Add 注册新连接(支持多端/多窗口)
func (m *SSEManager) Add(userID string, client *Client) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	if m.clients[userID] == nil {
		m.clients[userID] = make(map[*Client]bool)
	}
	m.clients[userID][client] = true
}

// Remove 注销连接,防止内存泄漏
func (m *SSEManager) Remove(userID string, client *Client) {
	m.mutex.Lock()
	defer m.mutex.Unlock()
	if _, ok := m.clients[userID]; ok {
		delete(m.clients[userID], client)
		close(client.Message)
		if len(m.clients[userID]) == 0 {
			delete(m.clients, userID)
		}
	}
}

// SendToUser 向指定用户的所有终端(多窗口)广播消息
func (m *SSEManager) SendToUser(userID, message string) {
	m.mutex.RLock()
	defer m.mutex.RUnlock()
	if clients, ok := m.clients[userID]; ok {
		for client := range clients {
			select {
			case client.Message <- message:
			default:
				// 非阻塞丢弃,防止慢客户端阻塞整体推送
			}
		}
	}
}

// ServeHTTP 处理 SSE 长连接请求
func (m *SSEManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 允许跨域(仅供本地测试使用)
	w.Header().Set("Access-Control-Allow-Origin", "*")

	userID := r.URL.Query().Get("user_id")
	if userID == "" {
		http.Error(w, "缺少 user_id 参数", http.StatusUnauthorized)
		return
	}

	client := &Client{Message: make(chan string, 10)}
	m.Add(userID, client)
	defer m.Remove(userID, client)

	// 设置 SSE 标准响应头
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "当前环境不支持流式输出", http.StatusInternalServerError)
		return
	}

	for {
		select {
		case <-r.Context().Done():
			// 监听客户端断开
			return
		case msg := <-client.Message:
			// 按 SSE 格式输出,同时携带分布式消息 ID 供前端幂等去重
			fmt.Fprintf(w, "data: {\"msg_id\": \"id_%s\", \"content\": \"%s\"}\n\n", msg, msg)
			flusher.Flush()
		}
	}
}

func main() {
	manager := NewSSEManager()

	// 1. 注册 SSE 长连接路由
	http.Handle("/events", manager)

	// 2. 注册业务触发推送接口
	http.HandleFunc("/send", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")

		uid := r.URL.Query().Get("user_id")
		msg := r.URL.Query().Get("msg")
		if uid == "" || msg == "" {
			http.Error(w, "缺少 user_id 或 msg 参数", http.StatusBadRequest)
			return
		}

		manager.SendToUser(uid, msg)
		fmt.Fprintf(w, "已成功尝试向用户 [%s] 推送消息: %s\n", uid, msg)
	})

	fmt.Println("服务启动成功: http://localhost:8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		fmt.Printf("服务启动失败: %v\n", err)
	}
}

三、 快速验证步骤

你可以通过以下步骤在本地验证该 SSE 服务的完整流程:

  1. 启动后端服务
bash 复制代码
go run main.go
  1. 建立客户端监听(模拟接收端)
    新开一个终端,使用 curl 保持长连接(-N 参数禁止缓冲):
bash 复制代码
curl -N "http://localhost:8080/events?user_id=alice"

(执行后命令行会挂起转圈,代表长连接建立成功)

  1. 触发业务推送(模拟发送端)
    再新开一个终端,调用推送接口:
bash 复制代码
curl "http://localhost:8080/send?user_id=alice&msg=Hello_SSE"

此时,在第二步的终端窗口中会瞬间收到服务端流式下发的数据:

bash 复制代码
data: {"msg_id": "id_Hello_SSE", "content": "Hello_SSE"}
相关推荐
小小龙学IT1 小时前
Day 28 项目调试与优化 —— 给聊天室做一次“全面体检“
c语言·开发语言
hy.z_7771 小时前
【C++】9. Vector
开发语言·c++
Tim_102 小时前
【C++】023、移动语义&深拷贝
开发语言·c++·算法
(轻舟已过万重山)2 小时前
第40章 Spring AI 实战:企业级 AI 应用架构
人工智能·spring·架构
ctlover2 小时前
Python模块与包
开发语言·python
雨田言炎2 小时前
十、QThread多线程
linux·服务器·开发语言·前端·qt
重庆小透明2 小时前
深入探寻微服务【第三篇微服务的组件】
运维·微服务·架构
djjjx.2 小时前
【 C++ 】stack、queue、优先级队列、仿函数、容器适配器
开发语言·c++
在世修行3 小时前
从零打造 C# 工业视觉检测系统(十一):Task 异步编程与实时性保障
开发语言·c#·task异步
ltl3 小时前
混沌工程:主动验证系统的韧性
架构