diff --git a/backlink/backlink.go b/backlink/backlink.go index e450683..7adb850 100644 --- a/backlink/backlink.go +++ b/backlink/backlink.go @@ -250,7 +250,7 @@ func (r *Runner) aggregate(filter func(*storage.SiteInfo) bool, stats *siteStats }) // 向量余弦过滤:去除 Server 类型特征偏离核心向量的域名(可能是噪音/作弊) - d = vectorFilter(d, vectors, desc) + d = vectorFilter(d, vectors, desc, r.storagePath) // 最终清理:分数 ≤ 0.16 的域名不写入(低于此阈值认为不繁荣) for k, v := range d { @@ -313,7 +313,7 @@ func (r *Runner) aggregateWithScores(scores map[string]float64, stats *siteStats return nil }) - d = vectorFilter(d, vectors, desc) + d = vectorFilter(d, vectors, desc, r.storagePath) for k, v := range d { if v <= 0.16 { delete(d, k) @@ -326,7 +326,7 @@ func (r *Runner) aggregateWithScores(scores map[string]float64, stats *siteStats // vectorFilter 使用余弦相似度过滤域名分数:保留与核心 Server 类型向量相似的域名。 // 与核心方向偏离的域名可能是噪音(如作弊农场、链接买卖)。 -func vectorFilter(d map[string]float64, vectors map[string][]float32, desc string) map[string]float64 { +func vectorFilter(d map[string]float64, vectors map[string][]float32, desc string, storagePath string) map[string]float64 { // 计算全网站的 Server 类型核心向量(所有向量求和) core := make([]float64, 64) for _, vec := range vectors { @@ -370,7 +370,8 @@ func vectorFilter(d map[string]float64, vectors map[string][]float32, desc strin cosMap[k] = dot32_64(vec, core) / (vn * coreNorm) } } - _ = writeJSON(desc+"_cos.json", cosMap) + cosPath := filepath.Join(storagePath, desc+"_cos.json") + _ = writeJSON(cosPath, cosMap) return newD } diff --git a/search/server.go b/search/server.go index 2373e93..e8ae2c5 100644 --- a/search/server.go +++ b/search/server.go @@ -37,7 +37,7 @@ type Server struct { // 以下为收获服务(harvester)内嵌字段 mem map[string][]storage.IndexEntry // 内存索引聚合器:关键词 → [权重, URL] 条目 - memMu sync.Mutex // 保护内存索引的并发写入 + memMu sync.RWMutex // 保护内存索引的读写(刷盘时读操作不阻塞) rowCount int64 // 内存中累计的索引条目总数(触发刷盘) flushMu sync.Mutex // 确保同一时刻只有一个 flush 在执行 } @@ -508,7 +508,7 @@ func (s *Server) query(tokens []string, from, to int, siteFilter string) ([]sear return nil, 0 } - // 加载每个词对应的倒排索引条目 + // 加载每个词对应的倒排索引条目(磁盘 + 内存) type tokenIndex struct { token string entries []storage.IndexEntry @@ -516,8 +516,29 @@ func (s *Server) query(tokens []string, from, to int, siteFilter string) ([]sear } tokenIndexes := make([]tokenIndex, 0, len(tokens)) maxURLsPerKey := config.MaxURLsPerKey() + + // 读锁保护内存索引访问(与刷盘互斥,但多个搜索可并发) + s.memMu.RLock() for _, t := range tokens { - entries, _ := s.db.GetIndex(t) + // 1. 从磁盘加载 + diskEntries, _ := s.db.GetIndex(t) + // 2. 从内存加载(尚未刷盘的数据) + memEntries := s.mem[t] + // 3. 合并(内存数据优先,因为更新) + entries := make([]storage.IndexEntry, 0, len(diskEntries)+len(memEntries)) + seen := make(map[string]bool, len(diskEntries)+len(memEntries)) + for _, e := range memEntries { + if !seen[e.URL] { + entries = append(entries, e) + seen[e.URL] = true + } + } + for _, e := range diskEntries { + if !seen[e.URL] { + entries = append(entries, e) + seen[e.URL] = true + } + } // 计算缺省权重:当条目数达到上限时,权重低于第 MaxURLsPerKey 名的条目使用缺省权重 defVal := 1.0 / 10000 * float64(max(100, len(entries))) / float64(maxURLsPerKey) if len(entries) >= maxURLsPerKey { @@ -530,6 +551,7 @@ func (s *Server) query(tokens []string, from, to int, siteFilter string) ([]sear } tokenIndexes = append(tokenIndexes, tokenIndex{t, entries, defVal}) } + s.memMu.RUnlock() // 构建 URL → (词 → 权重) 映射,收集所有候选 URL urlWeights := make(map[string]map[string]float64)