在日常开发中,我们使用 Redis 时常常会遇到这样的场景:需要存储复杂的结构化数据(比如用户信息、商品详情),还要支持灵活的查询(按年龄筛选、按技能搜索)。直接用 Redis 的基础命令处理 JSON 数据不仅繁琐,查询起来更是头疼。而 Redis OM for Python 的出现,正好解决了这些问题 —— 它让我们能用 Python 类轻松建模,用简洁的代码实现 CRUD 和复杂查询。今天我们就结合 Flask 框架,手把手教你用 Redis OM Python 构建一个文档型 API。
一、Redis OM Python 是什么?
Redis OM Python 是 Redis 官方推出的客户端工具,专为简化 Redis 中的文档数据管理而生。它基于 Pydantic 框架提供数据建模能力,能自动处理:
- 唯一 ID 生成(使用 ULID,比 UUID 更友好)
- Redis 键名的自动生成(无需手动拼接键名)
- JSON 格式的存储与解析
- 索引创建与复杂查询(支持条件组合、范围查询、全文搜索等)
简单说,有了它,我们可以像操作 ORM 一样操作 Redis,无需再手写复杂的 Redis 命令。
二、环境准备与快速启动
在开始之前,我们需要准备好开发环境,一步步搭建起基础框架。
2.1 环境要求
- git:用于克隆示例代码
- Python 3.9+:运行应用
- Redis Stack:提供 JSON 和搜索功能(或安装了 JSON、Search 模块的 Redis)
- curl/Postman:测试 API
- (可选)Redis Insight:可视化查看 Redis 数据
2.2 步骤 1:获取源码并启动 Redis
首先克隆示例仓库,然后启动 Redis Stack(用 Docker 最方便):
bash
# 克隆仓库
git clone https://github.com/redis-developer/redis-om-python-flask-skeleton-app.git
cd redis-om-python-flask-skeleton-app
# 用Docker启动Redis Stack(包含JSON和搜索功能)
docker-compose up -d
如果用 Redis Cloud,需要配置环境变量(替换为你的 Redis 信息):
bash
export REDIS_OM_URL=redis://default:<password>@<host>:<port>
2.3 步骤 2:配置 Python 环境
创建虚拟环境并安装依赖(Flask、Redis OM 等):
bash
# 创建虚拟环境
python3 -m venv venv
# 激活环境(Windows用venv\Scripts\activate)
. ./venv/bin/activate
# 安装依赖
pip install -r requirements.txt
2.4 步骤 3:启动 Flask 应用
以开发模式启动,修改代码后自动重启:
bash
# 设置开发模式
export FLASK_ENV=development
# 启动应用
flask run
此时访问http://127.0.0.1:5000/,能看到主页说明启动成功。
2.5 步骤 4:加载示例数据
运行数据加载脚本,批量添加测试数据到 Redis:
bash
python dataloader.py
输出会显示每个新增用户的 ULID(类似01FX8RMR7NRS45PBT3XP9KNAZH),后续查询会用到这些 ID。
三、核心:用 Redis OM 构建数据模型
Redis OM 的核心是数据建模 —— 通过 Python 类定义数据结构,自动映射到 Redis 的 JSON 文档。我们以Person模型为例,看看如何定义。
3.1 模型定义:Person 与嵌入式 Address
在person.py中,我们定义两个类:Person(主模型)和Address(嵌入式模型):
python
from pydantic import Field
from typing import List, Optional
from redis_om import JsonModel, EmbeddedJsonModel, NotFoundError
# 嵌入式地址模型(作为Person的字段)
class Address(EmbeddedJsonModel):
# 街道号码(必选,索引)
street_number: int = Field(index=True)
# 单元号(可选,不索引)
unit: Optional[str] = Field(index=False)
# 街道名称(必选,索引)
street_name: str = Field(index=True)
# 城市(必选,索引)
city: str = Field(index=True)
# 州/省(必选,索引)
state: str = Field(index=True)
# 邮编(必选,索引)
postal_code: str = Field(index=True)
# 国家(必选,索引,默认英国)
country: str = Field(index=True, default="United Kingdom")
# 人员主模型
class Person(JsonModel):
# 名(必选,索引)
first_name: str = Field(index=True)
# 姓(必选,索引)
last_name: str = Field(index=True)
# 年龄(必选,正整数,索引)
age: int = Field(index=True)
# 地址(必选,嵌入式模型)
address: Address
# 个人陈述(必选,支持全文搜索)
personal_statement: str = Field(index=True, full_text_search=True)
# 技能列表(必选,字符串数组,索引)
skills: List[str] = Field(index=True)
关键说明:
- 继承关系:
Person继承JsonModel(表示作为独立 JSON 文档存储),Address继承EmbeddedJsonModel(表示嵌入到其他模型中,不单独存储)。 - 字段参数:
index=True:为字段创建索引,支持查询。full_text_search=True:开启全文搜索(如personal_statement)。Optional:标记可选字段(如unit),需配合index=False。default:设置默认值(如country默认 "United Kingdom")。
- 自动 ID:Redis OM 自动生成 ULID 作为主键(
pk属性),格式如01FX8SSSDN7PT9T3N0JZZA758G。
四、CRUD 操作实战:从 API 到 Redis 存储
有了模型,我们就能通过简单的代码实现增删改查。下面结合 Flask API 和 curl 命令,逐个演示。
4.1 创建(Create):新增人员数据
代码实现(Flask 路由):
python
# app.py
from flask import Flask, request, jsonify
from person import Person, NotFoundError
app = Flask(__name__)
@app.route("/person/new", methods=["POST"])
def create_person():
# 从请求JSON初始化Person实例
new_person = Person(**request.json)
# 保存到Redis
new_person.save()
# 返回生成的ULID
return new_person.pk
测试(curl 命令):
bash
curl --location --request POST 'http://127.0.0.1:5000/person/new' \
--header 'Content-Type: application/json' \
--data-raw '{
"first_name": "Joanne",
"last_name": "Peel",
"age": 36,
"personal_statement": "Music is my life, I love gigging and playing with my band.",
"address": {
"street_number": 56,
"unit": "4A",
"street_name": "The Rushes",
"city": "Birmingham",
"state": "West Midlands",
"postal_code": "B91 6HG",
"country": "United Kingdom"
},
"skills": ["synths", "vocals", "guitar"]
}'
存储格式:
数据在 Redis 中以 JSON 文档存储,键名格式为:person.Person:<ULID>。用redis-cli查看:
bash
redis-cli
127.0.0.1:6379> json.get :person.Person:01FX8SSSDN7PT9T3N0JZZA758G
返回的 JSON 包含所有字段,包括嵌入式address的详细信息。
4.2 读取(Read):灵活查询数据
Redis OM 的find方法支持多种查询条件,满足不同场景需求。
4.2.1 按 ID 查询
python
@app.route("/person/byid/<id>", methods=["GET"])
def find_by_id(id):
try:
# 通过ID获取实例,转换为字典返回
person = Person.get(id)
return person.dict()
except NotFoundError:
return jsonify({"error": "Not found"}), 404
测试:
bash
curl --location --request GET 'http://localhost:5000/person/byid/01FX8SSSDN7PT9T3N0JZZA758G'
4.2.2 按姓名查询(多条件组合)
python
@app.route("/people/byname/<first>/<last>", methods=["GET"])
def find_by_name(first, last):
# 条件:first_name == first 且 last_name == last
people = Person.find(
(Person.first_name == first) &
(Person.last_name == last)
).all()
return jsonify({"results": [p.dict() for p in people]})
测试(查询 Kareem Khan):
bash
curl --location --request GET 'http://127.0.0.1:5000/people/byname/Kareem/Khan'
4.2.3 按年龄范围查询(带排序)
python
@app.route("/people/byage/<min_age>/<max_age>", methods=["GET"])
def find_in_age_range(min_age, max_age):
# 条件:age >= min_age 且 age <= max_age,按age排序
people = Person.find(
(Person.age >= int(min_age)) &
(Person.age <= int(max_age))
).sort_by("age").all()
return jsonify({"results": [p.dict() for p in people]})
测试(30-47 岁):
bash
curl --location --request GET 'http://127.0.0.1:5000/people/byage/30/47'
4.2.4 按城市和技能查询(数组包含)
python
@app.route("/people/byskill/<skill>/<city>", methods=["GET"])
def find_matching_skill(skill, city):
# 条件:skills包含skill 且 address.city == city
# << 表示“包含”(数组查询)
people = Person.find(
(Person.skills << skill) &
(Person.address.city == city)
).all()
return jsonify({"results": [p.dict() for p in people]})
测试(Sheffield 的吉他手):
bash
curl --location --request GET 'http://127.0.0.1:5000/people/byskill/guitar/Sheffield'
4.2.5 全文搜索个人陈述
python
@app.route("/people/bystatement/<term>", methods=["GET"])
def find_matching_statements(term):
# % 表示全文搜索(匹配包含term的内容)
people = Person.find(Person.personal_statement % term).all()
return jsonify({"results": [p.dict() for p in people]})
测试(搜索含 “play” 的陈述):
bash
curl --location --request GET 'http://127.0.0.1:5000/people/bystatement/play'
注意:全文搜索会匹配近义词(如 “play”“plays”“playing”),非常适合自由文本查询。
4.3 更新(Update):修改已有数据
python
@app.route("/person/<id>/age/<new_age>", methods=["POST"])
def update_age(id, new_age):
try:
# 获取实例,修改字段后保存
person = Person.get(id)
person.age = int(new_age)
person.save()
return "ok"
except NotFoundError:
return "Not found", 404
测试(更新 Kareem 的年龄为 28):
bash
curl --location --request POST 'http://127.0.0.1:5000/person/01FX8RMR7T60ANQTS4P9NKPKX8/age/28'
4.4 删除(Delete):移除数据
python
@app.route("/person/<id>/delete", methods=["POST"])
def delete_person(id):
# 直接删除ID对应的记录
Person.delete(id)
return "ok"
测试(删除 ID 为 01FX8RMR8545RWW4DYCE5MSZA1 的记录):
bash
curl --location --request POST 'http://127.0.0.1:5000/person/01FX8RMR8545RWW4DYCE5MSZA1/delete'
4.5 高级:设置数据过期时间
通过Person.db()获取 Redis 连接,调用expire设置 TTL(生存时间):
python
@app.route("/person/<id>/expire/<seconds>", methods=["POST"])
def expire_by_id(id, seconds):
try:
person = Person.get(id)
# 对实例的键设置过期时间(秒)
Person.db().expire(person.key(), int(seconds))
return "ok"
except NotFoundError:
return "Not found", 404
测试(600 秒后过期):
bash
curl --location --request POST 'http://localhost:5000/person/01FX8RMR82D091TC37B45RCWY3/expire/600'
用redis-cli验证 TTL:
bash
127.0.0.1:6379> ttl :person.Person:01FX8RMR82D091TC37B45RCWY3
五、常见问题排查
- Flask 启动失败:检查 Redis 是否启动(Docker 容器是否运行),Redis Cloud 的
REDIS_OM_URL是否正确。 - 数据加载失败:确保 Flask 应用已启动,
dataloader.py依赖的接口正常。 - 查询无结果:检查条件是否正确(如姓名区分大小写),索引是否设置(
index=True)。
总结
Redis OM for Python 让 Redis 的文档型数据管理变得简单:用 Python 类定义模型,用直观的 API 实现 CRUD 和复杂查询,无需手动拼接 Redis 命令。无论是构建小型 API 还是复杂的文档型应用,它都能大幅提升开发效率。
如果本文对你有帮助,别忘了点赞收藏,关注我,一起探索更高效的开发方式~

1126

被折叠的 条评论
为什么被折叠?



