php memcached互斥,使用Memcached实现分布式互斥锁 | 楚吟风

最近我(楚吟风http://Chuyinfeng.com)查看服务器异常日志,发现有很多如下报错:

Duplicate entry ‘xxxxxx’ for key ‘yyyyy’

很显然,是重复插入相同唯一字段记录造成的。

但是,业务逻辑里面明明有做判断,先SELECT不到再做INSERT的,怎么还会重复呢?

继续跟踪后发现,统一请求竟然发送了3次,每次间隔才几ms而已。

这就可能造成如下情况:

请求A在00:00:00.000最先到达,在00:00:00.002先SELECT发现没有记录,在00:00:00.004进行INSERT,在00:00:00.007操作完成。

请求B在00:00:00.004正好到达,在00:00:00.006先SELECT发现没有记录,在00:00:00.008进行INSERT,发生Duplicate异常。

从经验判断,这种重复请求,很可能是对DOM中的包含了href的a元素做了click事件的ajax绑定。

但是IE6下这样做有个问题,a语义为超链接跳转的元素,就算在click后return false,也会进行一次页面重新导航,在IE编程中来说就是触发了navigate事件。这时候,ajax操作实际上会被浏览器强制中断。

这是产生问题的直接原因,根本原因在于后端代码不够强壮,没有做到对相同数据的请求做异常处理。

我们的多台WEB机使用nginx的upstream做负载均衡,采用默认轮询方式,所以需要实现分布式WEB上的相同数据请求的互斥锁。

mysql的select和insert操作都要消耗时间,并且由于mysql的锁等导致的性能抖动、网络抖动等原因,不能保证每台WEB机进行select和insert都消耗同样的时间。

DB层的解决方案可以忽略了。内存层面呢?没法做到分布式。那么分布式内存呢?很好,那就memcached吧。

很自然的,我们可以想到,对相同数据进行md5得到唯一key,先get判断该请求是否存在,如果不存在则set到memc中并对数据进行处理,否则直接丢弃请求。ok , just do it.

但是,同样的异常还在产生!

究其原因,是因为当PHP和MEMC在不同机器上时,每次操作的耗时并且均衡,甚至是一台机器上也是如此。

请求A在00:00:00.000到达,在00:00:00.001 get,在00:00:00.003返回数据,为空,在00:00:00.005 set成功。

请求B在00:00:00.001到达,在00:00:00.002 get,在00:00:00.004返回数据,为空,在00:00:00.006 set成功。

这样看来,由于要发起get/set两次操作才能得出结果,耗时已经在毫秒级了,自然起不到毫秒级过滤的作用。

再想想,memc的原子操作,自增incr呢?不存在则设置setex呢?很不好意思,setex是redis中的。那么找个类似的,不存在则增加add呢?

ok,再来尝试用memc的add做分布式锁吧。我们先看看add方法如何使用

Memcache::add()方法在缓存服务器之前不存在key时, 以key作为key存储一个变量var到缓存服务器。 同样可以使用函数 memcache_add()。

成功时返回 TRUE, 或者在失败时返回 FALSE。 如果这个key已经存在返回FALSE。

这样我们只需要一个add操作就可以得到结果了,处理速度基本依赖内存IO和cpu频率上,比毫秒级了不知还要小多少倍。我们假设一下:

情况1

请求A在00:00:00.000到达,在00:00:00.001 add 成功,在00:00:00.004返回数据,为true。

请求B在00:00:00.001到达,在00:00:00.002 add 成功,在00:00:00.003返回数据,为false。

情况2

请求A在00:00:00.000到达,在00:00:00.004 add,在00:00:00.005返回数据,为false。

请求B在00:00:00.001到达,在00:00:00.002 add,在00:00:00.003返回数据,为true。

可见,网络抖动对基于add的判断结果是没有影响的,可以保证同时只有一个请求能够返回true。

最终实现代码很简单,但是效果却非常好

1

2

3

4

5

6

7

8

9

10

11

12

13

14/**

* lock, 分布式互斥锁

*

*  楚吟风 http://chuyinfeng.com

*

* @param string $key, memcache键名

* @param int $timeout, kv对生存时间,默认30s

* @return boolean

*/

public function lock($key,$timeout = 30)

{

$memc = memcache_connect("localhost", 11211);

return $memc->add($key, 1, FALSE,$timeout);

}

Tags: Memcache, PHP, PHP

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值