构建一个可靠的分布式计数器--memcached之incr/decr操做实战分析

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

   incr/decr是memcached 1.2.4加入的原子性整数操做(changelog:2006-10-03)。这个功能经常使用于分布式项目中的计数。 github

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

   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

    为了验证,在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)。 spa

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? server

   等等,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。

相关文章
相关标签/搜索