Redis 存储断点续传文件状态的最佳实践

server/2024/12/24 3:32:40/

目录

      • 1. Redis 中存储文件上传状态
        • 使用 Hash 存储文件状态
        • 存储分块上传状态
        • 使用 TTL 进行状态过期管理
      • 2. Redis 与数据库保持一致
        • 方法 1:定期同步
        • 方法 2:实时同步
        • 方法 3:双写机制
        • 方法 4:系统重启后的恢复
      • 3. 一致性保障
      • 4. 总结
      • 5. 代码实践
        • 5.1 在 Redis 中存储文件上传状态
        • 5.2 存储已上传的分块状态
        • 5.3 数据同步到数据库

在断点续传系统中,如何高效地存储和更新文件上传状态是关键。得益于 Redis 高效的内存操作和多种数据结构的支持,它非常适合用于存储上传过程中的临时状态信息。下面,我们将探讨如何利用 Redis 实现文件上传状态的存储,并与数据库保持一致,确保系统的稳定与数据的可靠。

1. Redis 中存储文件上传状态

Redis 提供了丰富的数据结构,可以灵活地存储和更新文件上传的各类状态。以下是几种常见的实现方式。

使用 Hash 存储文件状态

在 Redis 中,每个文件的上传状态可以使用一个独特的键(如 file_id 或者用户 ID + 文件名的组合)来标识,所有与文件上传相关的数据(如已上传字节数、文件总大小、已上传的分块等)则存储在一个 Hash 表中。例如:

Key: file_upload:<file_id>
Fields:- uploaded_size: 已上传的字节数- file_size: 文件的总大小- chunks: 已上传的分块索引列表- status: 当前上传状态(如 "uploading", "paused", "completed")- last_update_time: 上次更新的时间
存储分块上传状态

对于大文件分块上传,Redis 的集合(Set)或者列表(List)可以存储每个已上传的分块。比如:

Key: file_chunks:<file_id>
Set: {chunk_1, chunk_2, chunk_3, ...}

这样,每个上传的分块都会被记录,上传状态能被精准地追踪和管理。

使用 TTL 进行状态过期管理

对于文件上传的临时状态,可以设置适当的过期时间。比如,当文件上传完成后,自动清理 Redis 中的状态数据:

EXPIRE file_upload:<file_id> 86400  # 设置该文件状态一天后过期

这样避免了无用数据的长期占用内存。

2. Redis 与数据库保持一致

尽管 Redis 高效且快速,但它毕竟是内存数据库,系统重启或故障时,存储的数据可能会丢失。因此,将 Redis 中的断点续传状态与数据库中的持久化数据保持一致显得尤为重要。

方法 1:定期同步

最简单的方式是通过定时任务(如 Cron Job)定期将 Redis 中的上传状态同步到数据库。可以设置一个后台服务,每隔一定时间(如每小时)扫描 Redis 中所有的上传状态,将其写入数据库

数据库表设计

CREATE TABLE file_upload_status (file_id VARCHAR(255) PRIMARY KEY,uploaded_size BIGINT,file_size BIGINT,chunks TEXT,  -- 存储已上传的分块信息,格式为 JSONstatus ENUM('uploading', 'paused', 'completed'),last_update_time DATETIME
);
方法 2:实时同步

如果需要更高的实时性,可以采用实时同步的方法。每当 Redis 中某个文件的上传状态发生变化时,立即同步到数据库。可以使用消息队列(如 Kafka 或 RabbitMQ)来异步处理同步任务,或者直接在代码中同步更新。

例如:

  1. 更新 Redis 中的状态时,触发异步任务。
  2. 利用 Redis 的 Keyspace Notifications(键空间通知)来监听 Redis 中键的变化,并自动将变化同步到数据库
方法 3:双写机制

双写机制是在每次更新 Redis 时,直接同步更新数据库。这种方式确保了每次写操作都会同时影响 Redis 和数据库,从而避免了数据的不一致。

例如,在更新文件上传进度时:

MULTI  # Redis 事务
HSET file_upload:<file_id> uploaded_size 1024
EXEC-- 同时更新数据库
UPDATE file_upload_status SET uploaded_size = 1024 WHERE file_id = '<file_id>';
方法 4:系统重启后的恢复

为了在系统重启后能够恢复上传状态,可以在系统启动时从数据库加载上传状态,并同步到 Redis。这样即使服务重启,断点续传的状态也不会丢失。

for record in db.query("SELECT * FROM file_upload_status WHERE status = 'uploading'"):redis.hmset(f"file_upload:{record['file_id']}", {"uploaded_size": record['uploaded_size'],"file_size": record['file_size'],"status": record['status']})

3. 一致性保障

为了确保 Redis 和数据库中的数据一致性,我们可以采用以下策略:

  1. 事务控制:确保 Redis 和数据库的写入操作在同一个事务中完成,以保证数据的一致性。
  2. 消息队列:通过消息队列记录 Redis 的变更事件,再由后台服务同步到数据库,从而避免直接操作数据库带来的性能瓶颈。
  3. 幂等性设计:确保每次操作是幂等的,即即使重复执行,数据也不会出现冲突或不一致。
  4. 定期数据对账:定期对 Redis 和数据库中的数据进行比对,确保一致性。如果发现不一致,可以触发修复机制。

4. 总结

Redis 作为临时存储,能高效地支持断点续传系统的状态管理。结合定时同步、实时更新或双写机制,能够确保 Redis 和数据库中的数据保持一致性。在实现时,我们还要注意一致性保障,避免因 Redis 失效或重启导致的数据丢失。

5. 代码实践

5.1 在 Redis 中存储文件上传状态

首先,我们需要在 Redis 中为每个文件的上传状态创建一个 Hash 表来记录文件的状态。假设我们正在上传一个大文件,采用分块上传。

#include <hiredis/hiredis.h>
#include <iostream>
#include <string>// 连接 Redis
redisContext* connectToRedis() {redisContext* c = redisConnect("127.0.0.1", 6379);if (c == NULL || c->err) {if (c) {std::cerr << "Redis connection error: " << c->errstr << std::endl;} else {std::cerr << "Unable to allocate redis context\n";}exit(1);}return c;
}// 设置文件上传状态
void setFileUploadStatus(redisContext* c, const std::string& file_id, size_t uploaded_size, size_t file_size, const std::string& status) {redisReply* reply = (redisReply*)redisCommand(c, "HSET file_upload:%s uploaded_size %zu file_size %zu status %s",file_id.c_str(), uploaded_size, file_size, status.c_str());freeReplyObject(reply);
}int main() {redisContext* c = connectToRedis();std::string file_id = "file123";size_t uploaded_size = 5000;  // 已上传 5000 字节size_t file_size = 10000;     // 文件总大小 10000 字节std::string status = "uploading";  // 上传状态:正在上传// 更新 Redis 中的文件状态setFileUploadStatus(c, file_id, uploaded_size, file_size, status);redisFree(c);return 0;
}
5.2 存储已上传的分块状态

对于分块上传,可以在 Redis 中使用 Set 来记录已上传的分块。

// 添加已上传分块到 Redis Set
void addUploadedChunk(redisContext* c, const std::string& file_id, const std::string& chunk_id) {redisReply* reply = (redisReply*)redisCommand(c,"SADD file_chunks:%s %s", file_id.c_str(), chunk_id.c_str());freeReplyObject(reply);
}int main() {redisContext* c = connectToRedis();std::string file_id = "file123";std::string chunk_id = "chunk_1";  // 上传的第一个分块// 将已上传的分块存储到 Redis Set 中addUploadedChunk(c, file_id, chunk_id);redisFree(c);return 0;
}
5.3 数据同步到数据库

将 Redis 中的状态同步到 MySQL 数据库,以确保持久化存储的一致性。

#include <mysql/mysql.h>// 连接 MySQL 数据库
MYSQL* connectToDatabase() {MYSQL* conn = mysql_init(NULL);if (conn == NULL) {std::cerr << "mysql_init() failed\n";exit(1);}conn = mysql_real_connect(conn, "localhost", "root", "password", "file_upload", 3306, NULL, 0);if (conn == NULL) {std::cerr << "mysql_real_connect() failed\n";exit(1);}return conn;
}// 将文件上传状态同步到数据库
void syncToDatabase(MYSQL* conn, const std::string& file_id, size_t uploaded_size, size_t file_size, const std::string& status) {std::string query = "UPDATE file_upload_status SET uploaded_size = " + std::to_string(uploaded_size) + ", file_size = " + std::to_string(file_size) + ", status = '" + status + "' WHERE file_id = '" + file_id + "'";if (mysql_query(conn, query.c_str())) {std::cerr << "MySQL query failed: " << mysql_error(conn) << std::endl;}
}int main() {MYSQL* conn = connectToDatabase();std::string file_id = "file123";size_t uploaded_size = 5000;size_t file_size = 10000;std::string status = "uploading";// 将文件上传状态同步到数据库syncToDatabase(conn, file_id, uploaded_size, file_size, status);mysql_close(conn);return 0;
}

通过这种方式,我们可以实现高效、稳定的断点续传系统,同时确保 Redis 和数据库中的数据一致性。


http://www.ppmy.cn/server/152117.html

相关文章

用C#(.NET8)开发一个NTP(SNTP)服务

完整源码&#xff0c;附工程下载&#xff0c;工程其实也就下面两个代码。 想在不能上网的服务器局域网中部署一个时间服务NTP&#xff0c;当然系统自带该服务&#xff0c;可以开启&#xff0c;本文只是分享一下该协议报文和能跑的源码。网上作为服务的源码不太常见&#xff0c;…

第十七章:反射+设计模式

一、反射 1. 反射(Reflection)&#xff1a;允许在程序运行状态中&#xff0c;可以获取任意类中的属性和方法&#xff0c;并且可以操作任意对象内部的属 性和方法&#xff0c;这种动态获取类的信息及动态操作对象的属性和方法对应的机制称为反射机制。 2. 类对象 和 类的对象(实…

[Unity Shader]【图形渲染】 数学基础4 - 矩阵定义和矩阵运算详解

矩阵是计算机图形学中的重要数学工具,尤其在Shader编程中,它被广泛用于坐标变换、投影变换和模型动画等场景。本文将详细介绍矩阵的定义、基本运算以及如何在Shader中应用矩阵,为初学者打下坚实的数学基础。 一、什么是矩阵? 矩阵是一个由数字排列成的长方形数组,通常记作…

旋转目标检测数据格式转换:RoLabelImg 与 DOTA 格式

引言 在旋转目标检测任务中&#xff0c;数据格式的选择与处理对于模型的训练至关重要。本文将介绍两种常见的旋转目标检测数据格式&#xff1a;RoLabelImg 格式 和 DOTA 格式&#xff0c;并详细说明二者之间的转换方法及其实现代码。 数据格式简介 1. RoLabelImg 格式 RoLa…

【数据库系列】MongoTemplate 基本入门:MongoDB 的增删改查

MongoDB 是一种流行的 NoSQL 数据库&#xff0c;适合存储大量的非结构化数据。在 Spring 框架中&#xff0c;MongoTemplate 提供了一种方便的方式来与 MongoDB 进行交互&#xff0c;支持基本的增删改查操作。本文将详细介绍 MongoTemplate 的基本用法&#xff0c;包含语法介绍和…

[数据结构] 链表

目录 1.链表的基本概念 2.链表的实现 -- 节点的构造和链接 节点如何构造? 如何将链表关联起来? 3.链表的方法(功能) 1).display() -- 链表的遍历 2).size() -- 求链表的长度 3).addFirst(int val) -- 头插法 4).addLast(int val) -- 尾插法 5).addIndex -- 在任意位置…

进程间通信方式---消息队列(System V IPC)

进程间通信方式—消息队列&#xff08;System V IPC&#xff09; 文章目录 进程间通信方式---消息队列&#xff08;System V IPC&#xff09;消息队列1.消息队列进程间通信原理2.msgget 系统调用3.msgsnd 系统调用4.msgrcv 系统调用5.msgctl 系统调用6.函数使用案例7.实现生产者…

Java学习教程,从入门到精通,Java LinkedList(链表)语法知识点及案例代码(62)

Java LinkedList&#xff08;链表&#xff09;语法知识点及案例代码 一、LinkedList概述 LinkedList是Java集合框架中的一个类&#xff0c;位于java.util包中。它实现了List、Deque、Queue等接口&#xff0c;提供了链表数据结构的实现。链表是一种线性数据结构&#xff0c;其…