啦啦啦啦啦zzzz头像
关注

SingleFlight机制

引言

在redis里面,我们经常会涉及到一个严重的问题,就是缓存的击穿,雪崩和穿透

缓存击穿:当key值过期的一瞬间,同一个时间有大量的请求访问一个相同的数据,导致所有请求都落在了数据库上面,数据库崩溃

缓存穿透:大量的key值同时间失效,导致请求全部都落在了数据库上面

缓存雪崩:查询不存在的数据,导致大量请求落在了数据库上面

为了解决这个问题,我们引进了SingleFlight机制,这个机制的主要目的是防止重复的昂贵操作,比如访问数据库,其核心思想就是合并重复的请求,只允许有一个请求访问数据资源。

代码实现

我们要一个Func,这个函数的目的就是把传进来的请求转化成需要的数据返回

    using Result = std::optional<ByteView>;
    using Func = std::function<Result()>;

这是一个线程间通信的结构体,涉及到两个,这两个类一般是同时间出现的,一个是promise,一个是future,promise是把结果从一个线程传递到另一个线程,而future是负责等待并且获取结果。那么我们获取结果,如果是future.get(),只可以获取一次,而我们的目的是对于多个相同的请求只获取第一个请求,但是这并不意味不回应其他的请求了,所以我们需要对一个相同的数据读取很多次,然后返回给那些客户端

    struct Call 
    {
        /*
        std::promise 是 C++11 提供的一个工具,用来把结果从一个线程传给另一个线程。
        它通常和 std::future 配合使用
        promise:负责设置结果。
        future:负责等待并获取结果。
        一个 promise 通常只能设置一次结果,不适合反复传数据
        future.get() 只能调用一次;需要多次读取可以用 std::shared_future
        如果 promise 销毁时还没提供结果,接收方的 get() 会抛出 broken_promise 对应的 std::future_error
        */ 
        std::promise<Result> prom;
        // get_future():从 promise 拿到接收结果的 future  share():把这个 future 转成可以共享的 shared_future
        std::shared_future<Result> fut = prom.get_future().share();
    };

最后就是一个核心的调用接口DO,我们只需要传入键值还有怎么找到这个数据的函数,就可以返回我们需要的结果。这个map里面存储的是都是正在进行调用的call,因为当收到了多个相同的请求之后,我们只会处理第一个请求,那么当一个key到来的时候,我们会存储这个key值对应的call,当同样的key来的时候,我们会查map,如果发现key值存在,就说明我们的call还在调用,这个时候就不需要调用我们传进来的func函数,而是直接等待future的结果,也就是前面的请求处理之后的结果。那如果这是第一个请求,那么先要把key存储进去,然后调用函数,当得到结果之后再删除key值。注意一下子我们锁的释放和加锁的地方,我们只在访问map的地方加锁。

然后我们可以理解一下promise和future的使用,call在这里是一个智能指针,多个线程都可以访问这个智能指针,第一个线程得到结果之后设置promise的值,其他的线程就可以通过future来得到这个。而多线程之间怎么共享这个map,其实我们之后会涉及到一个内存组的概念,这个组之间可以共享一个map

    Result Do(const std::string& key, Func func)
    {
        std::unique_lock<std::mutex> lock(mutex_);

        // 检查是否有进行中的调用
        if (map_.find(key) != map_.end())
        {
            auto existing_call = map_[key];
            lock.unlock(); // 释放组锁,避免阻塞其他键的处理

            // 直接等待 future 的结果
            auto result = existing_call->fut.get();
            return result;
        }

        auto new_call = std::make_shared<Call>();
        map_[key] = new_call;
        lock.unlock();

        // 执行用户函数并设置promise
        Result val = func();
        new_call->prom.set_value(val);

        // 确保从映射中删除条目
        std::lock_guard<std::mutex> lock_(mutex_);
        map_.erase(key);
        return val;
    }

总结

本篇文章到这里就结束了!!!希望可以帮助大家理解~~~

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2503_93247646/article/details/167283380

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--