1.增
set key value :
如果key已经持有其他值,SET就覆写旧值,无视类型。
返回值:
总是返回 OK ,因为 SET 不可能失败。setnx key value :
如果key已经存在了,就不做任何操作。
返回:
1或者0
setex key seconds value
将值value关联到key,并将key的生存时间设为seconds(以秒为单位)。如果key存在,就覆写旧值。
返回值:
设置成功时返回OK。
当seconds参数不合法时,返回一个错误。
mset key value [key value ....]
一个原子操作,新值覆盖旧值
返回值:
msetnx 是原子操作,如果一个key不存在,就全部不执行
返回值:1或者0
2.删
del key
3.改
setrange key offset value:
把key的第offset个字符开始,设置为value,如果key不存在,就创建
append key value
在key的基础上,追加value,如果key不存在,就和set 是一样的
一个原子操作,新值覆盖旧值
getset key value:
设置新值,返回key的旧值。
如果key不存在,返回nil
decr key :
将key的值减一。
如果可以不存在,就以0减一,如果key的value为str,就报错,
decrby key decrement :减去decrement
如果key不存在,按照0开始算。如果key的value 是字符串,则报错。
incr key
incrby key increment
同上
4.查
mget key [key....]
如果其中一个key 不存在,则该key返回nil
getrange key start end
key 不存在 返回“” ,如果超出字符串的长度,就返回字符串本身
strlen key :
不存在key,返回0
exists key:
不存在key,返回0
知识点:
设计模式(Design pattern): 将SETNX用于加锁(locking)
SETNX可以用作加锁原语(locking primitive)。比如说,要对关键字(key)foo加锁,客户端可以尝试以下方式:
SETNX lock.foo <current Unix time + lock timeout + 1>
如果SETNX返回1,说明客户端已经获得了锁,key设置的unix时间则指定了锁失效的时间。之后客户端可以通过DEL lock.foo来释放锁。
如果SETNX返回0,说明key已经被其他客户端上锁了。如果锁是非阻塞(non blocking lock)的,我们可以选择返回调用,或者进入一个重试循环,直到成功获得锁或重试超时(timeout)。
处理死锁(deadlock)
上面的锁算法有一个问题:如果因为客户端失败、崩溃或其他原因导致没有办法释放锁的话,怎么办?
这种状况可以通过检测发现——因为上锁的key保存的是unix时间戳,假如key值的时间戳小于当前的时间戳,表示锁已经不再有效。
但是,当有多个客户端同时检测一个锁是否过期并尝试释放它的时候,我们不能简单粗暴地删除死锁的key,再用SETNX上锁,因为这时竞争条件(race condition)已经形成了:
- C1和C2读取lock.foo并检查时间戳,SETNX都返回0,因为它已经被C3锁上了,但C3在上锁之后就崩溃(crashed)了。
- C1向lock.foo发送DEL命令。
- C1向lock.foo发送SETNX并成功。
- C2向lock.foo发送DEL命令。
- C2向lock.foo发送SETNX并成功。
- 出错:因为竞争条件的关系,C1和C2两个都获得了锁。
幸好,以下算法可以避免以上问题。来看看我们聪明的C4客户端怎么办:
- C4向lock.foo发送SETNX命令。
- 因为崩溃掉的C3还锁着lock.foo,所以Redis向C4返回0。
- C4向lock.foo发送GET命令,查看lock.foo的锁是否过期。如果不,则休眠(sleep)一段时间,并在之后重试。
- 另一方面,如果lock.foo内的unix时间戳比当前时间戳老,C4执行以下命令:
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
- 因为GETSET的作用,C4可以检查看GETSET的返回值,确定lock.foo之前储存的旧值仍是那个过期时间戳,如果是的话,那么C4获得锁。
- 如果其他客户端,比如C5,比C4更快地执行了GETSET操作并获得锁,那么C4的GETSET操作返回的就是一个未过期的时间戳(C5设置的时间戳)。C4只好从第一步开始重试。