增加es归档 分布式和constants变量
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,4 +3,4 @@ rabbitmq/开发指南.md
|
|||||||
ragflow/agent文档.md
|
ragflow/agent文档.md
|
||||||
ragflow/README_GLOBAL.md
|
ragflow/README_GLOBAL.md
|
||||||
redis/stream使用示例.md
|
redis/stream使用示例.md
|
||||||
ragflow/client_http.go
|
ragflow/client_backup.go.bak
|
||||||
|
|||||||
@@ -32,3 +32,11 @@ var ArchiveDelay = 3600
|
|||||||
|
|
||||||
// HistoryContextLimit 读取历史对话轮数(用于新 Session 上下文注入)
|
// HistoryContextLimit 读取历史对话轮数(用于新 Session 上下文注入)
|
||||||
var HistoryContextLimit int64 = 5
|
var HistoryContextLimit int64 = 5
|
||||||
|
|
||||||
|
// -------------------- Stream 消费配置 --------------------
|
||||||
|
|
||||||
|
// DefaultBatchSize 批量读取消息数量(削峰填谷)
|
||||||
|
var DefaultBatchSize int64 = 200
|
||||||
|
|
||||||
|
// DefaultBlockTimeout 阻塞超时时间(毫秒)
|
||||||
|
var DefaultBlockTimeout int64 = 2000
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitee.com/red-future---jilin-g/common/consts"
|
"gitee.com/red-future---jilin-g/common/consts"
|
||||||
|
"gitee.com/red-future---jilin-g/common/do"
|
||||||
"gitee.com/red-future---jilin-g/common/redis"
|
"gitee.com/red-future---jilin-g/common/redis"
|
||||||
"gitee.com/red-future---jilin-g/common/utils"
|
"gitee.com/red-future---jilin-g/common/utils"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
@@ -21,7 +22,12 @@ import (
|
|||||||
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
var db = new(mongo.Database)
|
var db *mongo.Database
|
||||||
|
|
||||||
|
// GetDB 获取 MongoDB 数据库实例
|
||||||
|
func GetDB() *mongo.Database {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
@@ -88,12 +94,49 @@ func oneOptionsToMap(ctx context.Context, opts ...options.Lister[options.FindOne
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTenantInfo 获取租户信息
|
||||||
|
// 优先从 token 获取,失败则从请求参数 customerServiceId 查询 customer_service_account 表
|
||||||
|
func GetTenantInfo(ctx context.Context) (user do.User, err error) {
|
||||||
|
// 1. 优先从 token 获取
|
||||||
|
user, err = utils.GetUserInfo(ctx)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. token 获取失败,尝试从请求参数获取 customerServiceId
|
||||||
|
req := g.RequestFromCtx(ctx)
|
||||||
|
if req == nil {
|
||||||
|
return user, gerror.New("无法获取租户信息:无 token 且无 request")
|
||||||
|
}
|
||||||
|
|
||||||
|
customerServiceId := req.Get("customerServiceId").String()
|
||||||
|
if customerServiceId == "" {
|
||||||
|
customerServiceId = req.Get("customer_service_id").String()
|
||||||
|
}
|
||||||
|
if customerServiceId == "" {
|
||||||
|
return user, gerror.New("无法获取租户信息:无 token 且无 customerServiceId 参数")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 直接查询 customer_service_account 表获取 tenantId
|
||||||
|
filter := bson.M{"customerServiceId": customerServiceId, "isDeleted": false}
|
||||||
|
var account struct {
|
||||||
|
TenantId interface{} `bson:"tenantId"`
|
||||||
|
}
|
||||||
|
if findErr := db.Collection("customer_service_account").FindOne(ctx, filter).Decode(&account); findErr != nil {
|
||||||
|
return user, gerror.Newf("通过 customerServiceId 查询租户失败: %v", findErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
user.TenantId = account.TenantId
|
||||||
|
user.UserName = customerServiceId
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Find 查询多条记录
|
// Find 查询多条记录
|
||||||
func Find(ctx context.Context, filter bson.M, result interface{}, collection string, opts ...options.Lister[options.FindOptions]) (err error) {
|
func Find(ctx context.Context, filter bson.M, result interface{}, collection string, opts ...options.Lister[options.FindOptions]) (err error) {
|
||||||
if err = utils.ValidStructPtr(result); err != nil {
|
if err = utils.ValidStructPtr(result); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err := utils.GetUserInfo(ctx)
|
user, err := GetTenantInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -135,7 +178,7 @@ func FindOne(ctx context.Context, filter bson.M, result interface{}, collection
|
|||||||
if err = utils.ValidStructPtr(result); err != nil {
|
if err = utils.ValidStructPtr(result); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err := utils.GetUserInfo(ctx)
|
user, err := GetTenantInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -198,7 +241,7 @@ func Delete(ctx context.Context, filter bson.M, collection string, opts ...optio
|
|||||||
err = gerror.New("缺少查询条件")
|
err = gerror.New("缺少查询条件")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err := utils.GetUserInfo(ctx)
|
user, err := GetTenantInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -219,7 +262,7 @@ func Update(ctx context.Context, filter bson.M, update bson.M, collection string
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
filter["isDeleted"] = false
|
filter["isDeleted"] = false
|
||||||
user, err := utils.GetUserInfo(ctx)
|
user, err := GetTenantInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -238,7 +281,7 @@ func Update(ctx context.Context, filter bson.M, update bson.M, collection string
|
|||||||
|
|
||||||
// Insert 插入多条记录
|
// Insert 插入多条记录
|
||||||
func Insert(ctx context.Context, documents []interface{}, collection string, opts ...options.Lister[options.InsertManyOptions]) (ids []interface{}, err error) {
|
func Insert(ctx context.Context, documents []interface{}, collection string, opts ...options.Lister[options.InsertManyOptions]) (ids []interface{}, err error) {
|
||||||
user, err := utils.GetUserInfo(ctx)
|
user, err := GetTenantInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -265,7 +308,7 @@ func Insert(ctx context.Context, documents []interface{}, collection string, opt
|
|||||||
|
|
||||||
// Count 查询总数
|
// Count 查询总数
|
||||||
func Count(ctx context.Context, filter bson.M, collection string) (count int64, err error) {
|
func Count(ctx context.Context, filter bson.M, collection string) (count int64, err error) {
|
||||||
user, err := utils.GetUserInfo(ctx)
|
user, err := GetTenantInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package ragflow
|
package ragflow
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -12,9 +14,13 @@ import (
|
|||||||
"github.com/gogf/gf/v2/encoding/gjson"
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/net/gclient"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// gclient 完全不能用!
|
||||||
|
// 1. New() 默认 ResponseHeaderTimeout=30s
|
||||||
|
// 2. Clone() 内部调用 New(),链式调用会重置 Transport
|
||||||
|
// 3. 必须用原生 http.Client
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// globalClient 全局 RAGFlow 客户端(单例,延迟初始化)
|
// globalClient 全局 RAGFlow 客户端(单例,延迟初始化)
|
||||||
globalClient *Client
|
globalClient *Client
|
||||||
@@ -51,15 +57,15 @@ func initClient() {
|
|||||||
ResponseHeaderTimeout: 180 * time.Second, // 等待响应头超时(关键!)
|
ResponseHeaderTimeout: 180 * time.Second, // 等待响应头超时(关键!)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化全局客户端
|
// 使用原生 http.Client(gclient 完全不能用,Clone() 内部调用 New() 会重置 Transport)
|
||||||
httpClient := gclient.New()
|
httpClient := &http.Client{
|
||||||
httpClient.SetBrowserMode(false)
|
Transport: transport,
|
||||||
httpClient.SetHeader("Authorization", "Bearer "+apiKey)
|
Timeout: 0, // 不设置全局超时,由 context 控制
|
||||||
httpClient.SetHeader("Content-Type", "application/json")
|
}
|
||||||
httpClient.SetTimeout(180 * time.Second) // RAGFlow AI 推理需要较长时间
|
|
||||||
|
|
||||||
// 设置自定义 Transport
|
// 验证 Transport 设置
|
||||||
httpClient.Client.Transport = transport
|
g.Log().Infof(ctx, "✅ Transport 配置: ResponseHeaderTimeout=%v, MaxIdleConnsPerHost=%d, DisableKeepAlives=%v",
|
||||||
|
transport.ResponseHeaderTimeout, transport.MaxIdleConnsPerHost, transport.DisableKeepAlives)
|
||||||
|
|
||||||
globalClient = &Client{
|
globalClient = &Client{
|
||||||
BaseURL: strings.TrimSuffix(baseURL, "/"),
|
BaseURL: strings.TrimSuffix(baseURL, "/"),
|
||||||
@@ -90,7 +96,7 @@ func GetGlobalClient() *Client {
|
|||||||
type Client struct {
|
type Client struct {
|
||||||
BaseURL string
|
BaseURL string
|
||||||
APIKey string
|
APIKey string
|
||||||
HTTPClient *gclient.Client // HTTP 客户端
|
HTTPClient *http.Client // 原生 HTTP 客户端(gclient 不能用)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommonResponse 通用响应结构
|
// CommonResponse 通用响应结构
|
||||||
@@ -118,39 +124,44 @@ func (c *Client) request(ctx context.Context, method, path string, body interfac
|
|||||||
reqBody = string(jsonData)
|
reqBody = string(jsonData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 180 秒超时(RAGFlow AI 推理需要较长时间)
|
// 使用独立的 context 设置 300 秒超时(RAGFlow 高并发时响应较慢)
|
||||||
reqCtx, cancel := context.WithTimeout(ctx, 180*time.Second)
|
reqCtx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
var resp *gclient.Response
|
// 创建请求
|
||||||
|
req, err := http.NewRequestWithContext(reqCtx, method, fullURL, bytes.NewReader([]byte(reqBody)))
|
||||||
switch method {
|
if err != nil {
|
||||||
case "GET":
|
return gerror.Newf("create request failed: %v", err)
|
||||||
resp, err = c.HTTPClient.Get(reqCtx, fullURL)
|
|
||||||
case "POST":
|
|
||||||
resp, err = c.HTTPClient.Post(reqCtx, fullURL, reqBody)
|
|
||||||
case "PUT":
|
|
||||||
resp, err = c.HTTPClient.Put(reqCtx, fullURL, reqBody)
|
|
||||||
case "DELETE":
|
|
||||||
resp, err = c.HTTPClient.Delete(reqCtx, fullURL, reqBody)
|
|
||||||
default:
|
|
||||||
return gerror.Newf("unsupported method: %s", method)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// 发送请求
|
||||||
|
g.Log().Infof(ctx, "[RAGFlow HTTP] 发送请求: method=%s, url=%s", method, fullURL)
|
||||||
|
resp, err := c.HTTPClient.Do(req)
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.Log().Errorf(ctx, "[RAGFlow HTTP] 请求失败: method=%s, url=%s, error=%v", method, fullURL, err)
|
g.Log().Errorf(ctx, "[RAGFlow HTTP] 请求失败(耗时 %v): method=%s, url=%s, error=%v", elapsed, method, fullURL, err)
|
||||||
return gerror.Newf("request failed: %v", err)
|
return gerror.Newf("request failed: %v", err)
|
||||||
}
|
}
|
||||||
defer resp.Close()
|
g.Log().Infof(ctx, "[RAGFlow HTTP] 收到响应(耗时 %v): status=%d, url=%s", elapsed, resp.StatusCode, fullURL)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
respBody := resp.ReadAll()
|
// 读取响应
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return gerror.Newf("read response failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 打印响应详情
|
// 打印响应详情
|
||||||
g.Log().Debugf(ctx, "[RAGFlow HTTP] 响应: status=%d, body=%s", resp.StatusCode, string(respBody))
|
g.Log().Debugf(ctx, "[RAGFlow HTTP] 响应: status=%d, body=%s", resp.StatusCode, respBody)
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
g.Log().Errorf(ctx, "[RAGFlow HTTP] 非200响应: status=%d, body=%s", resp.StatusCode, string(respBody))
|
g.Log().Errorf(ctx, "[RAGFlow HTTP] 非200响应: status=%d, body=%s", resp.StatusCode, respBody)
|
||||||
return gerror.Newf("http status %d: %s", resp.StatusCode, string(respBody))
|
return gerror.Newf("http status %d: %s", resp.StatusCode, respBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = gjson.DecodeTo(respBody, result); err != nil {
|
if err = gjson.DecodeTo(respBody, result); err != nil {
|
||||||
|
|||||||
@@ -2,189 +2,111 @@ package ragflow
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gitee.com/red-future---jilin-g/common/redis"
|
"gitee.com/red-future---jilin-g/common/redis"
|
||||||
"github.com/gogf/gf/v2/os/glog"
|
"github.com/gogf/gf/v2/os/glog"
|
||||||
"github.com/gogf/gf/v2/os/grpool"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 默认协程池大小
|
// 默认批量大小(每次从 Redis 读取并发送的消息数)
|
||||||
const defaultPoolSize = 200
|
const defaultBatchSize = 200
|
||||||
|
|
||||||
// workerPool 协程池单例(grpool.New 是原型模式,需要变量引用)
|
// QueueProcessor Stream 处理器,批量读取消息并发送到 RAGFlow
|
||||||
var workerPool = grpool.New(defaultPoolSize)
|
|
||||||
|
|
||||||
// WorkerPool RAGFlow 请求处理协程池(封装 grpool)
|
|
||||||
type WorkerPool struct {
|
|
||||||
pool *grpool.Pool
|
|
||||||
size int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pool 协程池单例实例(直接引用使用)
|
|
||||||
var Pool = &WorkerPool{
|
|
||||||
pool: workerPool,
|
|
||||||
size: defaultPoolSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Submit 提交任务到协程池
|
|
||||||
// 参数:
|
|
||||||
// - ctx: 上下文
|
|
||||||
// - task: 要执行的任务函数
|
|
||||||
//
|
|
||||||
// 返回:error 提交失败时返回错误
|
|
||||||
func (w *WorkerPool) Submit(ctx context.Context, task func(ctx context.Context)) error {
|
|
||||||
return w.pool.Add(ctx, func(ctx context.Context) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
glog.Errorf(ctx, "协程池任务执行 panic: %v", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
task(ctx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size 获取协程池大小
|
|
||||||
func (w *WorkerPool) Size() int {
|
|
||||||
return w.size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jobs 获取当前等待执行的任务数量
|
|
||||||
func (w *WorkerPool) Jobs() int {
|
|
||||||
return w.pool.Jobs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close 关闭协程池
|
|
||||||
func (w *WorkerPool) Close() {
|
|
||||||
w.pool.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WorkerStats 协程池统计信息
|
|
||||||
type WorkerStats struct {
|
|
||||||
PoolSize int // 协程池大小
|
|
||||||
Jobs int // 等待执行的任务数
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stats 获取协程池统计信息
|
|
||||||
func (w *WorkerPool) Stats() WorkerStats {
|
|
||||||
return WorkerStats{
|
|
||||||
PoolSize: w.size,
|
|
||||||
Jobs: w.pool.Jobs(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintStats 打印协程池统计信息
|
|
||||||
func (w *WorkerPool) PrintStats(ctx context.Context) {
|
|
||||||
stats := w.Stats()
|
|
||||||
glog.Infof(ctx, "协程池统计 - 池大小: %d, 等待任务: %d", stats.PoolSize, stats.Jobs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueueProcessor Stream 处理器,从 Redis Stream 中取出任务并提交到协程池
|
|
||||||
type QueueProcessor struct {
|
type QueueProcessor struct {
|
||||||
pool *WorkerPool
|
streamKey string // Stream 键名
|
||||||
streamKey string // Stream 键名
|
groupName string // 消费者组名称
|
||||||
groupName string // 消费者组名称
|
consumerName string // 消费者名称
|
||||||
consumerName string // 消费者名称
|
timeout int64 // 阻塞超时时间(毫秒)
|
||||||
timeout int64 // 阻塞超时时间(毫秒)
|
batchSize int64 // 最大并发数(信号量容量)
|
||||||
batchSize int64 // 每次读取的消息数量
|
stopChan chan struct{} // 停止信号
|
||||||
stopChan chan struct{}
|
semaphore chan struct{} // 并发信号量(控制最大并发)
|
||||||
handleFunc func(ctx context.Context, message map[string]interface{}) error
|
handleFunc func(ctx context.Context, message map[string]interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewQueueProcessor 创建 Stream 处理器
|
// NewQueueProcessor 创建 Stream 处理器
|
||||||
// 参数:
|
func NewQueueProcessor(streamKey, groupName, consumerName string, timeout, batchSize int64, handleFunc func(ctx context.Context, message map[string]interface{}) error) *QueueProcessor {
|
||||||
// - pool: 协程池
|
|
||||||
// - streamKey: Redis Stream 键名
|
|
||||||
// - groupName: 消费者组名称
|
|
||||||
// - consumerName: 消费者名称(唯一标识)
|
|
||||||
// - timeout: 从 Stream 取消息的超时时间(毫秒)
|
|
||||||
// - batchSize: 每次读取的消息数量
|
|
||||||
// - handleFunc: 消息处理函数
|
|
||||||
func NewQueueProcessor(pool *WorkerPool, streamKey, groupName, consumerName string, timeout int64, batchSize int64, handleFunc func(ctx context.Context, message map[string]interface{}) error) *QueueProcessor {
|
|
||||||
return &QueueProcessor{
|
return &QueueProcessor{
|
||||||
pool: pool,
|
|
||||||
streamKey: streamKey,
|
streamKey: streamKey,
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
consumerName: consumerName,
|
consumerName: consumerName,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
batchSize: batchSize,
|
batchSize: batchSize,
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
|
semaphore: make(chan struct{}, batchSize), // 信号量容量 = 最大并发数
|
||||||
handleFunc: handleFunc,
|
handleFunc: handleFunc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start 启动 Stream 处理器
|
// Start 启动 Stream 处理器
|
||||||
// 会阻塞运行,持续从 Redis Stream 中取出消息并提交到协程池处理
|
// 削峰填谷:每次读取 batchSize 条消息,并发发送,发完立刻读下一批
|
||||||
func (q *QueueProcessor) Start(ctx context.Context) error {
|
func (q *QueueProcessor) Start(ctx context.Context) error {
|
||||||
glog.Infof(ctx, "Stream 处理器启动 - Stream: %s, 消费者组: %s, 消费者: %s, 超时: %dms",
|
glog.Infof(ctx, "Stream 处理器启动 - Stream: %s, 消费者组: %s, 消费者: %s, 批量大小: %d",
|
||||||
q.streamKey, q.groupName, q.consumerName, q.timeout)
|
q.streamKey, q.groupName, q.consumerName, q.batchSize)
|
||||||
|
|
||||||
|
// 确保 Consumer Group 存在(重试直到成功)
|
||||||
|
for {
|
||||||
|
if err := redis.CreateConsumerGroup(ctx, q.streamKey, q.groupName); err != nil {
|
||||||
|
// BUSYGROUP 表示已存在,不是错误
|
||||||
|
if strings.Contains(err.Error(), "BUSYGROUP") {
|
||||||
|
glog.Debugf(ctx, "Consumer Group 已存在")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
glog.Warningf(ctx, "创建 Consumer Group 失败: %v,1秒后重试", err)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
glog.Infof(ctx, "Consumer Group 创建成功")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
loopCount := 0
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-q.stopChan:
|
case <-q.stopChan:
|
||||||
glog.Info(ctx, "Stream 处理器收到停止信号")
|
glog.Info(ctx, "Stream 处理器收到停止信号")
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
loopCount++
|
// 1. 从 Redis Stream 读取一批消息
|
||||||
if loopCount%10 == 1 {
|
messages, err := redis.ReadFromStream(ctx, q.streamKey, q.groupName, q.consumerName, q.batchSize, q.timeout)
|
||||||
glog.Debugf(ctx, "[DEBUG] 第 %d 次循环,准备读取消息...", loopCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从 Redis Stream 中读取消息
|
|
||||||
messages, err := q.fetchMessages(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf(ctx, "从 Stream 读取消息失败: %v", err)
|
glog.Errorf(ctx, "从 Stream 读取消息失败: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有新消息,继续等待
|
|
||||||
if len(messages) == 0 {
|
if len(messages) == 0 {
|
||||||
if loopCount%10 == 1 {
|
|
||||||
glog.Debugf(ctx, "[DEBUG] 第 %d 次循环,无新消息", loopCount)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.Infof(ctx, "[DEBUG] 收到 %d 条消息", len(messages))
|
glog.Debugf(ctx, "读取 %d 条消息,开始发送", len(messages))
|
||||||
|
|
||||||
// 处理每条消息
|
// 2. 用信号量控制并发:获取信号量后发送,完成后释放
|
||||||
for _, msg := range messages {
|
for _, msg := range messages {
|
||||||
glog.Infof(ctx, "[DEBUG] 处理消息 ID: %s, Values: %+v", msg.ID, msg.Values)
|
// 获取信号量(阻塞直到有空位)
|
||||||
// 提交到协程池处理
|
q.semaphore <- struct{}{}
|
||||||
if err := q.submitTask(ctx, msg); err != nil {
|
go func(m redis.StreamMessage) {
|
||||||
glog.Errorf(ctx, "提交任务到协程池失败: %v, 消息ID: %s", err, msg.ID)
|
defer func() { <-q.semaphore }() // 完成后释放信号量
|
||||||
}
|
q.processMessage(ctx, m)
|
||||||
|
}(msg)
|
||||||
}
|
}
|
||||||
|
// 3. 立刻读下一批(不等待,信号量自动控制并发数)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processMessage 处理单条消息(异步执行)
|
||||||
|
func (q *QueueProcessor) processMessage(ctx context.Context, message redis.StreamMessage) {
|
||||||
|
// 调用处理函数发送到 RAGFlow
|
||||||
|
if err := q.handleFunc(ctx, message.Values); err != nil {
|
||||||
|
glog.Errorf(ctx, "消息处理失败: %v, 消息ID: %s", err, message.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无论成功失败都 ACK(避免重复消费)
|
||||||
|
if err := redis.AckMessage(ctx, q.streamKey, q.groupName, message.ID); err != nil {
|
||||||
|
glog.Errorf(ctx, "确认消息失败: %v, 消息ID: %s", err, message.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop 停止队列处理器
|
// Stop 停止队列处理器
|
||||||
func (q *QueueProcessor) Stop() {
|
func (q *QueueProcessor) Stop() {
|
||||||
close(q.stopChan)
|
close(q.stopChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchMessages 从 Redis Stream 中读取消息
|
|
||||||
func (q *QueueProcessor) fetchMessages(ctx context.Context) ([]redis.StreamMessage, error) {
|
|
||||||
// 从消费者组读取消息
|
|
||||||
return redis.ReadFromStream(ctx, q.streamKey, q.groupName, q.consumerName, q.batchSize, q.timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// submitTask 将消息处理任务提交到协程池
|
|
||||||
func (q *QueueProcessor) submitTask(ctx context.Context, message redis.StreamMessage) error {
|
|
||||||
return q.pool.Submit(ctx, func(ctx context.Context) {
|
|
||||||
// 处理消息
|
|
||||||
if err := q.handleFunc(ctx, message.Values); err != nil {
|
|
||||||
glog.Errorf(ctx, "处理消息失败: %v, 消息ID: %s", err, message.ID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理成功后确认消息
|
|
||||||
if err := redis.AckMessage(ctx, q.streamKey, q.groupName, message.ID); err != nil {
|
|
||||||
glog.Errorf(ctx, "确认消息失败: %v, 消息ID: %s", err, message.ID)
|
|
||||||
} else {
|
|
||||||
glog.Debugf(ctx, "消息处理完成并已确认: %s", message.ID)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -95,6 +95,13 @@ func AddToStream(ctx context.Context, streamKey string, msg interface{}) (messag
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateConsumerGroup 创建消费者组(如果不存在)
|
||||||
|
// XGROUP CREATE streamKey groupName $ MKSTREAM
|
||||||
|
func CreateConsumerGroup(ctx context.Context, streamKey, groupName string) error {
|
||||||
|
_, err := redisClient.Do(ctx, "XGROUP", "CREATE", streamKey, groupName, "$", "MKSTREAM")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// ReadFromStream 从 Stream 读取消息(消费者组模式)
|
// ReadFromStream 从 Stream 读取消息(消费者组模式)
|
||||||
// 使用 gredis Do() 方法执行 XREADGROUP 命令
|
// 使用 gredis Do() 方法执行 XREADGROUP 命令
|
||||||
func ReadFromStream(ctx context.Context, streamKey, groupName, consumerName string, count int64, blockMs int64) ([]StreamMessage, error) {
|
func ReadFromStream(ctx context.Context, streamKey, groupName, consumerName string, count int64, blockMs int64) ([]StreamMessage, error) {
|
||||||
@@ -447,3 +454,24 @@ func DelSessionCache(ctx context.Context, userId string) error {
|
|||||||
_, err := redisClient.Del(ctx, key)
|
_, err := redisClient.Del(ctx, key)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TryLock 尝试获取分布式锁(非阻塞)
|
||||||
|
// key: 锁的键名
|
||||||
|
// expireSeconds: 锁的过期时间(秒),防止死锁
|
||||||
|
// 返回 true 表示获取成功,false 表示锁已被其他节点持有
|
||||||
|
func TryLock(ctx context.Context, key string, expireSeconds int) bool {
|
||||||
|
// SET key value NX EX expireSeconds
|
||||||
|
result, err := redisClient.Do(ctx, "SET", key, gtime.Now().String(), "NX", "EX", expireSeconds)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf(ctx, "获取分布式锁失败: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return result.String() == "OK"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock 释放分布式锁
|
||||||
|
func Unlock(ctx context.Context, key string) {
|
||||||
|
if _, err := redisClient.Del(ctx, key); err != nil {
|
||||||
|
glog.Errorf(ctx, "释放分布式锁失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user