geek的技术日志,记录每一次技术思考的闪光点。

使用memcached构建一个可靠的分布式计数器

使用memcached构建一个可靠的分布式计数器–memcached之incr/decr操作实战分析

最近的项目要依赖于一个分布式计数器的实现,因为公司使用memcached历史已久,所以就想到了使用memcached来作为计数器。之前也用过memcached的incr操作,但是有人封装好了,也没有深究,自己测试起来,越到了问题。经过大半天的调试、查阅文档、查看源码,解决了问题,现在将收集到的信息整理一下。

incr/decr是memcached 1.2.4加入的原子性整数操作。这个功能常用于分布式项目中的计数。

1.incr/decr在memcached中的保存方式是:字符串(十进制)表示的无符号64bit整数。    

memcached的wiki中这样描述:

Increment and Decrement. If an item stored is the string representation of a 64bit integer, you may run incr or decr commands to modify that number. You may only incr by positive values, or decr by positive values. They does not accept negative values.

为了验证,在memcached telnet终端中如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
seta 0 0 2
12
STORED
  #设置key=a的项为”12″
incr a 1
13
  #incr 1 返回13
append a 0 0 1
3
STORED
  #在a项后面增加一个字符’3′
get a
VALUE a 0 3
133
END
  #a变为133
incr a 1
134
  #a incr 1后变为134

可以看到,在把a当做字符串进行append操作后,得到字符串133,此时incr 1,变为134,证明memcached内部确实incr项将保存为字符串。

那么,如果我们使用二进制协议,将a写入数字后,再使用incr会产生什么结果呢?(使用Java语言,memcached客户端为:spymemcached-2.5)。

1
2
3
4
5
6
memcachedClient.set(key, exp,51);
Object object = memcachedClient.get(key);
System.out.println(“init “+ object);
longincr = memcachedClient.incr(key,1);
object = memcachedClient.get(key);
System.out.println(“after incr “+ incr +” “+ (object));

输出为:

1
2
init 51
after incr 4 52

是不是无法理解?按道理,incr的返回值就是memcached中的最终结果,与get结果相同。结果一个返回4,一个返回52?

等等,4和52,为什么看起来那么眼熟?

查阅ascii表得知,4的ascii值刚好是52,难道真这么巧,第二次get的时候,spymemcached客户端将4的字符值当做整型展开,得到了52?

不急,先使用telnet终端连接memcached,得到:

1
2
3
get a
VALUE a 512 1
4

其中512是flag属性。

这里简单提到一下memcached协议中的flag机制。flag是memcached除了key、value、expireTime之外额外保存的一个16bit(1.2.4之后为32bit)值,它标志着这个值的”类型“。这个值对于memcached-server是无意义的,它提供给client,来定义对value的处理方式(主要是编码,也有些客户端用flag作为是否压缩的依据)。

在spymemcached-client中,0为字符串,512为整数。所以这里虽然值保存为字符串4,但是spymemcached仍然将其当做整数解析,那么就得到了52!

于是我们得到一个教训:初始化计数器的时候,请使用字符串memcachedClient.set(key, exp, “0”),或者自行封装方法。否则可能得到不可预知的结果!

2. incr/decr操作无法刷新过期时间。

memcached的协议可以看这里。incr/decr操作无法刷新过期时间,所以过期时间以初始化的时间为准。

最开始以为spy的memcacheClient的incr(String key, int by, long def, int exp)可以刷新过期时间,后来才发现,此方法是封装了incr/decr和add的组合操作。这个exp指的是,若incr失败,则将def值add到此key,并使用这个过期时间exp,如果成功,过期时间不变!

因此,如果使用memcached作为长期的计数器,必须用额外的机制定时刷新item。memcached协议提供了touch方法,只刷新时间,不对值作修改,最新的spymemcached 客户端中提供了这个功能。

3. 如果对应值不存在,incr/decr会失败,而不会从0开始计数。

telnet下输入:

1
2
incr b 1
NOT_FOUND

返回NOT_FOUND,没有incr成功。memcachedClient.incr(key,delta)调用之后,若key不存在,则返回-1。

未经允许不得转载:极客技术 » 使用memcached构建一个可靠的分布式计数器

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址