开发者

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

开发者 https://www.devze.com 2025-03-19 09:55 出处:网络 作者: TravisBytes
目录1. Redis 中存储文件上传状态使用 Hash 存储文件状态存储分块上传状态使用 TTL 进行状态过期管理2. Redis 与数据库保持一致方法 1:定期同步方法 2:实时同步方法 3:双写机制方法 4:系统重启后的恢复
目录
  • 1. Redis 中存储文件上传状态
    • 使用 Hash 存储文件状态
    • 存储分块上传状态
    • 使用 TTL 进行状态过期管理
  • 2. Redis 与数据库保持一致
    • 方法 1:定期同步
    • 方法 2:实时同步
    • 方法 3:双写机制
    • 方法 4:系统重启后的恢复
  • 3. 一致性保障
    • 4. 总结
      • 5. 代码实践
        • 5.1 在 Redis 中存储文件上传状态
        • 5.2 存储已上传的分块状态
        • 5.3 数据同步到数据库

      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 www.devze.comVARCHAR(255) PRIMARY KEY,
          uploaded_size BIGINT,
          file_size BIGINT,
          chunks TEXT,  -- 存储已上传的分块信息,格式为 jsON
          status 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)APfkWrliU;
          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, stAPfkWrliUatus);
      
          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\nhttp://www.devze.com";
              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 和数据库中的数据一致性。

      到此这篇关于Redis存储断点续传文件状态的最佳实践的文章就介绍到这了,更多相关Redis存储断点续传文件内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号