memcached使用中的竞争条件

来源:互联网 发布:java case switch 编辑:程序博客网 时间:2024/04/30 18:28

 原贴:http://www.robinlu.com/blog/archives/166

在通过ruby的memcached-client使用memcached的过程中,遇到一些问题,数据更新时清除了缓存,缓存重建的时候却仍然是老数据,在并发密集的情况下更容易出现。研究了一下,类似这样典型的memcache使用方法:

Controller里:

...
def foo_action
...
unless d = Cache.get("key")
d = Data.find(...)
Cache.put("key", d)
end
...
end
...

Model里

...
def after_save
...
Cache.delete("key")
...
end
...

存在下面的竞争条件(race condition):

存在两个rails应用实例(比如两个并发的mongrel)A和B,

  1. memcached清除过期缓存c。
  2. 实例A运行foo_action,发现缓存c不存在(Cache.get失败),读取数据d。
  3. 实例B更新数据d’,清除过期缓存c(Cache.delete)。
  4. 实例A保存缓存c(Cache.put),其中的数据是老数据d。

这时,再有数据访问缓存c的时候,c已经存在,到下一次缓存c被清除前,这个缓存都是存在问题的过期数据。不难看出,即使将第三步中的清除过期缓存c改成更新缓存c,c仍然会被实例A在第四步覆盖。

其实,memcached为了避免这种竞争条件,提供了一些便利的原子操作(参看memcached protocol):

“add” means “store this data, but only if the server *doesn’t* already
hold data for this key”.

“cas” is a check and set operation which means “store this data but
only if no one else has updated since I last fetched it.”

将第三步中的“清除过期缓存c”变成“用memcachedset方法(也就是memcached-client中的Cache.put)来更新缓存c”,然后在第四步中始终用add方法来更新缓存c,就可以解决问题。也就是说,在能够确认数据是最新的地方,比如aftersave中,不采用Cache.delete,而直接用Cache.put来更新缓存,在不能确认是否是最新数据的其它地方,只使用Cache.add,就能保证过期数据不会在race condition下覆盖新数据。(更新无论是Cache.delete还是Cache.put,放在after_save中仍然有问题,会因为activerecord的built-in transaction而破坏了数据的完整性,具体参见再谈rails缓存机制的问题)

memcached-client从1.4.0起才开始支持add方法,目前还不支持cas方法。不过add方法已经能够解决不少竞争条件了。如果你也有类似的问题,升级memcached-client,修改缓存更新策略吧。

<script type="text/javascript">&lt;!--google_ad_client = "pub-3840511892024359";google_ad_width = 468;google_ad_height = 60;google_ad_format = "468x60_as";google_ad_type = "text_image";google_ad_channel ="";google_color_border = "FFFFFF";google_color_bg = "FFFFFF";google_color_link = "000000";google_color_text = "999999";google_color_url = "999999";//--&gt;</script>

有8 条关于 “memcached使用中的竞争条件”的留言

  • CaiwangqinOctober 27th, 2007 17:43

    Thanks for sharing.

  • 好文推荐 程字(20071112)号 » 不合格的程序员November 12th, 2007 20:20

    [...] - memcached使用中的竞争条件 » 石锅拌饭 | 互联网 Mac & 软件开发,累计共2个收藏 ror中解决memcached缓存无法及时更新的问题。cas(clear and set) [...]

  • adouNovember 29th, 2007 00:37

    大多数情况把取数据时候生成缓存的set改成add应该就够了

    修改或删除数据时 如果需要set的话 又得去获取一次完整的缓存数据 直接delete 等取数据的时候再生成好了

    这样应该会简单些

    当然如果能很方便得到完整的需要缓存的数据 在修改后直接set更好 那样可以直接让下次被访问时就直接从缓存取数据

  • Robin LuNovember 29th, 2007 08:42

    楼上,请仔细阅读我的原文,你就能理解为什么不能在取数据的时候再生成。
    直接把set改成add是不能解决问题的,除非你在add的时候能够确认你使用的是新数据,否则可能会把事情搞得更糟糕。

  • adouNovember 29th, 2007 12:06

    嗯 有竞争的时候确实是会有问题 但是可能被查询到的数据有上亿条 并且有各种组合 不可能把它们全部放进缓存 现在只是当个别被访问到的时候才进行缓存 然后设置一个过期时间 如果不在取数据的时候生成缓存 有什么好方法吗 :) 不吝赐教

  • adouNovember 29th, 2007 13:12

    设置缓存的时候 数据是从master而不是slave数据库取 基本能保证数据是新的 在取出数据到设置缓存这一瞬间如果有并发的写 确实会有问题

  • Robin LuNovember 29th, 2007 14:45

    对于你上面提到的情况,如果数据更新相对频繁,对展示准确性要求不是十分严格的情况下,可以通过缩短缓存过期时间来缓解这个问题。如果对数据一致性要求非常严格,可以配合cas方法来解决。cas方法为数据添加了“版本”信息,能解决更多的竞争问题。

    目前ruby的memcached-client没有实现cas方法,需要自己实现。

  • 再谈rails缓存机制的问题 » 石锅拌饭 | 互联网 Mac & 软件开发January 31st, 2008 19:54

    [...]我们首先怀疑是出现了类似“memcached使用中的竞争条件”中提到的问题,于是在缓存清除和创建的地方分别添加日志。结果很让我们吃惊,在after_save里用find取出的数据的确是新的,创建缓存的时间明显晚于after_save被调用的时间,但用同样的方法取出的数据却是旧的。[...]

 

原创粉丝点击