Lua在Redis中的应用—分布式锁,限制访问次数

Lua在Redis中的应用—分布式锁,限制访问次数

Lua是一个高效的轻量级脚本语言。它是开源的,非常小巧,整个源码也才五百来K,可以很方便地嵌入到程序中(无论是桌面端还是移动端)

1.分布式锁

分布式锁可以用多种方式来实现常用为以下方式:

1、基于数据库表做乐观锁,用于分布式锁。

2、memcached

3、redis

4、zookeeper

我们本次只说一下redis(r2m)的实现方式,并由简单分布式锁,以及问题分析,逐渐改进分布式锁的问题。最终达到完成一个尽可能完美的解决方案。(完美是相对的,最终并不能解决集群中锁所在服务器redis进程崩溃,而引起的锁失效问题)。

1.1首先看下简单锁:

* 获取锁(简单)

redis lockName tryNum

uuid

i i tryNum i

n redislockName uuid

n

uuid

e

e

* 释放锁

redis lockName lockValue

lockname lockName

chek lockValueredislockname

chek

redislockname

thread_Num

countDownLatch thread_Num

lockName

i i thread_Num i

executor

redis

lockValue

lockValue redis lockName

lockValue

redis lockName lockValue

countDownLatch

redis

countDownLatch

executor

这个简单的锁有什么问题呢?当持有锁线程死掉了会发生什么?这时锁就不可能再释放,释放锁的线程已死。

so我以将以上简单的锁改为可自动释放的锁

1.2自动释放锁

* 当获持有线程崩溃时,自动释放

redis lockName tryNum lock_timeout

uuid lock_timeout

key lockName

i i tryNum i

n rediskey uuid

n

uuid

time rediskey

time

t time

t

oldTime rediskey uuid

oldTime

uuid

e

e

* 释放锁

redis lockName lockValue

lockname lockName

chek lockValueredislockname

chek

time redislockname

time

redislockname

thread_Num

countDownLatch thread_Num

lockName

i i thread_Num i

executor

redis

lockValue

lockValue redislockName

lockValue

redislockNamelockValue

countDownLatch

redis

countDownLatch

executor

这一种实现基本没有太大的问题了,它比上种有很大的改善,它获取锁不再单纯依赖setnx

其中需要说明的有以下内容:

value 修改为(当前时间+过期时间): lock_timeout + System.currentTimeMillis

time rediskey

time

t time

t

oldTime rediskey uuid

oldTime

uuid

如果setnx为0时 看当前时间 是否大于保存的value如果是说明过期了,此时正常应该是直接就获取到锁了。

但如果此时两个线程都获取过这个过期信号。

so此时 redis.getSet(key, uuid) 执行getset命令将先设置一个时间,并返回老的时间,再对比一次与当前时间的值大小,就可以避免这种情况了。但这时问题来了此时未获取到的线程也会修改这个key的时间(getset) 但这个影响不大。那有没有更好的解决办法呢:

1.3 set nx px实现锁

* set nx px 进行设置锁

* nx当不存在时才设置 xx为存在时才设置,px为毫秒 ex为秒

* 这个有什么坏处呢?:

* 1、删除锁时如果这个锁已过期了页,而过期期间锁已被其它线程拿到,之后当前线程处理完了,del锁时已经删除的不是自己的锁了。

* 如下:A客户端拿到对象锁,但在因为一些原因被阻塞导致无法及时释放锁。

因为过期时间已到,Redis中的锁对象被删除。

B客户端请求获取锁成功。

A客户端此时阻塞操作完成,删除key释放锁。

C客户端请求获取锁成功。

这时B、C都拿到了锁,因此分布式锁失效。

* 2、要避免1中的情况发生,就要保证key的值是唯一的,且每一个拿到该key锁的值不一样,只有拿到锁的客户端才能进行删除。

* 基于这个原因,普通的del命令是不能满足要求的,我们需要一个能判断客户端传过来的value和锁对象的value是否一样的命令。Redis并没有这样的原子命令,这时可以通过Lua脚本来完成:

redis lockName value tryNum lock_timeout

i i tryNum i

valuel redislockName value lock_timeout

valuel

e

e

* 释放锁

redis lockName value

script

result redisscript lockNamevalue

result

thread_Num

countDownLatch thread_Num

lockName

i i thread_Num i

executor

redis

lockValue

value UUID

lockValue redislockNamevalue

lockValue

redislockNamevalue

countDownLatch

redis

countDownLatch

executor

也可以将上同set nx px 改为lua 是一样的

* lua的一个获取锁的方法 效果与相同当然弊端也一样;

redis lockName value tryNum lock_timeout

script

i i tryNum i

num redisscript lockNamevaluelock_timeout

errnum

num

e

e

至此锁就完了。。

2.IP防问次数限制

通过上面的例子我们也可以发现,只要有判断的,其实在分布式的系统中就已不再是原子操作,就算是在本地程序中加了锁,也只能保证在本JVM下的线程安全,但往往现在有服务都在多服务器部署。SOjava中的多并发大部分是用在处理数据上,而且往往不能多服务同时执行,除非从逻辑上进行分配数据如通过hash各服务器处理不同的数据 或者通过分布式锁等方法。redis的天生单线程和单进程。如果能将一些简单逻辑操作做为原子操作进行一块执行,就可以很方便的实现多服务器的原子操作。这可以通过redis的事务实现。但r2m(京东自研的分布式redis集群)并不支持事务,而且r2m的文档也说明了,所有需要事务的地方推荐使用lua脚本来实现。只要能事务实现的都可以用lua脚本实现。

如下这个业务可以简单的用lua很方便的实现。

redis ip limit_time limit_count

script

result redisscript iplimit_timelimit_count

result

以上是 Lua在Redis中的应用—分布式锁,限制访问次数 的全部内容, 来源链接: www.h5w3.com/php/705761.html

回到顶部