浅析Redis实现lock互斥访问资源
Redis是当前很流行的一种开源键值数据库。目前睿思的后台架构在数据库层采用了Redis和MySQL组合的形式,其中Redis主要用来存储状态信息(比如当前种子的peer)和读写频繁的数据。Redis完全运行在内存之上,无lock设计,速度非常快!通过实测,在睿思服务器上读写速度达到3万次/s。
在高并发的应用中,很多时候我们需要对某些资源进行竞争访问,比如在很多人下载一个热门资源,就可能存在很多请求去修改某个资源的peer信息(就是保存了当前保种人的ip地址和端口号),需要保证某个请求修改peer信息的时候,不允许其他请求修改,否则就会出现数据覆盖的问题。但是Redis没有提供对数据的加锁,所以需要我们通过Redis提供的命令自己实现:
思路一:
通过get 和set 命令实现
这中方式很容易想到,就是当每次请求到来时通过get判断这个锁是否存在,如果不存在则set创建。这种方法有一个弊端,由于get和set是两次Redis请求,二者之间有延时,在高并发的环境下,有可能在get检测到锁不存之后在set之前已经被其他线程set,这时当前线程再set,这样锁就失效了。所以这种方法只能应对并发量不是很高的情况。
思路二:
通过setnx 和 expire命令实现
在访问需要互斥访问的资源时,通过setnx命令去设置一个lock 键,setnx的作用是判断锁是否存在,如果不存在则创建,返回成功,如果存在则返回失败,服务器返回给客户端,指示客户端稍后重试。expire命令用于给该锁设定一个过期时间,用于防止线程crash,导致锁一直有效,从而导致死锁。例如:设定锁的有效期为100秒,那么即使线程奔溃,在100秒后锁会自动失效。
- > setnx lock "lock"
如果成功则设置过期时间
- >expire lock 100
访问互斥资源结束后,删除锁
- >del lock
思路三:
通过watch和Redis的事务命令实现
这种方式的效果和思路二类似。在请求到时先watch改资源锁,然后再通过在事务执行 创建锁的过程,锁的键值能唯一标识改请求(比如用时间+用户标识)。如果当前还有其他线程请求该资源,当判断该锁存在时则返回错误重试(例如睿思BT tracker返回“服务器过载,自动重试的”的提示就属于此类情况)。如果有多个请求同时判断改锁不存在而创建锁,这样也会由于watch了这个锁,导致之前watch的线程执行事务失败,返回客户端自动重试。这样达最终达到了锁的目的。
积极乐观,好好coding