简单文件存储服务 去重存储

此篇基于之前写过的一个文件存储的函数,最初版本是只接收数据存储后返回文件id(这个文件id是随机生成的),在写完之后就有考虑加一个去重功能,因为实际用在项目里这代码看来很呆,测试时也看到存储了大量重复文件在系统中.也是最近突然有兴致想到重写一下,当然我不保证这段代码一定正确,同时我的代码也只是理论上的实现,实际使用也不一定适合.如果有问题欢迎您的指正,我先提供修改后的代码,最后再是最初版本.

逻辑简单说就是 获取文件的SHA256值来判断是否已经存储过 ==> 存储文件.

然后实际编写下来就会感受到几种特殊情况:
多个线程同时存储相同文件,检查到没有存储过,都一起存储了一遍,然后u_map也插入key冲突了.
然后就使用到提前存储到u_map中防止另一线程拿到锁发现没存储过也来存储,这时候就又出现新问题那出现存储失败怎么办?另一线程已经判断了存在直接返回了fileId了.
这时候我就再添加一个u_map用于保存文件存储状态,在判断是否已经有存储的时候,再去判断这个存储是否完毕,如果未完毕就等待存储完毕,再返回这里的等待就不考虑重试了.

20250827
bthread | bRPC
使用其他的mutex是否会有问题 · Issue #114 · apache/brpc
bthread 中使用 std::mutex和std::condition_variable导致线程丢失 · Issue #2590 · apache/brpc
Apache BRPC项目中跨版本锁机制的兼容性设计解析 - GitCode博客
今天看了下文档看到了bthread是brpc使用的M:N线程库,然后就在想我原先在brpc服务中直接使用标准库的锁会不会造成一些印象,只找到一些使用标准库出现了问题,想了想和参看俩个issue后毕竟使用的是brpc那么还是使用配套的bthread的锁更好点,这篇的代码我就不改了,参看下面俩个test
brpc/test/bthread_cond_unittest.cpp at master · apache/brpc
brpc/test/bthread_mutex_unittest.cpp at master · apache/brpc

//
#include <brpc/server.h>
#include <butil/logging.h>
#include <openssl/sha.h>
//我写的一些库文件就不贴出来献丑了

class FileServiceImpl : public FileService{
public:
    FileServiceImpl(const std::string& base_path)
        :__base_filepath(base_path){
            umask(0);
            mkdir(base_path.c_str(), 0775);
            DEBUG("文件根目录{}",base_path);
            if (__base_filepath.back() != '/') __base_filepath.push_back('/');
        }
    ~FileServiceImpl(){}
    //
    //上传单个文件
    void PutSingleFile(::google::protobuf::RpcController *controller,
                       const ::PutSingleFileReq *request,
                       ::PutSingleFileRsp *response,
                       ::google::protobuf::Closure *done){
        brpc::ClosureGuard guard(done);
        response->set_request_id(request->request_id());
        response->mutable_file_info()->set_file_size(request->file_data().file_size());
        response->mutable_file_info()->set_file_name(request->file_data().file_name());

        StoreResult r = StoreFile(request->file_data().file_content());
        response->set_success(r.success); 
        //存储失败
        if(!r.success){
            response->set_errmsg(r.errmsg);
            return;
        }
        response->mutable_file_info()->set_file_id(r.file_id);
        return ;
    }
private:
    /**
     * 用于文件存储状态
     */
    struct FileState
    {
        bool finished = false;
        bool success = false;
        std::string errmsg;
        std::condition_variable cv;
        std::mutex mtx;
    };
    /**
     * 存储返回,用于复用的
     */
    struct StoreResult
    {
        bool success;
        std::string file_id;
        std::string errmsg;
    };
    // 计算文件内容的 SHA256 哈希
    std::string calculateFileHash(const std::string& content) {
        unsigned char hash[SHA256_DIGEST_LENGTH];
        SHA256((const unsigned char*)content.data(), content.size(), hash);
        return std::string(reinterpret_cast<char*>(hash), SHA256_DIGEST_LENGTH);
    }
    
    /**
     * 存储文件,包含哈希去重、并发控制、写入逻辑
     */
    StoreResult StoreFile(const std::string& file_content) {
        StoreResult result;
    
        std::string hash_file = calculateFileHash(file_content);
        std::string file_id = chat_im::util::uuid();
        std::shared_ptr<FileState> file_state;
    	bool need_wait = false;
        {
            std::lock_guard lock(__file_mutex);
            auto ite = __file_hashMap.find(hash_file);
            /**
             * 可能已经存储过,检查是否是其他插入key值,但还没存储完毕
             */
            if(ite != __file_hashMap.end()){
                file_id = ite->second;
                //判断是否提取插入的值
                auto s_ite = __writing_files.find(file_id);
                if(s_ite == __writing_files.end()){
                    /**
                     * 直接返回
                     */
                    result.success = true;
                    result.file_id = file_id;
                    return result;
                }else{
                    /**
                     * 需要等待
                     */
                    file_state = s_ite->second;
                    need_wait = true;
                }
            }else{
                /**
                 * 没有存储过,提前插入map中
                 * 避免多个线程同时进行写入同一文件
                 */
                __file_hashMap[hash_file] = file_id;
                file_state = std::make_shared<FileState>();
                __writing_files[file_id] = file_state;
            }
        }
        /**
         * 等到另一线程写入完毕
         */
        if (need_wait){
            std::unique_lock<std::mutex> lock(file_state->mtx);
            if(!file_state->cv.wait_for(lock,std::chrono::seconds(3),[&](){return file_state->finished;})){
                // 等待超时处理
                result.success = false;
                result.errmsg = "文件处理超时";
                return result;
            }
            result.success = file_state->success;
            result.errmsg = file_state->errmsg;
            result.file_id = file_id;
            return result;
        }
        /**
         * 存储文件,
         */
        std::string file_name = __base_filepath+file_id;
        bool status = chat_im::util::writeFile(file_name,file_content);
        {
            std::lock_guard lock(file_state->mtx);
            file_state->finished = true;
            file_state->success = status;
            if(!status){
                file_state->errmsg = "写入文件失败";
            }
        }
        /**
         * 通知等待的线程
         */
        file_state->cv.notify_all();
        
        {
            std::lock_guard lock(__file_mutex);
            __writing_files.erase(file_id);
        }
        if(status == false){
            /**
             * 取消原先插入的map值
             */
            std::lock_guard lock(__file_mutex);
            __file_hashMap.erase(hash_file);
            result.success = false;
            result.errmsg = "写入文件数据失败!";
            return result;
        }
    
        result.success = true;
        result.file_id = file_id;
        return result;
    }


private:
    std::string __base_filepath;
    std::unordered_map<std::string,std::string> __file_hashMap;
    std::unordered_map<std::string, std::shared_ptr<FileState>> __writing_files;
    std::mutex __file_mutex;

};
//
//最初版本
    void PutSingleFile(::google::protobuf::RpcController *controller,
                       const ::PutSingleFileReq *request,
                       ::PutSingleFileRsp *response,
                       ::google::protobuf::Closure *done){
        brpc::ClosureGuard guard(done);

        response->set_request_id(request->request_id());

        std::string file_id = chat_im::util::uuid();
        std::string file_name = __base_filepath+file_id;
        bool status = util::writeFile(file_name,request->file_data().file_content());
        if(status == false){
            ERROR("上传文件请求{}:写入失败",request->request_id());
            response->set_success(false);
            response->set_errmsg("写入文件数据失败!");
            return;
        }
        response->set_success(true);
        response->mutable_file_info()->set_file_id(file_id);
        response->mutable_file_info()->set_file_size(request->file_data().file_size());
        response->mutable_file_info()->set_file_name(request->file_data().file_name());
        return ;

    }
posted @ 2025-08-16 21:44  haoyouxiaoju  阅读(18)  评论(0)    收藏  举报