Python + PostgreSQL + Elasticsearch + Redis 多级缓存架构学习文档
一份从零开始的实战指南,涵盖环境搭建、架构设计、代码实现与数据可视化
📖 目录
- 概述
- Windows 环境部署
- 1.1 Docker Desktop 安装与启动
- 1.2 使用 Docker Compose 一键启动服务
- 1.3 Python 虚拟环境配置(uv)
- 1.4 PyCharm 解释器配置
- 数据接入与可视化
- 2.1 PostgreSQL 接入
- 2.2 Elasticsearch 接入
- 2.3 Redis 接入
- 多级缓存架构设计
- 3.1 三级缓存架构图
- 3.2 各层职责与选型理由
- 3.3 数据流转流程
- 代码实战讲解
- 4.1 项目目录结构
- 4.2 核心代码解析
- 4.3 运行与验证
- 多级缓存一致性与风险分析
- 5.1 写入失败的半失败场景
- 5.2 并发读写竞态条件
- 5.3 缓存雪崩 / 穿透 / 击穿
- 常见问题与避坑指南
- 附录:命令速查表
概述
本教程带你构建一个 Python + PostgreSQL + Elasticsearch + Redis 的多级缓存演示项目。你将学到:
- 在 Windows 上用 Docker Desktop 搭建完整的数据库 / 缓存 / 搜索引擎环境
- 使用 uv 管理 Python 虚拟环境和依赖
- 理解为什么互联网后端需要 "三级缓存"(DB + ES + Redis)
- 亲手写一个
ProductService类,体验 Cache-Aside(旁路缓存) 模式的完整流程 - 通过多种工具(命令行、浏览器、PyCharm 插件)查看各层数据
Windows 环境部署
1.1 Docker Desktop 安装与启动
- 访问 Docker Desktop 官网 下载 Windows 安装包。
- 双击安装,建议勾选 "Use WSL 2 instead of Hyper-V"(性能更好)。
- 安装完成后,重启电脑。
- 启动 Docker Desktop,任务栏右下角出现鲸鱼图标即表示运行成功。
💡 验证:打开 PowerShell 或 Git Bash,执行
docker --version,看到版本号即安装成功。
1.2 使用 Docker Compose 一键启动服务
在项目根目录(如 D:\Project\...\database_learn)创建 docker-compose.yml:
version: '3.8'
services:
postgres:
image: postgres:15
container_name: my_postgres
environment:
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
POSTGRES_DB: mydb
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7
container_name: my_redis
ports:
- "6379:6379"
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
container_name: my_es
environment:
- discovery.type=single-node
- xpack.security.enabled=false # 本地测试关闭安全认证
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
- "9300:9300"
volumes:
pgdata:
启动命令(在 Git Bash 中执行):
docker-compose up -d
验证服务是否就绪:
# 查看容器状态
docker ps
# 检查 PostgreSQL
docker exec -it my_postgres pg_isready -U myuser
# 检查 Elasticsearch(浏览器访问)
# 打开 http://localhost:9200,看到 JSON 响应即正常
# 检查 Redis
docker exec -it my_redis redis-cli ping # 应返回 PONG
🧹 清理:测试完成后,执行
docker-compose down -v停止并删除容器及数据卷。
1.3 Python 虚拟环境配置(uv)
uv 是一个极速的 Python 包和项目管理工具,用 Rust 编写,速度比 pip 快 10~100 倍。
① 安装 uv
在 PowerShell(管理员模式) 中执行:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
安装完成后,关闭并重新打开 Git Bash 或 PowerShell,验证:
uv --version
② 初始化项目
在项目根目录执行:
# 初始化 pyproject.toml
uv init
# 创建虚拟环境(默认在 .venv 目录)
uv venv
# 安装依赖
uv add elasticsearch redis psycopg2-binary
③ 激活虚拟环境(Git Bash)
source .venv/Scripts/activate
激活后,命令行前出现 (.venv) 字样。之后你可以直接使用 python 和 pip。
💡 退出环境:执行
deactivate。
1.4 PyCharm 解释器配置
为了让 PyCharm 识别你的 uv 虚拟环境,需要手动配置解释器:
- 打开 File → Settings → Project: ... → Python Interpreter。
- 点击齿轮图标 ⚙️ → Add... → 选择 Existing environment。
- 在 Interpreter 字段,浏览并选择:
D:\Project\...\database_learn\.venv\Scripts\python.exe
- 勾选 Make available to all projects(可选),点击 OK。
- 等待 PyCharm 索引完成,右下角状态栏应显示
.venv字样。
验证配置是否成功:在 PyCharm 的终端(Terminal)中执行:
where python
如果输出路径包含 .venv\Scripts\python.exe,说明配置正确。
数据接入与可视化
2.1 PostgreSQL 接入
方式一:Git Bash 命令行(psql)
docker exec -it my_postgres psql -U myuser -d mydb
进入后执行:
-- 查看所有表
\dt
-- 查看 products 表结构(字段、类型、约束)
\d products
-- 查看表中的所有数据
SELECT * FROM products;
退出:输入 \q 回车。
方式二:PyCharm 图形化(Database Navigator 插件)
- 安装插件:File → Settings → Plugins,搜索 Database Navigator 并安装,重启 PyCharm。
- 打开 DB Browser:左侧边栏点击 DB Browser 标签(或 View → Tool Windows → DB Browser)。
- 添加数据源:点击 + → PostgreSQL,填写:
| 字段 | 值 |
|---|---|
| Host | localhost |
| Port | 5432 |
| Database | mydb |
| User | myuser |
| Password | mypassword |
- 点击 Test Connection,成功后点击 OK。
- 查看数据:展开 Tables → 右键 products → View Data 或 Edit Data。
⚠️ 首次连接可能提示下载 JDBC 驱动,点击 Download 即可自动完成。
2.2 Elasticsearch 接入
Elasticsearch 通过 RESTful API 交互,因此有多种查看方式。
方式一:浏览器直接访问(最简单)
在浏览器地址栏输入:
http://localhost:9200/products/_search?pretty
返回的 JSON 中,hits.hits 数组里就是所有商品数据。
方式二:Git Bash 命令行(curl)
# 查看所有索引
curl -X GET "http://localhost:9200/_cat/indices?v"
# 查看 products 索引的映射结构(相当于表结构)
curl -X GET "http://localhost:9200/products/_mapping?pretty"
# 查询所有数据(默认10条)
curl -X GET "http://localhost:9200/products/_search?pretty"
# 查询更多数据(size=100)
curl -X GET "http://localhost:9200/products/_search?pretty&size=100"
方式三:图形化工具(GUI)
- Elasticvue:浏览器插件或桌面应用,官网下载
- Kibana:Elastic 官方可视化工具(功能最强大,但需单独下载)
💡 对于快速查看数据,浏览器方式最便捷;如果需要频繁操作,推荐安装 Elasticvue。
2.3 Redis 接入
Redis 是键值对存储,你的缓存键通常是 product:p999。
方式一:Git Bash 命令行(redis-cli)
docker exec -it my_redis redis-cli
进入后执行:
# 查看所有键
KEYS *
# 查看 product:p999 的值(JSON 字符串)
GET product:p999
# 查看键的剩余生存时间(TTL,单位秒)
TTL product:p999
# 退出
exit
方式二:图形化工具(GUI)
Another Redis Desktop Manager(免费开源,推荐)
连接参数:
| 字段 | 值 |
|---|---|
| Host | localhost |
| Port | 6379 |
| Auth | 无(默认空密码) |
连接后即可在界面中浏览所有 Key 及其值。
方式三:PyCharm Python 控制台
在 PyCharm 中打开 Python 控制台,输入:
import redis
r = redis.Redis.from_url("redis://localhost:6379", decode_responses=True)
r.get("product:p999")
会直接打印出缓存的商品数据。
多级缓存架构设计
3.1 三级缓存架构图
下面用 Mermaid 流程图展示三级缓存的整体架构:
flowchart TD
Client[客户端请求] --> App["ProductService
Python 应用层"]
subgraph ReadFlow["读取流程(Cache-Aside 模式)"]
App -->|1. GET product:id| Redis
Redis -->|✅ 命中 → 返回 JSON| App
Redis -->|❌ 未命中| App
App -->|2. GET products/_doc| ES
ES -->|✅ 命中 → 回写 Redis| App
ES -->|❌ 未命中| App
App -->|3. SELECT FROM products| PG
PG -->|补偿写入 ES + 回写 Redis| App
end
subgraph WriteFlow["写入流程(Cache Invalidation 策略)"]
App -->|1. UPSERT| PG
App -->|2. INDEX| ES
App -->|3. DELETE 缓存| Redis
end
Redis[(Redis
缓存层)]
ES[(Elasticsearch
搜索引擎层)]
PG[(PostgreSQL
Source of Truth)]
3.2 各层职责与选型理由
| 层级 | 核心职责 | 优势(为什么用它) | 劣势(为什么不只用它) |
|---|---|---|---|
| PostgreSQL (数据源头) |
事务与持久化 (Source of Truth) |
ACID 事务、数据绝对安全、支持复杂关联查询、强一致性 | 全文搜索慢(LIKE '%xx%' 无法走索引)、高并发读压力大 |
| Elasticsearch (搜索引擎) |
全文搜索与聚合分析 (Search Engine) |
倒排索引,毫秒级响应模糊搜索;支持海量数据聚合(Group By) | 非事务性(最终一致性)、写入成本高(索引刷新消耗 IO)、不支持 JOIN |
| Redis (缓存层) |
热数据加速 | 纯内存操作,读写速度极快(微秒级),极大减轻 DB 和 ES 压力 | 容量有限(内存贵)、不支持复杂查询(只能根据 Key 查 Value) |
如果只用两层,会发生什么?
| 组合 | 问题 |
|---|---|
| DB + Redis(去掉 ES) | 无法实现商品名称模糊搜索,PostgreSQL 的 LIKE '%python%' 会导致全表扫描,百万级数据直接超时 |
| DB + ES(去掉 Redis) | 每次查询都访问 ES,虽然比 DB 快,但扛不住热点商品的千万级 QPS,响应时间从 0.1ms 飙升到 10ms+ |
| ES + Redis(去掉 DB) | ❌ 不可行!ES 不是 ACID 数据库,无法保证数据不丢失,不支持事务 |
3.3 数据流转流程
读取流程(Cache-Aside 模式)
通过时序图可以清晰看到三种不同的读取路径:
sequenceDiagram
participant C as 客户端
participant S as ProductService
participant R as Redis
participant E as Elasticsearch
participant P as PostgreSQL
rect rgb(230, 255, 230)
Note right of C: 场景一:缓存命中(~1ms,最优路径)
C->>+S: get_product("p999")
S->>+R: GET product:p999
R-->>-S: ✅ JSON 数据
S-->>-C: 返回结果
end
rect rgb(255, 245, 230)
Note right of C: 场景二:缓存未命中 → ES命中(~10ms)
C->>+S: get_product("p998")
S->>+R: GET product:p998
R-->>-S: ❌ nil
S->>+E: GET products/_doc/p998
E-->>-S: ✅ 数据
S->>R: SETEX product:p998 30s
S-->>-C: 返回结果
end
rect rgb(255, 230, 230)
Note right of C: 场景三:全未命中 → DB 补偿(~50ms,最慢路径)
C->>+S: get_product("p997")
S->>+R: GET product:p997
R-->>-S: ❌ nil
S->>+E: GET products/_doc/p997
E-->>-S: ❌ 404
S->>+P: SELECT * WHERE id='p997'
P-->>-S: ✅ 数据
S->>E: INDEX 补偿写入 ES
S->>R: SETEX product:p997 30s
S-->>-C: 返回结果
end
写入流程(Cache Invalidation)
sequenceDiagram
participant C as 客户端
participant S as ProductService
participant P as PostgreSQL
participant E as Elasticsearch
participant R as Redis
C->>+S: update_product("p999", "Python实战", 79.90)
S->>+P: UPSERT products SET ...
P-->>-S: ✅ 持久化成功
Note over S: ACID 事务保证数据不丢
S->>+E: INDEX products/_doc/p999
E-->>-S: ✅ 索引建立
Note over S: 倒排索引就绪,支持模糊搜索
S->>+R: DELETE product:p999
R-->>-S: ✅ 缓存失效
Note over S: 旧数据清除
下次读请求自动拉取最新数据
S-->>-C: 更新完成
代码实战讲解
4.1 项目目录结构
database_learn/
├── docker-compose.yml # Docker 编排文件
├── pyproject.toml # 项目依赖清单(uv 管理)
├── uv.lock # 锁定文件
├── README.md
├── src/
│ └── database_learn/ # 核心业务包
│ ├── __init__.py
│ ├── config.py # 连接配置
│ └── service.py # ProductService 业务类
├── scripts/
│ └── demo.py # 完整流程演示脚本
└── docs/
└── learning-wiki.md # 本文档
4.2 核心代码解析
① src/database_learn/config.py —— 连接配置
PG_DSN = "dbname=mydb user=myuser password=mypassword host=localhost port=5432"
REDIS_URL = "redis://localhost:6379"
ES_HOST = "http://localhost:9200"
💡 注意:生产环境应将配置放入环境变量或
.env文件,不应硬编码。
② src/database_learn/service.py —— 核心业务服务
import json
import psycopg2
import redis
from elasticsearch import Elasticsearch, NotFoundError
from .config import PG_DSN, REDIS_URL, ES_HOST
# 全局连接(演示简化,生产应使用连接池)
pg_conn = psycopg2.connect(PG_DSN)
pg_conn.autocommit = True
redis_client = redis.Redis.from_url(REDIS_URL, decode_responses=True)
es_client = Elasticsearch(ES_HOST)
def init_db():
"""初始化 PostgreSQL 表"""
with pg_conn.cursor() as cur:
cur.execute("""
CREATE TABLE IF NOT EXISTS products (
id VARCHAR(50) PRIMARY KEY,
name TEXT NOT NULL,
price DECIMAL(10,2),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
print("✅ PostgreSQL 表已准备")
def init_es():
"""初始化 Elasticsearch 索引和映射"""
if not es_client.indices.exists(index="products"):
es_client.indices.create(index="products", body={
"mappings": {
"properties": {
"id": {"type": "keyword"},
"name": {"type": "text"},
"price": {"type": "float"}
}
}
})
print("✅ Elasticsearch 索引已创建")
else:
print("ℹ️ ES 索引已存在")
class ProductService:
def __init__(self, cache_ttl=30):
self.cache_ttl = cache_ttl
# ---------- 写操作 ----------
def update_product(self, product_id: str, name: str, price: float):
"""写入/更新:DB → ES → 删除缓存(Cache Invalidation)"""
# 1. 写 PostgreSQL(UPSERT)
with pg_conn.cursor() as cur:
cur.execute(
"INSERT INTO products (id, name, price) VALUES (%s, %s, %s) "
"ON CONFLICT (id) DO UPDATE SET "
" name = EXCLUDED.name, "
" price = EXCLUDED.price, "
" updated_at = CURRENT_TIMESTAMP",
(product_id, name, price)
)
print(f"📝 DB 已更新: {product_id}")
# 2. 写 Elasticsearch
es_client.index(index="products", id=product_id, body={
"id": product_id,
"name": name,
"price": price
})
print(f"📄 ES 已索引: {product_id}")
# 3. 删除 Redis 缓存(使旧数据失效)
cache_key = f"product:{product_id}"
redis_client.delete(cache_key)
print(f"🗑️ 缓存已删除: {cache_key}")
# ---------- 读操作 ----------
def get_product(self, product_id: str):
"""读取:缓存 → ES → DB(Cache-Aside 三级回源)"""
cache_key = f"product:{product_id}"
# --- 第1层:查 Redis 缓存 ---
cached = redis_client.get(cache_key)
if cached:
print(f"✅ 缓存命中: {cache_key}")
return json.loads(cached)
# --- 第2层:查 Elasticsearch ---
try:
res = es_client.get(index="products", id=product_id)
data = res["_source"]
print(f"🔍 ES 查询成功: {product_id}")
except NotFoundError:
# --- 第3层:查 PostgreSQL(DB 兜底) ---
print(f"⚠️ ES 未命中,回查 PostgreSQL: {product_id}")
with pg_conn.cursor() as cur:
cur.execute(
"SELECT id, name, price FROM products WHERE id = %s",
(product_id,)
)
row = cur.fetchone()
if not row:
print(f"❌ 商品 {product_id} 不存在(任何层级均无数据)")
return None
data = {"id": row[0], "name": row[1], "price": float(row[2])}
# 补偿写入 ES(保证 ES 数据最终一致)
es_client.index(index="products", id=product_id, body=data)
print(f"📄 从 DB 补偿写入 ES: {product_id}")
# --- 回写 Redis 缓存(设置 TTL) ---
redis_client.setex(cache_key, self.cache_ttl, json.dumps(data))
print(f"💾 缓存已写入: {cache_key} (TTL={self.cache_ttl}s)")
return data
🔑 关键点解读:
- 更新时采用"失效"(Invalidate)策略,而非更新缓存——避免并发写脏数据
- 读取时采用 Cache-Aside 模式:先查缓存,未命中再查 ES/DB,并回写缓存
- DB 兜底 + 补偿写 ES:即使 ES 因同步延迟没有数据,也能从 DB 拉取并补偿写回 ES,保证最终一致性
③ scripts/demo.py —— 运行演示
import json
from database_learn.service import ProductService, init_db, init_es, pg_conn, redis_client, es_client
def main():
print("🚀 初始化数据库和索引...")
init_db()
init_es()
service = ProductService()
test_id = "p999"
print("\n--- 1. 写入商品 ---")
service.update_product(test_id, "Python 从入门到实践", 59.90)
print("\n--- 2. 第一次读取(缓存未命中,走 ES) ---")
data = service.get_product(test_id)
print(f" 结果: {data}")
print("\n--- 3. 第二次读取(应该命中缓存) ---")
data = service.get_product(test_id)
print(f" 结果: {data}")
print("\n--- 4. 更新商品价格(触发缓存删除) ---")
service.update_product(test_id, "Python 从入门到实践", 79.90)
print("\n--- 5. 更新后再次读取(缓存失效,走 ES 拿新数据) ---")
data = service.get_product(test_id)
print(f" 结果: {data}")
# 验证数据库
print("\n--- 6. 验证数据库中的最终数据 ---")
with pg_conn.cursor() as cur:
cur.execute("SELECT id, name, price FROM products WHERE id = %s", (test_id,))
row = cur.fetchone()
print(f" PostgreSQL: id={row[0]}, name={row[1]}, price={row[2]}")
print("\n--- 7. 验证 Redis 缓存状态 ---")
cached = redis_client.get(f"product:{test_id}")
if cached:
print(f" Redis 缓存存在: {json.loads(cached)}")
else:
print(" Redis 缓存已过期或不存在")
print("\n--- 8. 验证 Elasticsearch 数据 ---")
res = es_client.get(index="products", id=test_id)
print(f" Elasticsearch: {res['_source']}")
print("\n🎉 全部验证通过!")
if __name__ == "__main__":
main()
4.3 运行与验证
# 确保 Docker 容器正在运行
docker ps
# 激活虚拟环境(如果尚未激活)
source .venv/Scripts/activate
# 运行演示脚本
python scripts/demo.py
预期输出:
🚀 初始化数据库和索引...
✅ PostgreSQL 表已准备
ℹ️ ES 索引已存在
--- 1. 写入商品 ---
📝 DB 已更新: p999
📄 ES 已索引: p999
🗑️ 缓存已删除: product:p999
--- 2. 第一次读取(缓存未命中,走 ES) ---
🔍 ES 查询成功: p999
💾 缓存已写入: product:p999 (TTL=30s)
结果: {'id': 'p999', 'name': 'Python 从入门到实践', 'price': 59.9}
--- 3. 第二次读取(应该命中缓存) ---
✅ 缓存命中: product:p999
结果: {'id': 'p999', 'name': 'Python 从入门到实践', 'price': 59.9}
--- 4. 更新商品价格(触发缓存删除) ---
📝 DB 已更新: p999
📄 ES 已索引: p999
🗑️ 缓存已删除: product:p999
--- 5. 更新后再次读取(缓存失效,走 ES 拿新数据) ---
🔍 ES 查询成功: p999
💾 缓存已写入: product:p999 (TTL=30s)
结果: {'id': 'p999', 'name': 'Python 从入门到实践', 'price': 79.9}
--- 6. 验证数据库中的最终数据 ---
PostgreSQL: id=p999, name=Python 从入门到实践, price=79.90
--- 7. 验证 Redis 缓存状态 ---
Redis 缓存存在: {'id': 'p999', 'name': 'Python 从入门到实践', 'price': 79.9}
--- 8. 验证 Elasticsearch 数据 ---
Elasticsearch: {'id': 'p999', 'name': 'Python 从入门到实践', 'price': 79.9}
🎉 全部验证通过!
多级缓存一致性与风险分析
本节讨论生产环境中多级缓存架构可能遇到的实际问题。理解这些风险,才能设计出更健壮的系统。
5.1 写入失败的半失败场景
写入流程是 DB → ES → Redis 三步。如果某一步失败,怎么办?
flowchart LR
subgraph Normal["正常流程"]
A[写 DB] --> B[写 ES] --> C[删 Redis]
end
subgraph Partial["部分失败场景"]
A1[写 DB ✅] -->|成功| B1[写 ES ❌]
B1 -->|ES 异常| C1[问题:DB有数据,ES搜不到]
A2[写 DB ✅] -->|成功| B2[写 ES ✅]
B2 -->|成功| C2[删 Redis ❌]
C2 -->|Redis 超时| D2[问题:Redis 残留旧数据]
end
应对策略:
| 失败场景 | 影响 | 解决方案 |
|---|---|---|
| ES 写入失败 | 模糊搜索暂时查不到该商品 | ① 重试机制(队列 + 定时任务补偿)② 读取时 DB 兜底自动补偿写 ES |
| Redis 删除失败 | 旧缓存残留,读取到脏数据 | ① 设短 TTL(如30s),让缓存自动过期 ② 重试删除 |
| DB 写入失败 | 整个操作应回滚 | 前置事务,DB 失败时 ES 不写(或在业务层做回滚) |
当前代码的应对: 读取路径中的 DB 兜底 + 补偿写 ES(第 4.2 节)已经天然处理了"ES 写失败"的场景。
5.2 并发读写竞态条件
Cache-Aside 模式在高并发下有一个经典的竞态问题:
sequenceDiagram
participant A as 请求A(写)
participant B as 请求B(读)
participant R as Redis
participant E as Elasticsearch
participant P as PostgreSQL
Note over A,P: 时间轴 ↓ 并发场景
A->>P: ① 写 DB(新数据: 100元)
A->>E: ② 写 ES
A->>R: ③ 删缓存
Note over R: 此时缓存已删除
B->>R: ④ 读缓存 → nil
B->>P: ⑤ 读 DB → 新数据(100元)✅
Note over B: 场景A:B读到新数据,正确
但另一种交错可能出问题:
sequenceDiagram
participant A as 请求A(写)
participant B as 请求B(读)
participant R as Redis
participant E as Elasticsearch
participant P as PostgreSQL
A->>P: ① 写 DB(新: 100元)
A->>E: ② 写 ES(新: 100元)
A->>R: ③ 删缓存
B->>R: ④ 读缓存 → nil
B->>E: ⑤ 读 ES → 旧: 50元 ❌
Note over B: 如果ES主从同步延迟
可能读到旧数据
B->>R: ⑥ 回写缓存 → 旧: 50元 ❌
Note over R: 缓存被旧数据污染!
解决办法:
- 设短 TTL(如 30~60s),即使误写了旧数据,也能快速自动修正
- 延迟双删(Delayed Double Delete):写操作先删缓存,等几百毫秒再删一次,兜住并发读回写的旧数据
- 如果要求强一致性,读请求走 DB 主库,不用缓存(牺牲性能换一致性)
5.3 缓存雪崩 / 穿透 / 击穿
| 问题 | 现象 | 原因 | 对策 |
|---|---|---|---|
| 缓存穿透 | 请求打到 DB | 查一个根本不存在的 key,缓存和 DB 都没有 | ① 布隆过滤器 ② 缓存空值(None)并设短 TTL |
| 缓存击穿 | 单 key 瞬间压垮 DB | 热点 key 刚好过期,大量并发请求同时回源 | ① 互斥锁(只让一个请求回源)② 热点 key 不过期 |
| 缓存雪崩 | 大量 key 同时过期压垮 DB | 同一时间大量 key TTL 到期,或 Redis 宕机 | ① TTL 加随机偏移 ② Redis 高可用(主从/哨兵)③ 本地缓存兜底 |
常见问题与避坑指南
Q1:为什么没加本地内存缓存(如 Python dict)?
A:本地内存是"单机缓存",在分布式部署(多个 Python 实例)下会导致数据不一致(实例 A 更新了缓存,实例 B 还是旧数据)。生产环境如需本地缓存,需配合消息队列(如 Redis Pub/Sub)同步失效消息,复杂度较高,故入门演示未引入。
Q2:为什么不用 PostgreSQL 替代 Elasticsearch?
A:PostgreSQL 的 B-Tree 索引不支持全文模糊搜索,LIKE '%xx%' 会导致全表扫描,百万级数据下查询超时。Elasticsearch 的倒排索引专为文本搜索设计,毫秒级响应。
Q3:为什么更新时要"删除缓存"而不是"更新缓存"?
A:更新缓存存在并发风险。假设两个请求同时更新同一商品,先更新的数据可能被后更新的旧数据覆盖。而删除缓存 + 被动重建(Cache-Aside) 是更安全的策略——下一次读请求会从 ES/DB 获取最新数据并回写缓存。
Q4:Docker 容器启动后,Python 连接报错"Connection refused"?
A:检查以下几点:
- 容器是否正在运行:
docker ps - 端口映射是否正确:
docker port my_postgres - 是否修改过
docker-compose.yml中的端口映射,导致代码中的端口不匹配
Q5:PyCharm 中 from database_learn.service import ... 报"未解析的引用"?
A:原因是 PyCharm 未将 src/ 目录标记为 Sources Root。解决方法:
- 右键点击
src文件夹 → Mark Directory as → Sources Root - 或通过 File → Settings → Project Structure 手动添加
Q6:Elasticsearch 返回空数据(hits 为空)?
A:检查索引名称是否正确(代码中为 products),或者数据是否已写入。可以先用浏览器访问 http://localhost:9200/_cat/indices?v 查看所有索引列表,确认 products 是否存在。
Q7:运行 uv 命令时提示"不是内部或外部命令"?
A:安装 uv 后需要重新打开终端才能生效。如果仍不行,尝试手动将 uv 安装路径(通常位于 %USERPROFILE%\.local\bin)添加到系统环境变量 PATH 中。
附录:命令速查表
Docker 相关
| 用途 | 命令 |
|---|---|
| 启动所有服务 | docker-compose up -d |
| 查看容器状态 | docker ps |
| 查看容器日志 | docker logs my_postgres |
| 停止并删除所有容器(保留数据) | docker-compose down |
| 停止并删除所有容器(含数据卷) | docker-compose down -v |
PostgreSQL
| 用途 | 命令 |
|---|---|
| 进入 psql 交互 | docker exec -it my_postgres psql -U myuser -d mydb |
| 查看所有表 | \dt |
| 查看表结构 | \d products |
| 查询数据 | SELECT * FROM products; |
Elasticsearch
| 用途 | 命令 / URL |
|---|---|
| 查看所有索引 | curl http://localhost:9200/_cat/indices?v |
| 查询全部数据 | curl http://localhost:9200/products/_search?pretty |
| 查看索引映射 | curl http://localhost:9200/products/_mapping?pretty |
| 浏览器直接查询 | http://localhost:9200/products/_search?pretty |
Redis
| 用途 | 命令 |
|---|---|
| 进入 redis-cli | docker exec -it my_redis redis-cli |
| 查看所有 key | KEYS * |
| 获取值 | GET product:p999 |
| 查看过期时间(秒) | TTL product:p999 |
Python 虚拟环境
| 用途 | 命令 |
|---|---|
| 激活环境(Git Bash) | source .venv/Scripts/activate |
| 退出环境 | deactivate |
| 运行演示脚本 | python scripts/demo.py |
还没有评论,来第一个吧