3 - 电商问数:开发环境与基础服务准备


本章课程目标:

  • 理解「电商问数」项目为什么使用 uv 管理 Python 环境与依赖。
  • 掌握 uv adduv removeuv sync 这几个最常用命令。
  • 明确本项目依赖哪些基础服务,以及这些服务分别解决什么问题。
  • 了解 Docker Desktop、镜像加速、代理配置和 docker compose 的基本使用方式。

学习建议: 这一章更适合当成环境清单来读。先把项目创建、依赖安装、.env 配置这些后端准备做完,再确认 MySQL、Qdrant、Elasticsearch、Embedding 服务是否能启动。不要急着看业务代码;只要基础服务没跑稳,后面的报错大多都会变成环境问题。

对应代码分支: 03-env-services


1、先建立整体认识

在正式动手之前,建议先知道「电商问数」这套工程大致由哪些部分组成。

当前实际项目仓库是 shopkeeper-agent,前后端代码放在同一个仓库里:

  • 后端入口:shopkeeper-agent/main.py
  • 后端代码:shopkeeper-agent/app/
  • 前端项目:shopkeeper-agent/frontend/

不过这一章的重点会放在后端环境和基础服务准备上,本项目不会涉及前端项目的创建过程,前端代码已经放在当前仓库的 frontend 目录下,后面需要启动前端时,进入这个目录安装依赖并运行即可。

其中:

  • 后端使用 Python 3.14+,并通过 uv 管理虚拟环境和依赖。
  • 前端使用 React + Vite + Tailwind CSS,需要 Node.jspnpm
  • 后端项目运行时还依赖 MySQLQdrantElasticsearchKibana 以及 Embedding 推理服务。

如果只从“这一章先要准备什么”来看,最重要的是下面四件事:

  1. 安装好 Python 3.14+
  2. 安装好 uv
  3. 安装好 Docker Desktop
  4. 准备一个可用的开发工具,例如 PyCharmVS Code

一句话总结:后端靠 uv,前端靠 pnpm,基础服务靠 Docker


2、创建后端项目与使用 uv

2.1 为什么本项目使用 uv

官网:https://docs.astral.sh/uv/

先用一句话理解:uv 是一个用于管理 Python 项目、虚拟环境和依赖的现代化工具。

这一章需要先建立一个认识:这个项目希望尽量采用更贴近企业真实开发的工程化方式,而不是只把代码“能跑起来”。

在 Python 生态里,大家以前比较熟悉的做法通常有两类:

  • 组合使用 venv + pip,其中 venv 负责创建虚拟环境,pip 负责安装依赖。
  • 直接使用 conda,由它统一管理环境和依赖。

而在这个项目里,选择的是 uv。相比传统做法,它更像一套一体化方案:把创建项目、创建虚拟环境、安装依赖、移除依赖、同步环境这些动作统一到了同一套命令里。

它的几个主要优点是:

  • 速度更快uvRust 编写,环境创建和依赖安装通常都比传统方式快。
  • 工程化更顺手:项目初始化、依赖管理和虚拟环境管理统一在一套命令里。
  • 更适合团队协作:依赖会写入 pyproject.toml 和锁文件,方便别人拉到项目后快速同步环境。

一句话概括:在「电商问数」中,uv 既负责管理虚拟环境,也负责管理项目依赖,是后端开发环境的基础工具。

补充理解:uvvenv 是什么关系

你可能会问:既然我们在用 uv,为什么项目目录里看到的却还是 .venv
原因并不复杂:uv 在虚拟环境这一层,本质上仍然是围绕 Python 的 venv 机制来工作的。
也就是说:

  • venv 负责“虚拟环境”这件事
  • uv 负责把“创建环境、初始化项目、安装依赖、锁定版本”这些流程整合起来
    所以项目目录中出现 .venv 是正常的,这不代表没有使用 uv

2.2 安装 uv

在使用 uv 之前,需要先把它安装到系统环境中。

最直接的方式是使用 pip

1
pip install uv

如果下载速度较慢,也可以结合国内镜像:

1
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple uv

如果你使用的是 macOS,请执行:

1
brew install uv

注意:

  • 这一步不需要先激活某个已有虚拟环境
  • 这里安装的是系统层面的 uv 工具
  • 后面创建具体项目时,uv 才会为项目生成独立的 .venv

安装完成后,可以先检查一下:

1
uv --version

如果能够正常输出版本号,就说明 uv 已经可以使用了。

1
2
didilili@DidililiMacBook-Pro shopkeeper-agent % uv --version
uv 0.11.6 (65950801c 2026-04-09 aarch64-apple-darwin)

看到版本号即可,不需要和这里的示例完全一致。

2.3 创建后端项目

这一节主要讲后端项目 shopkeeper-agent 的创建方式。

PyCharm 下载地址:https://www.jetbrains.com/zh-cn/pycharm/

根据你的开发工具情况,可以分成两种场景:

  • PyCharm 已经支持 uv
  • PyCharm 没有 uv 选项,需要手动创建

这里也可以顺手说明一下:
如果你使用的是 VS Code 或其他编辑器,通常也可以直接采用方式二。因为方式二的核心并不是依赖某个特定 IDE,而是先在终端里把 uv 项目创建好,再用你习惯的编辑器打开这个目录。

方式一:在 PyCharm 中直接创建 uv 项目

如果你的 PyCharm 版本较新,并且系统里已经安装了 uv,那么在新建项目时,通常可以直接看到 uv 这个解释器类型选项。

PyCharm 中新建 uv 项目的界面示意

创建时可以按下面这个顺序操作:

  1. 选择 New Project
  2. 在解释器类型中选择 uv
  3. 指定项目目录,例如 shopkeeper-agent
  4. 选择 Python 版本,建议与当前项目保持一致,使用 3.14
  5. 点击 Create

uv 项目创建完成后生成 .venv、pyproject.toml 和 uv.lock 的目录示意

创建完成后,你通常会看到项目目录中自动生成了这些内容:

  • pyproject.toml
  • uv.lock
  • .venv

其中:

  • pyproject.toml 用来描述项目和依赖
  • uv.lock 用来锁定依赖版本
  • .venv 是这个项目自己的虚拟环境

简单来说: pyproject.toml 负责描述“项目需要什么”,uv.lock 负责固定“最终装成什么版本”,.venv 负责隔离“这个项目自己的运行环境”。

方式二:PyCharm 没有 uv 选项时,手动创建项目

如果你在 PyCharm 里看不到 uv 选项,通常是因为 IDE 版本较老。这种情况下也不用担心,完全可以在终端里手动创建,再导入 IDE。

第一步,在你希望放项目的目录下执行:

1
uv init shopkeeper-agent

这个命令的作用,是初始化一个新的 uv 项目目录。

然后进入项目目录:

1
cd shopkeeper-agent

接着创建虚拟环境:

1
uv venv

执行完成后,项目目录里通常会出现 .venv。这时再回到 PyCharm,直接打开这个项目目录即可。
如果 IDE 没有自动选中解释器,就手动把解释器指向项目里的虚拟环境:

  • macOS / Linux 一般是 .venv/bin/python
  • Windows 一般是 .venv\\Scripts\\python.exe

也就是说,即使 PyCharm 没有直接集成 uv,你依然可以:

  1. 先在终端里手动创建 uv 项目
  2. 再用 IDE 打开目录
  3. 最后把解释器切到 .venv 对应的 Python

2.4 常用 uv 命令

在这个项目里,uv 最常用的其实就三类命令:

  • 添加依赖:uv add
  • 移除依赖:uv remove
  • 同步依赖:uv sync

添加依赖

如果你想往当前项目里新增一个依赖,可以使用:

1
2
3
uv add 包名
# 例如
uv add fastapi

执行完成后,依赖会被写入 pyproject.toml

删除依赖

如果你想删除某个依赖,可以使用:

1
2
3
uv remove 包名
# 例如
uv remove fastapi

同步依赖

uv sync 是这个项目里非常重要的一个命令。它的作用可以简单理解成:

根据当前项目的 pyproject.tomluv.lock,把你的本地环境同步到正确状态。

例如,当你拿到别人发来的项目代码时,最常见的做法不是自己一个个安装依赖,而是直接执行:

1
uv sync

这样它就会读取项目中已经声明好的依赖,并自动把环境补齐。

2.5 安装后端依赖

如果你是第一次接触 uv,可以这样理解:

  • uv add 更像“往项目里新增依赖”
  • uv sync 更像“把项目需要的依赖一次性装齐”

手动添加项目依赖:

1
2
3
4
5
# Windows
uv add fastapi[standard] sqlalchemy asyncmy qdrant-client "elasticsearch[async]>=8,<9" langchain langchain-deepseek langchain-huggingface langgraph jieba omegaconf pyyaml loguru cryptography huggingface-hub greenlet python-dotenv
# macOS
uv add "fastapi[standard]" sqlalchemy asyncmy qdrant-client "elasticsearch[async]>=8,<9" langchain langchain-deepseek langchain-huggingface langgraph jieba omegaconf pyyaml loguru cryptography huggingface-hub greenlet python-dotenv

执行完命令以后,在pyproject.toml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[project]
name = "shopkeeper-agent"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"asyncmy>=0.2.11",
"cryptography>=46.0.7",
"elasticsearch[async]>=8,<9",
"fastapi[standard]>=0.135.3",
"greenlet>=3.4.0",
"huggingface-hub>=1.10.1",
"jieba>=0.42.1",
"langchain>=1.2.15",
"langchain-deepseek>=1.0.1",
"langchain-huggingface>=1.2.1",
"langgraph>=1.1.6",
"loguru>=0.7.3",
"omegaconf>=2.3.0",
"python-dotenv>=1.2.2",
"pyyaml>=6.0.3",
"qdrant-client>=1.17.1",
"sqlalchemy>=2.0.49",
]

2.6 核心依赖说明

依赖 主要作用
fastapi[standard] 高性能异步 Web 框架,作为整个服务的 API 入口
sqlalchemy Python 常用 ORM,用统一模型管理数据库读写与事务
asyncmy MySQL 的高性能 asyncio 驱动,配合 SQLAlchemy 实现全异步数据库访问
qdrant-client 向量数据库客户端,用于 Embedding 向量存储与相似度检索
elasticsearch[async] 异步 Elasticsearch 客户端,用于全文检索
langchain LLM 应用编排框架,用于构建 RAG、工具调用和 Prompt 链路
langchain-deepseek LangChain 与 DeepSeek 模型的接入层,用于连接项目实际使用的大模型服务
langchain-huggingface LangChain 与 HuggingFace 模型的桥接层,主要用于 Embedding 或模型加载
langgraph 用图结构构建可控的 Agent 流程
jieba 中文分词与关键词提取工具
omegaconf 分层配置管理工具,适合多环境、多模型、多参数场景
python-dotenv .env 文件读取环境变量,避免把密钥写死在代码中
pyyaml 用于 YAML 文件的读取与写入
loguru 更简洁的日志库,适合工程化项目统一管理日志输出
cryptography 加密与证书基础库,也是很多网络、数据库和安全相关依赖的底层基础
greenlet SQLAlchemy 等异步/同步协作场景中的底层协程支持库
huggingface-hub 用于模型下载以及 HuggingFace 生态接入

现阶段你只需要先知道:

  • fastapisqlalchemyasyncmy 负责后端服务和数据库访问
  • qdrant-clientelasticsearch[async] 负责检索能力
  • langchainlanggraphlangchain-deepseek 负责智能体与大模型编排
  • jiebaomegaconfpyyamlloguru 负责关键词提取、配置管理和日志等基础工程能力

阅读建议: 这部分不需要死记。后面每一章真正用到哪个库,我们再回头看它的作用,会更容易记住。


3、创建后端所需的基础服务

除了前后端代码环境,这个项目还依赖几类基础服务。先在这里建立整体认识,下一章会继续展开这些基础服务的具体配置。

3.1 基础服务总览

本项目主要会用到下面这些服务:

服务 主要作用
MySQL 用于存储数据仓库的元数据信息,包括表信息、字段信息和指标信息;同时也充当业务数据仓库使用,用来模拟真实数仓场景
Qdrant 作为向量数据库,用于存储字段信息和指标信息的向量表示;当用户提问时,系统会通过语义相似度检索出最相关的元数据,为生成 SQL 提供高相关性的上下文
Elasticsearch 对各维度字段的实际取值建立倒排索引;当用户问题中出现“华北地区”“数码品类”这类自然语言描述时,可以通过全文检索匹配到数据库中真实存在的字段取值,为生成 SQL 时的 WHERE 条件或 GROUP BY 分组提供依据
Kibana Elasticsearch 的可视化管理与调试界面,用于索引管理、查询语句调试以及检索效果验证,方便在开发过程中观察和优化检索行为
Text Embedding Inference 用于部署 Embedding 模型推理服务,将元数据文本和用户问题转换成向量表示,为 Qdrant 提供向量数据来源,是语义检索能力的基础

从整体分工上看,它们本质上是一套互相配合的基础服务:MySQL 负责保存结构化数据,Qdrant 负责语义相似度检索,Elasticsearch 负责全文匹配,Kibana 负责调试和验证检索效果,Embedding 服务负责把文本转换成向量。

后端代码负责“组织流程”,而这些基础服务负责“提供数据、检索和向量能力”。

3.2 为什么使用 Docker 启动服务

这里推荐的方式是:尽量通过 Docker 统一启动这些服务

如果你对 Docker 的基础概念、镜像、容器或常见操作还有疑问,建议先参考:

第 8 章 Docker 入门与 Dify 部署常见问题

这样做的好处很直接:

  • 不用在本机分别手动安装 MySQLQdrantElasticsearchKibana
  • 不同同学的环境差异会小很多
  • 后面如果要重建环境,也更方便

课程里的思路其实是:

  • Docker Desktop 作为本机容器运行环境
  • docker compose 一次性启动多项服务

你可以先把 docker compose 理解成:

用一个配置文件同时描述多个容器,然后通过一条命令把它们统一启动起来。

3.3 安装 Docker Desktop

启动基础服务,首先需要安装 Docker Desktop

3.3.1 Windows 环境

在 Windows 上安装时,建议优先使用 WSL 2 作为 Docker 的运行基础。

原因是:

  • Docker 本身更适合运行在 Linux 生态上
  • WSL 2 比较贴近真实 Linux 环境
  • 性能和兼容性通常比旧方案更好

如果需要检查本机是否已经安装 WSL,先执行:

1
wsl --version

如果没有安装,可以参考 Docker Desktop 或微软官方文档先完成 WSL 2 安装,再继续安装 Docker Desktop。

参考文档:https://docs.docker.com/desktop/setup/install/windows-install

3.3.2 macOS 环境

在 macOS 上相对简单一些,直接根据自己的芯片类型选择安装包即可:

  • Apple 芯片选择 Apple Silicon
  • Intel 芯片选择 Intel

安装完成后,打开 Docker Desktop,确认它能正常启动即可。

3.3.3 图形界面和命令行都可以用

安装好 Docker Desktop 后,你既可以通过图形界面观察容器、镜像、卷。

Docker Desktop 图形界面中的容器与资源管理入口

也可以继续使用命令行,例如:

1
docker ps

如果这个命令能够正常输出当前容器列表,就说明 Docker 环境已经基本可用。

3.4 Docker 拉镜像失败怎么办

这里还有一个很实际的问题: 装好 Docker 之后,并不代表你立刻就能顺利执行 docker pull

常见原因是默认镜像源访问不稳定,因此一般有两种解决思路:

  • 配置镜像加速
  • 配置代理

这部分属于 Docker 通用问题,完整解释统一放在 第 8 章 - 网络慢或镜像拉取失败。本节只保留本项目启动前需要知道的操作入口。

3.5 服务启动方式

现在 Docker 已经安装配置完毕,并且启动成功,接下来配置本套项目所需的基础服务。在shopkeeper-agent仓库的根目录下有 docker 目录,里面有以下文件:

  • docker-compose.yaml
  • mysql 初始化脚本
  • elasticsearch 自定义镜像构建文件
  • embedding 模型相关文件

3.5.1 启动前准备 Embedding 模型

这里有一个很容易漏掉的前置步骤:Embedding 模型需要单独下载。

项目通过 Text Embeddings Inference 加载 BAAI/bge-large-zh-v1.5,这个模型文件体积比较大,没有直接提交到 Git 仓库中。也就是说,仓库里有 docker/embedding/ 这个目录约定,但第一次启动基础服务之前,你需要先把模型文件放进去。

项目要求的本地目录是:

1
shopkeeper-agent/docker/embedding/bge-large-zh-v1.5

shopkeeper-agent 项目根目录下,可以使用下面的命令下载:

1
uv run hf download BAAI/bge-large-zh-v1.5 --local-dir docker/embedding/bge-large-zh-v1.5

如果你是手动下载模型,也要把解压后的模型文件放到同一个目录下。目录中至少应该能看到类似下面这些文件:

1
2
3
4
5
config.json
tokenizer.json
tokenizer_config.json
pytorch_model.bin
vocab.txt

后面 docker-compose.yaml 中的 Embedding 服务会通过目录挂载读取这里的模型文件:

1
2
volumes:
- ./embedding/bge-large-zh-v1.5:/models/bge-large-zh-v1.5

如果这个目录不存在,或者里面没有完整模型文件,Embedding 容器即使启动了,也无法正常提供向量化服务。后续构建元数据知识库、字段向量索引、指标向量索引时都会依赖它,所以需要在第一次 docker compose up -d 之前就准备好。

3.5.2 启动基础服务

可以按照以下步骤操作:

  1. 进入 docker-compose.yaml 所在目录
  2. 执行 docker compose up -d 启动整套服务

第一次启动时,时间可能会比较长,因为需要下载镜像,之后再启动时,时间会非常快。

执行 docker compose up -d 启动整套基础服务的终端界面

启动成功后,可以在 Docker Desktop 中查看容器运行状态:

执行 docker compose up -d 启动整套基础服务的终端界面

如果要停止并删除这组容器,则执行:

1
docker compose down

如果只是临时停止,也可以使用:

1
docker compose stop

总结:

  • docker compose up -d
    后台启动整套服务
  • docker compose stop
    临时停止服务,但保留容器
  • docker compose down
    停止并删除容器

upstopdowndown -v 的区别见 第 8 章 - 常用 Compose 命令。这里尤其要记住:如果只是停止本项目基础服务,不要随手执行 docker compose down -v,它会删除命名卷中的 MySQL、Elasticsearch、Qdrant 数据。


4、docker-compose.yaml 文件分析

4.1 基本理解

项目对应文件路径:shopkeeper-agent/docker/docker-compose.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
services:
mysql:
image: mysql:8.0
container_name: mysql
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: dili123
MYSQL_USER: didilili
MYSQL_PASSWORD: dili123
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./mysql:/docker-entrypoint-initdb.d
command: --character-set-server=utf8mb4
--collation-server=utf8mb4_general_ci

elasticsearch:
build: ./elasticsearch
container_name: elasticsearch
restart: unless-stopped
environment:
discovery.type: single-node
xpack.security.enabled: "false"
ports:
- "9200:9200"
volumes:
- es_data:/usr/share/elasticsearch/data
kibana:
image: kibana:8.19.10
container_name: kibana
restart: unless-stopped
environment:
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
ports:
- "5601:5601"
depends_on:
- elasticsearch

qdrant:
image: qdrant/qdrant:v1.16
container_name: qdrant
restart: unless-stopped
ports:
- "6333:6333" # HTTP
- "6334:6334" # gRPC
volumes:
- qdrant_data:/qdrant/storage

embedding:
image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.8
platform: linux/amd64
container_name: embedding
restart: unless-stopped
ports:
- "8081:80"
environment:
MODEL_ID: /models/bge-large-zh-v1.5
MAX_CONCURRENT_REQUESTS: "16"
MAX_BATCH_TOKENS: "16384"
volumes:
- ./embedding/bge-large-zh-v1.5:/models/bge-large-zh-v1.5

volumes:
mysql_data:
es_data:
qdrant_data:

如果只把 docker compose up -d 理解成“一条启动命令”,其实会错过很重要的一层工程认识。Compose 文件不是单纯为了少敲几条命令,而是用一份配置把“这个项目依赖哪些服务、这些服务怎么启动、端口怎么映射、数据怎么持久化、目录怎么挂载”统一描述清楚。

在「电商问数」里,这份 Compose 文件可以理解成:一份基础服务清单。也就是说,它描述的不是某一个容器,而是“让这个项目跑起来,需要哪几类容器协同工作”。

4.2 基础服务说明

  • MySQL 容器:负责元数据库和模拟数据仓库
  • Elasticsearch 容器:负责全文检索
  • Kibana 容器:负责 Elasticsearch 的可视化操作
  • Qdrant 容器:负责向量检索
  • Embedding 服务容器:负责把文本转换成向量

把这些服务合在一起看,就会发现这份文件准备的其实不是零散的 5 个容器,而是三类基础能力:

  • 结构化数据能力:MySQL
  • 全文检索能力:Elasticsearch + Kibana
  • 向量化与向量检索能力:Embedding 服务 + Qdrant

这也是为什么说它很重要。后面的问数流程、指标匹配、检索召回,并不是只靠业务代码完成的,而是建立在这几类基础能力已经准备好的前提下。

4.3 配置项具体说明

如果你顺着这份 Compose 文件往下看,最值得先掌握的是下面几类配置:

  • image
    表示直接使用现成镜像,例如 mysql:8.0kibana:8.19.10qdrant/qdrant:v1.16

  • build
    表示不是直接使用现成镜像,而是先根据当前目录中的 Dockerfile 自行构建镜像。这里的 elasticsearch 使用 build: ./elasticsearch,就是因为通常还需要额外安装中文分词插件。

  • container_name
    用来给容器指定一个更直观的名字,后面在 Docker Desktop 或命令行里查看时会更方便。

  • restart: unless-stopped
    表示容器如果因为异常原因停止,会自动重启;只有你手动停止它时,才不会继续自动拉起。

  • environment
    用来给容器注入启动时所需的环境变量。例如:

    • MySQL 这里配置了 MYSQL_ROOT_PASSWORDMYSQL_USERMYSQL_PASSWORD,用于初始化数据库密码和普通用户。
    • Kibana 这里通过 ELASTICSEARCH_HOSTS 连接到 Elasticsearch。
    • Embedding 服务这里通过 MODEL_ID 指定加载的模型目录。
  • ports
    用来做宿主机和容器之间的端口映射。比如:

    • 3306:3306 表示本机通过 3306 访问 MySQL
    • 9200:9200 表示本机通过 9200 访问 Elasticsearch
    • 5601:5601 表示本机通过 5601 打开 Kibana
    • 6333:63336334:6334 分别对应 Qdrant 的 HTTP 和 gRPC 端口
    • 8081:80 表示本机通过 8081 访问 Embedding 服务
  • volumes
    用来做数据持久化或目录挂载。这一项很重要,因为如果不挂载,容器删掉之后,里面的数据或文件通常也会一起丢失。

    这里先分清两种最常见的写法:

    • 命名卷(named volume)
      形如 mysql_data:/var/lib/mysql。左边的 mysql_data 不是当前目录下的文件夹,而是 Docker 管理的一块持久化存储;右边的 /var/lib/mysql 是容器里的目录。

    • 目录挂载(bind mount)
      形如 ./mysql:/docker-entrypoint-initdb.d。左边的 ./mysql 是你当前项目目录下的真实文件夹;右边的 /docker-entrypoint-initdb.d 是容器里的目录。

    这两种写法的核心区别可以记成一句话:

    • 命名卷更适合存“容器运行后产生的数据”
    • 目录挂载更适合把“宿主机现成的文件”交给容器使用

    也可以再补一个最直接的判断口诀:

    • 左边如果是 ./xxx../xxx/绝对路径/xxx,通常就是目录挂载
    • 左边如果只是 mysql_dataes_dataqdrant_data 这种名字,通常就是命名卷

    这里还有一个很关键但也最容易混淆的点:

    • 删除容器,不等于删除命名卷
    • 删除容器,通常也不等于删除宿主机上的挂载目录

    也就是说,只要你删掉的是容器本身,而没有把对应的 volume 或宿主机目录一起删掉,那么数据通常还在。真正容易导致数据丢失的,往往不是“删容器”这一步,而是把 volume 一并删除,或者手动删掉宿主机上的挂载目录。

    结合当前这份 docker-compose.yaml,可以逐条这样理解:

    • mysql_data:/var/lib/mysql
      这是 MySQL 的数据目录持久化。MySQL 真正的数据文件会写到容器内的 /var/lib/mysql,而 mysql_data 负责把这份数据长期保存下来。这样即使把 MySQL 容器删掉,只要没有把这个 volume 一起删掉,数据库数据通常还在。

    • ./mysql:/docker-entrypoint-initdb.d
      这是把项目里的初始化 SQL 脚本挂进 MySQL 容器。MySQL 官方镜像第一次启动时,会自动执行 /docker-entrypoint-initdb.d 目录下的脚本,所以这里的作用不是“保存数据”,而是“把建库建表和初始化数据脚本交给容器执行”。

    • es_data:/usr/share/elasticsearch/data
      这是 Elasticsearch 的数据持久化目录。如果不挂载,索引数据会跟着容器生命周期走;容器一删,全文检索的数据也容易丢。

    • qdrant_data:/qdrant/storage
      这是 Qdrant 的向量数据持久化目录。它和 MySQL、Elasticsearch 的思路一样,都是把检索所需的数据留在 Docker 的持久化存储里,而不是只留在容器内部。

    • ./embedding/bge-large-zh-v1.5:/models/bge-large-zh-v1.5
      这是把宿主机上的本地 Embedding 模型目录挂进容器。这里挂载的不是数据库数据,而是模型文件本身。容器启动后,会从 /models/bge-large-zh-v1.5 读取模型权重。

    另外,底部这段:

    1
    2
    3
    4
    volumes:
    mysql_data:
    es_data:
    qdrant_data:

    表示这份 Compose 文件额外声明了 3 个命名卷。它的含义是:上面各服务在使用这些 volume,这里则是统一把它们定义出来。

    如果把 volumes 再压缩成一句最容易记忆的话,可以这样理解:

    • 左边决定“数据或文件从哪里来”
    • 右边决定“容器到哪里去读或写”
  • depends_on
    用来表达服务之间的依赖关系。比如 kibana 依赖 elasticsearch,它的意思不是“等 Elasticsearch 一切业务状态都完全正常后再启动”,而是“先把依赖服务启动起来,再启动当前服务”。

你可以按这个顺序去读一份 Compose 文件:

  1. services 里一共定义了哪些服务
  2. 看每个服务是 image 还是 build
  3. ports,确认本机通过哪些端口访问
  4. volumes,确认哪些数据会落到宿主机
  5. environment,确认这个服务启动时依赖什么配置
  6. depends_on,确认服务之间的依赖关系

4.4 工程思路具体分析

再结合各服务本身来看,这份 Compose 文件还体现了几个很重要的工程思路:

  • MySQL 不只是启动了一个数据库容器
    它还通过 ./mysql:/docker-entrypoint-initdb.d 挂载了初始化脚本目录。这样容器第一次启动时,就会自动执行其中的 SQL 文件,把 meta 数据库、dw 数据库、相关表结构以及模拟数据一起初始化好。

  • MySQL 的数据目录被单独持久化了
    mysql_data:/var/lib/mysql 的作用,是把 MySQL 真正存数据的目录挂载出来。这样即使容器被删除,数据也不会因为容器消失而丢失。

  • Elasticsearch 使用 build 而不是 image
    这是因为项目里的全文检索是中文场景,通常需要额外安装 IK 分词器,所以不能直接拿一个原生镜像就结束。

  • Kibana 本质上是 Elasticsearch 的调试界面
    它不是必需的核心存储服务,但在开发阶段非常有用,因为可以直接通过 Web 页面观察索引、执行查询和验证检索结果。

  • Qdrant 同时开放了两类端口
    一个是更常用的 HTTP 端口,一个是 gRPC 端口。教学项目里通常优先使用 HTTP 即可,但把两个端口都开放出来,后续扩展会更方便。

  • Embedding 服务被单独部署成了一个独立容器
    这意味着“文本转向量”这件事不是耦合在业务代码里完成的,而是通过一个单独的推理服务来提供接口,这样后端代码只需要调用服务即可。

    这里也可以补充理解一下:Embedding 并不是只能用这种“独立服务”的方式来部署。它也可以直接耦合在业务代码里,也就是由后端程序自己加载模型、自己完成文本转向量的过程。只是当前这套教程为了让工程结构更清晰、服务职责更独立,选择了“单独部署成推理服务”这条方案。

    这里的容器里运行的,也不是我们平时理解的那种“对话大模型”,而是一个专门负责把文本转换成向量的 Embedding 模型。它的职责不是生成回答,而是把字段说明、指标说明、用户问题等文本编码成向量,供后续的向量检索使用。

  • Embedding 服务挂载了本地模型目录
    ./embedding/bge-large-zh-v1.5:/models/bge-large-zh-v1.5 的作用,是把本地模型权重映射到容器中,再通过 MODEL_ID 告诉服务从哪里加载模型。

  • 教学环境使用的是 CPU 版本的 Embedding 服务
    这样做主要是为了降低本地部署门槛。真实生产环境中,如果追求更高性能,通常会改用 GPU 版本。

如果把这一整段压缩成一句更容易记忆的话,可以这样理解:

这份 Compose 文件的价值,不只是启动容器,而是把“数据库、检索、向量化”这一整套基础能力一次性准备好。


本章小结:

这一章的目标,是先把开发环境的整体思路建立起来。你需要知道后端为什么使用 uv、项目可以如何创建、常用依赖命令有哪些、为什么课程里推荐用 Docker 统一启动服务,只要这一层认知搭好了,下一章进入基础服务配置学习时,你就不会觉得“代码是一套、环境又是另一套”。