Milvus中两种核心查询方式:暴力搜索(Brute-force Search)近似最近邻搜索(Approximate Nearest Neighbor, ANN)

  • 逐一计算相似度:这是暴力搜索,能保证100%找到最相似的向量,但速度极慢,不适用于大规模数据。
  • 使用Milvus的召回工具:这是近似最近邻(ANN)搜索,它通过构建索引来极大地提升查询速度,但牺牲了绝对的准确性,因此“有可能”找不到理论上最相似的那个向量。

1 Milvus 的两种核心查询模式

1.1 暴力搜索 (Brute-force / Exact Search)

  • 工作原理:将您的查询向量与集合中的每一个向量进行距离计算,然后排序,返回距离最近的结果。
  • 优点
    • 100%准确:能保证找到理论上最精确的结果,召回率永远是100%。
  • 缺点
    • 性能极差:计算量与数据总量成正比(O(n)复杂度)。当数据量达到百万、千万甚至亿级时,查询会变得极其缓慢,甚至无法在可接受的时间内完成。
  • 适用场景
    • 数据集非常小(例如几万到几十万级别)。
    • 对准确率有绝对要求,不容许任何误差的场景,如金融或医疗领域的某些关键匹配任务。
  • 在Milvus中的实现:使用FLAT类型的索引。当您不对向量字段建立任何其他索引时,Milvus默认就会采用这种方式。

1.2 近似最近邻搜索 (ANN Search)

这是Milvus等向量数据库的核心与精髓。

  • 工作原理:它不对整个数据集进行详尽搜索。相反,它在数据入库时会预先通过特定算法(如聚类、图、树等)将相似的向量组织在一起,构建出一个“索引”数据结构。查询时,它利用这个索引快速定位到可能包含最近邻的一个或多个“区域”,然后只在这些小区域内进行精确搜索。
  • 优点
    • 速度极快:通过牺牲少量精度,查询速度可以比暴力搜索快几个数量级,能够轻松应对海量数据的实时查询需求。
  • 缺点
    • 结果是近似的:由于只搜索了部分数据,存在一定的概率会错过真正的最近邻,导致召回率(Recall)低于100%。
    • 需要调优:索引的类型和参数选择会直接影响查询的速度和召回率,需要根据具体场景进行权衡和调整。
  • 适用场景
    • 绝大多数大规模向量检索应用,如以图搜图、推荐系统、语义搜索等,这些场景下用户对微小的精度损失不敏感,但对查询速度要求很高。
  • 在Milvus中的实现:通过创建如 IVF_FLAT, IVF_PQ, HNSW, DiskANN 等索引类型来实现。

2.为什么ANN搜索会“找不到”最相似的向量?

这正是 速度与准确率的权衡(Trade-off) 的体现。

以常用的IVF(Inverted File,倒排文件)索引为例:

  1. 构建索引时:Milvus会将所有向量分成nlist个“簇”(Cluster),每个簇有一个中心点。
  2. 查询时:Milvus会先计算您的查询向量与所有nlist个簇中心的距离,然后只选择最接近的nprobe个簇。
  3. 最终搜索:Milvus只在这nprobe个簇包含的向量中进行暴力搜索,找出最终结果。

如果最相似的那个目标向量,不幸地被分到了一个距离您的查询向量稍远的簇中,而这个簇又没有被选入nprobe个搜索范围内,那么这个目标向量就永远不会被找到。

3.如何提升ANN搜索的召回率(Recall)?

既然您遇到了召回率问题,以下是几种在Milvus中提升召回率的有效方法:

3.1. 调整搜索参数

这是最直接、最常用的方法。 在执行search()时,可以调整search_params来扩大搜索范围。

  • 对于IVF系列索引(如IVF_FLAT, IVF_PQ
    • 提高 nprobe:这个参数决定了要搜索多少个“簇”。nprobe值越高,搜索的范围越大,找到真正最近邻的概率就越高,召回率也随之提升。但同时,查询时间也会增加。 建议您可以从一个较小的值(如16, 32)开始,逐步增加并测试召回率和延迟,找到最佳平衡点。
  • 对于HNSW索引
    • 提高 ef:这个参数控制了搜索时维护的动态候选列表的大小。ef越大,搜索的路径越多,越不容易错过最近邻,召回率越高,但查询也越慢。

3.2. 调整索引构建参数

在创建索引时,合理的参数也能提升后续的查询效果。

  • 对于IVF系列索引
    • 选择合适的 nlistnlist(簇的数量)需要根据您的数据量来定。一个常用的经验法则是设置为 4 * sqrt(n)(n是向量总数)。nlist过小或过大都可能影响效果。
  • 对于HNSW索引
    • 提高 MefConstructionM定义了图中每个节点的最大出度,efConstruction控制了建图时的搜索深度。增加这两个值可以构建出质量更高、连通性更好的图,从而提升召回率,但会增加索引构建时间和内存占用。

3.3. 使用混合搜索与重排(Hybrid Search & Reranking)

这是一种更高级的策略,通过结合多种信息来提升最终结果的准确性。

  • 两阶段搜索
    1. 召回阶段:使用ANN索引快速召回一个较大的候选集(例如,您需要Top 10的结果,可以先召回Top 100或Top 200)。
    2. 重排阶段:对这个小规模的候选集(100或200个向量)进行精确的暴力计算,或者使用更复杂的重排模型(Reranker Model)进行排序,最后返回最精准的Top 10结果。
  • 多向量混合搜索:如果您的数据可以使用不同模型生成多种向量(例如,一个向量代表颜色,一个向量代表纹理),Milvus 2.4版本以上支持多向量搜索,可以综合多个向量字段的结果进行重排,显著提升召回率。
    代码示例:

3.4. 范围搜索(Range Search)

如果您关心的不是返回“前K个最相似的”,而是返回“所有相似度高于某个阈值的”,可以使用范围搜索。这种方式可以根据您设定的距离范围来确保所有符合条件的结果都被召回。

3.5 代码实践

仅针对3和4撰写测试代码,供参考,可能存在错误:

import numpy as np
import asyncio
from typing import List, Dict
import logging
import uuid
import time# --- 配置 ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)DIMENSION = 128  # 为方便演示,降低维度
TOTAL_VECTORS = 2000 # 模拟数据总量
SIMILAR_COUNT = 5     # 制造5个与基准相似的向量# --- 向量工具函数 ---
def normalize_l2(vectors: np.ndarray) -> np.ndarray:"""对Numpy向量或向量数组进行L2归一化"""if vectors.ndim == 1:norm = np.linalg.norm(vectors)return vectors / max(norm, 1e-10)else:norms = np.linalg.norm(vectors, axis=1, keepdims=True)norms = np.maximum(norms, 1e-10)return vectors / norms# --- 步骤 1: 构造模拟数据 ---
def generate_mock_data() -> List[Dict]:"""生成包含已知相似簇的模拟数据"""logger.info("开始构造模拟数据...")# a. 创建一个归一化的基准向量base_vector = normalize_l2(np.random.random(DIMENSION).astype(np.float32))mock_data = []# b. 制造 SIMILAR_COUNT 个与基准相似的向量for i in range(SIMILAR_COUNT):# 添加微小噪声noise = np.random.random(DIMENSION).astype(np.float32) * 0.05 similar_vector = normalize_l2(base_vector + noise)mock_data.append({"id": str(uuid.uuid4()),"image_name": f"similar_image_{i+1}.jpg","image_path": f"/path/to/similar_{i+1}.jpg","vector": similar_vector,"label": "similar_group"})# c. 制造大量随机的背景噪声向量for i in range(TOTAL_VECTORS - SIMILAR_COUNT):random_vector = normalize_l2(np.random.random(DIMENSION).astype(np.float32))mock_data.append({"id": str(uuid.uuid4()),"image_name": f"random_image_{i+1}.jpg","image_path": f"/path/to/random_{i+1}.jpg","vector": random_vector,"label": f"random_group_{i % 10}"})logger.info(f"数据构造完成,共 {len(mock_data)} 条记录。")return mock_data, base_vector# --- 步骤 2: 模拟 Milvus 环境 ---
class MockMilvus:"""一个简单的内存数据库,用于模拟Milvus的行为"""def __init__(self, data: List[Dict]):self._data = {item['id']: item for item in data}self._vectors = np.array([item['vector'] for item in data])self._ids = [item['id'] for item in data]logger.info("模拟Milvus环境已就绪,数据已加载到内存。")def search(self, query_vector: np.ndarray, limit: int, params: Dict) -> List[Dict]:"""模拟 ANN search,返回的结果顺序可能不精确"""logger.info(f"[模拟Search] 参数: limit={limit}, params={params}")# 精确计算所有向量的相似度scores = np.dot(self._vectors, query_vector)# 模拟 ANN 的不确定性:打乱前 30% 的结果顺序top_30_percent_idx = int(len(scores) * 0.3)top_indices = np.argsort(-scores) # 获取降序排序的索引# 打乱前30%的索引shuffled_top_part = top_indices[:top_30_percent_idx]np.random.shuffle(shuffled_top_part)top_indices[:top_30_percent_idx] = shuffled_top_part# 范围搜索逻辑if 'range_filter' in params:radius = params.get('radius', 1.0)range_filter = params.get('range_filter', 0.0)# 筛选出在范围内的indices_in_range = [i for i in top_indices if range_filter <= scores[i] <= radius]result_indices = indices_in_range[:limit]else:result_indices = top_indices[:limit]# 返回模拟的 Milvus Hit 对象results = [{"id": self._ids[i], "distance": scores[i]} for i in result_indices]return [results]def query(self, expr: str, output_fields: List[str]) -> List[Dict]:"""模拟 query by id"""# 这是一个简化的解析,只处理 "id in [...]" 的情况try:id_list_str = expr.split(' in ')[1].strip('[]')id_list = [s.strip().strip("'\"") for s in id_list_str.split(',')]results = [self._data[id_] for id_ in id_list if id_ in self._data]logger.info(f"[模拟Query] 表达式 '{expr[:50]}...' 命中 {len(results)} 条记录。")return resultsexcept Exception as e:logger.error(f"模拟Query解析失败: {e}")return []# --- 步骤 3: 实现高级搜索逻辑 ---
class AdvancedSearcher:def __init__(self, mock_db: MockMilvus):self.db = mock_dbasync def hybrid_search_with_rerank(self, query_vec, final_top_k=10, recall_multiplier=10):recall_top_k = final_top_k * recall_multiplierlogger.info(f"\n--- [混合搜索] 开始 ---")logger.info(f"阶段 1: ANN 召回 Top {recall_top_k}...")# 1. 召回recall_results = self.db.search(query_vec, limit=recall_top_k, params={"nprobe": 64})candidate_hits = recall_results[0]logger.info(f"召回了 {len(candidate_hits)} 个候选者。")# 打印召回结果的前几个,展示其顺序可能不精确print("召回结果(前5,顺序可能不精确):")for hit in candidate_hits[:5]:print(f"  ID: ...{hit['id'][-12:]}, ANN分数: {hit['distance']:.4f}")# 2. 重排logger.info("阶段 2: 精确重排...")candidate_ids = [hit['id'] for hit in candidate_hits]candidate_entities = self.db.query(f"id in {candidate_ids}", output_fields=['*'])reranked_candidates = []for entity in candidate_entities:exact_score = np.dot(np.array(entity['vector']), query_vec)entity['score'] = float(exact_score)reranked_candidates.append(entity)reranked_candidates.sort(key=lambda x: x['score'], reverse=True)logger.info("重排完成。")return reranked_candidates[:final_top_k]async def range_search_with_rerank(self, query_vec, threshold=0.9, limit=100):logger.info(f"\n--- [范围搜索] 开始 (阈值 > {threshold}) ---")logger.info(f"阶段 1: 范围召回...")# 1. 范围召回recall_results = self.db.search(query_vec, limit=limit, params={"radius": 1.0, "range_filter": threshold})candidate_hits = recall_results[0]logger.info(f"范围召回了 {len(candidate_hits)} 个候选者。")# 2. 重排logger.info("阶段 2: 精确重排...")candidate_ids = [hit['id'] for hit in candidate_hits]candidate_entities = self.db.query(f"id in {candidate_ids}", output_fields=['*'])reranked_candidates = []for entity in candidate_entities:exact_score = np.dot(np.array(entity['vector']), query_vec)if exact_score >= threshold:entity['score'] = float(exact_score)reranked_candidates.append(entity)reranked_candidates.sort(key=lambda x: x['score'], reverse=True)logger.info("重排完成。")return reranked_candidates# --- 步骤 4: 执行与结果展示 ---
async def main():# 1. 构造数据和查询向量mock_data, base_vector = generate_mock_data()# 构造一个与基准向量极其相似的查询向量query_vector = normalize_l2(base_vector + np.random.random(DIMENSION).astype(np.float32) * 0.01)# 2. 初始化模拟环境和搜索器mock_db = MockMilvus(mock_data)searcher = AdvancedSearcher(mock_db)# 3. 执行混合搜索final_hybrid_results = await searcher.hybrid_search_with_rerank(query_vector, final_top_k=5, recall_multiplier=10)print("\n✅ 混合搜索与重排的最终结果 (Top 5):")for i, result in enumerate(final_hybrid_results):is_truly_similar = "similar_image" in result['image_name']print(f"  {i+1}. 名称: {result['image_name']:<20} | 精确分数: {result['score']:.4f} | 是否为已知相似项: {is_truly_similar}")# 4. 执行范围搜索similarity_threshold = 0.98 # 设置一个较高的阈值,理论上只有我们制造的相似向量能满足final_range_results = await searcher.range_search_with_rerank(query_vector, threshold=similarity_threshold)print(f"\n✅ 范围搜索与重排的最终结果 (相似度 > {similarity_threshold}):")for i, result in enumerate(final_range_results):is_truly_similar = "similar_image" in result['image_name']print(f"  {i+1}. 名称: {result['image_name']:<20} | 精确分数: {result['score']:.4f} | 是否为已知相似项: {is_truly_similar}")# 验证:检查是否所有已知的相似项都被找到了found_similar_count = sum(1 for r in final_range_results if "similar_image" in r['image_name'])print(f"\n验证: 在范围搜索中找到了 {found_similar_count} / {SIMILAR_COUNT} 个已知的相似项。")if __name__ == "__main__":asyncio.run(main())

你会看到类似的输出:

INFO:__main__:开始构造模拟数据...
INFO:__main__:数据构造完成,共 2000 条记录。
INFO:__main__:模拟Milvus环境已就绪,数据已加载到内存。--- [混合搜索] 开始 ---
INFO:__main__:阶段 1: ANN 召回 Top 50...
INFO:__main__:[模拟Search] 参数: limit=50, params={'nprobe': 64}
INFO:__main__:召回了 50 个候选者。
召回结果(前5,顺序可能不精确):ID: ...e4d8c83a0b4c, ANN分数: 0.9317ID: ...a9e0f13e1a3e, ANN分数: 0.9998ID: ...7a2b97c234a4, ANN分数: 0.9421ID: ...a59a729e8c4e, ANN分数: 0.9997ID: ...3f4b5a2d1e9f, ANN分数: 0.9288
INFO:__main__:阶段 2: 精确重排...
INFO:__main__:[模拟Query] 表达式 'id in ['e4d8c83a-0b4c-4c...' 命中 50 条记录。
INFO:__main__:重排完成。✅ 混合搜索与重排的最终结果 (Top 5):1. 名称: similar_image_2.jpg  | 精确分数: 0.9999 | 是否为已知相似项: True2. 名称: similar_image_4.jpg  | 精确分数: 0.9998 | 是否为已知相似项: True3. 名称: similar_image_1.jpg  | 精确分数: 0.9998 | 是否为已知相似项: True4. 名称: similar_image_5.jpg  | 精确分数: 0.9997 | 是否为已知相似项: True5. 名称: similar_image_3.jpg  | 精确分数: 0.9997 | 是否为已知相似项: True--- [范围搜索] 开始 (阈值 > 0.98) ---
INFO:__main__:阶段 1: 范围召回...
INFO:__main__:[模拟Search] 参数: limit=100, params={'radius': 1.0, 'range_filter': 0.98}
INFO:__main__:范围召回了 5 个候选者。
INFO:__main__:阶段 2: 精确重排...
INFO:__main__:[模拟Query] 表达式 'id in ['a9e0f13e-1a3e-4d...' 命中 5 条记录。
INFO:__main__:重排完成。✅ 范围搜索与重排的最终结果 (相似度 > 0.98):1. 名称: similar_image_2.jpg  | 精确分数: 0.9999 | 是否为已知相似项: True2. 名称: similar_image_4.jpg  | 精确分数: 0.9998 | 是否为已知相似项: True3. 名称: similar_image_1.jpg  | 精确分数: 0.9998 | 是否为已知相似项: True4. 名称: similar_image_5.jpg  | 精确分数: 0.9997 | 是否为已知相似项: True5. 名称: similar_image_3.jpg  | 精确分数: 0.9997 | 是否为已知相似项: True验证: 在范围搜索中找到了 5 / 5 个已知的相似项。

4.总结与建议

  • 理解权衡:首先要明确,在生产环境中,使用ANN搜索是在速度和100%准确率之间做出的必要权衡
  • 从搜索参数入手:对于您当前的问题,最简单的尝试就是逐步增大您搜索时的nprobe(如果用IVF索引)或ef(如果用HNSW索引)参数,观察召回率是否能满足您的要求,同时监控查询延迟是否在可接受范围内。
  • 评估索引:如果调整搜索参数效果不佳,可以考虑重新构建索引,选择更合适的索引类型和构建参数。
  • 考虑重排:如果对结果的精度要求非常高,引入一个基于ANN的粗召回+精确重排的两阶段策略是业界的常用最佳实践。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92493.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92493.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/92493.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker Neo4j

Day 1 &#xff1a;Docker Desktop 基础熟悉 运行官方 hello-world 测试&#xff1a; docker -run hello-world 运行 Nginx 体验容器暴露端口&#xff1a; docker run -d -p 8080:80 nginx -d --detach 以 分离模式 运行容器 -p --publish 设置 宿主机与容器的端口映射。…

Win10_Qt6_C++_YOLO推理 -(1)MingW-opencv编译

先上效果图&#xff1a; 因为是一个为了尝试跑通的demo&#xff0c;美观、功能都先忽略哈。 一、环境 库版本下载链接备注cmakecmake-4.1.0-rc2-windows-x86_64.msihttps://cmake.org/download/make x86_64-15.1.0-release-posix-seh-ucrt-rt_v12-rev0.7zhttps://github.com/…

day060-zabbix监控各种客户端

文章目录0. 老男孩思想-一个人的背书1. zabbix各种客户端1.1 Windows Server监控1.2 网络设备监控1.3 java应用监控1.4 前端监控java程序故障2. 相关项监控3. 思维导图0. 老男孩思想-一个人的背书 学历、能力、态度、特长、人品、口碑&#xff08;身边的人、领导&#xff09; …

OpenCV 官翻 2 - 图像处理

文章目录色彩空间转换目标色彩空间转换目标追踪如何确定要追踪的HSV值&#xff1f;练习图像的几何变换目标变换缩放翻译旋转仿射变换透视变换其他资源图像阈值处理目标简单阈值化自适应阈值化大津二值化法Otsu二值化算法原理其他资源练习图像平滑处理目标二维卷积&#xff08;图…

动态路由协议基础

一、动态路由协议简介2.动态路由协议的基本功能二、动态路由协议分类对比项距离矢量&#xff08;如 RIP&#xff09;链路状态&#xff08;如 OSPF&#xff09;信息来源只听直接邻居说收集全网链路状态&#xff0c;自己建 “地图”计算逻辑邻居给的距离 1&#xff0c;简单累加用…

netstat -tunlp | grep的作用

​​一、命令整体结构解析​​命令由两部分通过管道符 |连接&#xff1a;netstat -tunlp&#xff1a;核心网络状态统计命令&#xff0c;输出指定类型的网络连接信息&#xff1b;grep&#xff1a;文本搜索工具&#xff0c;用于过滤 netstat的输出结果&#xff0c;仅保留符合特定…

教育数字化革命:低代码破局与未来展望

当下&#xff0c;教育领域正经历前所未有的深刻变革——教育数字化转型。这并非简单的技术叠加&#xff0c;而是从教育理念到模式的全方位重塑&#xff0c;已成为推动教育高质量发展、助力我国迈向教育强国的核心驱动力。数字技术正以前所未有的速度和力度&#xff0c;全方位重…

云服务器磁盘IO性能优化的测试与配置方法

云服务器磁盘IO性能优化的测试与配置方法在云计算环境中&#xff0c;磁盘IO性能直接影响着应用程序的响应速度和系统整体稳定性。本文将深入解析云服务器磁盘IO性能优化的关键技术路径&#xff0c;从测试方法论到配置调整方案&#xff0c;帮助运维人员突破存储瓶颈。我们将重点…

Python Day22 - 复习日

浙大疏锦行 Pythonday22 本周学习内容主要是有关降维的一些内容以及基本的数组操作&#xff1a; 数组的常见操作以及shape聚类算法的选择以及常用评估指标、聚类后的结果分析特征筛选方法&#xff1a;方差筛选、lasso等SVD进行降维常见的降维算法&#xff1a;LDA、PCA等

飞算JavaAI文字需求描述功能:高效驱动项目开发的智能解决方案

在数字化开发浪潮中&#xff0c;如何将模糊的需求快速转化为具体的开发指令&#xff0c;是提升项目效率的关键环节。飞算JavaAI推出的文字需求描述功能&#xff0c;以自然语言交互为核心&#xff0c;为开发者和项目管理者提供了一套高效、精准的需求转化与项目管理方案&#xf…

探索自然语言处理NLP的Python世界

文本预处理&#xff1a;数据清洗与标准化 在自然语言处理&#xff08;NLP&#xff09;的旅程中&#xff0c;文本预处理是至关重要的第一步。原始文本数据往往包含噪声、不一致性以及各种格式问题&#xff0c;直接影响后续模型的性能。文本预处理旨在将文本转化为统一、规范的格…

ECMAScript(简称 ES)和 JavaScript 的关系

ECMAScript&#xff08;简称ES&#xff09;和JavaScript的关系常常令人困惑。简单来说&#xff1a;ECMAScript是标准&#xff0c;JavaScript是实现。以下从多个维度详细解析它们的区别与联系&#xff1a; 一、定义与核心关系ECMAScript 标准化规范&#xff1a;由ECMA国际&#…

笔试——Day16

文章目录第一题题目思路代码第二题题目&#xff1a;思路代码第三题题目&#xff1a;思路代码优化&#xff08;滑动窗口&#xff09;第一题 题目 字符串替换 思路 模拟 当遍历到正常字符时&#xff0c;直接加入结果答案&#xff1b;当遍历到占位符时&#xff0c;按顺序使用arg…

第十四届蓝桥杯青少Scratch国赛真题——太空大战

明天蓝桥杯大赛青少组省赛报名就开始报名了&#xff0c;小伙伴们记得设好闹钟&#xff0c;去抢报呀~&#xff08;去年是名额有限&#xff0c;全靠抢&#xff0c;今年估计也是&#xff0c;大家伙记得快点报名就对了&#xff09;报名通道将于&#x1f4c5;2025年7月23日13&#x…

小玩 Lifecycle

导包 [versions] lifecycle_version "2.3.1"[libraries] androidx-viewmodel { group "androidx.lifecycle", name "lifecycle-viewmodel-ktx", version.ref "lifecycle_version" } androidx-livedata { group "androidx…

HttpSecurity详解

HttpSecurity 是 Spring Security 中用于配置 HTTP 安全性的核心类。它允许你定义各种安全规则和过滤器,以保护 Web 应用程序中的不同 URL 和请求。下面是对 HttpSecurity 中常见配置的详细解析,以及每个配置的意义。 1. csrf 配置: http.csrf(customizers -> customi…

FFmpeg+javacpp中仿ffplay播放

FFmpegjavacpp中仿ffplay播放1、[ffplay 基于 SDL 和 FFmpeg 库的简单媒体播放器](https://ffmpeg.org/ffplay.html)2、FFmpeg帧捕获器 : FFmpegFrameGrabber2.1 grabSamples()2.2 grabImage()2.3 grab() 获取音视频帧FFmpegjavacppjavacv使用 ffmpeg-6.0\fftools\ffplay.c 1、…

【后端】 FastAPI

&#x1f680; FastAPI 是什么&#xff1f;FastAPI 是一个用于构建 Web API 的 Python 框架。可以理解成&#xff1a;&#x1f9f0; “一个工具箱&#xff0c;让你用 Python 写出能被浏览器、App、小程序调用的接口&#xff08;API&#xff09;。”&#x1f527; 那什么是 API&…

不画一张架构图讲透架构思维

&#x1f449;目录1 架构的定义2 架构是为了解无解的问题-分工3 抱残守缺的好架构应该是怎样的4 适可而止的设计、恰如其分的架构与成败论英雄本文深入探讨软件架构的本质与设计方法论&#xff0c;从架构定义演变到现代架构实践挑战&#xff0c;系统分析架构设计面临的业务复杂…

SpringCloudGateWay 使用nacos网关自动负载均衡

安装好nacos后&#xff08;参考以前文章SpringCloud 使用nacos注册服务&#xff0c;使用openFeign调用服务-CSDN博客&#xff09; 新建一个项目&#xff0c;添加 spring-cloud-starter-gateway-server-webmvc spring-cloud-loadbalancer spring-cloud-starter-alibaba-nacos-d…