redis 基础
Redis 是一个高性能的 key-value 开源数据库,遵守 BSD 协议。Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。主要优势如下:
- 性能极高: 读11万 pps,写8万 pps
- 丰富的数据类型:Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
- 原子: Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
- 丰富的特性: Redis还支持 publish/subscribe, 通知, key 过期等等特性。
安装使用
Redis 支持 32 位和 64 位。这个需要根据你系统平台的实际情况选择,这里我们下载 Redis-x64-xxx.zip压缩包到 C 盘,解压后,将文件夹重新命名为 redis。
打开一个 cmd 窗口 使用 cd 命令切换目录到 C:\redis 运行:
redis-server.exe redis.windows.conf
在另外一个cmd,运行
redis-cli.exe -h 127.0.0.1 -p 6379
设置键值对:
set myKey abc
取出键值对:
get myKey
数据类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
DEL runoob 用于删除前面测试用过的 key
string (字符串)
string 是 redis 最基本的类型,一个 key 对应一个 value。string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。string 类型的值最大能存储 512MB。
redis 127.0.0.1:6379> SET runoob "菜鸟教程"
OK
redis 127.0.0.1:6379> GET runoob
"菜鸟教程"
Hash (哈希)
Redis hash 是一个键值(key=>value)对集合。
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
> hmset h1 k1 v1 k2 v2
OK
> hget h1 k1
"v1"
> hget h1 k2
"v2"
> hset h1 k3 v3
1
> hget h1 k3
"v3"
实例中我们使用了 Redis HMSET, HGET 命令,HMSET 设置了两个 field=>value 对, HGET 获取对应 field 对应的 value。每个 hash 可以存储 2^32 -1 键值对(40多亿)。
List (列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
链表(双向链表),增删快,提供了操作某一段元素的API。
> lpush l1 s1 s2
(integer) 2
> lpush l1 s3
(integer) 3
> lrange l1 0 10
1) "s3"
2) "s2"
3) "s1"
列表最多可存储 2^32 - 1 元素。
Set(集合)
Redis 的 Set 是 string 类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
> sadd set1 sval1 sval2 sval3 sval1
(integer) 3
> smembers set1
1) "sval2"
2) "sval1"
3) "sval3"
注意:以上实例中 sval1 添加了两次,但根据集合内元素的唯一性,第二次插入的元素将被忽略。
集合中最大的成员数为 2^32 - 1(4294967295, 每个集合可存储40多亿个成员)。
zset (有序集合)
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
// zadd key score member
// 添加元素到集合,元素在集合中存在则更新对应score
> zadd zset1 0.1 zset_val1
(integer) 1
> zadd zset1 0.2 zset_val2
(integer) 1
> zadd zset1 0.3 zset_val3
(integer) 1
> zadd zset1 0.15 zset_val4
> ZRANGEBYSCORE zset1 0 1000
1) "zset_val1"
2) "zset_val4"
3) "zset_val2"
4) "zset_val3"
发布订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。redis 客户端可以订阅任意数量的频道。
相关命令如下:
PSUBSCRIBE
PUBLISH
PUBSUB
PUNSUBSCRIBE
SUBSCRIBE
UNSUBSCRIBE
SUBSCRIBE、UNSUBSCRIBE和PUBLISH实现发布/订阅消息。发送者(发布者)不会为特定接收者(订阅者)发送消息,发布的消息被描述为通道,而不知道可能有什么订阅者(如果有的话)。订阅者表示对一个或多个频道感兴趣,只接收感兴趣的消息,而不知道有哪些(如果有的话)发布者。发布者和订阅者的这种分离可以实现更大的可伸缩性和更动态的网络拓扑。
例如,为了订阅频道 $channel_name1
和 $channel_name2
,客户机发出一个订阅,提供频道名称:
SUBSCRIBE $channel_name1 $channel_name2
PUBLISH $channel_name1 "content1"
其他客户端发送到这些通道的消息将由Redis推送到所有订阅的客户端。
订阅一个或多个频道的客户端不应发出命令,尽管它可以订阅和取消订阅其他频道。对订阅和取消订阅操作的回复以消息的形式发送,这样客户端就可以读取一致的消息流,其中第一个元素指示消息的类型。在已订阅客户端的上下文中允许的命令有SUBSCRIBE、PSUBSCRIBE、UNSUBSCRIBE、PUNSUBSCRIBE、PING和QUIT。
以下实例演示了发布订阅是如何工作的,需要开启两个 redis-cli 客户端。
在我们实例中我们创建了订阅频道名为 runoobChat
redis-client1:
redis 127.0.0.1:6379> SUBSCRIBE runoobChat
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
redis-client2:
redis 127.0.0.1:6379> PUBLISH runoobChat "Redis PUBLISH test"
(integer) 1
redis 127.0.0.1:6379> PUBLISH runoobChat "Learn redis by runoob.com"
(integer) 1
# 订阅者的客户端会显示如下消息
1) "message"
2) "runoobChat"
3) "Redis PUBLISH test"
1) "message"
2) "runoobChat"
3) "Learn redis by runoob.com"
消息格式
消息是包含三个元素的 Array reply。
第一个元素是消息的类型:
- subscribe:表示我们成功订阅了作为回复中第二个元素提供的频道。第三个参数表示我们当前订阅的频道数。
- unsubscribe :意味着我们成功地从作为回复中第二个元素的频道取消订阅。第三个参数表示我们当前订阅的频道数。当最后一个参数为零时,我们不再订阅任何频道,当- 我们处于发布/订阅状态之外时,客户端可以发出任何类型的Redis命令。
- message:它是由于另一个客户端发出的发布命令而接收到的消息。第二个元素是原始通道的名称,第三个参数是实际的消息负载。
数据库与作用域
发布/订阅与键空间没有关系。这是为了不干扰它在任何层面上,包括数据库号码。
db 10上的发布将由db 1上的订户听到。如果需要某种范围,请在通道前面加上环境名称(测试、暂存、生产等)。
模式匹配 subscriptions
Redis发布/订阅实现支持模式匹配。客户端可以订阅glob样式的模式,以便接收发送到与给定模式匹配的通道名称的所有消息。例如:
PSUBSCRIBE news.*
将收到所有发送到频道news.art的信息,如news.art.figurative, news.music.jazz
等。所有全局样式模式都是有效的,因此支持多个通配符。
注意:若客户端通过普通与模式匹配的方式都订阅了频道,其将收到两份消息:
SUBSCRIBE foo
PSUBSCRIBE f*
在订阅、取消订阅、PSUSCRIBE和PUNSCRIBE消息类型中,最后一个参数是仍处于活动状态的订阅计数。这个数字实际上是客户端仍然订阅的通道和模式的总数。因此,只有当从所有频道和模式取消订阅导致该计数降至零时,客户端才会退出发布/订阅状态。
客户端库实现提示:
由于收到的所有消息都包含导致消息传递的原始订阅(消息类型为通道,pmessage类型为原始模式),因此客户端库可以使用哈希表将原始订阅绑定到回调(可以是匿名函数、块、函数指针)。当收到消息时,可以进行O(1)查找,以便将消息传递到已注册的回调。
Redis 事务
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
以下是一个事务的例子, 它先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令:
redis 127.0.0.1:6379> MULTI
OK
redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED
redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis 127.0.0.1:6379> SMEMBERS tag
QUEUED
redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
2) "C++"
3) "Programming"
键空间通知机制
从Redis 2.8.0开始,Redis加入了发布/订阅模式以及键空间通知(keyspace notification)功能。
键空间通知提供了客户端通过订阅指定信道获取Redis数据变化(receive events affecting the Redis data set )的能力。
客户端可以订阅的事件包括:
- 指定key的所有命令;
- 接收
LPUSH
操作的所有 keys; - 数据库 0 的所有超时的 keys。
键空间通知通过 redis 的 pub/sub 传递消息,所以若client实现了 pub/sub 的能力,则不需要任何修改就可以支持 键空间通知机制。
注意: 键空间通知并非可靠的,它不会对订阅端是否接收到消息进行确认。例如某个订阅的客户端暂时断开连接,在其直到恢复连接期间发生的事件将无法再次获得。在未来,计划允许更可靠的事件交付,但这可能会在更一般的级别上解决,要么为Pub/Sub本身带来可靠性,要么允许Lua脚本拦截Pub/Sub消息,以执行操作,如将事件推入列表。
事件类型
Keyspace 通知是通过为每个影响Redis数据空间的操作发送两种不同类型的事件来实现的。例如,针对数据库0中名为mykey的键的DEL操作将触发两个消息的传递,这与以下两个PUBLISH命令完全相同:
PUBLISH __keyspace@0__:mykey del
PUBLISH __keyevent@0__:del mykey
显然,client 通过订阅__keyspace@0__:mykey
频道,可以获得所有的mykey
操作事件;通过订阅__keyevent@0__:del
频道,可以获得所有进行了del
操作的keys。以__keyspace
开头的频道,称之为 key-space 通知,以_keyevent
开头的频道,称之为 key-event 通知。
上面的例子中,当对 mykey 进行 del 操作时,将有如下事件:
- key-space频道收到操作名称的消息;
- key-event 频道收到 key名称的消息。
为了只传递我们感兴趣的事件子集,可以只启用一种通知。
配置
在默认情况下,Redis并未开启键空间通知功能。为了打开该功能,需要通过notify-keyspace-events配置进行设置:
// 查看 notify-keyspace-events 配置
redis> CONFIG GET notify-keyspace-events
1) "notify-keyspace-events"
2) ""
// 配置
redis> CONFIG SET notify-keyspace-events KEA
OK
redis> CONFIG GET notify-keyspace-events
1) "notify-keyspace-events"
2) "AKE"
若将参数设置为空字符串将禁用通知。为了启用该功能,使用由多个字符组成的非空字符串,其中每个字符根据下表具有特殊含义:
K Keyspace events, published with __keyspace@<db>__ prefix.
E Keyevent events, published with __keyevent@<db>__ prefix.
g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
$ String commands
l List commands
s Set commands
h Hash commands
z Sorted set commands
t Stream commands
d Module key type events
x Expired events (events generated every time a key expires)
e Evicted events (events generated when a key is evicted for maxmemory)
m Key miss events (events generated when a key that doesn't exist is accessed)
A Alias for "g$lshztxed", so that the "AKE" string means all the events except "m".
至少K或E应该出现在字符串中,否则无论字符串的其余部分如何,都不会交付任何事件。例如,要仅为列表启用Key-space事件,必须将配置参数设置为Kl,等等。
字符串KEA可用于启用每个可能的事件。
订阅指定事件
在完成配置后,可通过SUBSCRIBE命令订阅指定信道实现对一个或多个指定事件的订阅。例如通过订阅__keyevent@0__:expired实现订阅数据库0中的键过期事件例:
redis1> SUBSCRIBE __keyevent@0__:expired
1) "subscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
# redis2> SETEX greeting 1 "hello world"
# 等待1秒后:
1) "message"
2) "__keyevent@0__:expired"
3) "greeting"
命令事件
Redis为许多命令提供了不同的事件,在本文中将选择其中部分命令及其对应的事件进行介绍:
- DEL:在某个键被删除时产生del事件
- EXPIRE、PEXPIRE、EXPIREAT以及PEXPIREAT:当设置正数过期时间或未来时间的时间戳,则产生expire事件,否则产生del事件(将立即被删除)
- SET以及同类的SETEX、SETNX、GETSET:产生set事件,若使用SETEX则也会产生expire事件
- MSET:将会为每个键都产生一个set事件
- LPUSH、LPUSHX与RPUSH、RPUSHX:根据插入的方向分别产生lpush或rpush事件
- RPOP、LPOP:分别产生rpop与lpop事件,若移出的是列表中的最后一个元素,将会同时产生del事件
- LSET:产生lset事件
- LREM:产生lrem事件,同样若移除的元素为列表中的最后一个元素时将同时产生del事件
- HSET、HSETNX以及HMSET:产生一个hset事件
- HDEL:产生一个hdel事件,且在移除后哈希表为空的情况下产生del事件
- SADD:产生一个sadd事件
- SREM:产生一个srem事件,且在移除后集合为空的情况下产生del事件
- SMOVE:原键中产生srem事件且在目标键中产生sadd事件
- SINTERSTORE、SUNIONSTORE、SDIFFSTORE:分别产生sinterstore、sunionstore以及sdiffstore事件,且在结果为空集且目标键存在的情况下,将会产生del事件
- ZADD:无论添加几个元素都只产生一个zadd事件
- ZREM:无论移除几个元素都只产生一个zrem事件,当移除后有序集合为空时产生del事件
- XADD:产生xadd事件,若使用MAXLEN子命令可能会同时产生xtrim事件
- XDEL:产生xdel事件
- PERSIST:如果对应的键所关联的过期事件成功被移除,则产生persist事件
- 在键发生过期时产生expired事件
- 在达到maxmemory设定的内存值后发生键淘汰时产生evicted事件
不同命令产生的事件
不同的命令根据以下列表生成不同类型的事件。所有的事件列表见Events generated by different commands
注意:仅当目标键确实被修改时,所有命令才会生成事件。例如,SREM 从集合中删除不存在的元素实际上不会更改键的值,因此不会生成任何事件。如果对给定命令的事件生成方式有疑问,最简单的方法是观察自己:
$ redis-cli config set notify-keyspace-events KEA
$ redis-cli --csv psubscribe '__key*__:*'
Reading messages... (press Ctrl-C to quit)
"psubscribe","__key*__:*",1
此时,在另一个终端中使用redis cli向redis服务器发送命令并观察生成的事件:
"pmessage","__key*__:*","__keyspace@0__:foo","set"
"pmessage","__key*__:*","__keyevent@0__:set","foo"
...
过期事件的时间安排
Redis通过两种方式与生存时间相关联的密钥过期:
- 当通过命令访问key并发现key已过期时。
- 通过后台系统,在后台逐步查找过期的key,以便能够收集从未访问过的key。
当key被访问并被上述系统之一发现过期时,会生成过期事件,因此无法保证Redis服务器能够在key生存时间达到零时生成过期事件。
如果没有命令持续以key为目标,并且有许多key与TTL关联,则在key生存时间降至零和生成过期事件之间可能存在显著延迟。
基本上,过期事件是在Redis服务器删除key时生成的,而不是在生存时间理论上达到零时生成的。
集群中的events
如上所述,Redis集群的每个节点都会生成关于其自己的键空间子集的事件。但是,与集群中的常规发布/订阅通信不同,事件通知不会广播到所有节点。换句话说,键空间事件是特定于节点的。这意味着要接收集群的所有key空间事件,客户端需要订阅每个节点。
使用场景
Keyspace Notifications 特性允许客户端可以以 订阅/发布(Sub/Pub)模式,接收那些对数据库中的键和值有影响的操作事件。Redis的订阅与发布功能采用的是发送即忘(fire and forget)的策略,当订阅事件的客户端断线时,它会丢失所有在断线期间分发给它的事件。
-
- 类似审计或者监控的场景;
-
- 通过 expire 来实现不可靠的定时器;
-
- 借助 expire 来实现不可靠的注册发现;
分库
Redis 可以分库,相当于 MySQL 中的 database。Redis 支持多个数据库,并且每个数据库的数据是隔离的不能共享,并且基于单机才有,如果是集群就没有数据库的概念。
每个数据库对外都是一个从0开始的递增数字命名,Redis默认支持16个数据库(可以通过配置文件支持更多,无上限),可以通过配置databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用SELECT命令更换数据库,如要选择1号数据库:
redis>select 1
OK
reidis 中的操作,默认是 数据库 0;
每个数据库都有属于自己的空间,不必担心数据库之间的key冲突。
然而这些以数字命名的数据库又与我们理解的数据库有所区别:
- 没有字符串名称:Redis不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据。
- 没有独立密码:Redis也不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。
- 没有完全隔离:最重要的一点是多个数据库之间并不是完全隔离的,比如FLUSHALL命令可以清空一个Redis实例中所有数据库中的数据。综上所述,这些数据库更像是一种命名空间,而不适宜存储不同应用程序的数据。比如可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库B应用的数据,不同的应用应该使用不同的Redis实例存储数据。由于Redis非常轻量级,一个空Redis实例占用的内在只有1M左右,所以不用担心多个Redis实例会额外占用很多内存。
FAQs
如何避免中文乱码?
在 redis-cli 后面加上 --raw,就可以避免中文乱码了。
redis-cli --raw