文章目录
命令
1.插入键值对
1.1.set key value
返回值(OK:成功)
若已经存在相同的key值 -> 覆盖原值
set 命令可加选项
- NX
- 加上此选项时,则与setnx功能相同
- 成功则返回OK
- 失败时(当已经存在key值的键值对时),返回nil。
- PX miliseconds
- 指定过期时间,指定该键值对的生存时间为 millisecond 毫秒
- 与 set key value + expire key seconds 功能一样
1.2.setnx key value
返回值(1:成功;0:失败)
- 1:成功;
- 0:失败,若key值已存在则不允许重复赋值
因为不允许用户创建key值重复的键值对,所以可用于实现分布式锁,只要结果返回“1”,说明本对象获取了分布锁。
若key值已经存在,则用户利用setnx 命令尝试创建相同key值的操作将会失败。
2.get key
返回值(value)
根据key值获取value值
3.getset key newValue
返回值(被newValue覆盖的原value)
根据key值获取当前value值,并将value值覆盖为新value值。
4.del key
返回值
- 1:成功;
- 0:失败,若key值不存在则会返回失败
删除键值对(key,value)
5.exists key
返回值
- 1:存在;
- 0:不存在
判断键值对(key,value)是否存在
6.TTL key
返回值
- -2:key值不存在;
- -1:key存在但没有设置过期时间;
- 剩余生存时间(存在过期时间)
获取键值对(key,value)的剩余生存时间(单位:毫秒)
7.expire key seconds
返回值
- 1:成功;
- 0:失败,若key值不存在则会返回失败
对键值对(key,value)设置过期时间(number单位:秒),过期后该键值对会被自动删除;
还有其他设置过期时间的命令,只是number单位不同。
8.persist key
返回值
- 1:成功;
- 0:失败
将键值对设置为永不过期。
如何启动redis服务
1.启动redis 服务端
redis-server.exe redis.windows.conf
只需要存在.exe文件和conf配置文件这两个,即可成功的启动一个实例(linux需要.sh文件)
2.启动redis客户端,需要指定连接的redis服务端提供的ip地址
redis-cli.exe -h [host-address] -p [port] -a [password]
若不输入 -a [password],可以在连接上服务端后,使用 auth [password] 命令,进行校验。
//举例:
redis-cli.exe -h 127.0.0.1 -p 6379 -a redis
3.redis集群部署
重启实例会自动部署到集群中,因为存在配置文件、aop\rdb文件。所以,只有在删除这些文件后,才需要重新部署集群。
A.首先开启若干个redis的server服务(至少三个才能部署有效集群);
B.其次输入语句,调用redis-trib.rb文件,对服务实例进行集群部署。
redis-trib.rb create --replicas [raplica’s number] [serverip:port] [serverip:port] [serverip:port] …
参数 --replicas 的作用是指明主节点的从节点数量。若为0个,则redis集群不存在从节点备份主节点–>容易导致主节点挂掉引起的数据丢失和不可用,因此建议至少有一个从节点。
//举例
redis-trib.rb create --replicas 1 127.0.0.1:6631 127.0.0.1:6632 127.0.0.1:6633 127.0.0.1:6634 127.0.0.1:6635 127.0.0.1:6636
C.测试是否部署成功
启动redis客户端,登陆到任意一个服务实例;
使用 get set命令,查看结果。
分布式锁的实现
实现原理
通过对某个key值的键值对的写操作结果成功与否,判断进行写操作的用户是否成功获取了分布式锁。
成功获取锁的条件:
- 锁对象:key值符合
- 锁对象状态:过期(键值对的生存时间超过了应该生存的最长时间)—— 避免死锁
- 写(或写覆盖)结果:成功
因此,一个key,就是一个锁。锁的获取与释放生存周期就是该key键值对的创建到删除的过程。
锁的获取与释放:
- 获取锁 : set key value NX PX miliseconds
- 不能使用 setnx key value 或 set key value NX,组合expire key seconds 的方式获取锁,因为加锁过程应该为原子操作,若redis在创建key锁后设置过期时间前宕机,则该key锁将永远存在(造成死锁)。
- 释放锁 : del key
- 注意释放锁前先判断锁是自己创建的锁(不能破坏被锁状态);
- 简单方法是保证value唯一,通过value值判断待删除键值对是否为自己创建的
一组用户都希望向键值固定为key的键值对中写入自己的value,“写入成功”就称为“成功获取了分布式锁key”。不过为了解决死锁的问题,引入写操作成功的另外一个条件:只有对已经超过过期时间的value进行成功覆盖写的用户,才是获取了分布式锁的用户。
分布式死锁
1.什么是分布式死锁
若没有设置“锁”(即键值对)的过期时间,则当需要释放锁的对象在成功释放锁前崩溃,则该锁将永远无法被释放,造成其他尝试获取锁的对象一直以为该锁被占用,造成死锁现象。
一般释放锁的操作由持有锁的对象进行释放。但若持有锁的对象无法主动释放锁(如对象存在的机器在释放锁前崩溃了,无法在进行任何操作),则分布式锁中其他等待获取该锁的对象会以为该锁一直被占用着,进入无限等待(redis死锁)。
2.如何避免死锁
对锁设置过期时间,超过过期时间则主动“解锁”。
3.进行释放死锁的对象
3.1.redis 服务
在创建键值对时就为他设置过期时间,这样过期后锁会被自动释放(即键值对的存储值会在一定时间后被自动清除)。
setnx只有当原key键值对不存在时才能成功写入,这样等待锁的对象只需要利用setnx 不断尝试获取锁即可。
3.2.其他等待锁的对象
判断锁已经过期,主动删除并创建锁(覆盖原key值)
这样可能出现很多对象同时 ttl 发现这个锁过期了,并都对该键值对进行了写覆盖动作(getset 命令)。
只要redis服务正常:覆盖动作(getset)结果都是成功的,因此需要double-check。
只有在double-check 成功后,才应该继续执行下面的任务。
任务执行完成后不要忘记释放锁。
3.3.比较
redis服务对于过期键的删除有多种策略1,若需要redis服务实现避免死锁,那么就只能选择“立即删除”或“惰性删除”的策略,但这些策略有一些缺点。
- a).对于立即删除,比如若程序在当过期时间点恰好正在执行某个比较占内存占cpu的任务,那么若此时开始执行删除过期键值对的任务,则会增大系统压力(此时“错峰”会比较好,等系统不忙了再执行)。
- b).对于立即删除,另外由于redis的事件执行器的特点,删除过期键这种时间事件的寻址是根据无序链表,时间复杂度为O(n),在缓存数据比较多的系统中,将是一个比较耗时的任务,这对cpu的消耗将会导致系统提供给其他应用的服务能力降低。因此,为每个键值对都进行立即删除并不是一个理想的策略。
- c).对于惰性删除,会比较占内存,对于内存空间对性能影响较大的程序不友好。
因此,鉴于redis服务的一些缺点,可以选择两者结合的方式来释放锁。
4.利用double-check实现分布式锁:double-check 剩余生存时间避免死锁
当不依靠redis服务删除过期键值来规避死锁时,需要double-check
真实使用时,应该使用 set key value NX PX miliseconds 不断尝试获取锁 即可(保证锁操作的原子性)。
如何double-check:
- 1).first-check:利用 ttl command 发现锁已经过期,第一次"check过期"成功,进行键值覆盖动作,
- 2).second-check:在first-check 成功后,进行getset commmand 操作,然后利用 ttl command 进行sencond-check。
- 失败 —— 没有获取锁
- 被自己覆盖的键值是被其他对象覆盖的新值:返回值发现被覆盖值的生存剩余时间不为0(未达到过期时间),说明被自己覆盖的键值是被其他对象覆盖的新值,则继续陷入锁等待状态;
- 第二次check过期失败,以返回的被覆盖值的真实过期时间为新的过期时间作为首次check依据(等待生存剩余时间后在进行尝试锁的过期检查)
- 成功 —— 获取锁
- 在获取锁的竞争中自己是第一个完成了键值覆盖的用户:若返回值发现被覆盖值的生存剩余时间已经为0,则获取了锁。
- 失败 —— 没有获取锁
经过上述释放锁的过程可以看出,需要谨慎选择过期时间,避免持有锁对象正常执行时被剥夺锁的持有,这样就丧失了同步锁的意义。
注意
1、一定要用SET key value NX PX milliseconds 命令
如果不用,先设置了值,再设置过期时间,这个不是原子性操作,有可能在设置过期时间之前宕机,会造成死锁(key永久存在) 。
double-check只是获取锁的逻辑,要保证锁的特性,必须保证加\解锁操作具有原子性。
2、value要具有唯一性
这个是为了在解锁的时候,需要验证value是和加锁的一致才删除key。
这是避免了一种情况:假设A获取了锁,过期时间30s,此时35s之后,锁已经自动释放了,A去释放锁,但是此时可能B获取了锁。A客户端就不能删除B的锁了。
redis过期删除策略:三种策略(立即删除,惰性删除,定时删除),redis采取惰性删除与定时删除的组合方式作为默认的过期删除策略。 ↩︎