美文网首页erlang
erlang在处理缓存击穿的优势

erlang在处理缓存击穿的优势

作者: Alking | 来源:发表于2019-12-10 00:27 被阅读0次

    缓存穿透,是指查询一个数据库一定不存在的数据。正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存。如果数据库查询对象为空,则不放进缓存。

    1.缓存击穿的伪代码

    Value find(Key){
        RedisKey = RedisKeyFunc(Key);
        V = redisGet(RedisKey);
        if (V != null){
            // 命中缓存
            return V;
        }
        V = dbLookUp(Key);
        if (v != null){
           redisPut(RedisKey,V);
        }
        // 如果没有数据库中更没有数据,那么每次查找这条记录就会去数据库查询一次,数据库就像被攻击一样
        return V;
    }
    
    
    

    2.传统的应对缓存击穿的处理方案

    采用redis全局锁 + 自旋锁

    这样子虽然是可以解决这个问题,但是带来的负面影响是空跑CPU

    伪代码:

    Value find(Key){
        RedisKey = RedisKeyFunc(Key);
        V = redisGet(RedisKey);
        if (V != null){
            // 命中缓存
            return V;
        }
        
        RedisLockKey = RedisLockKeyFunc(Key)
        while(true){
            if(redisLock(RedisLockKey)){
                V = redisGet(RedisKey);
                if( V == null){
                   // 从数据库查询放入缓存
                   V = dbLookUp(Key);
                   redisSet(RedisKey,V);
                }
                redisUnLock(RedisLockKey);
                return V
            }
            // 这边空跑消耗CPU
        }
    }
    

    3.erlang actor的模型

    利用actor的消息传递机制,很轻松的避免自旋锁(空跑)

    伪代码:

    
    handle_call({find,Key},From,#{req=Req}=State) ->
      case lists:keyfind(Key,1, Req) of
       {_,L} ->
         Req2 = lists:keyreplace(Key,1,Req,{Key,[From|L]}),
         {noreply,State#state{req = Req2};
       false ->
         Actor = self(),
         % 新开一个proc去处理查询,将结果放进缓存
         erlang:spawn(fun()-> V = lookup(Key), Actor ! {Key,V}), 
         Req2 = [{Key,[From]}|Req],
         {noreply,State#state{req = Req2};
    
    
    handle_info({Key,V},#state{req = Req})->
      case lists:keyfind(Key,1, Req) of
       {_,L} ->
         % 将结果发给所有的请求者,CPU不会空跑
         [ gen_server:reply(From,V) ||  From<-L],
         Req2 = lists:keydelete(Key,1,Req),
         {noreply,State#state{req = Req2}};
       false ->
         {noreply,State#state{}};
    

    相对上面而言主要节约了2点

    1. 自旋锁带来的CPU消耗
    2. 在获得分布式锁之后还要去缓存查一下,还有释放分布式锁,2次IO消耗

    4.总结

    合理利用消息传递机制,可以很轻松的解决一些经典问题。或许这就是一种范式吧。

    相关文章

      网友评论

        本文标题:erlang在处理缓存击穿的优势

        本文链接:https://www.haomeiwen.com/subject/vxkwgctx.html