背景
之前在系统中有做一个在客户端重试时,进行去重的逻辑,大致思路是将客户端的请求缓存到分布式缓存中,每次请求到达时先在缓存中查询,如果有就直接返回,没有就帮用户重试该请求。一天晚上(不凑巧,刚好是结婚当天),一个微信会话来了,某某老大的评论重复……
分析
上面在写数据前,会先请求一次缓存,但是这个操作在分布式环境下显然会有如下场景出现:
① 节点A,Get数据,返回不存在
② 节点B,Get数据,返回不存在
③ 节点A写数据
④ 节点A返回
⑤ 节点B写数据
⑥ 节点B返回
根本原因就是Get数据和Set数据不是原子操作,导致出现了A,B都认为数据不存在。
问题很明显,就是没有对数据操作进行同步,如果在单机环境下,这个可以很简单的通过操作系统提供的各种同步原语处理,但是现在节点A,B是处在不同机器上,这就涉及到分布式锁的问题了。类似于单机环境下的线程同步原语,我们只需要一种机制让应用程序知道某个资源被占用了(例如mutex如果lock失败,操作系统即会将该进程挂起),在分布式缓存中一般都存在一个叫做add的操作,该操作保证只有在资源不存在时才能执行成功,否则会告知调用者失败,且标注为特殊的错误码。
实现
这里只简单给出了获取锁一般实现伪代码,不同的业务场景有不同的处理,比如失败后继续重试直到成功。
在memcache中:
if(cache->add(key, value, expire))
{
//get lock successful
}
else
{
//get lock fail
}
在Redis中可以通过SET命令(2.6.12以上版本)或者SETNX命令,类似memcache中的ADD
SET key value NX EX max_lock_time。在redis-py中有一个封装好的lock实现可以直接使用。
总结
像memcache,redis这里系统一般都是作为缓存来使用,但是在某些时候通过深入挖掘其实也可以有一些意想不到的作用,通过一个简单的语句既可以实现一个基本上够用的分布式锁,其性价比不言而喻。在网上随便一搜,也有好多类似的同行遇到这个问题,下面是几个链接,有国产的也有国外:
http://abhinavsingh.com/blog/2009/12/how-to-use-locks-for-assuring-atomic-operation-in-memcached/