使用memcache实现高并发计数器

标签:Google App Engine

以前曾介绍了一下《使用Sharding Counters技术提升GAE的计数性能》,现在再介绍一下《High-concurrency counters without sharding》

思路很简单,使用2个memcache,一个用于存储增加的计数器,另一个用于上锁。
锁有时间限制,如果未到期,那么就更新memcache里的计数器;如果到期,就创建一个新锁,并把memcache里的值增加到数据库里,并删除现有的memcache里的计数器。

实现代码如下(我稍微改了下):
def incrementCounter(key, update_interval=10):
  """Increments a memcached counter.
  Args:
    key: 数据库里计数器实体的key.
    update_interval: 更新频率.
  """
  lock_key = "counter_lock:%s" % key
  count_key = "counter_value:%s" % key
  if memcache.add(lock_key, None, time=update_interval):
    count = int(memcache.get(count_key) or 0) + 1
    def tx():
      entity = db.get(key)
      entity.counter += count
      entity.put()
    db.run_in_transaction(tx)
    memcache.delete(count_key)
  else:
    memcache.incr(count_key, initial_value=0)
注意那个memcache.add函数,如果未到期,那么加锁失败,直接增加memcache的值;如果到期,那么加锁成功,更新memcache到datastore。

这种方法可能存在这些缺陷:
  • 如果计数频率太低,会导致memcache几乎没用,每次都得更新数据库。这比直接访问数据库多用了3次memcache操作。
  • 如果memcache里的计数器过期,那就会丢失更新间隔内的计数值。但一般是计算频率过低导致的,而频率越低,丢失的计数值就越小。
  • 需要真正的计数值时,需要同时访问datastore和memcache。
  • 在更新数据库和删除memcache里的计数器时存在不同步,可能丢失计数值。解决办法是把delete函数改成decr函数,剪掉之前获取的计数值即可。不过这样就会长期占用memcache了。
实际上如果memcache能提供一个失效事件,每次失效时自动更新到数据库,将会是个很不错的特性。

0条评论 你不来一发么↓

    想说点什么呢?