手机建设银行网站首页,佛山建站模板,广州网页设计软件培训,wordpress自定义注册页面模板Redis 根据命令所操作对象的不同#xff0c;可以分为三大类#xff1a;对 Redis 进行基础性操作的命令#xff0c;对 Key 的操作命令#xff0c;对 Value 的操作命令。3.1 Redis 基本命令首先通过 redis-cli 命令进入到 Redis 命令行客户端#xff0c;然后再运行下面的命令…Redis 根据命令所操作对象的不同可以分为三大类对 Redis 进行基础性操作的命令对 Key 的操作命令对 Value 的操作命令。3.1 Redis 基本命令首先通过 redis-cli 命令进入到 Redis 命令行客户端然后再运行下面的命令。3.1.1 心跳命令 ping键入 ping 命令会看到 PONG 响应则说明该客户端与 Redis 的连接是正常的。该命令亦称为心跳命令。自己操作3.1.2 读写键值命令set key value 会将指定 key-value 写入到 DB。get key 则会读取指定 key 的 value 值。关于更多 set 与 get 命令格式后面会详细学习。自己操作补安装redis-desktop-manager-0.8.8.3841说明Redis Desktop Manager是一款简单快速、跨平台的Redis桌面管理工具也被称作Redis可视化工具支持命令控制台操作以及常用查询keyrenamedelete等操作。2安装方法3使用方法如果没有链接通的话 请查找我的另一篇文章https://blog.csdn.net/m0_59281987/article/details/1290900043.1.3 DB 切换 selectRedis 默认有 16 个数据库。这个在 Redis Desktop ManagerRDM图形客户端中可以直观地看到。默认使用的是 0 号 DB可以通过 select db 索引来切换 DB。例如如下命令会切换到DB3并会将 age-23 写入到 DB3 中。并且这个结果在 RDM 中是可以直观地看到的。3.1.4 查看 key 数量 dbsizedbsize 命令可以查看当前数据库中 key 的数量。从以上查看情况看DB0 中有 2 个 keyDB1 中没有 keyDB3 中有 1 个 key。3.1.5 删除当前库中数据 flushdbflushdb 命令仅仅删除的是当前数据库中的数据不影响其它库。3.1.6 删除所有库中数据命令 flushallflushall 命令可以删除所有库中的所有数据。所以该命令的使用一定要慎重。3.1.7 退出客户端命令使用 exit 或 quit 命令均可退出 Redis 命令行客户端。3.2 Key 操作命令Redis 中存储的数据整体是一个 Map其 key 为 String 类型而 value 则可以是 String、Hash 表、List、Set 等类型。3.2.1 keys 格式KEYS pattern 功能查找所有符合给定模式 pattern 的 keypattern 为正则表达式。 说明KEYS 的速度非常快但在一个大的数据库中使用它可能会阻塞当前服务器的服务。所以生产环境中一般不使用该命令而使用 scan 命令代替。3.2.2 exists 格式EXISTS key 功能检查给定 key 是否存在。 说明若 key 存在返回 1 否则返回 0 。3.2.3 del 格式DEL key [key ...] 功能删除给定的一个或多个 key 。不存在的 key 会被忽略。 说明返回被删除 key 的数量。3.2.4 rename 格式RENAME key newkey 功能将 key 改名为 newkey。 说明当 key 和 newkey 相同或者 key 不存在时返回一个错误。当 newkey 已经存在时 RENAME 命令将覆盖旧值。改名成功时提示 OK 失败时候返回一个错误。3.2.5 move 格式MOVE key db 功能将当前数据库的 key 移动到给定的数据库 db 当中。 说明如果当前数据库(源数据库)和给定数据库(目标数据库)有相同名字的给定 key 或者 key 不存在于当前数据库那么 MOVE 没有任何效果。移动成功返回 1 失败则返回 0 。3.2.6 type 格式TYPE key 功能返回 key 所储存的值的类型。 说明返回值有以下六种none (key 不存在) string (字符串) list (列表) set (集合) zset (有序集) hash (哈希表)3.2.7 expire 与 pexpire 格式EXPIRE key seconds 功能为给定 key 设置生存时间。当 key 过期时(生存时间为 0)它会被自动删除。expire 的时间单位为秒pexpire 的时间单位为毫秒。在 Redis 中带有生存时间的 key被称为“易失的”(volatile)。 说明生存时间设置成功返回 1。若 key 不存在时返回 0 。rename 操作不会改变 key的生存时间。3.2.8 ttl 与 pttl 格式TTL key 功能TTL, time to live返回给定 key 的剩余生存时间。 说明其返回值存在三种可能 当 key 不存在时返回 -2 。 当 key 存在但没有设置剩余生存时间时返回 -1 。 否则返回 key 的剩余生存时间。ttl 命令返回的时间单位为秒而 pttl 命令返回的时间单位为毫秒。3.2.9 persist 格式PERSIST key 功能去除给定 key 的生存时间将这个 key 从“易失的”转换成“持久的”。 说明当生存时间移除成功时返回 1若 key 不存在或 key 没有设置生存时间则返回 0。3.2.10 randomkey 格式RANDOMKEY 功能从当前数据库中随机返回(不删除)一个 key。 说明当数据库不为空时返回一个 key。当数据库为空时返回 nil。3.2.11 scan 格式SCAN cursor [MATCH pattern] [COUNT count] [TYPE type] 功能用于迭代数据库中的数据库键。其各个选项的意义为 cursor本次迭代开始的游标。 pattern 本次迭代要匹配的 key 的模式。 count 本次迭代要从数据集里返回多少元素默认值为 10 。 type本次迭代要返回的 value 的类型默认为所有类型。SCAN 命令是一个基于游标 cursor 的迭代器SCAN 命令每次被调用之后都会向用户返回返回一个包含两个元素的数组 第一个元素是用于进行下一次迭代的新游标而第二个元素则是一个数组 这个数组中包含了所有被迭代的元素。用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数以此来延续之前的迭代过程。当SCAN 命令的游标参数被设置为 0 时服务器将开始一次新的迭代。如果新游标返回 0 表示迭代已结束。 说明使用间断的、负数、超出范围或者其他非正常的游标来执行增量式迭代不会造成服务器崩溃。当数据量很大时count 的数量的指定可能会不起作用Redis 会自动调整每次的遍历数目。由于 scan 命令每次执行都只会返回少量元素所以该命令可以用于生产环境而不会出现像 KEYS 命令带来的服务器阻塞问题。增量式迭代命令所使用的算法只保证在数据集的大小有界的情况下迭代才会停止换句话说如果被迭代数据集的大小不断地增长的话增量式迭代命令可能永远也无法完成一次完整迭代。即当一个数据集不断地变大时想要访问这个数据集中的所有元素就需要做越来越多的工作 能否结束一个迭代取决于用户执行迭代的速度是否比数据集增长的速度更快。 相关命令另外还有 3 个 scan 命令用于对三种类型的 value 进行遍历。 hscan属于 Hash 型 Value 操作命令集合用于遍历当前 db 中指定 Hash 表的所有 field-value 对。 sscan属于 Set 型 Value 操作命令集合用于遍历当前 db 中指定 set 集合的所有元素 zscan属于 ZSet 型 Value 操作命令集合用于遍历当前 db 中指定有序集合的所有元素数值与元素值3.3 String 型 Value 操作命令Redis 存储数据的 Value 可以是一个 String 类型数据。String 类型的 Value 是 Redis 中最基本最常见的类型。String 类型的 Value 中可以存放任意数据包括数值型甚至是二进制的图片、音频、视频、序列化对象等。一个 String 类型的 Value 最大是 512M 大小。3.3.1 set 格式SET key value [EX seconds | PX milliseconds] [NX|XX] 功能SET 除了可以直接将 key 的值设为 value 外还可以指定一些参数。 EX seconds为当前 key 设置过期时间单位秒。等价于 SETEX 命令。 PX milliseconds为当前 key 设置过期时间单位毫秒。等价于 PSETEX 命令。 NX指定的 key 不存在才会设置成功用于添加指定的 key。等价于 SETNX 命令。 XX指定的 key 必须存在才会设置成功用于更新指定 key 的 value。 说明如果 value 字符串中带有空格则该字符串需要使用双引号或单引号引起来否则会认为 set 命令的参数数量不正确报错。3.3.2 setex 与 psetex 格式SETEX/PSETEX key seconds value 功能set expire其不仅为 key 指定了 value还为其设置了生存时间。setex 的单位为秒psetex 的单位为毫秒。 说明如果 key 已经存在 则覆写旧值。该命令类似于以下两个命令不同之处是SETEX 是一个原子性操作关联值和设置生存时间两个动作会在同一时间内完成该命令在 Redis 用作缓存时非常实用。SET key valueEXPIRE key seconds # 设置生存时间3.3.3 setnx 格式SETNX key value 功能SET if Not eXists将 key 的值设为 value 当且仅当 key 不存在。若给定的 key已经存在则 SETNX 不做任何动作。成功返回 1否则返回 0。 说明该命令等价于 set key value nx3.3.4 getset 格式GETSET key value 功能将给定 key 的值设为 value 并返回 key 的旧值。 说明当 key 存在但不是字符串类型时返回一个错误当 key 不存在时返回 nil 。3.3.5 mset 与 msetnx 格式MSET/MSETNX key value [key value ...] 功能同时设置一个或多个 key-value 对。 说明如果某个给定 key 已经存在那么 MSET 会用新值覆盖原来的旧值如果这不是你所希望的效果请考虑使用 MSETNX 命令它只会在所有给定 key 都不存在的情况下进行设置操作。MSET/MSETNX 是一个原子性(atomic)操作所有给定 key 都会在同一时间内被设置某些给定 key 被更新而另一些给定 key 没有改变的情况不可能发生。该命令永不失败。3.3.6 mget 格式MGET key [key ...] 功能返回所有(一个或多个)给定 key 的值。 说明如果给定的 key 里面有某个 key 不存在那么这个 key 返回特殊值 nil 。因此该命令永不失败。3.3.7 append 格式APPEND key value 功能如果 key 已经存在并且是一个字符串 APPEND 命令将 value 追加到 key 原来的值的末尾。如果 key 不存在 APPEND 就简单地将给定 key 设为 value 就像执行 SET key value 一样。 说明追加 value 之后 key 中字符串的长度。3.3.8 incr 与 decr 格式INCR key 或 DECR key 功能increment自动递增。将 key 中存储的数字值增一。 decrement自动递减。将 key 中存储的数字值减一。 说明如果 key 不存在那么 key 的值会先被初始化为 0然后再执行增一/减一操作。如果值不能表示为数字那么返回一个错误提示。如果执行正确则返回增一/减一后的值。3.3.9 incrby 与 decrby 格式INCRBY key increment 或 DECRBY key decrement 功能将 key 中存储的数字值增加/减少指定的数值这个数值只能是整数可以是负数但不能是小数。 说明如果 key 不存在那么 key 的值会先被初始化为 0然后再执行增/减操作。如果值不能表示为数字那么返回一个错误提示。如果执行正确则返回增/减后的值。3.3.10 incrbyfloat 格式INCRBYFLOAT key increment 功能为 key 中所储存的值加上浮点数增量 increment 。 说明与之前的说明相同。没有 decrbyfloat 命令但 increment 为负数可以实现减操作效果。3.3.11 strlen 格式STRLEN key 功能返回 key 所储存的字符串值的长度。 说明当 key 储存的不是字符串值时返回一个错误当 key 不存在时返回 0 。3.3.12 getrange 格式GETRANGE key start end 功能返回 key 中字符串值的子字符串字符串的截取范围由 start 和 end 两个偏移量决定包括 start 和 end 在内。 说明end 必须要比 start 大。支持负数偏移量表示从字符串最后开始计数-1 表示最后一个字符-2 表示倒数第二个以此类推。3.3.13 setrange 格式SETRANGE key offset value 功能用 value 参数替换给定 key 所储存的字符串值 str从偏移量 offset 开始。 说明当 offset 值大于 str 长度时中间使用零字节\x00 填充即 0000 0000 字节填充对于不存在的 key 当作空串处理。3.3.14 位操作命令名称中包含 BIT 的命令都是对二进制位的操作命令例如setbit、getbit、bitcount、bittop、bitfield这些命令不常用。3.3.15 典型应用场景Value 为 String 类型的应用场景很多这里仅举这种典型应用场景的例子1 数据缓存Redis 作为数据缓存层MySQL 作为数据存储层。应用服务器首先从 Redis 中获取数据如果缓存层中没有则从 MySQL 中获取后先存入缓存层再返回给应用服务器。2 计数器在 Redis 中写入一个 value 为数值型的 key 作为平台计数器、视频播放计数器等。每个有效客户端访问一次或视频每播放一次都是直接修改 Redis 中的计数器然后再以异步方式持久化到其它数据源中例如持久化到 MySQL。3 共享 Session对于一个分布式应用系统如果将类似用户登录信息这样的 Session 数据保存在提供登录服务的服务器中那么如果用户再次提交像收藏、支付等请求时可能会出现问题在提供收藏、支付等服务的服务器中并没有该用户的 Session 数据从而导致该用户需要重新登录。对于用户来说这是不能接受的。此时可以将系统中所有用户的 Session 数据全部保存到 Redis 中用户在提交新的请求后系统先从 Redis 中查找相应的 Session 数据如果存在则再进行相关操作否则跳转到登录页面。这样就不会引发“重新登录”问题。4 限速器现在很多平台为了防止 DoSDenial of Service拒绝服务攻击一般都会限制一个 IP不能在一秒内访问超过 n 次。而 Redis 可以可以结合 key 的过期时间与 incr 命令来完成限速功能充当限速器。注意其无法防止 DDoSDistributed Denial of Service分布式拒绝服务攻击。// 客户端每提交一次请求都会执行下面的代码
// 等价于 set 192.168.192.55 1 ex 60 nx
// 指定新 ip 作为 key 的缓存过期时间为 60 秒
Boolean isExists redis.set(ip, 1, “EX 60”, “NX”);
if(isExists ! null || redis.incr(ip) 5) {// 通过
} else { // 限流
}3.4 Hash 型 Value 操作命令Redis 存储数据的 Value 可以是一个 Hash 类型。Hash 类型也称为 Hash 表、字典等。Hash 表就是一个映射表 Map也是由键-值对构成为了与整体的 key 进行区分这里的键称为 field值称为 value。注意Redis 的 Hash 表中的 field-value 对均为 String 类型。3.4.1 hset 格式HSET key field value 功能将哈希表 key 中的域 field 的值设为 value 。 说明如果 key 不存在一个新的哈希表被创建并进行 HSET 操作。如果域 field 已经存在于哈希表中旧值将被覆盖。如果 field 是哈希表中的一个新建域并且值设置成功返回 1 。如果哈希表中域 field 已经存在且旧值已被新值覆盖返回 0 。3.4.2 hget 格式HGET key field 功能返回哈希表 key 中给定域 field 的值。 说明当给定域不存在或是给定 key 不存在时返回 nil 。3.4.3 hmset 格式HMSET key field value [field value ...] 功能同时将多个 field-value (域-值)对设置到哈希表 key 中。 说明此命令会覆盖哈希表中已存在的域。如果 key 不存在一个空哈希表被创建并执行 HMSET 操作。如果命令执行成功返回 OK 。当 key 不是哈希表(hash)类型时返回一个错误。3.4.4 hmget 格式HMGET key field [field ...] 功能按照给出顺序返回哈希表 key 中一个或多个域的值。 说明如果给定的域不存在于哈希表那么返回一个 nil 值。因为不存在的 key 被当作一个空哈希表来处理所以对一个不存在的 key 进行 HMGET 操作将返回一个只带有 nil 值的表。3.4.5 hgetall 格式HGETALL key 功能返回哈希表 key 中所有的域和值。 说明在返回值里紧跟每个域名(field name)之后是域的值(value)所以返回值的长度是哈希表大小的两倍。若 key 不存在返回空列表。若 key 中包含大量元素则该命令可能会阻塞 Redis 服务。所以生产环境中一般不使用该命令而使用 hscan 命令代替。3.4.6 hsetnx 格式HSETNX key field value 功能将哈希表 key 中的域 field 的值设置为 value 当且仅当域 field 不存在。 说明若域 field 已经存在该操作无效。如果 key 不存在一个新哈希表被创建并执行 HSETNX 命令。3.4.7 hdel 格式HDEL key field [field ...] 功能删除哈希表 key 中的一个或多个指定域不存在的域将被忽略。 说明返回被成功移除的域的数量不包括被忽略的域。3.4.8 hexits 格式HEXISTS key field 功能查看哈希表 key 中给定域 field 是否存在。 说明如果哈希表含有给定域返回 1 。如果不含有给定域或 key 不存在返回 0 。3.4.9 hincrby 与 hincrbyfloat 格式HINCRBY key field increment 功能为哈希表 key 中的域 field 的值加上增量 increment 。hincrby 命令只能增加整数值而 hincrbyfloat 可以增加小数值。 说明增量也可以为负数相当于对给定域进行减法操作。如果 key 不存在一个新的哈希表被创建并执行 HINCRBY 命令。如果域 field 不存在那么在执行命令前域的值被初始化为 0。对一个储存字符串值的域 field 执行 HINCRBY 命令将造成一个错误。3.4.10 hkeys 与 hvals 格式HKEYS key 或 HVALS 功能返回哈希表 key 中的所有域/值。 说明当 key 不存在时返回一个空表。3.4.11 hlen 格式HLEN key 功能返回哈希表 key 中域的数量。 说明当 key 不存在时返回 0 。3.4.12 hstrlen 格式HSTRLEN key field 功能返回哈希表 key 中 与给定域 field 相关联的值的字符串长度string length。 说明如果给定的键或者域不存在 那么命令返回0 。 3.4.13 应用场景Hash 型 Value 非常适合存储对象数据。key 为对象名称value 为描述对象属性的 Map对对象属性的修改在 Redis 中就可直接完成。其不像 String 型 Value 存储对象那个对象是序列化过的例如序列化为 JSON 串对对象属性值的修改需要先反序列化为对象后再修改修改后再序列化为 JSON 串后写入到 Redis。3.5 List 型 Value 操作命令Redis 存储数据的 Value 可以是一个 String 列表类型数据。即该列表中的每个元素均为String 类型数据。列表中的数据会按照插入顺序进行排序。不过该列表的底层实际是一个无头节点的双向链表所以对列表表头与表尾的操作性能较高但对中间元素的插入与删除的操作的性能相对较差。3.5.1 lpush/rpush 格式LPUSH key value [value ...] 或 RPUSH key value [value ...] 功能将一个或多个值 value 插入到列表 key 的表头/表尾表头在左表尾在右 说明如果有多个 value 值对于 lpush 来说各个 value 会按从左到右的顺序依次插入到表头对于 rpush 来说各个 value 会按从左到右的顺序依次插入到表尾。如果 key不存在一个空列表会被创建并执行操作。当 key 存在但不是列表类型时返回一个错误。执行成功时返回列表的长度。3.5.2 llen 格式LLEN key 功能返回列表 key 的长度。 说明如果 key 不存在则 key 被解释为一个空列表返回 0 。如果 key 不是列表类型返回一个错误。3.5.3 lindex 格式LINDEX key index 功能返回列表 key 中下标为 index 的元素。列表从 0 开始计数。 说明如果 index 参数的值不在列表的区间范围内(out of range)返回 nil 。3.5.4 lset 格式LSET key index value 功能将列表 key 下标为 index 的元素的值设置为 value 。 说明当 index 参数超出范围或对一个空列表key 不存在进行 LSET 时返回一个错误。3.5.5 lrange 格式LRANGE key start stop 功能返回列表 key 中指定区间[start, stop]内的元素即包含两个端点。 说明List 的下标从 0 开始即以 0 表示列表的第一个元素以 1 表示列表的第二个元素以此类推。也可以使用负数下标以 -1 表示列表的最后一个元素 -2 表示列表的倒数第二个元素以此类推。超出范围的下标值不会引起错误。如果 start 下标比列表的最大下标 还要大那么 LRANGE 返回一个空列表。如果 stop 下标比最大下标还要大Redis 将 stop 的值设置为最大下标。3.5.6 lpushx 与 rpushx 格式LPUSHX key value 或 RPUSHX key value 功能将值 value 插入到列表 key 的表头/表尾当且仅当 key 存在并且是一个列表。 说明当 key 不存在时命令什么也不做。若执行成功则输出表的长度。3.5.7 linsert 格式LINSERT key BEFORE|AFTER pivot value 功能将值 value 插入到列表 key 当中位于元素 pivot 之前或之后。 说明当 pivot 元素不存在于列表中时不执行任何操作返回-1当 key 不存在时key 被视为空列表不执行任何操作返回 0如果 key 不是列表类型返回一个错误如果命令执行成功返回插入操作完成之后列表的长度。3.5.8 lpop / rpop 格式LPOP key [count] 或 RPOP key [count] 功能从列表 key 的表头/表尾移除 count 个元素并返回移除的元素。count 默认值 1 说明当 key 不存在时返回 nil3.5.9 blpop / brpop 格式BLPOP key [key ...] timeout 或 BRPOP key [key ...] timeout 功能BLPOP/BRPOP 是列表的阻塞式(blocking)弹出命令。它们是 LPOP/RPOP 命令的阻塞版本当给定列表内没有任何元素可供弹出的时候连接将被 BLPOP/BRPOP 命令阻塞直到等待 timeout 超时或发现可弹出元素为止。当给定多个 key 参数时按参数 key的先后顺序依次检查各个列表弹出第一个非空列表的头元素。timeout 为阻塞时长单位为秒其值若为 0则表示只要没有可弹出元素则一直阻塞。 说明假如在指定时间内没有任何元素被弹出则返回一个 nil 和等待时长。反之返回一个含有两个元素的列表第一个元素是被弹出元素所属的 key 第二个元素是被弹出元素的值。3.5.10 rpoplpush 格式RPOPLPUSH source destination 功能命令 RPOPLPUSH 在一个原子时间内执行以下两个动作 将列表 source 中的最后一个元素(尾元素)弹出并返回给客户端。 将 source 弹出的元素插入到列表 destination 作为 destination 列表的的头元素。如果 source 不存在值 nil 被返回并且不执行其他动作。如果 source 和 destination相同则列表中的表尾元素被移动到表头并返回该元素可以把这种特殊情况视作列表的旋转(rotation)操作。3.5.11 brpoplpush 格式BRPOPLPUSH source destination timeout 功能BRPOPLPUSH 是 RPOPLPUSH 的阻塞版本当给定列表 source 不为空时BRPOPLPUSH 的表现和 RPOPLPUSH 一样。当列表 source 为空时 BRPOPLPUSH 命令将阻塞连接直到等待超时或有另一个客户端对 source 执行 LPUSH 或 RPUSH 命令为止。timeout 为阻塞时长单位为秒其值若为 0则表示只要没有可弹出元素则一直阻塞。 说明假如在指定时间内没有任何元素被弹出则返回一个 nil 和等待时长。反之返回一个含有两个元素的列表第一个元素是被弹出元素的值第二个元素是等待时长。3.5.12 lrem 格式LREM key count value 功能根据参数 count 的值移除列表中与参数 value 相等的元素。count 的值可以是以下几种 count 0 : 从表头开始向表尾搜索移除与 value 相等的元素数量为 count 。 count 0 : 从表尾开始向表头搜索移除与 value 相等的元素数量为 count 的绝对值。 count 0 : 移除表中所有与 value 相等的值。 说明返回被移除元素的数量。当 key 不存在时 LREM 命令返回 0 因为不存在的 key 被视作空表(empty list)。3.5.13 ltrim 格式LTRIM key start stop 功能对一个列表进行修剪(trim)就是说让列表只保留指定区间内的元素不在指定区间之内的元素都将被删除。 说明下标(index)参数 start 和 stop 都以 0 为底也就是说以 0 表示列表的第一个元素以 1 表示列表的第二个元素以此类推。也可以使用负数下标以 -1 表示列表的最后一个元素 -2 表示列表的倒数第二个元素以此类推。当 key 不是列表类型时返回一个错误。如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大或者 start stop LTRIM 返回一个空列表因为 LTRIM 已经将整个列表清空。如果 stop 下标比 end 下标还要大Redis 将 stop 的值设置为 end 。3.5.14 应用场景Value 为 List 类型的应用场景很多主要是通过构建不同的数据结构来实现相应的业务功能。这里仅对这些数据结构的实现方式进行总结不举具体的例子。1 栈通过 lpush lpop 可以实现栈数据结构效果先进后出。通过 lpush 从列表左侧插入数据通过 lpop 从列表左侧取出数据。当然通过 rpush rpop 也可以实现相同效果只不过操作的是列表右侧。2 队列通过 lpush rpop 可以实现队列数据结构效果先进先出。通过 lpush 从列表左侧插入数据通过rpop 从列表右侧取出数据。当然通过 rpush lpop 也可以实现相同效果只不过操作的方向正好相反。3 阻塞式消息队列通过 lpush brpop 可以实现阻塞式消息队列效果。作为消息生产者的客户端使用 lpush从列表左侧插入数据作为消息消费者的多个客户端使用 brpop 阻塞式“抢占”列表尾部数据进行消费保证了消费的负载均衡与高可用性。brpop 的 timeout 设置为 0表示只要没有数据可弹出就永久阻塞。4 动态有限集合通过 lpush ltrim 可以实现有限集合。通过 lpush 从列表左侧向列表中添加数据通过ltrim 保持集合的动态有限性。像企业的末位淘汰、学校的重点班等动态管理都可通过这种动态有限集合来实现。当然通过 rpush ltrim 也可以实现相同效果只不过操作的方向正好相反。3.6 Set 型 Value 操作命令Redis 存储数据的 Value 可以是一个 Set 集合且集合中的每一个元素均 String 类型。Set与 List 非常相似但不同之处是 Set 中的元素具有无序性与不可重复性而 List 则具有有序性与可重复性。Redis 中的 Set 集合与 Java 中的 Set 集合的实现相似其底层都是 value 为 null 的 hash表。也正因为此才会引发无序性与不可重复性。3.6.1 sadd 格式SADD key member [member ...] 功能将一个或多个 member 元素加入到集合 key 当中已经存在于集合的 member 元素将被忽略。 说明假如 key 不存在则创建一个只包含 member 元素作成员的集合。当 key 不是集合类型时返回一个错误。3.6.2 smembers 格式SMEMBERS key 功能返回集合 key 中的所有成员。 说明不存在的 key 被视为空集合。若 key 中包含大量元素则该命令可能会阻塞 Redis服务。所以生产环境中一般不使用该命令而使用 sscan 命令代替。3.6.3 scard 格式SCARD key 功能返回 Set 集合的长度 说明当 key 不存在时返回 0 。3.6.4 sismember 格式SISMEMBER key member 功能判断 member 元素是否集合 key 的成员。 说明如果 member 元素是集合的成员返回 1 。如果 member 元素不是集合的成员或 key 不存在返回 0 。3.6.5 smove 格式SMOVE source destination member 功能将 member 元素从 source 集合移动到 destination 集合。 说明如果 source 集合不存在或不包含指定的 member 元素则 SMOVE 命令不执行任何操作仅返回 0 。否则 member 元素从 source 集合中被移除并添加到destination 集合中去返回 1。当 destination 集合已经包含 member 元素时 SMOVE命令只是简单地将 source 集合中的 member 元素删除。当 source 或 destination 不是集合类型时返回一个错误。3.6.6 srem 格式SREM key member [member ...] 功能移除集合 key 中的一个或多个 member 元素不存在的 member 元素会被忽略且返回成功移除的元素个数。 说明当 key 不是集合类型返回一个错误。3.6.7 srandmember 格式SRANDMEMBER key [count] 功能返回集合中的 count 个随机元素。count 默认值为 1。 说明若 count 为正数且小于集合长度那么返回一个包含 count 个元素的数组数组中的元素各不相同。如果 count 大于等于集合长度那么返回整个集合。如果count 为负数那么返回一个包含 count 绝对值个元素的数组但数组中的元素可能会出现重复。3.6.8 spop 格式SPOP key [count] 功能移除并返回集合中的 count 个随机元素。count 必须为正数且默认值为 1。 说明如果 count 大于等于集合长度那么移除并返回整个集合。3.6.9 sdiff / sdiffstore 格式SDIFF key [key ...] 或 SDIFFSTORE destination key [key ...] 功能返回第一个集合与其它集合之间的差集。差集difference。 说明这两个命令的不同之处在于sdiffstore 不仅能够显示差集还能将差集存储到指定的集合 destination 中。如果 destination 集合已经存在则将其覆盖。不存在的 key 被视为空集。3.6.10 sinter / sinterstore 格式SINTER key [key ...] 或 SINTERSTORE destination key [key ...] 功能返回多个集合间的交集。交集intersection。 说明这两个命令的不同之处在于sinterstore 不仅能够显示交集还能将交集存储到指定的集合 destination 中。如果 destination 集合已经存在则将其覆盖。不存在的 key 被视为空集。3.6.11 sunion / sunionstore 格式SUNION key [key ...] 或 SUNIONSTORE destination key [key ...] 功能返回多个集合间的并集。并集union。 说明这两个命令的不同之处在于sunionstore 不仅能够显示并集还能将并集存储到指定的集合 destination 中。如果 destination 集合已经存在则将其覆盖。不存在的 key 被视为空集。3.6.12 应用场景Value 为 Set 类型的应用场景很多这里对这些场景仅进行总结。1 动态黑白名单例如某服务器中要设置用于访问控制的黑名单。如果直接将黑名单写入服务器的配置文件那么存在的问题是无法动态修改黑名单。此时可以将黑名单直接写入 Redis只要有客户端来访问服务器服务器在获取到客户端 IP后先从 Redis的黑名单中查看是否存在该 IP如果存在则拒绝访问否则访问通过。2 有限随机数有限随机数是指返回的随机数是基于某一集合范围内的随机数例如抽奖、随机选人。通过 spop 或 srandmember 可以实现从指定集合中随机选出元素。3 用户画像社交平台、电商平台等各种需要用户注册登录的平台会根据用户提供的资料与用户使用习惯为每个用户进行画像即为每个用户定义很多可以反映该用户特征的标签这些标签就可以使用 sadd 添加到该用户对应的集合中。这些标签具有无序、不重复特征。同时平台还可以使用 sinter/sinterstore 根据用户画像间的交集进行好友推荐、商品推荐、客户推荐等。3.7有序 Set 型 Value 操作命令Redis 存储数据的 Value 可以是一个有序 Set这个有序 Set 中的每个元素均 String 类型。有序 Set 与 Set 的不同之处是有序 Set 中的每一个元素都有一个分值 scoreRedis 会根据score 的值对集合进行由小到大的排序。其与 Set 集合要求相同元素不能重复但元素的score 可以重复。由于该类型的所有命令均是字母 z 开头所以该 Set 也称为 ZSet。3.7.1 zadd 格式ZADD key score member [[score member] [score member] ...] 功能将一个或多个 member 元素及其 score 值加入到有序集 key 中的适当位置。 说明score 值可以是整数值或双精度浮点数。如果 key 不存在则创建一个空的有序集并执行 ZADD 操作。当 key 存在但不是有序集类型时返回一个错误。如果命令执行成功则返回被成功添加的新成员的数量不包括那些被更新的、已经存在的成员。若写入的 member 值已经存在但 score 值不同则新的 score 值将覆盖老 score。3.7.2 zrange 与 zrevrange 格式ZRANGE key start stop [WITHSCORES] 或 ZREVRANGE key start stop [WITHSCORES] 功能返回有序集 key 中指定区间内的成员。zrange 命令会按 score 值递增排序zrevrange命令会按score递减排序。具有相同 score 值的成员按字典序/逆字典序排列。可以通过使用 WITHSCORES 选项来让成员和它的 score 值一并返回。 说明下标参数从 0 开始即 0 表示有序集第一个成员以 1 表示有序集第二个成员以此类推。也可以使用负数下标-1 表示最后一个成员-2 表示倒数第二个成员以此类推。超出范围的下标并不会引起错误。例如当 start 的值比有序集的最大下标还要大或是 start stop 时ZRANGE 命令只是简单地返回一个空列表。再比如 stop 参数的值比有序集的最大下标还要大那么 Redis 将 stop 当作最大下标来处理。若 key 中指定范围内包含大量元素则该命令可能会阻塞 Redis 服务。所以生产环境中如果要查询有序集合中的所有元素一般不使用该命令而使用 zscan 命令代替。3.7.3 zrangebyscore 与 zrevrangebyscore 格式ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] 功能返回有序集 key 中所有 score 值介于 min 和 max 之间(包括等于 min 或max )的成员。有序集成员按 score 值递增/递减次序排列。具有相同 score 值的成员按字典序/逆字典序排列。可选的 LIMIT 参数指定返回结果的数量及区间(就像 SQL 中的SELECT LIMIT offset, count )注意当 offset 很大时定位 offset 的操作可能需要遍历整个有序集此过程效率可能会较低。可选的 WITHSCORES 参数决定结果集是单单返回有序集的成员还是将有序集成员及其 score 值一起返回。 说明min 和 max 的取值是正负无穷大的。默认情况下区间的取值使用闭区间 (小于等于或大于等于)也可以通过给参数前增加左括号“(”来使用可选的开区间 (小于或大于)。3.7.4 zcard 格式ZCARD key 功能返回集合的长度 说明当 key 不存在时返回 0 。3.7.5 zcount 格式ZCOUNT key min max 功能返回有序集 key 中score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max )的成员的数量。3.7.6 zscore 格式ZSCORE key member 功能返回有序集 key 中成员 member 的 score 值。 说明如果 member 元素不是有序集 key 的成员或 key 不存在返回 nil 。3.7.7 zincrby 格式ZINCRBY key increment member 功能为有序集 key 的成员 member 的 score 值加上增量 increment 。increment 值可以是整数值或双精度浮点数。 说明可以通过传递一个负数值 increment 让 score 减去相应的值。当 key 不存在或 member 不是 key 的成员时 ZINCRBY key increment member 等同于 ZADD key increment member 。当 key 不是有序集类型时返回一个错误。命令执行成功则返回 member 成员的新 score 值。3.7.8 zrank 与 zrevrank 格式ZRANK key member 或 ZREVRANK key member 功能返回有序集 key 中成员 member 的排名。zrank 命令会按 score 值递增排序zrevrank 命令会按 score 递减排序。 说明score 值最小的成员排名为 0 。如果 member 不是有序集 key 的成员返回 nil 。3.7.9 zrem 格式ZREM key member [member ...] 功能移除有序集 key 中的一个或多个成员不存在的成员将被忽略。 说明当 key 存在但不是有序集类型时返回一个错误。执行成功则返回被成功移除的成员的数量不包括被忽略的成员。3.7.10 zremrangebyrank 格式ZREMRANGEBYRANK key start stop 功能移除有序集 key 中指定排名(rank)区间内的所有成员。 说明排名区间分别以下标参数 start 和 stop 指出包含 start 和 stop 在内。排名区间参数从 0 开始即 0 表示排名第一的成员 1 表示排名第二的成员以此类推。也可以使用负数表示-1 表示最后一个成员-2 表示倒数第二个成员以此类推。命令执行成功则返回被移除成员的数量。3.7.11 zremrangebyscore 格式ZREMRANGEBYSCORE key min max 功能移除有序集 key 中所有 score 值介于 min 和 max 之间(包括等于 min 或max )的成员。 说明命令执行成功则返回被移除成员的数量。3.7.12 zrangebylex 格式ZRANGEBYLEX key min max [LIMIT offset count] 功能该命令仅适用于集合中所有成员都具有相同分值的情况。当有序集合的所有成员都具有相同的分值时有序集合的元素会根据成员的字典序lexicographical ordering来进行排序。即这个命令返回给定集合中元素值介于 min 和 max 之间的成员。如果有序集合里面的成员带有不同的分值 那么命令的执行结果与 zrange key 效果相同。 说明合法的 min 和 max 参数必须包含左小括号“(”或左中括号“[”其中左小括号“(”表示开区间 而左中括号“[”则表示闭区间。min 或 max 也可使用特殊字符“”和“-”分别表示正无穷大与负无穷大。3.7.13 zlexcount 格式ZLEXCOUNT key min max 功能该命令仅适用于集合中所有成员都具有相同分值的情况。该命令返回该集合中元素值本身而非 score 值介于 min 和 max 范围内的元素数量。3.7.14 zremrangebylex 格式ZREMRANGEBYLEX key min max 功能该命令仅适用于集合中所有成员都具有相同分值的情况。该命令会移除该集合中元素值本身介于 min 和 max 范围内的所有元素。3.7.15 应用场景有序 Set 最为典型的应用场景就是排行榜例如音乐、视频平台中根据播放量进行排序的排行榜电商平台根据用户评价或销售量进行排序的排行榜等。将播放量作为 score将作品 id 作为 member将用户评价积分或销售量作为 score将商家 id 作为 member。使用zincrby 增加排序 score使用 zrevrange 获取 Top 前几名使用 zrevrank 查询当前排名使用zscore 查询当前排序 score 等。3.8 benchmark 测试工具3.8.1 简介在Redis安装完毕后会自动安装一个redis-benchmark测试工具其是一个压力测试工具用于测试 Redis 的性能。通过 redis-benchmark –help 命令可以查看到其用法其选项 options 非常多下面通过例子来学习常用的 options 的用法。3.8.2 测试 11 命令解析以上命令中选项的意义 -h指定要测试的 Redis 的 IP若为本机则可省略 -p指定要测试的 Redis 的 port若为 6379则可省略 -c指定模拟有客户端的数量默认值为 50 -n指定这些客户端发出的请求的总量默认值为 100000 -d指定测试 get/set 命令时其操作的 value 的数据长度单位字节默认值为 3。在测试其它命令时该指定没有用处。以上命令的意义是使用 100 个客户端连接该 Redis这些客户端总共会发起 100000个请求set/get 的 value 为 8 字节数据。2 测试结果分析该命令会逐个测试所有 Redis 命令每个命令都会给出一份测试报告每个测试报告由四部分构成A、测试环境报告首先就是测试环境B、 延迟百分比分布这是按照百分比进行的统计报告每完成一次剩余测试量的 50%就给出一个统计数据。C、 延迟的累积分布这是按照时间间隔统计的报告基本是每 0.1 毫秒统计一次。D、总述报告这是总述性报告。3.8.3 测试 2以上命令中选项的意义 -t指定要测试的命令多个命令使用逗号分隔不能有空格 -q指定仅给出总述性报告3.9 简单动态字符串 SDS3.9.1 SDS 简介无论是 Redis 的 Key 还是 Value其基础数据类型都是字符串。例如Hash 型 Value 的field 与 value 的类型、List 型、Set 型、ZSet 型 Value 的元素的类型等都是字符串。虽然 Redis是使用标准 C 语言开发的但并没有直接使用 C 语言中传统的字符串表示而是自定义了一种字符串。这种字符串本身的结构比较简单但功能却非常强大称为简单动态字符串Simple Dynamic String简称 SDS。注意Redis 中的所有字符串并不都是 SDS也会出现 C 字符串。C 字符串只会出现在字符串“字面常量”中并且该字符串不可能发生变更。redisLog(REDIS_WARNNING, “sdfsfsafsafds”);3.9.2 SDS 结构SDS 不同于 C 字符串。C 字符串本身是一个以双引号括起来以空字符’\0’结尾的字符序列。但 SDS 是一个结构体定义在 Redis 安装目录下的 src/sds.h 中struct sdshdr {
// 字节数组用于保存字符串
char buf[];
// buf[]中已使用字节数量称为 SDS 的长度
int len;
// buf[]中尚未使用的字节数量
int free;
}例如执行 SET country “China”命令时键 country 与值”China”都是 SDS 类型的只不过一个是 SDS 的变量一个是 SDS 的字面常量。”China”在内存中的结构如下通过以上结构可以看出SDS 的 buf 值实际是一个 C 字符串包含空字符’\0’共占 6 个字节。但 SDS 的 len 是不包含空字符’\0’的。该结构与前面不同的是这里有 3 字节未使用空间。3.9.3 SDS 的优势C 字符串使用 Len1 长度的字符数组来表示实际长度为 Len 的字符串字符数组最后以空字符’\0’结尾表示字符串结束。这种结构简单但不能满足 Redis 对字符串功能性、安全性及高效性等的要求。1 防止”字符串长度获取”性能瓶颈对于 C 字符串若要获取其长度则必须要通过遍历整个字符串才可获取到的。对于超长字符串的遍历会成为系统的性能瓶颈。但由于 SDS 结构体中直接就存放着字符串的长度数据所以对于获取字符串长度需要消耗的系统性能与字符串本身长度是无关的不会成为 Redis 的性能瓶颈。2 保障二进制安全C 字符串中只能包含符合某种编码格式的字符例如 ASCII、UTF-8 等并且除了字符串末尾外其它位置是不能包含空字符’\0’的否则该字符串就会被程序误解为提前结束。而在图片、音频、视频、压缩文件、office 文件等二进制数据中以空字符’\0’作为分隔符的情况是很常见的。故而在 C 字符串中是不能保存像图片、音频、视频、压缩文件、office 文件等二进制数据的。但 SDS 不是以空字符’\0’作为字符串结束标志的其是通过 len 属性来判断字符串是否结束的。所以对于程序处理 SDS 中的字符串数据无需对数据做任何限制、过滤、假设只需读取即可。数据写入的是什么读到的就是什么。3 减少内存再分配次数SDS 采用了空间预分配策略与惰性空间释放策略来避免内存再分配问题。空间预分配策略是指每次 SDS 进行空间扩展时程序不但为其分配所需的空间还会为其分配额外的未使用空间以减少内存再分配次数。而额外分配的未使用空间大小取决于空间扩展后 SDS 的 len 属性值。 如果 len 属性值小于 1M那么分配的未使用空间 free 的大小与 len 属性值相同。 如果 len 属性值大于等于 1M 那么分配的未使用空间 free 的大小固定是 1M。SDS 对于空间释放采用的是惰性空间释放策略。该策略是指SDS 字符串长度如果缩短那么多出的未使用空间将暂时不释放而是增加到 free 中。以使后期扩展 SDS 时减少内存再分配次数。如果要释放 SDS 的未使用空间则可通过 sdsRemoveFreeSpace()函数来释放。4 兼容 C 函数Redis 中提供了很多的 SDS 的 API以方便用户对 Redis 进行二次开发。为了能够兼容 C函数SDS 的底层数组 buf[]中的字符串仍以空字符’\0’结尾。现在要比较的双方一个是 SDS一个是 C 字符串此时可以通过 C 语言函数strcmp(sds_str-bufc_str)3.9.4 常用的 SDS 操作函数下表列出了一些常用的 SDS 操作函数及其功能描述。3.10 集合的底层实现原理Redis 中对于 Set 类型的底层实现直接采用了 hashTable。但对于 Hash、ZSet、List 集合的底层实现进行了特殊的设计使其保证了 Redis 的高性能。3.10.1 两种实现的选择对于Hash与ZSet集合其底层的实现实际有两种压缩列表zipList与跳跃列表skipList。这两种实现对于用户来说是透明的但用户写入不同的数据系统会自动使用不同的实现。只有同时满足以配置文件 redis.conf 中相关集合元素数量阈值与元素大小阈值两个条件使用的就是压缩列表 zipList只要有一个条件不满足使用的就是跳跃列表 skipList。例如对于ZSet 集合中这两个条件如下 集合元素个数小于 redis.conf 中 zset-max-ziplist-entries 属性的值其默认值为 128 每个集合元素大小都小于 redis.conf 中 zset-max-ziplist-value 属性的值其默认值为 64字节3.10.2 zipList1 什么是 zipListzipList通常称为压缩列表是一个经过特殊编码的用于存储字符串或整数的双向链表。其底层数据结构由三部分构成head、entries 与 end。这三部分在内存上是连续存放的。2 headhead 又由三部分构成 zlbytes占 4 个字节用于存放 zipList 列表整体数据结构所占的字节数包括 zlbytes本身的长度。 zltail占 4 个字节用于存放 zipList 中最后一个 entry 在整个数据结构中的偏移量字节。该数据的存在可以快速定位列表的尾 entry 位置以方便操作。 zllen占 2 字节用于存放列表包含的 entry 个数。由于其只有 16 位所以 zipList 最多可以含有的 entry 个数为 216-1 65535 个。3 entriesentries 是真正的列表由很多的列表元素 entry 构成。由于不同的元素类型、数值的不同从而导致每个 entry 的长度不同。每个 entry 由三部分构成 prevlength该部分用于记录上一个 entry 的长度以实现逆序遍历。默认长度为 1 字节只要上一个 entry 的长度254 字节prevlength 就占 1 字节否则其会自动扩展为 3 字节长度。 encoding该部分用于标志后面的 data 的具体类型。如果 data 为整数类型固定长度为 1 字节。如果 data 为字符串类型则 encoding 长度可能会是 1 字节、2 字节或 5 字节。data 字符串不同的长度对应着不同的 encoding 长度。 data真正存储的数据。数据类型只能是整数类型或字符串类型。不同的数据占用的字节长度不同。4 endend 只包含一部分称为 zlend。占 1 个字节值固定为 255即二进制位为全 1表示一个 zipList 列表的结束。3.10.3 listPack对于 ziplist实现复杂为了逆序遍历每个 entry 中包含前一个 entry 的长度这样会导致在 ziplist 中间修改或者插入 entry 时需要进行级联更新。在高并发的写操作场景下会极度降低 Redis 的性能。为了实现更紧凑、更快的解析更简单的实现重写实现了 ziplist并命名为 listPack。在 Redis 7.0 中已经将 zipList 全部替换为了 listPack但为了兼容性在配置中也保留了 zipList 的相关属性。1 什么是 listPacklistPack 也是一个经过特殊编码的用于存储字符串或整数的双向链表。其底层数据结构也由三部分构成head、entries 与 end且这三部分在内存上也是连续存放的。listPack与zipList的重大区别在head与每个entry的结构上表示列表结束的end与zipList的 zlend 是相同的占一个字节且 8 位全为 1。2 headhead 由两部分构成 totalBytes占 4 个字节用于存放 listPack 列表整体数据结构所占的字节数包括totalBytes 本身的长度。 elemNum占 2 字节用于存放列表包含的 entry 个数。其意义与 zipList 中 zllen 的相同。与 zipList 的 head 相比没有了记录最后一个 entry 偏移量的 zltail。3 entriesentries 也是 listPack 中真正的列表由很多的列表元素 entry 构成。由于不同的元素类型、数值的不同从而导致每个 entry 的长度不同。但与 zipList 的 entry 结构相比listPack的 entry 结构发生了较大变化。其中最大的变化就是没有了记录前一个 entry 长度的 prevlength而增加了记录当前entry 长度的 element-total-len。而这个改变仍然可以实现逆序遍历但却避免了由于在列表中间修改或插入 entry 时引发的级联更新。每个 entry 仍由三部分构成 encoding该部分用于标志后面的 data 的具体类型。如果 data 为整数类型encoding长度可能会是 1、2、3、4、5 或 9 字节。不同的字节长度其标识位不同。如果 data为字符串类型则 encoding 长度可能会是 1、2 或 5 字节。data 字符串不同的长度对应着不同的 encoding 长度。 data真正存储的数据。数据类型只能是整数类型或字符串类型。不同的数据占用的字节长度不同。 element-total-len该部分用于记录当前 entry 的长度用于实现逆序遍历。由于其特殊的记录方式使其本身占有的字节数据可能会是 1、2、3、4 或 5 字节。3.10.4 skipList1 什么是 skipListskipList跳跃列表简称跳表是一种随机化的数据结构基于并联的链表实现简单查找效率较高。简单来说跳表也是链表的一种只不过它在链表的基础上增加了跳跃功能。也正是这个跳跃功能使得在查找元素时能够提供较高的效率。2 skipList 原理假设有一个带头尾结点的有序链表。在该链表中如果要查找某个数据需要从头开始逐个进行比较直到找到包含数据的那个节点或者找到第一个比给定数据大的节点或者找到最后尾结点后两种都属于没有找到的情况。同样当我们要插入新数据的时候也要经历同样的查找过程从而确定插入位置。为了提升查找效率在偶数结点上增加一个指针让其指向下一个偶数结点。这样所有偶数结点就连成了一个新的链表简称高层链表当然高层链表包含的节点个数只是原来链表的一半。此时再想查找某个数据时先沿着高层链表进行查找。当遇到第一个比待查数据大的节点时立即从该大节点的前一个节点回到原链表中进行查找。例如若想插入一个数据 20则先在8193142的链表中查找找到第一个比 20 大的节点 31然后再在高层链表中找到 31 节点的前一个节点 19然后再在原链表中获取到其下一个节点值为 23。比 20 大则将 20 插入到 19 节点与 23 节点之间。若插入的是 25比节点23 大则插入到 23 节点与 31 节点之间。该方式明显可以减少比较次数提高查找效率。如果链表元素较多为了进一步提升查找效率可以将原链表构建为三层链表或再高层级链表。层级越高查找效率就会越高。3 存在的问题这种对链表分层级的方式从原理上看确实提升了查找效率但在实际操作时就出现了问题由于固定序号的元素拥有固定层级所以列表元素出现增加或删除的情况下会导致列表整体元素层级大调整但这样势必会大大降低系统性能。例如对于划分两级的链表可以规定奇数结点为高层级链表偶数结点为低层级链表。对于划分三级的链表可以按照节点序号与 3 取模结果进行划分。但如果插入了新的节点或删除的原来的某些节点那么定会按照原来的层级划分规则进行重新层级划分那么势必会大大降低系统性能。4 算法优化为了避免前面的问题skipList 采用了随机分配层级方式。即在确定了总层级后每添加一个新的元素时会自动为其随机分配一个层级。这种随机性就解决了节点序号与层级间的固定关系问题。上图演示了列表在生成过程中为每个元素随机分配层级的过程。从这个 skiplist 的创建和插入过程可以看出每一个节点的层级数都是随机分配的而且新插入一个节点不会影响到其它节点的层级数。只需要修改插入节点前后的指针而不需对很多节点都进行调整。这就降低了插入操作的复杂度。skipList 指的就是除了最下面第 1 层链表之外它会产生若干层稀疏的链表这些链表里面的指针跳过了一些节点并且越高层级的链表跳过的节点越多。在查找数据的时先在高层级链表中进行查找然后逐层降低最终可能会降到第 1 层链表来精确地确定数据位置。在这个过程中由于跳过了一些节点从而加快了查找速度。3.10.5 quickList1 什么是 quickListquickList快速列表quickList 本身是一个双向无循环链表它的每一个节点都是一个zipList。从Redis3.2版本开始对于List的底层实现使用quickList替代了zipList 和 linkedList。zipList 与 linkedList 都存在有明显不足而 quickList 则对它们进行了改进吸取了 zipList 和 linkedList 的优点避开了它们的不足。quickList 本质上是 zipList 和 linkedList 的混合体。其将 linkedList 按段切分每一段使用 zipList 来紧凑存储若干真正的数据元素多个 zipList 之间使用双向指针串接起来。当然对于每个 zipList 中最多可存放多大容量的数据元素在配置文件中通过 list-max-ziplist-size属性可以指定。2 检索操作为了更深入的理解 quickList 的工作原理通过对检索、插入、删除等操作的实现分析来加深理解。对于 List 元素的检索都是以其索引 index 为依据的。quickList 由一个个的 zipList 构成每个 zipList 的 zllen 中记录的就是当前 zipList 中包含的 entry 的个数即包含的真正数据元素的个数。根据要检索元素的 index从 quickList 的头节点开始逐个对 zipList 的 zllen 做 sum求和直到找到第一个求和后 sum 大于 index 的 zipList那么要检索的这个元素就在这个zipList 中。3 插入操作由于 zipList 是有大小限制的所以在 quickList 中插入一个元素在逻辑上相对就比较复杂一些。假设要插入的元素的大小为 insertBytes而查找到的插入位置所在的 zipList 当前的大小为 zlBytes那么具体可分为下面几种情况 情况一当 insertBytes zlBytes list-max-ziplist-size 时直接插入到 zipList 中相应位置即可 情况二当 insertBytes zlBytes list-max-ziplist-size且插入的位置位于该 zipList 的首部位置此时需要查看该 zipList 的前一个 zipList 的大小 prev_zlBytes。 若 insertBytes prev_zlBytes list-max-ziplist-size 时直接将元素插入到前一个zipList 的尾部位置即可 若 insertBytes prev_zlBytes list-max-ziplist-size 时直接将元素自己构建为一个新的 zipList并连入 quickList 中 情况三当 insertBytes zlBytes list-max-ziplist-size且插入的位置位于该 zipList 的尾部位置此时需要查看该 zipList 的后一个 zipList 的大小 next_zlBytes。 若 insertBytes next_zlBytes list-max-ziplist-size 时直接将元素插入到后一个zipList 的头部位置即可 若 insertBytes next_zlBytes list-max-ziplist-size 时直接将元素自己构建为一个新的 zipList并连入 quickList 中 情况四当 insertBytes zlBytes list-max-ziplist-size且插入的位置位于该 zipList 的中间位置则将当前 zipList 分割为两个 zipList 连接入 quickList 中然后将元素插入到分割后的前面 zipList 的尾部位置4 删除操作对于删除操作只需要注意一点在相应的 zipList 中删除元素后该 zipList 中是否还有元素。如果没有其它元素了则将该 zipList 删除将其前后两个 zipList 相连接。3.10.6 key 与 value 中元素的数量前面讲述的 Redis 的各种特殊数据结构的设计不仅极大提升了 Redis 的性能并且还使得 Redis 可以支持的 key 的数量、集合 value 中可以支持的元素数量可以非常庞大。 Redis 最多可以处理 232个 key约 42 亿并且在实践中经过测试每个 Redis 实例至少可以处理 2.5 亿个 key。 每个 Hash、List、Set、ZSet 集合都可以包含 232 个元素。3.11 BitMap 操作命令3.11.1 BitMap 简介BitMap 是 Redis 2.2.0 版本中引入的一种新的数据类型。该数据类型本质上就是一个仅包含 0 和 1 的二进制字符串。而其所有相关命令都是对这个字符串二进制位的操作。用于描述该字符串的属性有三个key、offset、bitValue。 keyBitMap 是 Redis 的 key-value 中的一种 Value 的数据类型所以该 Value 一定有其对应的 key。 offset每个 BitMap 数据都是一个字符串字符串中的每个字符都有其对应的索引该索引从 0 开始计数。该索引就称为每个字符在该 BitMap 中的偏移量 offset。这个 offset的值的范围是[0232-1]即该 offset 的最大值为 4G-1即 429496729542 亿多。 bitValue每个 BitMap 数据中都是一个仅包含 0 和 1 的二进制字符串每个 offset 位上的字符就称为该位的值 bitValue。bitValue 的值非 0 即 1。3.11.2 setbit 格式SETBIT key offset value 功能为给定 key 的BitMap 数据的 offset 位置设置值为 value。其返回值为修改前该 offset位置的 bitValue 说明对于原 BitMap 字符串中不存在的 offset 进行赋值字符串会自动伸展以确保它可以将 value 保存在指定的 offset 上。当字符串值进行伸展时空白位置以 0 填充。当然设置的 value 只能是 0 或 1。不过需要注意的是对使用较大 offset 的 SETBIT 操作来说内存分配过程可能造成 Redis 服务器被阻塞。3.11.3 getbit格式GETBIT key offset功能对 key 所储存的 BitMap 字符串值获取指定 offset 偏移量上的位值 bitValue。说明当 offset 比字符串值的长度大或者 key 不存在时返回 0 。3.11.4 bitcount 格式BITCOUNT key [start] [end] 功能统计给定字符串中被设置为 1 的 bit 位的数量。一般情况下统计的范围是给定的整个 BitMap 字符串。但也可以通过指定额外的 start 或 end 参数实现仅对指定字节范围内字符串进行统计包括 start 和 end 在内。注意这里的 start 与 end 的单位是字节不是 bit并且从 0 开始计数。 说明start 和 end 参数都可以使用负数值 -1 表示最后一个字节 -2 表示倒数第二个字节以此类推。另外对于不存在的 key 被当成是空字符串来处理因此对一个不存在的 key 进行 BITCOUNT 操作结果为 0 。3.11.5 bitpos 格式BITPOS key bit [start] [end] 功能返回 key 指定的 BitMap 中第一个值为指定值 bit(非 0 即 1) 的二进制位的位置。pos即 position位置。在默认情况下 命令将检测整个 BitMap但用户也可以通过可选的 start 参数和 end 参数指定要检测的范围。 说明start 与 end 的意义与 bitcount 命令中的相同。3.11.6 bitop 格式BITOP operation destkey key *key … 功能对一个或多个 BitMap 字符串 key 进行二进制位操作并将结果保存到 destkey 上。 operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种 BITOP AND destkey key [key ...] 对一个或多个 BitMap 执行按位与操作并将结果保存到 destkey 。 BITOP OR destkey key [key ...] 对一个或多个 BitMap 执行按位或操作并将结果保存到 destkey 。 BITOP XOR destkey key [key ...] 对一个或多个 BitMap 执行按位异或操作并将结果保存到 destkey 。 BITOP NOT destkey key 对给定 BitMap 执行按位非操作并将结果保存到 destkey 。 说明 除了 NOT 操作之外其他操作都可以接受一个或多个 BitMap 作为输入。 除了 NOT 操作外其他对一个 BitMap 的操作其实就是一个复制。 如果参与运算的多个 BitMap 长度不同较短的 BitMap 会以 0 作为补充位与较长BitMap 运算且运算结果长度与较长 BitMap 的相同。3.11.7 应用场景由于 offset 的取值范围很大所以其一般应用于大数据量的二值性统计。例如平台活跃用户统计二值访问或未访问、支持率统计二值支持或不支持、员工考勤统计二值上班或未上班、图像二值化二值黑或白等。不过对于数据量较小的二值性统计并不适合 BitMap可能使用 Set 更为合适。当然具体多少数据量适合使用 Set超过多少数据量适合使用 BitMap这需要根据具体场景进行具体分析。例如一个平台要统计日活跃用户数量。如果使用 Set 来统计只需上线一个用户就将其用户 ID 写入 Set 集合即可最后只需统计出 Set 集合中的元素个数即可完成统计。即 Set 集合占用内存的大小与上线用户数量成正比。假设用户 ID 为 m 位 bit 位当前活跃用户数量为 n则该 Set 集合的大小最少应该是m*n 字节。如果使用 BitMap 来统计则需要先定义出一个 BitMap其占有的 bit 位至少为注册用户数量。只需上线一个用户就立即使其中一个 bit 位置 1最后只需统计出 BitMap 中 1 的个数即可完成统计。即 BitMap 占用内存的大小与注册用户数量成正比与上线用户数量无关。假设平台具有注册用户数量为 N则 BitMap 的长度至少为 N 个 bit 位即 N/8 字节。何时使用 BitMap 更合适令 m*n 字节 N/8 字节即 n N/8/m N/(8*m) 时使用Set 集合与使用 BitMap 所占内存大小相同。以淘宝为例其用户 ID 长度为 11 位(m)其注册用户数量为 8 亿(N)当活跃用户数量为 8 亿/(8*11) 0.09 亿 9*106 900 万使用 Set与 BitMap 占用的内存是相等的。但淘宝的日均活跃用户数量为 8 千万所以淘宝使用 BitMap更合适。3.12 HyperLogLog 操作命令3.12.1 HyperLogLog 简介HyperLogLog 是 Redis 2.8.9 版本中引入的一种新的数据类型其意义是 hyperlog log超级日志记录。该数据类型可以简单理解为一个 set 集合集合元素为字符串。但实际上HyperLogLog 是一种基数计数概率算法通过该算法可以利用极小的内存完成独立总数的统计。其所有相关命令都是对这个“set 集合”的操作。HyperLogLog 算法是由法国人 Philippe Flajolet 博士研究出来的Redis的作者 Antirez 为了纪念 Philippe Flajolet 博士对组合数学和基数计算算法分析的研究在设计 HyperLogLog 命令的时候使用了 Philippe Flajolet姓名的英文首字母 PF 作为前缀。遗憾的是 Philippe Flajolet 博士于 2011年 3 月 22 日因病在巴黎辞世。HyperLogLog 算法是一个纯数学算法我们这里不做研究。3.12.2 pfadd 格式PFADD key element *element … 功能将任意数量的元素添加到指定的 HyperLogLog 集合里面。如果内部存储被修改了返回 1否则返回 0。3.12.3 pfcount 格式PFCOUNT key *key … 功能该命令作用于单个 key 时返回给定 key 的 HyperLogLog 集合的近似基数该命令作用于多个 key 时返回所有给定 key 的 HyperLogLog 集合的并集的近似基数如果key 不存在则返回 0。3.12.4 pfmerge 格式PFMERGE destkey sourcekey *sourcekey … 功能将多个 HyperLogLog 集合合并为一个 HyperLogLog 集合并存储到 destkey 中合并后的 HyperLogLog 的基数接近于所有 sourcekey 的 HyperLogLog 集合的并集。3.12.5 应用场景HyperLogLog 可对数据量超级庞大的日志数据做不精确的去重计数统计。当然这个不精确的度在 Redis 官方给出的误差是 0.81%。这个误差对于大多数超大数据量场景是被允许的。对于平台上每个页面每天的 UV 数据非常适合使用 HyperLogLog 进行记录。3.13 Geospatial 操作命令3.13.1 Geospatial 简介Geospatial地理空间。Redis 在 3.2 版本中引入了 Geospatial 这种新的数据类型。该类型本质上仍是一种集合只不过集合元素比较特殊是一种由三部分构成的数据结构这种数据结构称为空间元素 经度longitude。有效经度为[-180180]。正的表示东经负的表示西经。 纬度latitude。有效纬度为[-85.0511287885.05112878]。正的表示北纬负的表示南纬。 位置名称为该经纬度所标注的位置所命名的名称也称为该 Geospatial 集合的空间元素名称。通过该类型可以设置、查询某地理位置的经纬度查询某范围内的空间元素计算两空间元素间的距离等。3.13.2 geoadd 格式GEOADD key longitude latitude member *longitude latitude member … 功能将一到多个空间元素添加到指定的空间集合中。 说明当用户尝试输入一个超出范围的经度或者纬度时该命令会返回一个错误。3.13.3 geopos 格式GEOPOS key member *member … 功能从指定的地理空间中返回指定元素的位置即经纬度。 说明因为 该命令接受可变数量元素作为输入所以即使用户只给定了一个元素命令也会返回数组。3.13.4 geodist 格式GEODIST key member1 member2 [unit] 功能返回两个给定位置之间的距离。其中 unit 必须是以下单位中的一种 m 米默认 km 千米 mi 英里 ft英尺 说明如果两个位置之间的其中一个不存在 那么命令返回空值。另外在计算距离时会假设地球为完美的球形 在极限情况下 这一假设最大会造成 0.5% 的误差。3.13.5 geohash 格式GEOHASH key member *member … 功能返回一个或多个位置元素的 Geohash 值。 说明GeoHash 是一种地址编码方法。他能够把二维的空间经纬度数据编码成一个字符串。该值主要用于底层应用或者调试 实际中的作用并不大。3.13.6 georadius 格式GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] 功能以给定的经纬度为中心返回指定地理空间中包含的所有位置元素中与中心距离不超过给定半径的元素。返回时还可携带额外的信息 WITHDIST 在返回位置元素的同时将位置元素与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致。 WITHCOORD 将位置元素的经维度也一并返回。 WITHHASH将位置元素的 Geohash 也一并返回不过这个 hash 以整数形式表示命令默认返回未排序的位置元素。 通过以下两个参数用户可以指定被返回位置元素的排序方式 ASC 根据中心的位置按照从近到远的方式返回位置元素。 DESC 根据中心的位置按照从远到近的方式返回位置元素。 说明在默认情况下 该命令会返回所有匹配的位置元素。虽然用户可以使用 COUNT count 选项去获取前 N 个匹配元素但因为命令在内部可能会需要对所有被匹配的元素进行处理所以在对一个非常大的区域进行搜索时即使使用 COUNT 选项去获取少量元素该命令的执行速度也可能会非常慢。3.13.7 georadiusbymember 格式GEORADIUSBYMEMBER key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [ASC|DESC] [COUNT count] 功能这个命令和 GEORADIUS 命令一样都可以找出位于指定范围内的元素但该命令的中心点是由位置元素形式给定的而不是像 GEORADIUS 那样使用输入的经纬度来指定中心点。 说明返回结果中也是包含中心点位置元素的3.13.8 应用场景Geospatial 的意义是地理位置所以其主要应用地理位置相关的计算。例如微信发现中的“附近”功能添加朋友中“雷达加朋友”功能QQ 动态中的“附近”功能钉钉中的“签到”功能等。3.14 发布/订阅命令3.14.1 消息系统发布/订阅即 pub/sub是一种消息通信模式发布者也称为消息生产者生产和发送消息到存储系统订阅者也称为消息消费者从存储系统接收和消费消息。这个存储系统可以是文件系统 FS、消息中间件 MQ、数据管理系统 DBMS也可以是 Redis。整个消息发布者、订阅者与存储系统称为消息系统。消息系统中的订阅者订阅了某类消息后只要存储系统中存在该类消息其就可不断的接收并消费这些消息。当存储系统中没有该消息后订阅者的接收、消费阻塞。而当发布者将消息写入到存储系统后会立即唤醒订阅者。当存储系统放满时不同的发布者具有不同的处理方式有的会阻塞发布者的发布等待可用的存储空间有的则会将多余的消息丢失。当然不同的消息系统消息的发布/订阅方式也是不同的。例如 RocketMQ、Kafka 等消息中间件构成的消息系统中发布/订阅的消息都是以主题 Topic 分类的。而 Redis 构成的消息系统中发布/订阅的消息都是以频道 Channel 分类的。3.14.2 subscribe 格式SUBSCRIBE channel *channel … 功能Redis 客户端通过一个 subscribe 命令可以同时订阅任意数量的频道。在输出了订阅了主题后命令处于阻塞状态等待相关频道的消息。3.14.3 psubscribe 格式PSUBSCRIBE pattern *pattern … 功能订阅一个或多个符合给定模式的频道。 说明这里的模式只能使用通配符 *。例如it* 可以匹配所有以 it 开头的频道像 it.news、it.blog、it.tweets 等news.*可以匹配所有以 news.开头的频道像 news.global.today、news.it 等。3.14.4 publish 格式PUBLISH channel message 功能Redis 客户端通过一条 publish 命令可以发布一个频道的消息。返回值为接收到该消息的订阅者数量。3.14.5 unsubscribe 格式UNSUBSCRIBE *channel *channel … 功能Redis 客户端退订指定的频道。 说明如果没有频道被指定也就是一个无参数的 UNSUBSCRIBE 命令被执行那么客户端使用 SUBSCRIBE 命令订阅的所有频道都会被退订。在这种情况下命令会返回一个信息告知客户端所有被退订的频道。3.14.6 punsubscribe 格式PUNSUBSCRIBE *pattern *pattern … 功能退订一个或多个符合给定模式的频道。 说明这里的模式只能使用通配符 *。如果没有频道被指定其效果与 SUBSCRIBE 命令相同客户端将退订所有订阅的频道。3.14.7 pubsub 格式PUBSUB subcommand [argument *argument … 功能PUBSUB 是一个查看订阅与发布系统状态的内省命令集它由数个不同格式的子命令组成下面分别介绍这些子命令的用法。1 pubsub channels 格式PUBSUB CHANNELS [pattern] 功能列出当前所有的活跃频道。活跃频道指的是那些至少有一个订阅者的频道。 说明pattern 参数是可选的。如果不给出 pattern 参数将会列出订阅/发布系统中的所有活跃频道。如果给出 pattern 参数那么只列出和给定模式 pattern 相匹配的那些活跃频道。pattern 中只能使用通配符*。2 pubsub numsub 格式PUBSUB NUMSUB [channel-1 … channel-N] 功能返回给定频道的订阅者数量。不给定任何频道则返回一个空列表。3 pubsub numpat 格式PUBSUB NUMPAT 功能查询当前 Redis 所有客户端订阅的所有频道模式的数量总和3.15 Redis 事务Redis 的事务的本质是一组命令的批处理。这组命令在执行过程中会被顺序地、一次性全部执行完毕只要没有出现语法错误这组命令在执行期间是不会被中断。3.15.1 Redis 事务特性Redis 的事务仅保证了数据的一致性不具有像 DBMS 一样的 ACID 特性。 这组命令中的某些命令的执行失败不会影响其它命令的执行不会引发回滚。即不具备原子性。 这组命令通过乐观锁机制实现了简单的隔离性。没有复杂的隔离级别。 这组命令的执行结果是被写入到内存的是否持久取决于 Redis 的持久化策略与事务无关。3.15.2 Redis 事务实现1 三个命令Redis 事务通过三个命令进行控制。 muti开启事务 exec执行事务 discard取消事务2 基本使用下面是定义并执行事务的用法事务执行后再访问事务中定义的变量其值是修改过后。下面是定义但取消事务的举例事务取消后事务中的命令是没有执行的。3.15.3 Redis 事务异常处理1 语法错误当事务中的命令出现语法错误时整个事务在 exec 执行时会被取消。exec 的提示是 exec 被忽略事务被取消因为之前的错误。此时访问 age 的值发现其仍为 19并没有变为事务中设置的 20。2 执行异常如果事务中的命令没有语法错误但在执行过程中出现异常该异常不会影响其它命令的执行。以上事务中第 2 条命令在执行时出现异常。因为 score 并非是整型无法被增加 20 的操作。但该异常并不会影响其前后命令的正确执行。查看 score 与 name 的值发现是执行成功的结果。3.15.4 Redis 事务隔离机制1 为什么需要隔离机制在并发场景下可能会出现多个客户端对同一个数据进行修改的情况。例如有两个客户端 C 左与 C 右C 左需要申请 40 个资源C 右需要申请 30 个资源。它们首先查看了当前拥有的资源数量即 resources 的值。它们查看到的都是 50都感觉资源数量可以满足自己的需求于是修改资源数量以占有资源。但结果却是资源出现了“超卖”情况。为了解决这种情况Redis 事务通过乐观锁机制实现了多线程下的执行隔离。2 隔离的实现Redis 通过 watch 命令再配合事务实现了多线程下的执行隔离。以上两个客户端执行的时间顺序为3 实现原理其内部的执行过程如下1) 当某一客户端对 key 执行了 watch 后系统就会为该 key 添加一个 version 乐观锁并初始化 version。例如初值为 1.0。2) 此后客户端 C 左将对该 key 的修改语句写入到了事务命令队列中虽未执行但其将该key 的 value 值与 version 进行了读取并保存到了当前客户端缓存。此时读取并保存的是version 的初值 1.0。3) 此后客户端 C 右对该 key 的值进行了修改这个修改不仅修改了 key 的 value 本身同时也增加了 version 的值例如使其 version 变为了 2.0并将该 version 记录到了该 key信息中。4) 此后客户端 C 左执行 exec开始执行事务中的命令。不过其在执行到对该 key 进行修改的命令时该命令首先对当前客户端缓存中保存的 version 值与当前 key 信息中的version 值。如果缓存 version 小于 key 的 version则说明客户端缓存的 key 的 value 已经过时该写操作如果执行可能会破坏数据的一致性。所以该写操作不执行。