@lemonguge
2017-09-13T14:50:14.000000Z
字数 7106
阅读 339
Redis
《Redis 实战》已经看过了好几遍了,所以想写写记录下对 Redis 的领悟。Redis 是一个性能强劲的远程内存数据库,提供了 5 种不同类型的数据结构,可以通过复制、持久化和客户端分片等特性进行扩展。
如果熟悉关系数据库,那么就知道关联两个表的数据的 SQL 查询。而 Redis 则属于人们常说的 NoSQL 数据库或者非关系型数据库,Redis 不使用表,也不会预定义,它存储键(key)与 5 种不同数据类型的值(value)之间的映射(mapping)。
高性能键值缓存服务器 memcached 常与 Redis 进行比较:
| 名称 | 类型 | 数据存储选项 | 查询类型 | 附加功能 |
|---|---|---|---|---|
| Redis | 使用内存存储(in-memory)的非关系数据库 | 字符串、列表、集合、散列表、有序集合 | 每种数据类型都有自己的专属命令,还有批量操作(bulk operation)和不完全(partial)的事务支持 | 发布与订阅,主从复制(master/slave replication),持久化,脚本(存储过程,stored procedure) |
| memcached | 使用内存存储的键值缓存 | 键值之间的映射 | 创建命令、读取命令、更新命令、删除命令以及其他几个命令 | 为提升性能而设的多线程服务器 |
在使用类似 Redis 的内存数据库时,一个首先要考虑的问题就是“当服务器被关闭时,服务器存储的数据将何去何从呢?”
Redis 拥有两种不同形式的持久化方法:第一种持久化方法为时间点转储(point-in-time dump),转储操作的触发条件为“指定时间段内有指定数量的写操作执行”,如果配置了多个触发条件,只需满足任何一条就可以执行;第二种持久化方法将所有修改了数据库的命令都写入一个只追加(append-only)文件里面,可以根据数据的重要程度来分别使用三种策略,将只追加写入设置为从不同步(sync)、每秒同步一次或者每写入一个命令就同步一次。
尽管 Redis 的性能很好,但受限于 Redis 的内存存储设计,有时候使用一台 Redis 服务器可能没办法处理所有请求。因此,为了扩展 Redis 的读性能,并为 Redis 提供故障转移(failover)支持,Redis 实现了主从复制特性:执行复制的从服务器会连上主服务器,接收主服务器发送的整个数据库的初始副本(copy);之后主服务器执行的写命令,都会被发送给所有连接着的从服务器去执行,从而实时地更新从服务器但数据集。因为从服务器包含的数据会不断地进行更新,所有客户端可以向任意一个从服务器发送读请求,以此来避免对主服务器进行集中式的访问。
Redis 的 5 种数据结构类型分别为 STRING(字符串)、LIST(列表)、SET(集合)、HASH(散列)和 ZSET(有序集合)。
| 结构类型 | 结构存储的值 | 结构的读写能力 |
|---|---|---|
| STRING | 可以是字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作;对整数和浮点数执行自增(increment)或者自减(decrement)操作 |
| LIST | 一个链表,链表上但每个节点都包含了一个字符串 | 从链表的两端推入或者弹出元素;根据偏移量对链表进行修建(trim);读取单个或者多个元素;根据值查找或者移除元素 |
| SET | 包含字符串的无序集合(unordered collection),并且被包含的每个字符串都独一无二、各不相同 | 添加、获取、移除单个元素;检查一个元素是否存在于集合中;计算交集、并集、差集;从集合里面随机获取元素 |
| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对;或者所有键值对 |
| ZSET | 字符串成员(member)与浮点数分值(score)之间的有序映射,元素的排列顺序由分值的大小决定 | 添加、获取、删除单个元素;根据分值范围(range)或者成员来获取元素 |
通过 redis-cli 控制台与 Redis 进行交互,熟悉基础的常用命令
STRING 类型中的命令,比如 GET(获取值)、SET(设置值)和 DEL(删除值)。
| 命令 | 行为 |
|---|---|
| GET key | 返回 key 的 value。如果 key 不存在,返回特殊值 nil。如果 key 的 value 不是字符串,就返回错误,因为 GET 只处理字符串类型的值 |
| SET key value | 将键 key 设定为指定的“字符串”值。如 key 已经保存了一个值,那么这个操作会直接覆盖原来的值,并且忽略原始类型。当 SET 命令执行成功之后,之前设置的过期时间都将失效 |
| DEL key [key ...] | 删除 key,该命令适用于所有类型,如果删除的 key 不存在,则直接忽略。返回被删除的 keys 的数量 |
$ ./redis-cli127.0.0.1:6379> set hello worldOK127.0.0.1:6379> get hello"world"127.0.0.1:6379> set hello1 world1OK127.0.0.1:6379> get hello1"world1"127.0.0.1:6379> set hello1 redisOK127.0.0.1:6379> get hello1"redis"127.0.0.1:6379> get hello2(nil)127.0.0.1:6379> del hello hello1 hello2(integer) 2127.0.0.1:6379> get hello(nil)127.0.0.1:6379> get hello1(nil)127.0.0.1:6379>
一个列表可以存储多个字符串,常用命令比如:RPUSH 命令和 LPUSH 命令分别用于将元素推入列表的左端和右端;LPOP 命令和 RPOP 命令分别用于从列表的左端和右端弹出元素;LINDEX 命令用于获取列表在给定位置上的一个元素;LRANGE 命令用于获取列表在给定范围的所有元素。
| 命令 | 行为 |
|---|---|
| RPUSH key value [value ...] | 向存于 key 的列表的尾部插入所有指定的值。如果 key 不存在,那么会创建一个空的列表然后再进行 push 操作。当 key 保存的不是一个列表,那么会返回一个错误。可以使用一个命令把多个元素打入队列,只需要在命令后面指定多个参数,元素是从左到右一个接一个从列表尾部插入。返回 push 操作后的列表长度 |
| LPOP key | 移除并且返回 key 对应的 list 的第一个元素,当 key 不存在时返回 nil |
| LINDEX key index | 返回列表里的元素的索引 index 存储在 key 里面。 下标是从 0 开始索引的,所以 0 是表示第一个元素, 1 表示第二个元素,并以此类推。负数索引用于指定从列表尾部开始索引的元素。在这种方法下,-1 表示最后一个元素,-2 表示倒数第二个元素,并以此往前推。当 key 位置的值不是一个列表的时候,会返回一个 error。当 index 超过范围的时候返回 nil |
| LRANGE key start stop | 返回存储在 key 的列表里指定范围内的元素。start 和 end 偏移量都是基于 0 的下标,即 list 的第一个元素下标是 0(list的表头),第二个元素下标是 1,以此类推。偏移量也可以是负数,表示偏移量是从 list 尾部开始计数。例如,-1 表示列表的最后一个元素,-2 是倒数第二个,以此类推。最右边的那个元素也会被包含在内。当下标超过 list 范围的时候不会产生 error。如果 start 比 list 的尾部下标大的时候,会返回一个空列表。如果 stop 比 list 的实际尾部大的时候,Redis 会当它是最后一个元素的下标 |
$ ./redis-cli127.0.0.1:6379> RPUSH list-key item(integer) 1127.0.0.1:6379> RPUSH list-key item3 item2(integer) 3127.0.0.1:6379> LPUSH list-key item4 item5(integer) 5127.0.0.1:6379> LRANGE list-key 0 -11) "item5"2) "item4"3) "item"4) "item3"5) "item2"127.0.0.1:6379> LINDEX list-key 3"item3"127.0.0.1:6379> LINDEX list-key -2"item3"127.0.0.1:6379> LPOP list-key"item5"127.0.0.1:6379> LRANGE list-key 0 -11) "item4"2) "item"3) "item3"4) "item2"127.0.0.1:6379> LRANGE list-key 0 61) "item4"2) "item"3) "item3"4) "item2"127.0.0.1:6379> LPUSH list-key2 item(integer) 1127.0.0.1:6379> RPOP list-key2"item"127.0.0.1:6379> RPOP list-key2(nil)127.0.0.1:6379> LRANGE list-key2 0 -1(empty list or set)127.0.0.1:6379> LRANGE list 0 -1(empty list or set)127.0.0.1:6379> GET list-key2(nil)127.0.0.1:6379> GET list(nil)127.0.0.1:6379> GET list-key(error) WRONGTYPE Operation against a key holding the wrong kind of value127.0.0.1:6379>
集合通过使用散列表来保证自己存储的每个字符串都是各不相同的,集合使用无序(unordered)方式存储元素,所以不能像使用列表那样,将元素推入集合的某一端,或者从集合的某一段弹出元素。不过用户可以使用 SADD 命令将元素加入集合,使用 SREM 从集合里面移除元素。另外可以通过 SISMEMBER 命令快速地检查一个元素是否已经存在于集合中,或者使用 SMEMBERS 命令获取集合包含的所有元素。
| 命令 | 行为 |
|---|---|
| SADD key member [member ...] | 添加一个或多个指定的 member 元素到集合的 key 中。指定的一个或者多个元素 member 如果已经在集合 key 中存在则忽略。如果集合 key 不存在,则新建集合 key,并添加 member 元素到集合 key 中。如果 key 的类型不是集合则返回错误。返回新成功添加到集合里元素的数量,不包括已经存在于集合中的元素 |
| SREM key member [member ...] | 在 key 集合中移除指定的元素。如果指定的元素不是 key 集合中的元素则忽略。如果 key 集合不存在则被视为一个空的集合,该命令返回 0。如果 key 的类型不是一个集合,则返回错误。返回从集合中移除元素的个数,不包括不存在的成员 |
| SMEMBERS key | 返回 key 集合所有的元素 |
| SISMEMBER key member | 返回成员 member 是否是存储的集合 key 的成员。如果 member 元素是集合 key 的成员,则返回 1;如果 member 元素不是 key 的成员,或者集合 key 不存在,则返回 0 |
$ ./redis-cli127.0.0.1:6379> SADD set-key item(integer) 1127.0.0.1:6379> SADD set-key item item3 item4 item2(integer) 3127.0.0.1:6379> SMEMBERS set-key1) "item3"2) "item2"3) "item4"4) "item"127.0.0.1:6379> SISMEMBER set-key item(integer) 1127.0.0.1:6379> SISMEMBER set-key item5(integer) 0127.0.0.1:6379> SREM set-key item(integer) 1127.0.0.1:6379> SREM set-key item(integer) 0127.0.0.1:6379> SREM set-key item5(integer) 0127.0.0.1:6379> SMEMBERS set-key1) "item3"2) "item2"3) "item4"127.0.0.1:6379>
集合除了基本的添加操作和移除操作,还支持很多其他操作,比如 SINTER、SUNION、SDIFF 这三个命令就可以分别执行常见的交集操作、并集操作和差集操作,之后会进行更详细介绍。
Redis 的散列可以存储多个键值对之间的映射。和字符串一样,散列存储的值可以是字符串又可以是数字值,并且用户同样可以对散列存储的数字执行自增操作或者自减操作。
| 命令 | 行为 |
|---|---|
| HSET key field value | 设置 key 指定的哈希集中指定字段的值。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联。如果字段在哈希集中存在,它将被重写。返回 1 表示 field 是一个新的字段,0 表示 field 原来在散列里面已经存在 |
| HGET key field | 返回 key 指定的哈希集中该字段所关联的值,当字段不存在或者 key 不存在时返回 nil |
| HGETALL key | 返回 key 指定的哈希集中所有的字段和值。返回值中,每个字段名的下一个是它的值,所以返回值的长度是哈希集大小的两倍。当 key 指定的哈希集不存在时返回空列表 |
| HDEL key field [field ...] | 从 key 指定的哈希集中移除指定的域。在哈希集中不存在的域将被忽略。如果 key 指定的哈希集不存在,它将被认为是一个空的哈希集,该命令将返回 0。返回从哈希集中成功移除的域的数量,不包括给定但不存在的那些域 |
$ ./redis-cli127.0.0.1:6379> HSET hash-key sub-key1 value1(integer) 1127.0.0.1:6379> HSET hash-key sub-key2 value2(integer) 1127.0.0.1:6379> HSET hash-key sub-key1 value3(integer) 0127.0.0.1:6379> HGET hash-key sub-key1"value3"127.0.0.1:6379> HGETALL hash-key1) "sub-key1"2) "value3"3) "sub-key2"4) "value2"127.0.0.1:6379> HDEL hash-key sub-key1 sub-key sub-key2(integer) 2127.0.0.1:6379> HGETALL hash-key(empty list or set)127.0.0.1:6379>
有序集合和散列一样都用于存储键值对:有序集合的键被称为成员(member),每个成员都是独一无二的;有序集合的值被称为分值(score),分值必须为浮点数。有序集合是 Redis 里面唯一一个既可以根据成员访问元素(这一点和散列一样),又可以根据分值以及分值的排列顺序来访问元素的结构。
| 命令 | 行为 |
|---|---|
| ZADD key score member [score member ...] | 将所有指定成员添加到键为 key 有序集合(sorted set)里面。添加时可以指定多个分数/成员(score/member)对。如果指定添加的成员已经是有序集合里面的成员,则会更新改成员的分数(scrore)并更新到正确的排序位置。如果 key 不存在,将会创建一个新的有序集合(sorted set)并将分数/成员(score/member)对添加到有序集合,就像原来存在一个空的有序集合一样。如果 key 存在,但是类型不是有序集合,将会返回一个错误应答。分数值是一个双精度的浮点型数字字符串,有序集合按照分数以递增的方式进行排序。返回添加到有序集合的成员数量,不包括已经存在更新分数的成员 |
| ZREM key member [member ...] | 返回的是从有序集合中删除的成员个数,不包括不存在的成员。当 key 存在,但是其不是有序集合类型,就返回一个错误 |
| ZRANGE key start stop [WITHSCORES] | 根据元素在有序集合中所处的位置,从有序集合里面获取多个元素 |
| ZRANGEBYSCORE key min max [WITHSCORES] | 获取有序集合在给定分值范围内的所有元素,可选参数 WITHSCORES 会返回元素和其分数,而不只是元素。 |
$ ./redis-cli127.0.0.1:6379> ZADD zset-key 728 member1(integer) 1127.0.0.1:6379> ZADD zset-key 982 member2 643 member3 770 member1(integer) 2127.0.0.1:6379> ZRANGE zset-key 0 -11) "member3"2) "member1"3) "member2"127.0.0.1:6379> ZRANGE zset-key 0 -1 withscores1) "member3"2) "643"3) "member1"4) "770"5) "member2"6) "982"127.0.0.1:6379> ZRANGEBYSCORE zset-key 0 800 withscores1) "member3"2) "643"3) "member1"4) "770"127.0.0.1:6379> ZREM zset-key member1(integer) 1127.0.0.1:6379> ZREM zset-key member1(integer) 0127.0.0.1:6379> ZRANGE zset-key 0 -11) "member3"2) "member2"127.0.0.1:6379>