分布式锁实现方式三 基干Memcache mutex设计模式

203 篇文章 1 订阅
49 篇文章 0 订阅

分布式锁实现方式三 Memcache mutex设计模式

应用场景

上周六去参加了csdn举办的TUP活动,最后一场的Tim Yang讲的《微博cache设计谈》,个人觉得讲得非常好和非常到位,其中有两点非常感同身受,就是内网流量问题和cache的key mutex问题导致大量请求穿透到db。后他又写了一篇博客《Memcache mutex设计模式》阐述这个问题。关于cache的key mutex问题在我的开发中叶碰到过很多,这里也就谈下我的解决办法。问题的产生如下图:
   
                                         图一:key mutex问题

      我这里指的cache不局限于memcached,事实上,key mutex在很多场合都是需要的,比如mysql中的innodb中说的机遇记录集的锁也可以当成key mutex。

      http反向代理服务器cache的key mutex问题

     我在09年为一个当时性能不是很高,但是短时间又难于优化的一个系统开发了一个反向代理服务器ICProxy(类似于squid,varnish等,只是增加了一些自己的业务逻辑)来提高系统的性能。但是很快问题就来了,

在访问高峰期,比如

一个热门的新闻页面,设置缓存5分钟
5分钟后,会有大量的请求在同一时间得到缓存失效的标识
大量请求并同时都穿透到后端web服务器请求加载数据到缓存,造成后端频繁宕机
这就是所说的key mutex问题,没有对统一资源的请求加锁来限制穿透访问,由于ICProxy是java开发的,可以很方便的使用锁来进行控制。这里就不贴源代码了,我相信你一定知道怎么实现。
      Memcached的key mutex问题
     memcached的key mutex问题在Tim Yang的博客《Memcache mutex设计模式》已经把产生的场景说得很清楚,也提到了两种解决办法。问题的解决如下图:
 

Memcache mutex设计模式


Mutex主要用于有大量并发访问并存在cache过期的场合,如

首页top 10, 由数据库加载到memcache缓存n分钟
微博中名人的content cache, 一旦不存在会大量请求不能命中并加载数据库
需要执行多个IO操作生成的数据存在cache中, 比如查询db多次
问题
在大并发的场合,当cache失效时,大量并发同时取不到cache,会同一瞬间去访问db并回设cache,可能会给系统带来潜在的超负荷风险。我们曾经在线上系统出现过类似故障。

解决方法

方法一

if (memcache.get(key) == null) {
    // 3 min timeout to avoid mutex holder crash
    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
        value = db.get(key);
        memcache.set(key, value);
        memcache.delete(key_mutex);
    } else {
        sleep(50);
        retry();
    }
}

在load db之前先add一个mutex key, mutex key add成功之后再去做加载db, 如果add失败则sleep之后重试读取原cache数据。为了防止死锁,mutex key也需要设置过期时间。伪代码如下
(注:下文伪代码仅供了解思路,可能存在bug,欢迎随时指出。)

方法二

在value内部设置1个超时值(timeout1), timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候,马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。伪代码如下

v = memcache.get(key);
if (v == null) {
    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
        value = db.get(key);
        memcache.set(key, value);
        memcache.delete(key_mutex);
    } else {
        sleep(50);
        retry();
    }
} else {
    if (v.timeout <= now()) {
        if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
            // extend the timeout for other threads
            v.timeout += 3 * 60 * 1000;
            memcache.set(key, v, KEY_TIMEOUT * 2);


            // load the latest value from db
            v = db.get(key);
            v.timeout = KEY_TIMEOUT;
            memcache.set(key, value, KEY_TIMEOUT * 2);
            memcache.delete(key_mutex);
        } else {
            sleep(50);
            retry();
        }
    }
}

相对于方案一
优点:避免cache失效时刻大量请求获取不到mutex并进行sleep
缺点:代码复杂性增大,因此一般场合用方案一也已经足够。


方案二在Memcached FAQ中也有详细介绍 How to prevent clobbering updates, stampeding requests,并且Brad还介绍了用他另外一个得意的工具 Gearman 来实现单实例设置cache的方法,见 Cache miss stampedes,不过用Gearman来解决就感觉就有点奇技淫巧了。

--- end ---


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值