CSDN 技术教程系列:文本与向量检索实战(.NET C# 体系)
系列主题:从内存到 Elasticsearch ------ .NET C# 体系下的文本、向量检索技术演进与应用实例教程
目标读者:中高级 .NET 后端开发工程师、AI应用开发者、技术架构师
技术栈:.NET 8/9、C# 12、ONNX Runtime、BGE-M3、CLIP、Elasticsearch、Python Flask
📚 文章系列规划(共5篇)
| 序号 | 文章标题 | 核心技术 |
|---|---|---|
| 1 | [BGE-M3 多语言向量模型实战:.NET C# 从原理到落地](# 从原理到落地 BGE-M3、ONNX Runtime、Tokenizer 2 内存向量检索引擎设计与实现:C# 轻量级 Milvus 替代方案 内存计算、读写锁、并行检索 3 Elasticsearch 语义搜索实战:.NET 向量+关键词混合检索 ES 8.x、Dense Vector、Hybrid Search 4 CLIP 多模态搜索实战:.NET + Python 跨语言图片检索 OpenCLIP、Python Flask、跨模态 5 从内存到 ES:.NET 企业级向量检索架构演进之路 架构设计、性能优化、容灾策略) | BGE-M3、ONNX Runtime、Tokenizer |
| 2 | [内存向量检索引擎设计与实现:C# 轻量级 Milvus 替代方案](# 从原理到落地 BGE-M3、ONNX Runtime、Tokenizer 2 内存向量检索引擎设计与实现:C# 轻量级 Milvus 替代方案 内存计算、读写锁、并行检索 3 Elasticsearch 语义搜索实战:.NET 向量+关键词混合检索 ES 8.x、Dense Vector、Hybrid Search 4 CLIP 多模态搜索实战:.NET + Python 跨语言图片检索 OpenCLIP、Python Flask、跨模态 5 从内存到 ES:.NET 企业级向量检索架构演进之路 架构设计、性能优化、容灾策略) | 内存计算、读写锁、并行检索 |
| 3 | [Elasticsearch 语义搜索实战:.NET 向量+关键词混合检索](# 从原理到落地 BGE-M3、ONNX Runtime、Tokenizer 2 内存向量检索引擎设计与实现:C# 轻量级 Milvus 替代方案 内存计算、读写锁、并行检索 3 Elasticsearch 语义搜索实战:.NET 向量+关键词混合检索 ES 8.x、Dense Vector、Hybrid Search 4 CLIP 多模态搜索实战:.NET + Python 跨语言图片检索 OpenCLIP、Python Flask、跨模态 5 从内存到 ES:.NET 企业级向量检索架构演进之路 架构设计、性能优化、容灾策略) | ES 8.x、Dense Vector、Hybrid Search |
| 4 | [CLIP 多模态搜索实战:.NET + Python 跨语言图片检索](# 从原理到落地 BGE-M3、ONNX Runtime、Tokenizer 2 内存向量检索引擎设计与实现:C# 轻量级 Milvus 替代方案 内存计算、读写锁、并行检索 3 Elasticsearch 语义搜索实战:.NET 向量+关键词混合检索 ES 8.x、Dense Vector、Hybrid Search 4 CLIP 多模态搜索实战:.NET + Python 跨语言图片检索 OpenCLIP、Python Flask、跨模态 5 从内存到 ES:.NET 企业级向量检索架构演进之路 架构设计、性能优化、容灾策略) | OpenCLIP、Python Flask、跨模态 |
| 5 | [从内存到 ES:.NET 企业级向量检索架构演进之路](# 从原理到落地 BGE-M3、ONNX Runtime、Tokenizer 2 内存向量检索引擎设计与实现:C# 轻量级 Milvus 替代方案 内存计算、读写锁、并行检索 3 Elasticsearch 语义搜索实战:.NET 向量+关键词混合检索 ES 8.x、Dense Vector、Hybrid Search 4 CLIP 多模态搜索实战:.NET + Python 跨语言图片检索 OpenCLIP、Python Flask、跨模态 5 从内存到 ES:.NET 企业级向量检索架构演进之路 架构设计、性能优化、容灾策略) | 架构设计、性能优化、容灾策略 |
文章5:从内存到 ES:.NET 企业级向量检索架构演进之路
📝 文章信息
-
分类:系统架构 / 技术演进 / 企业级应用 / .NET
-
标签 :
架构设计,向量检索,性能优化,容灾,.NET,C#
📖 章节大纲
1. 引言:架构演进的驱动力
-
业务增长带来的挑战
-
技术选型的权衡
2. 阶段一:内存向量检索(C# 实现)
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Client │────▶│ .NET API (C#) │────▶│ SQL DB │
└─────────────┘ │ │ │ (主数据) │
│ ┌────────────┐ │ └─────────────┘
│ │ C# 内存向量 │ │
│ │ 存储实现 │ │
│ │ (1024-dim) │ │
│ └────────────┘ │
└──────────────────┘
3. 阶段二:多模型并存(.NET + Python)
┌─────────────┐ ┌──────────────────────────────────────────┐
│ Client │────▶│ .NET API (C#) │
└─────────────┘ │ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ C# BGE-M3 │ │ C# CLIP 向量库 │ │
│ │ 内存向量库 │ │ (512-dim) │ │
│ │ (1024-dim) │ │ │ │
│ └─────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ C# 查询策略路由 │ │
│ └─────────────────────────────────────┘ │
└──────────────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│ SQL DB │ │ Python │ │Elasticsearch│
│(主数据) │ │ CLIP服务 │ │ (引入) │
└─────────┘ └──────────┘ └──────────┘
4. 阶段三:Elasticsearch 中心化
┌─────────────┐ ┌──────────────────────────────────────────┐
│ Client │────▶│ .NET API (C#) │
└─────────────┘ │ │
│ ┌─────────────────────────────────────┐ │
│ │ C# 统一查询接口(优先ES) │ │
│ └─────────────────────────────────────┘ │
└──────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Elasticsearch Cluster │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Dense Vector │ │ Keyword │ │
│ │ (向量检索) │ │ (关键词) │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ Hybrid Search │ │
│ │ 向量相似度 + 关键词匹配 │ │
│ └─────────────────────────────────────┘ │
└──────────────────────────────────────────┘
//核心代码
var esDoc = new ProductESDocument
{
};
// 4. 写入ES
var response = await client.IndexAsync(esDoc, i => i.Index(ProductsIndexName));
if (response.IsValidResponse)
{
await _db.Updateable<CollectData>()
.SetColumns(p => p.EsSyncStatus == 1)
.Where(p => p.Id == collectDataId)
.ExecuteCommandAsync();
return true;
}
else
{
await _db.Updateable<CollectData>()
.SetColumns(p => p.EsSyncStatus == 2)
.Where(p => p.Id == collectDataId)
.ExecuteCommandAsync();
return false;
}
var searchResponse = await client.SearchAsync<>(s => s
.Indices(ProductsIndexName)
.Query(q => q
.MultiMatch(mm => mm
.Fields(new[] { "title^3", "semanticKeywords^2", "description", "brandName" })
.Query(request.Query)
.Fuzziness(new Fuzziness("AUTO"))
)
)
.Sort(srt => srt.Score(new ScoreSort { Order = SortOrder.Desc }))
.From((request.Page - 1) * request.PageSize)
.Size(request.PageSize)
.SourceExcludes(new[] { "productVector", "imageVector" }) // 排除向量字段,减少返回数据量
.Explain() /
);
if (!searchResponse.IsValidResponse)
{
_logger.LogError("智能搜索失败: {Error}", searchResponse.ElasticsearchServerError?.Error?.Reason);
return new ***
{
Items = new
Total = 0,
Page = request.Page,
PageSize = request.PageSize
};
}
5. 容灾与降级策略(C#)
public class ResilientSearchService
{
private readonly HybridSearchService _esService;
private readonly InMemoryVectorStore<88> _memoryStore;
private readonly ILogger<99> _logger;
public async Task<List<SearchResult>> SearchWithFallbackAsync(string query, int topK)
{
try
{
// 优先 ES
return await _esService.SearchAsync(query, topK);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "ES 查询失败,降级到内存检索");
try
{
// 降级到内存检索
var queryVector = BgeM3EmbeddingGenerator.GenerateEmbedding(query);
return await _memoryStore.SearchSimilarAsync(queryVector, topK);
}
catch (Exception ex2)
{
_logger.LogError(ex2, "内存检索也失败,返回空结果");
return new List<SearchResult>();
}
}
}
}
6. 分层存储策略
| 层级 | 存储介质 | 数据范围 | 延迟 | 用途 |
|---|---|---|---|---|
| L1 | C# 本地内存 | 热点 1万条 | 1ms | 实时推荐 |
| L2 | Redis | 热点 10万条 | 5ms | 缓存加速 |
| L3 | Elasticsearch | 全量数据 | 50ms | 完整检索 |
| L4 | SQL DB | 全量数据 | 100ms | 数据持久化 |