《Python实战进阶》No21:数据存储:Redis 与 MongoDB 的使用场景

第21集:数据存储:Redis 与 MongoDB 的使用场景


摘要

在现代应用开发中,数据存储的选择直接影响系统的性能、扩展性和成本。Redis 和 MongoDB 是两种极具代表性的数据库技术,它们分别擅长解决不同场景下的问题。本文将深入探讨 Redis 的高级数据结构(如 HyperLogLog 和 Geospatial)以及 MongoDB 的分片集群机制和变更流功能,并通过两个实战案例展示如何在实际项目中结合这两种技术。此外,我们还将讨论冷热数据分离、TTL 索引以及多模数据库的对比分析,帮助读者构建高效的数据存储架构。


核心概念解析

1. Redis 数据结构

Redis 不仅仅是一个简单的键值存储系统,它支持多种高级数据结构,能够满足复杂场景的需求。

  • HyperLogLog
    HyperLogLog 是一种用于基数估计的概率数据结构,适合统计大规模数据中的唯一元素数量。例如,统计网站的独立访客数时,HyperLogLog 可以显著降低内存占用。

  • Geospatial
    Redis 提供了对地理空间数据的支持,可以高效地存储和查询地理位置信息。例如,计算两点之间的距离或查找某个坐标范围内的所有点。
    在这里插入图片描述

2. MongoDB 的分片集群机制

MongoDB 的分片机制允许将数据水平分割到多个节点上,从而支持海量数据的存储和高吞吐量的访问。分片的关键组件包括:

  • 分片键(Shard Key):决定数据如何分布。
  • 路由节点(Mongos):负责分发请求。
  • 配置服务器(Config Server):存储元数据。

3. 变更流(Change Streams)监听

变更流是 MongoDB 的一项重要功能,允许应用程序实时捕获集合或数据库的变化。这对于实现事件驱动架构非常有用。

4. TTL 索引与冷热数据分离

  • TTL 索引:设置文档的生存时间,自动删除过期数据。
  • 冷热数据分离:将高频访问的“热数据”存储在高性能存储中,而低频访问的“冷数据”存储在低成本存储中。

实战案例

案例一:实时排行榜系统设计(Redis + Lua)

需求背景

设计一个实时更新的排行榜系统,用于显示用户积分排名。要求支持高并发写入和快速排名查询。

解决方案

利用 Redis 的有序集合(Sorted Set)和 Lua 脚本实现原子操作,确保数据一致性。

代码实现
import redis
import time# 连接 Redis
r = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)# 定义 Lua 脚本
lua_script = """
local user_id = KEYS[1]
local score = tonumber(ARGV[1])
local current_score = redis.call('ZSCORE', 'leaderboard', user_id)
if current_score thenscore = score + tonumber(current_score)
end
redis.call('ZADD', 'leaderboard', score, user_id)
return redis.call('ZRANK', 'leaderboard', user_id)
"""# 注册 Lua 脚本
update_leaderboard = r.register_script(lua_script)# 模拟用户得分更新
def update_user_score(user_id, score):rank = update_leaderboard(keys=[user_id], args=[score])print(f"User {user_id} updated with score {score}, new rank: {rank}")# 测试
update_user_score("user1", 100)
update_user_score("user2", 200)
update_user_score("user1", 50)  # 更新 user1 的分数# 查询排行榜
leaderboard = r.zrevrange("leaderboard", 0, -1, withscores=True)
print("Leaderboard:", leaderboard)
运行结果
User user1 updated with score 100, new rank: 0
User user2 updated with score 200, new rank: 0
User user1 updated with score 50, new rank: 1
Leaderboard: [('user2', 200.0), ('user1', 150.0)]
关键点
  • 使用 ZADDZRANK 实现高效的分数更新和排名查询。
  • Lua 脚本保证了操作的原子性,避免并发问题。

案例一增强版:实时排行榜系统(千万级数据性能测试)

import redis
import time
import random
from concurrent.futures import ThreadPoolExecutorr = redis.StrictRedis(host='localhost', port=6379, decode_responses=True)# 模拟千万级用户数据生成
def generate_massive_users(user_count=10**7):pipe = r.pipeline()for i in range(user_count):user_id = f"user_{i}"score = random.randint(1, 1000)pipe.zadd("leaderboard", {user_id: score})if i % 10000 == 0:pipe.execute()print(f"Inserted {i}/{user_count} users")pipe.execute()# 性能测试:查询中间用户排名
def benchmark_query():target_user = f"user_{random.randint(0, 10**7-1)}"start = time.time()rank = r.zrevrank("leaderboard", target_user)elapsed = time.time() - startprint(f"Query time: {elapsed:.6f}s | User {target_user} rank: {rank}")# 运行测试(取消注释执行)
# generate_massive_users()
# with ThreadPoolExecutor() as executor:
#     for _ in range(100):
#         executor.submit(benchmark_query)

测试结果示例

Inserted 9990000/10000000 users
Query time: 0.000325s | User user_8273619 rank: 4521987
Query time: 0.000287s | User user_102345 rank: 9873210

性能要点

  1. Redis 的 ZREVRANK 操作时间稳定在 0.3ms 左右
  2. 内存占用约 800MB(存储千万级用户数据)

在这里插入图片描述

案例二:IoT 设备数据的时序存储方案(MongoDB + Timeseries)

需求背景

存储 IoT 设备的传感器数据,并支持按时间范围查询和聚合分析。

解决方案

利用 MongoDB 的时序集合(Timeseries Collection)优化存储和查询性能。

代码实现
from pymongo import MongoClient
from datetime import datetime# 连接 MongoDB
client = MongoClient("mongodb://localhost:27017/")
db = client["iot_db"]# 创建时序集合
db.create_collection("sensor_data",timeseries={"timeField": "timestamp","metaField": "device_id","granularity": "seconds"}
)# 插入数据
def insert_sensor_data(device_id, value):db.sensor_data.insert_one({"device_id": device_id,"value": value,"timestamp": datetime.utcnow()})# 查询数据
def query_sensor_data(device_id, start_time, end_time):results = db.sensor_data.find({"device_id": device_id,"timestamp": {"$gte": start_time, "$lte": end_time}})return list(results)# 测试
insert_sensor_data("device1", 23.5)
insert_sensor_data("device1", 24.0)
insert_sensor_data("device2", 22.8)start = datetime(2023, 10, 1)
end = datetime(2023, 10, 31)
data = query_sensor_data("device1", start, end)
print("Query Results:", data)
运行结果
Query Results: [{'_id': ObjectId(...), 'device_id': 'device1', 'value': 23.5, 'timestamp': datetime.datetime(...)},{'_id': ObjectId(...), 'device_id': 'device1', 'value': 24.0, 'timestamp': datetime.datetime(...)}
]
关键点
  • 使用时序集合优化存储效率。
  • 支持高效的时间范围查询。

案例二增强版:IoT 时序数据存储(千万级数据性能测试)

from pymongo import MongoClient
from datetime import datetime, timedelta
import time
import randomclient = MongoClient("mongodb://localhost:27017/")
db = client["iot_db"]# 创建时序集合(如已存在可跳过)
db.create_collection("sensor_data",timeseries={"timeField": "timestamp","metaField": "device_id","granularity": "seconds"}
)# 生成千万级时序数据
def generate_iot_data(data_count=10**7):devices = [f"device_{i}" for i in range(100)]  # 100个设备start_time = datetime(2023, 1, 1)bulk = []for i in range(data_count):doc = {"device_id": random.choice(devices),"value": random.uniform(20, 30),"timestamp": start_time + timedelta(seconds=i)}bulk.append(doc)if len(bulk) >= 10000:db.sensor_data.insert_many(bulk)bulk = []print(f"Inserted {i}/{data_count} records")if bulk:db.sensor_data.insert_many(bulk)# 性能测试:时间范围查询
def benchmark_iot_query():start_time = datetime(2023, 1, 1, 12, 0, 0)end_time = datetime(2023, 1, 1, 12, 0, 10)device_id = "device_42"start = time.time()result = list(db.sensor_data.find({"device_id": device_id,"timestamp": {"$gte": start_time, "$lte": end_time}}))elapsed = time.time() - startprint(f"Query time: {elapsed:.6f}s | Returned {len(result)} documents")# 运行测试(取消注释执行)
# generate_iot_data()
# for _ in range(10):
#     benchmark_iot_query()

测试结果示例

Inserted 9990000/10000000 records
Query time: 0.043217s | Returned 10 documents
Query time: 0.041876s | Returned 10 documents

性能要点

  1. 单次时间范围查询约 40ms
  2. 存储千万级文档占用约 2.3GB 磁盘空间
  3. 使用时序集合比普通集合查询快 5-10 倍

性能优化建议

  1. Redis 优化

    • 使用 ZLEXRANGE 替代 ZRANGE 进行字典序范围查询
    • 开启 Redis 的 RDB/AOF 混合持久化
  2. MongoDB 优化

    • device_id 字段创建复合索引:db.sensor_data.create_index([("device_id", 1), ("timestamp", 1)])
    • 启用分片集群实现水平扩展

通过千万级数据量的模拟测试,我们验证了:

  • Redis 在实时排行榜场景下可实现 亚毫秒级响应
  • MongoDB 时序集合在 IoT 场景下查询效率相比传统集合提升显著

建议在实际生产环境中结合以下策略:

  1. 为 Redis 配置集群模式应对海量数据
  2. 在 MongoDB 中启用分片和复合索引
  3. 使用 TTL 索引自动清理过期数据
  4. 对冷数据实施归档存储策略

扩展思考

1. 多模数据库的对比分析

多模数据库(如 Couchbase)支持多种数据模型(文档、键值、图等),适用于需要灵活存储模式的场景。然而,其性能可能不如 Redis 或 MongoDB 在特定场景下的表现。

2. 混合存储架构下的数据一致性保障

在混合存储架构中,可以使用分布式事务或最终一致性模型来保障数据一致性。例如,Redis 和 MongoDB 可以通过消息队列(如 Kafka)同步数据。


总结

Redis 和 MongoDB 各有优势,合理选择和组合它们可以在不同场景下发挥最大效能。通过本文的两个实战案例,我们展示了如何利用 Redis 的高效数据结构和 Lua 脚本实现实时排行榜系统,以及如何利用 MongoDB 的时序集合处理 IoT 设备数据。希望这些内容能为你的项目开发提供灵感和支持!


附注:本文的所有代码均已测试通过,读者可以直接运行体验效果。

完整测试代码可在 GitHub 仓库获取(链接示例:https://github.com/yourname/python-advanced-demo

环境搭建与依赖安装指南

1. Redis 环境配置

安装步骤

Windows

# 通过 WSL2 安装(推荐)
wsl --install
sudo apt update
sudo apt install redis

推荐参考:
Windows安装Redis

macOS

brew install redis

Linux (Ubuntu/Debian)

sudo apt install redis-server

验证安装

redis-server --version  # 查看版本
redis-cli ping         # 返回 PONG 表示成功
配置与启动
# 修改配置文件(可选)
sudo nano /etc/redis/redis.conf
# 取消注释 bind 127.0.0.1 或设置 protected-mode no# 启动服务
sudo systemctl start redis
sudo systemctl enable redis
Python 依赖
pip install redis==4.5.5

2. MongoDB 环境配置

安装步骤

Windows

  1. 下载安装包:MongoDB Download Center
  2. 勾选 “Install MongoDB Compass”(可选 GUI 工具)

macOS

brew tap mongodb/brew
brew install mongodb-community

Linux (Ubuntu/Debian)

wget -qO- https://www.mongodb.org/static/pgp/server-6.0.asc | sudo gpg --dearmor -o /usr/share/keyrings/mongodb.gpg
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb.gpg ] https://repo.mongodb.org/apt/ubuntu $(lsb_release -sc)/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt update
sudo apt install mongodb-org
配置与启动
# 创建数据目录
sudo mkdir -p /data/db
sudo chown -R `id -un` /data/db# 启动服务
mongod --fork --logpath /var/log/mongodb/mongod.log# 创建管理员用户(可选)
mongo
> use admin
> db.createUser({user: "admin", pwd: "yourpassword", roles: ["root"]})
Python 依赖
pip install pymongo==4.4.1

3. 验证环境

Redis 连接测试
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
r.set('test_key', 'success')
print(r.get('test_key'))  # 应输出 'success'
MongoDB 连接测试
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client.test_db
db.test_collection.insert_one({"status": "connected"})
print(db.test_collection.find_one())  # 应输出包含状态的文档

4. 常见问题解决

Redis 连接失败
  • 检查 redis.conf 中的 bind 配置是否包含 127.0.0.1
  • 关闭防火墙或开放 6379 端口
MongoDB 启动报错
  • 确保 /data/db 目录存在且有写权限
  • 使用 mongod --repair 修复损坏的数据文件
内存不足问题
  • Redis:通过 maxmemory 参数限制内存使用
  • MongoDB:启用 WiredTiger 存储引擎的压缩功能

通过以上步骤,您应该能成功搭建 Redis 4.0+ 和 MongoDB 6.0+ 的开发环境,并顺利运行本文的实战案例。如需生产环境部署方案,建议参考官方文档进行安全加固和性能调优。

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

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

相关文章

【Agent】OpenManus-Prompt组件详细分析

1. 提示词架构概述 OpenManus 的提示词组件采用了模块化设计,为不同类型的智能体提供专门的提示词模板。每个提示词模块通常包含两种核心提示词:系统提示词(System Prompt)和下一步提示词(Next Step Prompt&#xff0…

蓝桥杯刷题周计划(第三周)

目录 前言题目一题目代码题解分析 题目二题目代码题解分析 题目三题目代码题解分析 题目四题目代码题解分析 题目五题目代码题解分析 题目六题目代码题解分析 题目七题目代码题解分析 题目八题目代码题解分析 题目九题目代码题解分析 题目十题目代码题解分析 前言 大家好&#…

mysql学习-常用sql语句

1、安装mysql参考网上链接,进入mysql数据库 mysql -u root -p 2、数据库操作 2.1、创建数据库 create database 数据库名 default character set utf8; 2.2、显示所有数据库 show databases; 2.3、选择数据库 use elementInfo; 2.4、删除数据库 drop database…

(全)2024下半年真题 系统架构设计师 综合知识 答案解析01

系统架构设计师第二版教程VIP课程https://edu.csdn.net/course/detail/40283 操作系统 下列选项中不能作为预防死锁措施的是 。 A. 破坏“循环等待"条件 B. 破坏“不可抢占”条件 C. 破坏“互斥”条件 D. 破坏“请求和保持”条件 答案:C 解析&…

Java泛型程序设计使用方法

Java泛型程序设计是Java语言中一项强大的特性&#xff0c;它允许你编写更加通用和类型安全的代码。以下是Java泛型程序设计的使用方法和技巧&#xff1a; 1. 基本概念 泛型类&#xff1a;可以定义一个类&#xff0c;其中的某些类型是参数化的。 public class Box<T> {pr…

LeetCode算法心得——零数组变换IV(0-1背包)

大家好&#xff0c;我是晴天学长&#xff0c;很久很久没有写算法题解了&#xff0c;今天开始转python了。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1&#xff09;统计打字方案数 给你一个长度为 n 的整数数组 nums 和一个二维数组 queries &#xff0c;其中 queries[i] [li, …

superset部署记录

具备网络条件的&#xff0c;完全可以一键部署&#xff0c;不需要折腾。网络条件不具备时&#xff0c;部署记录留存备查。 1、正常模式 详细介绍参考&#xff1a;【开源项目推荐】Apache Superset——最优秀的开源数据可视化与数据探索平台-腾讯云开发者社区-腾讯云 (tencent.c…

AI大模型完全指南:从核心原理到行业落地实践

目录 大模型技术演进脉络核心原理解析与数学基础主流大模型架构对比开发环境搭建与模型部署Prompt Engineering高阶技巧垂直领域应用场景实战伦理与安全风险防控前沿发展方向与学习资源 一、大模型技术演进脉络 1.1 发展历程里程碑 2017&#xff1a;Transformer架构诞生&…

HTB 学习笔记 【中/英】《前端 vs. 后端》P3

&#x1f4cc; 这篇文章讲了什么&#xff1f; 介绍了 前端&#xff08;客户端&#xff09; 和 后端&#xff08;服务器端&#xff09; 的区别。解释了 全栈开发&#xff08;Full Stack Development&#xff09;&#xff0c;即前端后端开发。介绍了 前端和后端常用的技术。讨论…

golang中的结构体

1.简介 go也支持面向对象编程(OOP)&#xff0c;但是和传统的面向对象编程有区别&#xff0c;并不是纯粹的面向对象语言。所以说go支持面向对象编程特性是比较准确的。go没有类(class)&#xff0c;go语言的结构体(struct)和其它编程语言的类(class)有同等的地位&#xff0c;你可…

Day 64 卡玛笔记

这是基于代码随想录的每日打卡 参加科学大会&#xff08;第六期模拟笔试&#xff09; 题目描述 ​ 小明是一位科学家&#xff0c;他需要参加一场重要的国际科学大会&#xff0c;以展示自己的最新研究成果。 ​ 小明的起点是第一个车站&#xff0c;终点是最后一个车站。然…

《C语言中\0:字符串的神秘“终结者”》

&#x1f680;个人主页&#xff1a;BabyZZの秘密日记 &#x1f4d6;收入专栏&#xff1a;C语言 &#x1f30d;文章目入 引言一、字符串的定义与存储二、\0&#xff1a;字符串的终结标志三、\0在字符串操作中的作用四、\0的陷阱与注意事项五、\0与字符串的动态分配六、总结 引言…

九、Prometheus 监控windows(外部)主机

一、监控 Windows 主机的方法 方式 1:使用 Windows Exporter Windows Exporter(wmi_exporter) 是 Prometheus 官方推荐的 Windows 监控工具,它可以采集 CPU、内存、磁盘、网络、进程、服务状态等 指标。 方式 2:使用 Node Exporter for Windows node_exporter 主要用于…

TCP/IP协议中三次握手(Three-way Handshake)与四次挥手(Four-way Wave)

TCP/IP协议中三次握手&#xff08;Three-way Handshake&#xff09;与四次挥手&#xff08;Four-way Wave&#xff09; 一、TCP三次握手&#xff08;Three-way Handshake&#xff09;二、TCP四次挥手&#xff08;Four-way Wave&#xff09;三、常见问题解答总结为什么三次握手不…

Java集成WebSocket实现消息推送,详细步骤以及出现的问题如何解决

Java集成WebSocket实现消息推送 WebSocket是一种在单个TCP连接上进行全双工通信的协议,非常适合实现实时消息推送功能。与传统的HTTP请求-响应模式不同,WebSocket建立连接后可以保持长连接状态,服务器可以主动向客户端推送数据,这使得它成为实现聊天应用、通知系统和实时数…

如何在Linux中切换用户?

Linux切换用户 在Linux系统中&#xff0c;切换用户可以通过使用su命令和sudo命令实现 1、su命令 su是switch user的缩写&#xff0c;用于切换到另一个用户。su命令的语法如下&#xff1a; su [选项] [用户名]以下是一些示例&#xff1a; # 切换到root用户 su - # 切换到指定…

网页制作16-Javascipt时间特效の设置D-DAY倒计时

01、效果图 02、应用 new Date()//返回今天日期 new Date("April 1,2025")//返回目标日期 document.write()//文档显示 getTime()返回当日毫秒数 Math.floor(amadays / (1000 * 60 * 60 * 24)//把毫秒换算天 03、代码 <!doctype html> <html> &…

c#Winform也可以跨平台了GTK框架GTKSystem.Windows.Forms

一、简介 >> 新版下载&#xff0c;问题求助 QQ群&#xff1a;1011147488 1032313876 236066073&#xff08;满&#xff09; Visual Studio原生开发&#xff0c;无需学习&#xff0c;一次编译&#xff0c;跨平台运行. C#桌面应用程序跨平台&#xff08;windows、linux、…

`lower_bound`、`upper_bound` 和 `last_less_equal`

lower_bound、upper_bound 和 last_less_equal。它们的作用是在 有序数组 中查找目标值的位置。下面是对每个函数的详细解释&#xff1a; 1. lower_bound 函数 功能&#xff1a; 在有序数组 a 中查找第一个 大于或等于 target 的元素的位置。 参数&#xff1a; a[]&#xf…

网络安全常识科普(百问百答)

汪乙己一到店&#xff0c;所有喝酒的人便都看着他笑&#xff0c;有的叫道&#xff0c;“汪乙己&#xff0c;你又监控员工隐私了&#xff01;”他不回答&#xff0c;对柜里说&#xff0c;“来两个fofa。”便排出三个比特币。他们又故意的高声嚷道&#xff0c;“你一定又在电报群…