第2章 API的理解和使用

2.1全局命令

  1. 查看所有键keys *
  2. 键总数 dbsize
  3. 检查键是否存在 exists key
  4. 删除键 del key [key …]
  5. 键过期 expire key seconds
  6. 键的数据结构类型type key
  7. 键重命名 rename key newkey
  8. 随机返回一个键 randomkey
  9. 键过期expire key seconds:键在seconds秒后过期
  10. ·expireat key timestamp:键在秒级时间戳timestamp后过期。ttl命令和pttl都可以查询键的剩余过期时间,但是pttl精度更高可以达到
    毫秒级别,有3种返回值:
    ·大于等于0的整数:键剩余的过期时间(ttl是秒,pttl是毫秒)。
    ·-1:键没有设置过期时间。
    ·-2:键不存在。
  11. 毫秒级的过期方案
  1. pexpire key milliseconds:键在milliseconds毫秒后过期
  2. pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过
    期但
    无论是使用过期时间还是时间戳,秒级还是毫秒级,在Redis内部最
    终使用的都是pexpireat。
    1)如果expire key的键不存在,返回结果为0:
    2)如果过期时间为负值,键会立即被删除,犹如使用del命令一样
    3)persist命令可以将键的过期时间清除:
    4)对于字符串类型键,执行set命令会去掉过期时间,这个问题很容易
    在开发中被忽视
  1. 迁移键
    (1)move key db
    (2)dump+restore
    (3)migrate
  2. flushdb/flushall
    flushdb/flushall命令用于清除数据库,两者的区别的是flushdb只清除当
    前数据库,flushall会清除所有数据库
  3. 切换数据库
    select dbIndex
    Redis默认配置中是有16个数据库: databases 16

2.2 数据结构和内部编码

redis 客户端套接字在事件Loop中可写_Redis


redis 客户端套接字在事件Loop中可写_客户端_02

单线程架构:Redis使用了单线程架构I/O多路复用模型来实现高性能的内存数据库,每次客户端调用都经历了发送命令执行命令返回结果三个过程,因为Redis是单线程来处理命令的,所以一
条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行。不会产生并发问题

2.为什么单线程还能这么快

第一,纯内存访问,Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,这是Redis达到每秒万级别访问的重要基础。
第二,非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间

第三,单线程避免了线程切换和竞态产生的消耗

2.3 字符串

2.3.1 字符串类型的值实际可以是
  1. 字符串(简单的字符串、复杂的字符串(例如JSON、XML))
  2. 数字(整数、浮点数)
  3. 甚至是二进制(图片、音频、视频),但是值最大不能超过512MB。
2.3.2 命令

常用命令:

  1. 设置值 set key value [ex seconds] [px milliseconds] [nx|xx]set命令有几个选项:
    ·ex seconds:为键设置秒级过期时间。
    ·px milliseconds:为键设置毫秒级过期时间。
    ·nx:键必须不存在,才可以设置成功,用于添加。
    ·xx:与nx相反,键必须存在,才可以设置成功,用于更新。
    除了set选项,Redis还提供了setex和setnx两个命令:
    setex key seconds value
    setnx key value
  2. 获取值get key
  3. 批量设置值 mset key value [key value …]
  4. 批量获取值 mget key [key …]
  5. 计算 incr key
  6. decr key(自减)
    incrby key increment (自增指定数字)
    decrby key decrement (自减指定数字)
    incrbyfloat key increment (自增浮点数)
  7. 追加值 append key value
  8. 字符串长度 strlen key
  9. 设置并返回原值 getset key value
  10. 设置指定位置的字符 setrange key offeset value
  11. 获取部分字符串 getrange key start end
    批量操作命令可以有效提高开发效率,假如没有mget这样的命令,要执行n次get命令需要消耗多余的网络时间

Redis可以支撑每秒数万的读写操作,但是这指的是Redis服务端的处理能力,对于客户端来说,一次命令除了命令时间还是有网络时间,假设网络时间为1毫秒,命令时间为0.1毫秒(按照每秒处理1万条命令算),那么执行1000次get命令和1次mget命令的区别,因为Redis的处理能力已经足够高,对于开发人员来说,网络可能会成为性能的瓶颈。

2.3.3 内部编码

字符串类型的内部编码有3种:

  1. int:8个字节的长整型。
  2. embstr:小于等于39个字节的字符串。
  3. raw:大于39个字节的字符串。
    Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
2.3.4 典型使用场景
  1. 缓存功能
    其中Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用
  2. 计数
    使用Redis作为视频播放数计数的基础组件,用户每
    播放一次视频,相应的视频播放数就会自增1:
  3. 共享Session
    可以使用Redis将用户的Session进行集中管理
  4. 限速
    但是为了短信接口不被频繁访问,会限制用
    户每分钟获取验证码的频率,例如一分钟不能超过5次

2.4哈希hash

2.4.1 命令
  1. 设置值 hset key field value
  2. 获取值 hget key field
  3. 删除field hdel key field [field …]
  4. 计算field个数 hlen key
  5. 批量设置或获取field-value
    hmget key field [field …]
    hmset key field value [field value …]
  6. 判断field是否存在
    hexists key field
  7. 获取所有field
    hkeys key
  8. 获取所有value
    hvals key
  9. 获取所有的field-value
    hgetall key
  10. 计算value的字符串长度
    hstrlen key field
2.4.2 内部编码

哈希类型的内部编码有两种:

  1. ziplist(压缩列表):当哈希类型元素个数小于hash-max-ziplist-entries配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64
    字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
  2. hashtable(哈希表):当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
  3. 1)当field个数比较少且没有大的value时,内部编码为ziplist:
    2.1)当有value大于64字节,内部编码会由ziplist变为hashtable:
    2.2)当field个数超过512,内部编码也会由ziplist变为hashtable:
2.4.3 使用场景

1.法缓存用户信息 每个用户属性使用一对field-value,但是只用一个键保存。

redis 客户端套接字在事件Loop中可写_客户端_03

2.5列表list

2.5.1 命令

(1)从右边插入元素 rpush key value [value …]

(2)从左边插入元素 lpush key value [value …]

(3)向某个元素前或者后插入元素 linsert key before|after pivot value

2.查找

(1)获取指定范围内的元素列表 lrange key start end

(2)获取列表指定索引下标的元素 lindex key index

(3)获取列表长度 llen key

3.删除

(1)从列表左侧弹出元素 lpop key

(2)从列表右侧弹出 rpop key

(3)删除指定元素 lrem key count valuek

·count>0,从左到右,删除最多count个元素。
·count<0,从右到左,删除最多count绝对值个元素。
·count=0,删除所有。

(4)按照索引范围修剪列表 ltrim key start end

4.修改

修改指定索引下标的元素 lset key index newValue

5.阻塞操作

阻塞式弹出如下:

blpop key [key …] timeout
brpop key [key …] timeout

1)列表为空:如果timeout=3,那么客户端要等到3秒后返回,如果
timeout=0,那么客户端一直阻塞等下去:

127.0.0.1:6379> brpop list:test 3
(nil)
(3.10s)
127.0.0.1:6379> brpop list:test 0
…阻塞…

如果此期间添加了数据element1,客户端立即返回:

127.0.0.1:6379> brpop list:test 3

  1. “list:test”
  2. “element1”
    (2.06s)

2)列表不为空:客户端会立即返回。

127.0.0.1:6379> brpop list:test 0

  1. “list:test”
  2. “element1”

127.0.0.1:6379> brpop list:1 list:2 list:3 0
…阻塞…

此时另一个客户端分别向list:2和list:3插入元素:

client-lpush> lpush list:2 element2
(integer) 1
client-lpush> lpush list:3 element3
(integer) 1

客户端会立即返回list:2中的element2,因为list:2最先有可以弹出的
元素:

127.0.0.1:6379> brpop list:1 list:2 list:3 0

  1. “list:2”
  2. “element2_1”

第二点,如果多个客户端对同一个键执行brpop,那么最先执行brpop命
令的客户端可以获取到弹出的值。

2.5.2 内部编码
  1. ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置
    (默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时
    (默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使
    用。
  2. linkedlist(链表):当列表类型无法满足ziplist的条件时,Redis会使用
    linkedlist作为列表的内部实现。
2.5.3 使用场景
  1. 消息队列
    Redis的lpush+brpop命令组合即可实现阻塞队列,生产
    者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令
    阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用
    性。
  2. 文章列表
    每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以
    考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
    1)每篇文章使用哈希结构存储,例如每篇文章有3个属性title、timestamp、content:
    hmset acticle:1 title xx timestamp 1476536196 content xxxx…
    hmset acticle:k title yy timestamp 1476512536 content yyyy…
    2)向用户文章列表添加文章,
    user:{id}:articles作为用户文章列表的
    键:
    lpush user:1:acticles article:1 article3…
    lpush user:k:acticles article:5…
    3)分页获取用户文章列表,例如下面伪代码获取用户id=1的前10篇文章:
    articles = lrange user:1:articles 0 9
    for article in {articles}
    hgetall {article}

2.6集合set

redis 客户端套接字在事件Loop中可写_Redis_04

2.6.1命令

1.集合内操作

(1)添加元素 sadd key element [element …]

(2)删除元素 srem key element [element …]

(3)计算元素个数 scard key

(4)判断元素是否在集合中 sismember key element

(5)随机从集合返回指定个数元素 srandmember key [count]

(6)从集合随机弹出元素 spop key

(7)获取所有元素 smembers key

2.集合间操作

(1)求多个集合的交集 sinter key [key …]

(2)求多个集合的并集 suinon key [key …]

(3)求多个集合的差集 sdiff key [key …]

(4)将交集、并集、差集的结果保存

sinterstore destination key [key …]
suionstore destination key [key …]
sdiffstore destination key [key …]

2.6.2 内部编码

集合类型的内部编码有两种:

  1. intset(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实
    现,从而减少内存的使用。
  2. hashtable(哈希表):当集合类型无法满足intset的条件时,Redis会使
    用hashtable作为集合的内部实现
2.6.3 使用场景

(1)给用户添加标签

sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5

sadd user:k:tags tag1 tag2 tag4

(2)给标签添加用户

sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3

sadd tagk:users user:1 user:2

(3)删除用户下的标签

srem user:1:tags tag1 tag5

(4)删除标签下的用户。

srem tag1:users user:1
srem tag5:users user:1

(3)和(4)也是尽量放在一个事务执行。
(5)计算用户共同感兴趣的标签

sinter user:1:tags user:2:tags

2.7 有序集合

redis 客户端套接字在事件Loop中可写_Redis_05

2.7.1命令

1.集合内

(1)添加成员 zadd key score member [score member …]

(2)计算成员个数 zcard key

(3)计算某个成员的分数 zscore key member

(4)计算成员的排名 zrank是从分数从低到高返回排名,zrevrank反之

zrank key member
zrevrank key member

(5)删除成员 zrem key member [member …]

(6)增加成员的分数 zincrby key increment member

(7)返回指定排名范围的成员 如果加上withscores选项,同时会返回成员的分数
zrange key start end [withscores]
zrevrange key start end [withscores]

(8)返回指定分数范围的成员
zrangebyscore key min max [withscores] [limit offset count]
zrevrangebyscore key max min [withscores] [limit offset count]
(9)返回指定分数范围成员个数
zcount key min max
(10)删除指定排名内的升序元素
zremrangebyrank key start end
(11)删除指定分数范围的成员
zremrangebyscore key min max

2.7.2 集合间的操作

(1)交集
zinterstore destination numkeys key [key …] [weights weight [weight …]]
[aggregate sum|min|max]
destination:交集计算结果保存到这个键。
·numkeys:需要做交集计算键的个数。
·key[key…]:需要做交集计算的键。

2.7.3 内部编码

有序集合类型的内部编码有两种:

  1. ·ziplist(压缩列表):当有序集合的元素个数小于zset-max-ziplistentries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配
    置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist
    可以有效减少内存的使用。
    当元素个数较少且每个元素较小时,内部编码为skiplist:
    当元素个数超过128个,内部编码变为ziplist:
    当某个元素大于64字节时,内部编码也会变为hashtable
  2. ·skiplist(跳跃表):当ziplist条件不满足时,有序集合会使用skiplist作
    为内部实现,因为此时ziplist的读写效率会下降
2.7.4 使用场景

(1)添加用户赞数 zadd user:ranking:2016_03_15 mike 3

(2)取消用户赞数 zrem user:ranking:2016_03_15 mike

(3)展示获取赞数最多的十个用户 zrevrangebyrank user:ranking:2016_03_15 0 9

(4)展示用户信息以及用户分数
hgetall user:info:tom
zscore user:ranking:2016_03_15 mike
zrank user:ranking:2016_03_15 mike

本章重点回顾

  1. Redis提供5种数据结构,每种数据结构都有多种内部编码实现。
  2. 纯内存存储、IO多路复用技术、单线程架构是造就Redis高性能的三个因素。
  3. 由于Redis的单线程架构,所以需要每个命令能被快速执行完,否则会存在阻塞Redis的可能,理解Redis单线程命令处理机制是开发和运维Redis的核心之一。
  4. 批量操作(例如mget、mset、hmset等)能够有效提高命令执行的效率,但要注意每次批量操作的个数和字节数。
  5. 了解每个命令的时间复杂度在开发中至关重要,例如在使用keys、hgetall、smembers、zrange等时间复杂度较高的命令时,需要考虑数据规模对于Redis的影响。
  6. persist命令可以删除任意类型键的过期时间,但是set命令也会删除字符串类型键的过期时间,这在开发时容易被忽视。
  7. move、dump+restore、migrate是Redis发展过程中三种迁移键的方式,其中move命令基本废弃,migrate命令用原子性的方式实现了dump+restore,并且支持批量操作,是Redis Cluster实现水平扩容的重要工具。
  8. scan命令可以解决keys命令可能带来的阻塞问题,同时Redis还提供了hscan、sscan、zscan渐进式地遍历hash、set、zset。