首页 > 其他分享 >实现秒传与限速!深度解析万亿GB网盘系统架构

实现秒传与限速!深度解析万亿GB网盘系统架构

时间:2024-06-04 22:00:43浏览次数:22  
标签:文件 存储 String int 网盘 限速 GB public MD5

1. 系统需求与挑战

1.1 DBox核心功能

在设计一个面向万亿GB的网盘系统时,我们需要首先明确系统的核心功能需求。DBox 作为一个高并发、高可靠的网盘系统,核心功能需求主要包括以下几点:

  • 海量存储:支持存储海量数据,满足用户上传和下载需求。
  • 秒传功能:快速上传相同文件,避免重复存储和传输相同内容。
  • 限速功能:根据用户权限进行上传和下载速度的限制。
  • 高可用性:系统在任何时候都能提供服务,确保文件的安全性与可用性。
  • 数据安全性:提供文件的加密存储与传输,支持多方式的权限控制。

1.2 用户需求与使用场景

用户对网盘系统的需求主要体现在以下几个使用场景:

  • 个人用户:用于存储个人文件,实现数据备份和跨设备访问。个人用户对存储空间的需求相对较小,但对共享和便捷访问的要求较高。
  • 企业用户:需要存储和管理团队文件,支持多人协作和版本控制。企业用户通常需要较大的存储空间和更高的数据安全性。
  • 开发者与应用:一些开发者和应用服务使用网盘作为存储后端,要求提供丰富的API支持、高性能和稳定性。

1.3 面临的主要技术挑战

实现这样一个大规模网盘系统,面临以下几方面的技术挑战:

  • 大规模存储与管理:如何管理和存储海量数据,以及确保这些数据的高可用和可靠性。
  • 高并发访问:面对大量用户同时上传和下载文件,系统需要具备高并发处理能力。
  • 秒传与去重:如何快速判断文件是否已存在,并避免重复存储相同的文件。
  • 精细化限速:根据用户等级、文件类型等因素设置灵活的限速策略。
  • 数据一致性与安全性:在多副本存储和分布式环境下,确保数据的一致性和传输的安全性。

2. 如何实现秒传

2.1 MD5 hash判断文件是否存在

秒传的核心在于判定用户上传的文件是否已经存在于系统中。在用户上传文件时,首先计算文件的MD5值,因为MD5算法能够高效地生成文件的唯一标识。系统根据上传文件的MD5值,查询是否已存在同样的文件。

import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class FileMD5 {
    public static String getFileMD5(String filePath) {
        try (FileInputStream fis = new FileInputStream(filePath)) {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] dataBytes = new byte[1024];
            int bytesRead;
            
            while ((bytesRead = fis.read(dataBytes)) != -1) {
                md.update(dataBytes, 0, bytesRead);
            }
            
            byte[] mdBytes = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte b : mdBytes) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (IOException | NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }
    
    public static void main(String[] args) {
        String filePath = "path/to/your/file";
        System.out.println("MD5: " + getFileMD5(filePath));
    }
}

2.2 文件块(Block)的设计与管理

为了支持大文件的秒传和部分文件更新,文件按块(Block)进行管理。每个块都有一个唯一的标识,上传时分块计算其MD5值,每个块的MD5值存入数据库。

块存储设计:

  • Block表:存放每个块的数据与相关信息。
  • 文件块索引表:记录文件与块的对应关系,方便管理和查找。
CREATE TABLE Block (
  BlockID CHAR(32) PRIMARY KEY,
  Data BLOB,
  Size INT,
  MD5 CHAR(32)
);

CREATE TABLE FileBlockMapping (
  FileID CHAR(32),
  BlockID CHAR(32),
  BlockIndex INT,
  PRIMARY KEY (FileID, BlockID, BlockIndex)
);

2.3 从MD5到更严格判断

单纯依赖MD5可能存在碰撞风险,系统可以引入SHA-256或其他方式进行更严格的文件完整性校验。

public String getFileSHA256(String filePath) {
    try (FileInputStream fis = new FileInputStream(filePath)) {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] dataBytes = new byte[1024];
        int bytesRead;
        
        while ((bytesRead = fis.read(dataBytes)) != -1) {
            digest.update(dataBytes, 0, bytesRead);
        }
        
        byte[] hashBytes = digest.digest();
        StringBuilder sb = new StringBuilder();
        for (byte b : hashBytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    } catch (IOException | NoSuchAlgorithmException e) {
        e.printStackTrace();
        return null;
    }
}

2.4 物理文件表和逻辑文件表的设计

物理文件表记录实际存储在系统中的文件信息,逻辑文件表记录用户对文件的操作,如文件重命名、移动等。这样设计能够有效进行文件去重,同时保留用户的操作记录。

CREATE TABLE PhysicalFiles (
  FileID CHAR(32) PRIMARY KEY,
  StoragePath VARCHAR(256),
  MD5 CHAR(32),
  SHA256 CHAR(64),
  Size INT,
  UploadTime TIMESTAMP
);

CREATE TABLE LogicalFiles (
  LogicalFileID CHAR(32) PRIMARY KEY,
  PhysicalFileID CHAR(32),
  UserID CHAR(32),
  FileName VARCHAR(256),
  UploadTime TIMESTAMP,
  CONSTRAINT FK_PhysicalFileID FOREIGN KEY (PhysicalFileID) REFERENCES PhysicalFiles(FileID)
);

3. 文件存储架构设计

3.1 元数据与文件内容的分离

在设计网盘系统时,将文件的元数据和实际文件内容进行分离非常重要。元数据包含文件名、大小、上传时间等信息,实际文件内容则是用户上传的文件数据。这种分离有利于优化访问性能,简化存储管理。

  • 元数据存储:存储在高性能数据库中,如 MySQL 或 PostgreSQL,用于快速查询。
  • 文件内容存储:存储在高可用的分布式文件系统中,如 Ceph,以确保大文件的高效存储和传输。

3.2 API服务器与Block服务器的角色

网盘系统通常由多个服务器模块组成,每个模块承担不同的职能。

  • API服务器:处理用户请求,如文件上传、下载、删除等操作。API服务器与数据库交互,管理元数据,并与Block服务器协作传输文件内容。
  • Block服务器:负责文件的数据存储和分发,处理文件的分块上传和下载。Block服务器直接与存储文件的存储系统(如Ceph)进行交互。

3.3 对象存储(Ceph)的选用理由

Ceph 是一个开源的分布式存储系统,适用于对象存储、块存储等场景。选用 Ceph 作为网盘系统的存储后端,主要基于以下几方面的考虑:

  • 高可扩展性:Ceph无中心化设计,具备良好的横向扩展能力,可以方便地增加存储节点。
  • 高可靠性:支持多副本和纠删码,可以确保数据的高可靠性。
  • 灵活性:支持对象存储、块存储和文件系统三种接口,适应不同应用场景。

3.3.1 Ceph 配置示例

global:
  fsid: {{ ceph_fsid }}
  mon_host: {{ ceph_mon_host }}
  public_network: {{ ceph_public_network }}

osd:
  osd_objectstore: bluestore

mon:
  mon_data_avail_warn: 10
  mon_clock_drift_allowed: 0.05
  mon_osd_min_down_reporters: 1

mgr:
  mgr_init_modules: dashboard

3.4 文件上传与下载的时序图

3.4.1 文件上传时序图

file

3.4.2 文件下载时序图

file

通过这种架构设计,能充分利用 API 服务器处理用户请求的高并发能力,以及 Block 服务器对大文件传输的优化,从而提供网盘系统的高效存储和传输性能。

4. 限速设计方案

4.1 用户付费类型的决定因素

首先,我们需要根据用户的付费类型和使用情况对用户进行限速。通常有以下几种用户类型:

  • 免费用户:对上传和下载速度进行严格限制,以降低系统负担。
  • 付费用户:根据付费等级提供不同的上传和下载速度。
  • 企业用户:根据合同条款提供定制化的限速服务。

针对不同类型用户,我们可以在API服务器进行请求参数的解析和速率控制。

public class User {
    private String userId;
    private UserType userType;
    private int uploadSpeed; // 单位: KB/s
    private int downloadSpeed; // 单位: KB/s

    public User(String userId, UserType userType) {
        this.userId = userId;
        this.userType = userType;
        switch (userType) {
            case FREE:
                this.uploadSpeed = 500;
                this.downloadSpeed = 500;
                break;
            case PREMIUM:
                this.uploadSpeed = 2000;
                this.downloadSpeed = 2000;
                break;
            case ENTERPRISE:
                this.uploadSpeed = 10000;
                this.downloadSpeed = 10000;
                break;
            default:
                this.uploadSpeed = 100;
                this.downloadSpeed = 100;
        }
    }
    
    // Getter and Setter methods...
}

4.2 API服务器的分配策略

API服务器在处理用户请求时需要进行流量控制,根据用户类型的不同来分配带宽和并发资源。我们可以使用令牌桶 (Token Bucket)算法实现这种控制。

public class RateLimiter {
    private final int maxTokens;
    private int currentTokens;
    private final long refillInterval;
    private long lastRefillTimestamp;

    public RateLimiter(int maxTokens, long refillInterval) {
        this.maxTokens = maxTokens;
        this.currentTokens = maxTokens;
        this.refillInterval = refillInterval;
        this.lastRefillTimestamp = System.nanoTime();
    }

    public synchronized boolean tryConsume(int tokens) {
        refill();
        if (tokens <= currentTokens) {
            currentTokens -= tokens;
            return true;
        }
        return false;
    }
    
    private void refill() {
        long now = System.nanoTime();
        long elapsed = now - lastRefillTimestamp;
        int tokensToAdd = (int) (elapsed / refillInterval);
        if (tokensToAdd > 0) {
            currentTokens = Math.min(maxTokens, currentTokens + tokensToAdd);
            lastRefillTimestamp = now;
        }
    }
}

4.3 Block服务器的并发与传输控制

Block服务器接收API服务器的请求后,需要进行传输速率控制,根据用户等级和当前的网络情况来动态调整传输速率。从技术实现上看,我们可以通过调整网络的QoS (Quality of Service) 策略以及使用传输协议中的窗口大小来控制。

public void sendFileBlock(Socket socket, byte[] data, int speedLimitKBps) throws IOException {
    OutputStream out = socket.getOutputStream();
    int chunkSize = speedLimitKBps * 1024;
    int offset = 0;
    
    while (offset < data.length) {
        int length = Math.min(chunkSize, data.length - offset);
        out.write(data, offset, length);
        out.flush();
        offset += length;
        // 每秒传输KBps限制的大小
        Thread.sleep(1000);
    }
}

对于具体实现情况,可以进一步结合网络实际情况进行性能测试与优化,确保在高并发环境下依然能够有效控制网速。

5. 系统的高并发与高可靠设计

5.1 高可用服务与高可靠存储要求

在设计网盘系统时,高可用服务和高可靠存储是两个关键要素。高可用服务涉及到系统的负载均衡、服务熔断和快速恢复,而高可靠存储则关注数据的持久性、多副本存储和数据恢复能力。

  • 高可用服务:通过负载均衡(如Nginx、HAProxy)和微服务架构,确保在服务节点故障时,系统能够快速切换,不影响用户使用。
  • 高可靠存储:通过分布式存储系统(如Ceph),确保数据多副本存储,当某个存储节点发生故障时,数据能够快速恢复。

5.2 分布式对象存储(Ceph)的高可靠

Ceph通过CRUSH算法在集群中分布数据,确保数据的高可靠性和可用性。CRUSH(Controlled, Scalable, Decentralized Placement of Replicated Data)算法控制数据存储的位置,避免集中化的单点故障。

5.2.1 Ceph的高可靠性设计包括:

  • 多副本存储:每份数据有多个副本存储在不同的节点上。
  • 纠删码:在多副本存储的基础上,进一步通过纠删码技术减少存储开销,同时保证高可靠性。
  • 自动恢复:当某个存储节点故障后,Ceph能够自动感知并将数据副本恢复到其他健康节点。

5.3 数据库的分片存储设计

为了在高并发环境下保证数据库的读写性能,使用数据库分片(Sharding)技术,将数据水平拆分到不同的数据库实例中。

CREATE TABLE UserFileShard1 (
  FileID CHAR(32) PRIMARY KEY,
  UserID CHAR(32),
  FileName VARCHAR(256),
  UploadTime TIMESTAMP
);

CREATE TABLE UserFileShard2 (
  FileID CHAR(32) PRIMARY KEY,
  UserID CHAR(38),
  FileName VARCHAR(256),
  UploadTime TIMESTAMP
);

分片策略可以基于用户ID的哈希值、文件ID前缀等多种方式。

public class ShardingService {
    private static final int SHARD_COUNT = 2;

    public int getShardIndex(String userId) {
        return userId.hashCode() % SHARD_COUNT;
    }

    public void storeFile(String userId, File file) {
        int shardIndex = getShardIndex(userId);
        // 根据 shardIndex 决定存储到哪个分片数据库
        // 存储逻辑
    }
}

5.4 查询性能优化与缓存策略

在高并发环境下,频繁的数据库查询可能成为瓶颈,通过引入缓存机制可以显著提高系统性能。常见的缓存方案包括:

  • 内存缓存:如Redis、Memcached,用于存储热门数据,减少数据库访问频次。
  • 分布式缓存:在多个节点之间共享缓存,提高系统的横向扩展能力。
// 使用Redis缓存查询结果示例
public class FileService {
    private RedisCache redisCache;
    private DatabaseService databaseService;

    public File getFileMetadata(String fileId) {
        String cacheKey = "file_metadata:" + fileId;
        File metadata = redisCache.get(cacheKey);
        if (metadata == null) {
            metadata = databaseService.getFileMetadata(fileId);
            redisCache.put(cacheKey, metadata);
        }
        return metadata;
    }
}

通过上述设计,文件元数据的查询性能显著提升,同时在高并发环境下,缓存有效减少了数据库的读操作压力。

6. 实战案例分析与解决方案

6.1 用户海量文件导致的分片不均衡

在实际运作中,用户上传的海量文件可能会导致某些分片存储的负载过高,从而造成数据不均衡。这种情况可以通过以下方案来解决:

  • 哈希一致性:使用一致性哈希算法来分配文件到不同的分片,避免因哈希均匀性不足导致的数据分布不均衡。
  • 动态分片:对存储资源进行动态扩展,在发现某个分片负载过高时,系统可以自动增加新的分片,并将部分数据转移到新的分片上。
public class ConsistentHashing {
    private SortedMap<Integer, String> circle = new TreeMap<>();

    public void add(String node) {
        int hash = hash(node);
        circle.put(hash, node);
    }

    public void remove(String node) {
        int hash = hash(node);
        circle.remove(hash);
    }

    public String get(String key) {
        if (circle.isEmpty()) {
            return null;
        }
        int hash = hash(key);
        if (!circle.containsKey(hash)) {
            SortedMap<Integer, String> tailMap = circle.tailMap(hash);
            hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
        }
        return circle.get(hash);
    }

    private int hash(String key) {
        return key.hashCode() & 0xfffffff; // 简化版哈希函数
    }
}

6.2 上传巨大文件的MD5计算耗时问题

在上传巨大文件时,计算整个文件的MD5值耗时较长,可以采用分片计算MD5的方式,分块并行计算每一块的MD5值,最终整合每个块的MD5值进行再一次哈希计算,得到文件的最终哈希值。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.security.MessageDigest;

public class ParallelMD5 {
    private static final int BLOCK_SIZE = 64 * 1024; // 64KB

    public static String getFileMD5(String filePath) throws Exception {
        FileInputStream fis = new FileInputStream(filePath);
        long fileSize = new File(filePath).length();
        int blocks = (int) Math.ceil((double) fileSize / BLOCK_SIZE);

        ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        List<Future<byte[]>> futures = new ArrayList<>();

        for (int i = 0; i < blocks; i++) {
            final int blockIndex = i;
            futures.add(executor.submit(() -> {
                byte[] buffer = new byte[BLOCK_SIZE];
                int bytesRead = fis.read(buffer, 0, BLOCK_SIZE);
                MessageDigest md = MessageDigest.getInstance("MD5");
                return md.digest(Arrays.copyOf(buffer, bytesRead));
            }));
        }

        MessageDigest md = MessageDigest.getInstance("MD5");
        for (Future<byte[]> future : futures) {
            md.update(future.get());
        }

        executor.shutdown();
        byte[] mdBytes = md.digest();
        StringBuilder sb = new StringBuilder(32);
        for (byte b : mdBytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}

6.3 秒传功能的实际用户体验优化

为了进一步优化秒传和减少用户的等待时间,可以将秒传功能的校验步骤前置。即用户选择文件后,在文件正式上传前,迅速计算文件的校验值(MD5、SHA-256),并与服务器上的文件进行对比,如果文件存在则直接秒传成功。这个过程可以通过前端与后端协同完成:

// 前端代码示例 (假设使用的是JavaScript)
document.getElementById('uploadFile').addEventListener('change', async function() {
    const file = this.files[0];
    const fileMD5 = await calculateFileMD5(file);

    const response = await fetch('/checkFileExists', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ md5: fileMD5 })
    });

    const result = await response.json();
    if (result.exists) {
        alert('文件已存在,秒传成功');
    } else {
        // 继续上传流程
    }
});

async function calculateFileMD5(file) {
    const arrayBuffer = await file.arrayBuffer();
    const hashBuffer = await crypto.subtle.digest('MD5', arrayBuffer);
    return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
}

通过优化这些环节,可以显著提高用户的秒传体验,同时减轻后端的计算负担,提升系统整体的响应速度。

7. 技术选型与架构展望

7.1 对象存储的选择与比较(HDFS vs Ceph)

在构建一个高效的网盘系统时,选择合适的对象存储是至关重要的。这里我们将重点比较HDFS(Hadoop Distributed File System)和Ceph两种常用的分布式存储系统。

7.1.1 HDFS 的特点

  • 高吞吐量:HDFS 设计之初就是为了解决大数据存储和处理问题,能提供非常高的数据访问吞吐量。
  • 数据分片存储:将文件切分为若干块并存储在集群的不同节点上。
  • 主从架构:NameNode 管理元数据,DataNode 存储实际数据,单点故障的NameNode是系统的瓶颈。
  • 适用场景:适合大文件的批处理存储场景,不适用于频繁的小文件读写。

7.1.2 Ceph 的特点

  • 无中心化设计:通过CRUSH算法将数据分布到各个OSD,实现数据无中心化存储。
  • 高可靠性:数据存储支持副本和纠删码,提高数据的可靠性和存储效率。
  • 强一致性:Ceph 提供了强一致性的存储访问接口,确保数据的读写一致性。
  • 灵活性:支持对象存储、块存储和文件系统,适应不同的业务需求。
  • 适用场景:能很好地支持大文件和小文件的混合存储与读取,适合云存储和企业数据存储。

综合比较来看,Ceph 更加适合网盘系统这样需要兼顾大文件和小文件读写的场景,而且其无中心化设计和强一致性也符合高可用和高可靠的要求。

7.2 未来可能的技术迭代与升级

随着业务的发展和技术的进步,网盘系统在未来可能会进行多个方面的技术迭代与升级。以下是一些可能的方向:

7.2.1 使用更加高效的存储引擎

随着存储技术的发展,一些新的存储引擎如RocksDB、LevelDB等,提供了更高的读写性能和存储压缩率,可以在部分场景中替代传统的存储方案。

// 示例代码:使用RocksDB进行文件元数据存储
import org.rocksdb.*;

public class RocksDBExample {
    private RocksDB db;

    public RocksDBExample(String dbPath) throws RocksDBException {
        Options options = new Options().setCreateIfMissing(true);
        this.db = RocksDB.open(options, dbPath);
    }

    public void put(String key, String value) throws RocksDBException {
        db.put(key.getBytes(), value.getBytes());
    }

    public String get(String key) throws RocksDBException {
        byte[] value = db.get(key.getBytes());
        return value != null ? new String(value) : null;
    }

    public void close() {
        if (db != null) {
            db.close();
        }
    }
}

7.2.2 引入机器学习优化存储与传输

利用机器学习算法对用户行为进行分析和预测,可以进一步优化文件的存储与传输策略。例如,通过学习用户的访问模式,提前将常用文件缓存到用户附近的存储节点,从而提高访问速度。

7.2.3 分层存储策略

随着云存储技术的发展,采用分层存储策略,将热数据存储在高性能存储(如SSD),冷数据存储在低成本存储(如HDD),这样既能保证访问速度,又能控制存储成本。

标签:文件,存储,String,int,网盘,限速,GB,public,MD5
From: https://blog.csdn.net/feiying101/article/details/139454124

相关文章

  • 基于SpringBoot+Vue的足球社区管理系统(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示项目运行截图技术框架后端采用SpringBoot框架前端框架Vue可行性分析系统测试系统测试的目的系统功能测试数据库表设计代码参考数据库脚本为什么选择我?获取源码前言......
  • 基于SpringBoot+Vue的医院住院管理系统设计与实现(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示项目运行截图技术框架后端采用SpringBoot框架前端框架Vue可行性分析系统测试系统测试的目的系统功能测试数据库表设计代码参考数据库脚本为什么选择我?获取源码前言......
  • 基于springboot在线互动学习网站设计(11726)
     有需要的同学,源代码和配套文档领取,加文章最下方的名片哦一、项目演示项目演示视频二、资料介绍完整源代码(前后端源代码+SQL脚本)配套文档(LW+PPT+开题报告)远程调试控屏包运行三、技术介绍Java语言SSM框架SpringBoot框架Vue框架JSP页面Mysql数据库IDEA/Eclipse开发四、项......
  • 基于springboot校园资产管理系统(11725)
     有需要的同学,源代码和配套文档领取,加文章最下方的名片哦一、项目演示项目演示视频二、资料介绍完整源代码(前后端源代码+SQL脚本)配套文档(LW+PPT+开题报告)远程调试控屏包运行三、技术介绍Java语言SSM框架SpringBoot框架Vue框架JSP页面Mysql数据库IDEA/Eclipse开发四、项......
  • 基于springboot-vue的毕业论文管理系统(11728)
     有需要的同学,源代码和配套文档领取,加文章最下方的名片哦一、项目演示二、资料项目演示视频介绍完整源代码(前后端源代码+SQL脚本)配套文档(LW+PPT+开题报告)远程调试控屏包运行三、技术介绍Java语言SSM框架SpringBoot框架Vue框架JSP页面Mysql数据库IDEA/Eclipse开发四、项......
  • SpringBoot+微信支付-JSAPI
    引入微信支付SDKMaven:com.github.wechatpay-apiv3:wechatpay-java-core:0.2.12Maven:com.github.wechatpay-apiv3:wechatpay-java:0.2.12代码示例packagexxxx.cashier.payChannel.handler;importxxxx.common.domain.model.exception.BusinessException;importxxxx.c......
  • springboot3整合高版本spring data neo4j
    本博客适用于springboodataneo4j7.2.6版本,详情阅读官网https://docs.spring.io/spring-data/neo4j/reference/7.2/introduction-and-preface/index.html,中文网只更新到了6版本entity->nodeentity->relation@Node("Movie")//取代了老版本的nodeentity,他表示的就是labelp......
  • springboot+vue+mybatis学生奖惩管理系统+PPT+论文+讲解+售后
    在如今社会上,关于信息上面的处理,没有任何一个企业或者个人会忽视,如何让信息急速传递,并且归档储存查询,采用之前的纸张记录模式已经不符合当前使用要求了。所以,对学生奖惩信息管理的提升,也为了对学生奖惩信息进行更好的维护,学生奖惩管理系统的出现就变得水到渠成不可缺少。通过对......
  • springboot+vue+mybatis大学生就业管理系统+PPT+论文+讲解+售后
    现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本学生就业管理系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事......
  • Redis 结合 Docker 搭建集群,并整合SpringBoot
    软件版本Redis7.2.5Docker26.1.3 准备工作由于docker直接拉取运行了,所以需要提前准备配置文件Indexof/releases/(redis.io)下载后,把redis-7.2.5.tar.gz\redis-7.2.5.tar\redis-7.2.5\里的redis.confsentinel.conf复制出来 概览结构如上图所示,准备6个服务器。......