美文网首页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