赞
踩
命令 | 作用 | 语法格式 | 举个栗子 |
---|---|---|---|
keys | 查找所有符合给定模式 pattern 的 key | keys pattern | KEYS * 匹配数据库中所有 key KEYS t?? KEYS t[w]* KEYS *o* |
exists | 检查给定 key 是否存在 | exists key | EXISTS nick_name |
type | 返回 key 所储存的值的类型 | type key | TYPE page_views |
randomkey | 从当前数据库中随机返回一个 key | randomkey | RANDOMKEY |
expire | 为给定 key 设置生存时间,单位是秒 | expire key seconds | EXPIRE nick_name 120 |
pexpire | 为给定 key 设置生存时间,单位是毫秒 | pexpire key milliseconds | PEXPIRE age 60000 |
EXPIREAT | EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。 | expireat key timestamp | EXPIREAT cache 1714060800000 这个 key 将在 2024-4-26过期 |
PEXPIREAT | 这个命令和 EXPIREAT 命令类似,但它以毫秒为单位设置 key 的过期 unix 时间戳,而不是像 EXPIREAT 那样,以秒为单位 | pexpireat key milliseconds | PEXPIREAT cache 1714060800000000 |
ttl | 以秒为单位,返回剩余生存时间 | ttl key | TTL nick_name |
pttl | 以毫秒为单位,返回剩余生存时间 | pttl key | PTTL nick_name |
PERSIST | 移除给定 key 的生存时间,将这个 key 从『可挥发』的(带生存时间 key )转换成『持久化』的(一个不带生存时间、永不过期的 key )。 | persist key | PERSIST nick_name |
move | 将当前数据库的key移动到给定的数据库中 | move key db | MOVE goods_lock 3 |
del | 删除给定的一个或多个 key | del key [key …] | DEL name type website |
rename | 将 key 改名为 newkey,当 key 和 newkey 相同,或者 key 不存在时,返回一个错误。当 newkey 已经存在时,RENAME 命令将覆盖旧值 | rename key newkey | RENAME message msg |
RENAMENX | 当且仅当 newkey 不存在时,将 key 改名为 newkey 。当 key 不存在时,返回一个错误 | renamenx key newkey | RENAMENX player best_player |
flushdb | 清空当前库 | flushdb | flushdb |
flushall | 清空所有库 | flushall | flushall |
命令 | 作用 | 语法格式 | 举个栗子 |
---|---|---|---|
set | 添加一个键值对 | set key value | SET apple www.apple |
get | 获取 key 所关联的字符串值 | get key GET apple | |
mset | 同时设置多个key-value | mset <key1> <value1> <key2> <value2> … | MSET nick_name jack age 30 |
mget | 获取多个key对应的值 | mget <key1> <key2> … | MGET nick_name age |
append | 将给定的value追加到原值的末尾 | append key value | APPEND apple “.com” |
setex | 将值 value 关联到 key ,并将 key的生存时间设为 seconds (以秒为单位) | setex key seconds value | SETEX cache_user 100 10086 |
psetex | 将值 value 关联到 key ,并将 key的生存时间设为 milliseconds(以毫秒为单位) | psetex key milliseconds value | PSETEX cache_user_id 101 10000 |
setnx | key不存在时,设置key的值,可以实现分布式锁 | setnx <key> <value> | SETNX goods_lock 1 |
setrange | 覆盖指定位置的值 | setrange <key> <起始位置> <value> | SETRANGE nick_name 4 son |
getrange | 获取指定范围的值,类似java中的substring | getrange key start end | GETRANGE nick_name 4 6 |
strlen | 获取值的长度 | strlen <key> | STRLEN nick_name |
incr | 原子递增1 | incr <key> | INCR page_views |
decr | 原子递减1 | decr <key> | DECR page_views |
incrby/decrby | 将key中存储的数字值递增/递减指定的步长,若key不存在,则相当于在原值为0的值上递增/递减指定的步长。 | incrby/decrby <key> <步长> | INCRBY page_views 5 DECRBY page_views 5 |
getset | 以新换旧,设置新值同时返回旧值 | getset <key> <value> | GETSET nick_name tom |
MSETNX | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。即使只有一个给定 key 已存在, MSETNX 也会拒绝执行所有给定 key 的设置操作。 | msetnx key value [key value …] | MSETNX rmdbs “MySQL” nosql “MongoDB” key-value-store “redis” |
setnx key value
127.0.0.1:6379[1]> exists task #task 不存在
(integer) 0
127.0.0.1:6379[1]> setnx task do-something #此时task能够设置成功
(integer) 1
127.0.0.1:6379[1]> setnx task do-other #尝试覆盖task,但是失败
(integer) 0
127.0.0.1:6379[1]> get task #获取task,还是第一次设置的值
"do-something"
127.0.0.1:6379[1]>
可以将 SETNX 用于加锁(locking)
SETNX lock.foo <current Unix time + lock timeout + 1>
处理死锁(deadlock)
上面的锁算法有一个问题:如果因为客户端失败、崩溃或其他原因导致没有办法释放锁
的话,怎么办?
这种状况可以通过检测发现——因为上锁的 key 保存的是 unix 时间戳,假如 key 值
的时间戳小于当前的时间戳,表示锁已经不再有效。
但是,当有多个客户端同时检测一个锁是否过期并尝试释放它的时候,我们不能简单粗
暴地删除死锁的 key ,再用 SETNX 上锁,因为这时竞争条件(race condition)已经形成了:
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
注意,即便 C4 的 GETSET 操作对 key 进行了修改,这对未来也没什么影响。
警告:为了让这个加锁算法更健壮,获得锁的客户端应该常常检查过期时间以免锁因诸如 DEL 等命令的执行而被意外解开,因为客户端失败的情况非常复杂,不仅仅是崩溃这么简单,还可能是客户端因为某些操作被阻塞了相当长时间,紧接着 DEL 命令被尝试执行(但这时锁却在另外的客户端手上)。
127.0.0.1:6379[1]> set str "hello world"
OK
127.0.0.1:6379[1]> SETRANGE str 6 "redis" #对非空字符串进行 SETRANGE
(integer) 11
127.0.0.1:6379[1]> get str
"hello redis"
127.0.0.1:6379[1]> exists empty_str #对空字符串/不存在的 key 进行 SETRANGE
(integer) 0
127.0.0.1:6379[1]> SETRANGE empty_str 5 "redis" #对不存在的 key 进行 SETRANGE
(integer) 10
127.0.0.1:6379[1]> get empty_str # 空白处被"\x00"填充
"\x00\x00\x00\x00\x00redis"
127.0.0.1:6379[1]>
因为有了 SETRANGE 和 GETRANGE 命令,你可以将 Redis 字符串用作具有 O(1)随机访问时间的线性数组,这在很多真实用例中都是非常快速且高效的储存方式
127.0.0.1:6379[1]> mset str hellochina date 2024-4-26 time "9:57"
OK
127.0.0.1:6379[1]> mget str date time
1) "hellochina"
2) "2024-4-26"
3) "9:57"
127.0.0.1:6379[1]>
127.0.0.1:6379[1]> msetnx job dev str hellojava use springboot
(integer) 0
127.0.0.1:6379[1]> msetnx job dev use springboot
(integer) 1
127.0.0.1:6379[1]> mget job use
1) "dev"
2) "springboot"
127.0.0.1:6379[1]>
127.0.0.1:6379[1]> exists phone # 对不存在的 key 执行 APPEND
(integer) 0
127.0.0.1:6379[1]> append phone apple # 对不存在的 key 执行 APPEND,等同于 SET phone "apple"
(integer) 5 # 字符长度
127.0.0.1:6379[1]> append phone "- 17" # 对已存在的字符串进行 APPEN
(integer) 9 # 长度从 5 个字符增加到 9 个字符
127.0.0.1:6379[1]> get phone
"apple- 17"
127.0.0.1:6379[1]>
APPEND timeseries "fixed-size sample"
127.0.0.1:6379[1]> getset db mysql # 没有旧值,返回 nil
(nil)
127.0.0.1:6379[1]> get db
"mysql"
127.0.0.1:6379[1]> getset db redis
"mysql"
127.0.0.1:6379[1]> get db
"redis"
127.0.0.1:6379[1]>
GETSET 可以和 INCR 组合使用,实现一个有原子性(atomic)复位操作的计数器(counter)。
举例来说,每次当某个事件发生时,进程可能对一个名为 mycount 的 key 调用 INCR 操作,通常我们还要在一个原子时间内同时完成获得计数器的值和将计数器值复位为 0 两个操作。
可以用命令 GETSET mycounter 0 来实现这一目标
......
127.0.0.1:6379[1]> incr mycount
(integer) 10
127.0.0.1:6379[1]> getset mycount 0
"10"
127.0.0.1:6379[1]> get mycount
"0"
127.0.0.1:6379[1]>
127.0.0.1:6379[1]> set currentPage 5
OK
127.0.0.1:6379[1]> incr currentPage
(integer) 6
127.0.0.1:6379[1]> get currentPage
"6"
127.0.0.1:6379[1]>
INCR peter::2024.4.22 。
限速器是特殊化的计算器,它用于限制一个操作可以被执行的速率(rate)。
限速器的典型用法是限制公开 API 的请求次数,以下是一个限速器实现示例,它将 API 的最大请求数限制在每个 IP 地址每秒钟十个之内
FUNCTION LIMIT_API_CALL(ip) ts = CURRENT_UNIX_TIME() keyname = ip+":"+ts current = GET(keyname) IF current != NULL AND current > 10 THEN ERROR "too many requests per second" END IF current == NULL THEN MULTI INCR(keyname, 1) EXPIRE(keyname, 1) EXEC ELSE INCR(keyname, 1) END PERFORM_API_CALL()
这个实现每秒钟为每个 IP 地址使用一个不同的计数器,并用 EXPIRE 命令设置生存时间(这样 Redis 就会负责自动删除过期的计数器)。
注意,我们使用事务打包执行 INCR 命令和 EXPIRE 命令,避免引入竞争条件,保证每次调用 API 时都可以正确地对计数器进行自增操作并设置生存时间。
以下是另一个限速器实现:
FUNCTION LIMIT_API_CALL(ip):
current = GET(ip)
IF current != NULL AND current > 10 THEN
ERROR "too many requests per second"
ELSE
value = INCR(ip)
IF value == 1 THEN
EXPIRE(ip,1)
END
PERFORM_API_CALL()
END
这个限速器只使用单个计数器,它的生存时间为一秒钟,如果在一秒钟内,这个计数器的值大于 10 的话,那么访问就会被禁止。
这个新的限速器在思路方面是没有问题的,但它在实现方面不够严谨,如果我们仔细观察一下的话,就会发现在 INCR 和 EXPIRE 之间存在着一个竞争条件,假如客户端在执行INCR 之后,因为某些原因(比如客户端失败)而忘记设置 EXPIRE 的话,那么这个计数器就会一直存在下去,造成每个用户只能访问 10 次,这简直是个灾难!
要消灭这个实现中的竞争条件,我们可以将它转化为一个 Lua 脚本,并放到 Redis 中运行(这个方法仅限于 Redis 2.6 及以上的版本):
local current
current = redis.call("incr",KEYS[1])
if tonumber(current) == 1 then
redis.call("expire",KEYS[1],1)
end
通过将计数器作为脚本放到 Redis 上运行,我们保证了 INCR 和 EXPIRE 两个操作的原子性,现在这个脚本实现不会引入竞争条件,它可以运作的很好。
关于在 Redis 中运行 Lua 脚本的更多信息,请参考 EVAL 命令。
还有另一种消灭竞争条件的方法,就是使用 Redis 的列表结构来代替 INCR 命令,这个方法无须脚本支持,因此它在 Redis 2.6 以下的版本也可以运行得很好:
FUNCTION LIMIT_API_CALL(ip)
current = LLEN(ip)
IF current > 10 THEN
ERROR "too many requests per second"
ELSE
IF EXISTS(ip) == FALSE
MULTI
RPUSH(ip,ip)
EXPIRE(ip,1)
EXEC
ELSE
RPUSHX(ip,ip)
END
PERFORM_API_CALL()
END
新的限速器使用了列表结构作为容器, LLEN 用于对访问次数进行检查,一个事务包裹着 RPUSH 和 EXPIRE 两个命令,用于在第一次执行计数时创建列表,并正确设置地设置过期时间,最后, RPUSHX 在后续的计数操作中进行增加操作。
127.0.0.1:6379[1]> bitcount bits
(integer) 0
127.0.0.1:6379[1]> setbit bits 0 1 # 0001
(integer) 0
127.0.0.1:6379[1]> bitcount bits
(integer) 1
127.0.0.1:6379[1]> setbit bits 3 1 # 1001
(integer) 0
127.0.0.1:6379[1]> bitcount bits
(integer) 2
127.0.0.1:6379[1]>
SETBIT peter 100 1
;如果明天 peter 也继续阅览网站,那么执行命令 SETBIT peter 101 1
,以此类推。 BITCOUNT peter
,得出的结果就是 peter 上线的总天数。以下是 Redis String 数据类型的一些主要特性:
Redis 使用 SDS 简单动态字符串(Simple Dynamic String,SDS)来表示字符串,Redis 中字符串类型包含的数据结构有:“整数(R_INT)” 与 “字符串(R_RAW)”
SDS 被广泛应用在 Redis 的各个地方,包括:
SDS(Simple Dynamic String,简单动态字符串)是 Redis 自己构建的抽象字符串类型,其在 C 语言原生字符串类型的基础上进行了一些改进和扩展。SDS 的主要结构如下
属性 | 说明 |
---|---|
free | 表示 Buf 数组中未使用字节的数量,也就是 Buf 数组的剩余空间。这样可以在增加字符串长度时,避免频繁的内存重新分配 |
len | 表示 Buf 数组中已使用字节的数量,也就是字符串的长度。这样可以在 O(1) 的时间复杂度内获取字符串长度,而不需要像 C 语言字符串那样遍历整个字符串 |
buf[] | 字节数组,用于保存字符串。这个数组的末尾总是包含一个空字符(‘\0’),这样 SDS 就可以兼容 C 语言的字符串函数 |
这种设计使得 SDS 在保持与 C 语言字符串兼容的同时,具有更高的效率和更好的安全性。对比参照如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。