资讯详情

资讯详情

Redis 的原理和使用(2)

二、常见数据类型Redis 对外提供 string、hash、list、set、zset 五种数据结构。理解每种结构的特点与常见命令是使用 Redis 的基础。正式展开之前先补充三块预备知识全局命令、内部编码与单线程模型。2.1 预备知识1全局命令无论哪种数据类型都可以使用一组通用命令管理键常见几个命令的语法与复杂度汇总在下表命令作用复杂度KEYS pattern按模式匹配全部键会阻塞服务生产环境谨慎使用。O(n)EXISTS key判断键是否存在。O(1)DEL key [key ...]删除一个或多个键返回删除个数。O(1)EXPIRE key seconds为键设置过期时间到期后由 Redis 淘汰。O(1)TTL key返回剩余过期时间-1 表示未设置过期时间-2 表示键不存在。O(1)TYPE key返回键对应的数据类型。O(1)过期与淘汰的配合可以用一条时间轴理解键在 n 秒内可以正常读取n 秒过后被 Redis 淘汰再次读取会得到不存在的提示EXPIRE 与 TTL 还提供毫秒级版本 PEXPIRE 与 PTTL用于精度要求更高的场景。以命令示例感受一下执行效果redis SET mykey Hello OK redis EXPIRE mykey 10 (integer) 1 redis TTL mykey (integer) 102内部编码对外是五种数据结构对内每种结构还有多种底层实现Redis 会根据数据规模与形态动态选择。两个好处一是改进内部编码时对外命令无感知二是不同编码可以在各自场景发挥优势。五种结构与内部编码的对应关系见表数据结构内部编码stringint、embstr、rawhashziplist、hashtablelistziplist、linkedlistsetintset、hashtablezsetziplist、skiplist内部编码可以通过 object encoding 命令查看127.0.0.1:6379 set hello world OK 127.0.0.1:6379 object encoding hello embstr 127.0.0.1:6379 rpush mylist e1 e2 e3 (integer) 3 127.0.0.1:6379 object encoding mylist ziplist单线程能跑到每秒万级别原因可以归结为三点纯内存访问。内存响应时长大约 100 纳秒这是高吞吐的重要基础。非阻塞 IO。Redis 使用 epoll 作为 IO 多路复用实现把连接、读写、关闭都注册为事件不在网络 IO 上浪费过多时间。单线程避免了线程切换与竞态消耗也让数据结构和算法实现更简单。单线程也有代价单条命令执行时间过长会拖住后面所有命令的响应。因此 Redis 面向的是快速执行的场景耗时操作需要谨慎设计。2.2 String 字符串字符串是最基础的类型。两点留意Redis 中所有键的类型都是字符串其他数据结构建立在字符串之上字符串的值可以是普通字符串、JSON / XML 文本、数字甚至图片、音频等二进制流单个值上限 512 MB。由于内部按二进制流保存Redis 不处理编码问题客户端用什么编码写入就按什么编码存储。1常用命令字符串命令按功能可以分成几组下表摘录高频命令命令作用SET key value设置值键已存在时覆盖并清除原有过期时间。GET key获取值。MSET / MGET批量设置 / 批量获取一次往返完成多个键的操作。INCR / DECR值自增 / 自减 1返回新值可延伸出 INCRBY、DECRBY。SETNX key value键不存在时才设置成功返回 1已存在返回 0。SETEX key seconds value设置值的同时指定过期时间等于 SET EXPIRE。批量命令的价值可以从数据上直观感受。假设网络往返耗时 1 毫秒、命令执行耗时 0.1 毫秒1000 次 get 与一次 mget 1000 个键的时间差距明显操作时间1000 次 get1000 × 1 1000 × 0.1 1100 毫秒1 次 mget 1000 个键1 × 1 1000 × 0.1 101 毫秒批量操作能显著提升吞吐但单次批量携带的键数量也要控制避免单条命令执行时间过长拖累整个服务。2内部编码字符串有三种内部编码Redis 根据值的类型和长度动态选择int值是整数时使用节省内存。embstr值长度不超过 44 字节时使用连续分配内存读取更快。raw长度较长的字符串使用。127.0.0.1:6379 set number 10086 OK 127.0.0.1:6379 object encoding number int 127.0.0.1:6379 set hello world OK 127.0.0.1:6379 object encoding hello embstr 127.0.0.1:6379 set long_str a very very long string ... OK 127.0.0.1:6379 object encoding long_str raw3使用场景字符串类型覆盖了缓存、计数、会话、验证码等常见需求缓存以 uid 为键存放用户信息命中缓存直接返回未命中再回源存储层并回写缓存。这套读写流程可以用下图概括计数视频播放数、评论数等用 INCR 系列命令完成自增会话分布式 Web 服务把 Session 分散在各服务器上用户刷新时可能被均衡到另一台机器导致重新登录短信验证码限制用户每分钟获取验证码不超过 5 次用字符串配合过期时间实现频控。分布式锁利用 SETNX 的“仅当键不存在时写入”特性作为锁的最简实现。2.3 Hash 哈希哈希类型指值本身又是一个键值对结构形如 key { field1: value1, field2: value2, ... }。主流语言中它被称为哈希、字典或映射。1常用命令命令作用HSET key field value设置指定字段的值字段不存在时新建。HGET key field获取指定字段的值。HMSET / HMGET批量设置 / 批量获取多个字段。HGETALL key获取全部字段与值。HINCRBY key field increment字段值按指定步长自增。HSETNX key field value字段不存在时才设置成功。127.0.0.1:6379 hset user:1 name james (integer) 1 127.0.0.1:6379 hset user:1 age 28 (integer) 1 127.0.0.1:6379 hgetall user:1 1) name 2) james 3) age 4) 282内部编码哈希的内部编码有两种ziplist 与 hashtable。一旦某个 value 超过 64 字节或者字段个数超过 512编码会转换为 hashtable127.0.0.1:6379 object encoding user:1 ziplist 127.0.0.1:6379 hset user:2 big_field a value longer than 64 bytes ... (integer) 1 127.0.0.1:6379 object encoding user:2 hashtable3使用场景哈希适合存放对象类数据例如用户信息、商品信息。使用哈希也要注意控制 ziplist 与 hashtable 之间的转换转换过程会带来额外的内存消耗。2.4 List 列表列表类型用来存储多个有序的字符串元素允许重复。列表按插入顺序排列两端操作效率高lpush 从左侧插入rpush 从右侧插入lpop / rpop 从两端弹出。1常用命令命令作用LPUSH / RPUSH从左侧 / 右侧插入一个或多个元素。LPOP / RPOP从左侧 / 右侧弹出元素。LRANGE key start stop按索引范围获取元素支持正数与倒数下标。LINDEX key index获取指定索引位置的元素。LLEN key返回列表长度。LREM key count value删除指定数量的匹配元素。BRPOP key [key ...] timeout阻塞式弹出队列为空时等待指定时间。列表的获取与删除是两个不同操作lindex 只读不改列表长度不变lrem 会真正删除元素长度随之变化2内部编码列表同样有两种编码。元素较少且没有大元素时用 ziplist元素个数超过 512或者某个元素长度超过 64 字节转换为 linkedlist127.0.0.1:6379 rpush listkey e1 e2 e3 (integer) 3 127.0.0.1:6379 object encoding listkey ziplist 127.0.0.1:6379 rpush listkey e1 e2 e3 ... e512 e513 (integer) 513 127.0.0.1:6379 object encoding listkey linkedlist3使用场景列表的经典用法是消息队列。生产者用 lpush 从左侧插入消费者用 brpop 阻塞式从右侧争抢多个消费者客户端分摊消费兼顾负载均衡与高可用队列还可以按频道拆分消费者通过 brpop 监听不同的键实现订阅不同频道的效果频道之间互不干扰微博这类业务按时间倒序展示动态列表天然有序且支持按索引分页配合哈希保存每条的属性可以拼出完整的分页列表。2.5 Set 集合集合类型存储无序、不重复的字符串。集合提供了丰富的集合间运算适合标签、社交类业务。1常用命令命令作用SADD key member [member ...]添加一个或多个元素已存在的元素被忽略。SREM key member [member ...]删除一个或多个元素返回实际删除个数。SMEMBERS key返回集合全部元素。SISMEMBER key member判断元素是否在集合中。SCARD key返回元素个数。SPOP key [count]随机弹出元素适合抽奖场景。SINTER / SUNION / SDIFF求交集 / 并集 / 差集。redis SADD myset one two three (integer) 3 redis SREM myset one (integer) 1 redis SMEMBERS myset 1) three 2) two2内部编码集合有两种编码元素都是整数且个数小于 set-max-intset-entries 512时使用 intset节省内存不满足条件时使用 hashtable127.0.0.1:6379 sadd setkey 1 2 3 4 (integer) 4 127.0.0.1:6379 object encoding setkey intset 127.0.0.1:6379 sadd setkey hello (integer) 1 127.0.0.1:6379 object encoding setkey hashtable3使用场景交集、并集、差集的概念可以用集合间的圆圈关系表示典型场景包括标签系统给文章打标签用集合保存每个标签下的文章 id支持按标签检索。社交关系共同好友用 SINTER 求交集关注关系用集合存粉丝 / 关注列表。抽奖SPOP 随机弹出参与者一次一个或多个。2.6 Zset 有序集合有序集合在集合“元素不重复”的基础上为每个元素关联一个分数元素按分数排序。用户排行榜就是它的典型应用列表、集合、有序集合三者容易混淆异同点可以对比记忆数据结构是否允许重复元素是否有序有序依据应用场景列表是是索引下标时间轴、消息队列集合否否无标签、社交有序集合否是分数排行榜、社交1常用命令命令作用ZADD key score member添加元素并设置分数元素已存在则更新分数。ZRANGE key start stop按分数从小到大的顺序返回指定索引范围元素。ZREVRANGE key start stop按分数从大到小的顺序返回元素。ZSCORE key member获取元素分数。ZRANK / ZREVRANK获取元素排名正序 / 倒序。ZINCRBY key increment member元素分数按步长自增。2集合间操作有序集合也支持交集、并集等运算合并过程中按元素聚合分数聚合方式有 sum、max、min还可为各集合设置权重ZINTERSTORE dest 2 user:ranking:1 user:ranking:2 # 合并后各元素分数按 sum 方式聚合 # mike 16891 77martin 875625 250tom 1139888 2513内部编码有序集合的编码同样是 ziplist 与 skiplist 的组合元素个数少于 128 且成员长度不超过 64 字节时用 ziplist超过后转换为 skiplist127.0.0.1:6379 object encoding myzset ziplist 127.0.0.1:6379 zadd myzset 1.0 a 2.0 b ... 129 个元素 (integer) 129 127.0.0.1:6379 object encoding myzset skiplist4使用场景有序集合的应用集中在需要按分数排序与排名计算的场景游戏排行玩家分数作为 scoreZREVRANGE 取出前 N 名。热度榜单按点击、销量等指标排序ZINCRBY 实时更新分数。时间排序把时间戳作为分数实现按时间范围检索的数据结构。2.7 键遍历全库键遍历用 KEYS 会阻塞服务Redis 提供了渐进式遍历命令 SCAN。它每次返回一部分键通过游标继续迭代多个客户端可以分头并行扫描过程中不会阻塞主线程127.0.0.1:6379 scan 0 1) 1024 # 下一次遍历的游标 2) 1) key:1 2) key:12 ... 127.0.0.1:6379 scan 1024 1) 0 # 游标归零遍历结束 2) 1) key:24SCAN 也提供 MATCH 模式与 COUNT 提示参数用于按模式过滤和调整单次返回数量。数据库管理方面Redis 默认有 16 个逻辑库0-15用 SELECT 切换用 FLUSHDB 清空当前库、FLUSHALL 清空全部库。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →