Redis 开发与运维学习笔记(一)

Posted by Waynerv on

category: 数据库

Tags: Redis 读书笔记

1 Redis 简介

Redis 特性

  1. 速度快:
    • 数据在内存中存取
    • C语言实现
    • 单线程架构
    • 源代码写的好
  2. 基于键值对的数据结构服务器。Redis的全称是REmote Dictionary Server,它主要提供了5种数据结构:字符串、哈希、列表、集合、有序集合,还有Bitmaps、HyperLogLog、GEO等功能。
  3. 功能丰富:
    • 键过期功能,实现缓存。
    • 发布订阅,实现消息系统。
    • Lua脚本,创造新的Redis命令。
    • 简单的事务功能,一定程度保证事务特性。
    • 流水线Pipeline功能,批量传递命令。
  4. 简单稳定:源码少易理解,单线程模型,不依赖操作系统类库。
  5. 客户端语言多。
  6. 持久化:RDB和AOF两种持久化方式将数据保存到硬盘。
  7. 主从复制:复制功能是分布式Redis的基础。(类似于MongoDB的副本集)
  8. 高可用和分布式:故障发现和自动转移,Cluster集群。

Redis 使用场景

适合场景

  1. 网站的缓存。提供了键值过期时间设置,以及内存的控制策略。
  2. 排行榜系统。通过列表和有序集合数据结构构建各种排行榜。
  3. 计数器应用。计数操作并发量大,Redis天然支持计数功能且性能出色。
  4. 社交网络。保存点赞、粉丝、推送、下拉刷新等高访问量类型的数据。
  5. 消息队列系统。提供发布订阅功能和阻塞队列功能。(和专业的消息队列有差距)

不适合场景

  1. 不适合保存大规模数据,经济成本高。
  2. 加速需要频繁操作的热数据,保存冷数据浪费内存。

使用建议

理解原理,阅读源码。

安装并启动 Redis

安装

使用下载源码、解压并编译的方式安装Redis。官网步骤+额外步骤:

$ wget http://download.redis.io/releases/redis-5.0.3.tar.gz
$ tar xzf redis-5.0.3.tar.gz
$ ln -s redis-5.0.3 redis # 建立redis目录的软连接,方便升级版本
$ cd redis-5.0.3
$ make
$ sudo make install # 将Redis的相关运行文件放到/usr/local/bin/下,可以在任意目录下执行Redis的命令

查看Redis版本:

$ redis-cli -v
redis-cli 5.0.3

配置、启动与关闭

  1. 启动Redis(注意启动时的权限,否则无法生成持久化文件关闭):
    • redis-server 以默认配置启动,生产环境不推荐。
    • redis-server --port 6380 带自定义配置参数启动,但无法保存配置。
    • (推荐)redis-server xx/xx/redis.conf 以指定配置文件启动,配置文件可以使用Redis目录下的redis.conf默认配置文件作为模板。
  2. Redis命令客户端:通过redis-cli -h {host} -p {port}的方式连接到Redis服务器(类似于mysql)。如果未加参数将连接默认位置的实例。
  3. 停止Redis服务:
    • 使用redis-cli shutdown关闭默认位置(127.0.0.1,6379端口)上的Redis服务。
    • 还可以使用kill+进程号的方式关闭redis服务。但不要使用Kill 9方式关闭redis进程,这样redis不会进行持久化操作,除此之外,还会造成缓冲区等资源不能优雅关闭,极端情况下会造成AOF和复制丢失数据的情况。
    • shutdown还有一个参数,代表关闭redis服务前是否产生持久化文件:shutdown save|nosave

2 API的理解和使用

预备知识

全局命令

  1. 查看所有键:keys *。将遍历所有键,保存大量键时线上环境禁止使用。
  2. 返回键总数:dbsize。直接获取Redis内置的键总数变量,时间复杂度为O(1)。
  3. 检查键是否存在:exists key
  4. 删除键:del key [key...]。通用命令,可删除任何类型键。
  5. 键过期:expire key seconds。使用ttl命令返回键的剩余过期时间。
  6. 返回键的数据结构类型:type key。不存在则返回none

数据结构和内部编码

type命令实际返回的就是当前键的数据结构类型它们分别是: string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)。

每种数据结构都有两种以上的内部编码实现,Redis会在合适的场景选择或转换到合适的内部编码。这个转换过程是不可逆的,转换规则只能从小内存编码向大内存编码转换。通过object encoding key命令查询键的内部编码。

好处:

  • 可以改进内部编码,而对外的数据结构和命令没有影响。
  • 多种内部编码实现可以在不同场景下发挥各自的优势。

单线程架构

单线程指的是Redis的网络请求模块使用了一个线程处理所有网络请求,其他模块仍用了多个线程。

Redis是单线程来处理命令的,一条命令从客户端达到服务端不会立刻被执行,所有命令都会进入一个队列中,然后逐个被执行。不会有两条命令被同时执行,不会产生并发问题,这就是Redis单线程的基本模型。

Redis为何能实现单线程模型高性能:

  • 纯内存存储。
  • 非阻塞I/O,Redis使用IO多路复用技术(epoll)和自身事件模型。
  • 单线程避免了线程切换和竞态产生的消耗。

对于服务端开发来说锁和线程切换通常是性能杀手。

如果某个命令执行过长,会造成其他命令的阻塞,对于Redis这种高性能的服务来说是致命的,所以Redis是面向快速执行场景的数据库。

字符串(string)

键都是字符串类型,字符串类型是Redis最基础的数据结构,其他几种数据结构都是在字符串类型基础上构建的。

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

命令

常用命令
  1. 设置值:

    set key value [ex seconds] [px milliseconds] [nx|xx]
    
    • ex seconds: 为键设置秒级过期时间。
    • px milliseconds: 为键设置毫秒级过期时间。
    • nx: 键必须不存在,才可以设置成功,用于添加。
    • xx: 与nx相反,键必须存在,才可以设置成功,用于更新。

    还提供了setex和setnx两个命令,作用和ex和nx选项是一样的。

    注意:setex的使用方式为setex <key> <seconds> value

  2. 获取值:

    get key
    

    如果要获取的键不存在,则返回nil(空)。

  3. 批量设置值:

    mset key value [key value ...]
    

    一次性设置多个键值对。

  4. 批量获取值:

    mget key [key ...]
    

    批量操作命令可以有效提高开发效率,但数量过多可能造成Redis阻塞或者网络拥塞。

  5. 自增计数:

    incr key
    

    对值做自增操作,值必须为整数。若键不存在,将创建键并按照值为0自增,返回结果为1。

    incrby key increment
    

    除了incr命令,Redis提供了decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数)等命令。

    因为是单线程架构,Redis中的计数功能不需要使用CAS机制来实现计数功能,没有额外的CPU开销。

不常用命令
  1. 追加值:

    append key value
    

    向字符串尾部追加值。

  2. 返回字符串长度:

    strlen key
    

    每个中文占用3个字节。

  3. 设置并返回原值:

    getset key value
    

    相当于“get then set”。如果原来不存在则返回空值。

  4. 设置指定位置的字符:

    setrange key offset value
    

    将key的原值offset位置的字符设置为value。

  5. 获取部分字符串

    getrange key start end
    

    start和end分别是开始和结束的偏移量,偏移量从0开始计算。

时间复杂度

1553052998478

内部编码

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

  • int: 8个字节的长整型。
  • embstr: 小于等于39个字节的字符串。
  • raw: 大于39个字节的字符串。

Redis会根据当前值的类型和长度决定使用哪种内部编码实现。

典型使用场景

缓存功能

1553053035687

典型的缓存使用场景是Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取,由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。

Redis键名的命名惯例:业务名:对象名:id:[属性],注意适当减少键的长度。

伪代码:

UserInfo getUserInfo(long id){
    // 定义键
    userRedisKey = "user:info:" + id
    // 从Redis获取值
    value = redis.get(userRedisKey);
    UserInfo userInfo;
    if (value != null) {
        // 将值进行返回序列化为userInfo
        userInfo = deserialize(value);
    } else {
        // 从MySQL获取用户信息
        userInfo = mysql.get(id);
        if (userInfo != null)
            // 将userInfo序列化并存入Redis,设置过期时间
            redis.setex(userRedisKey, 3600, serialize(userInfo));
        }
    // 返回结果
    return userInfo;
}
计数

实现快速计数,查询缓存的功能。

伪代码(视频播放数计数):

long incrVideoCounter(long id) {
    key = "video:playCount:" + id;
    return redis.incr(key);
}
共享Session

使用Redis集中管理分布式Web服务器中的用户Session,每次用户更新或者查询登录信息都直接从Redis中集中获取。避免用户访问均衡到不同服务器后无法获取Session。

限速

限制用户每分钟获取验证码的频率,例如一分钟(通过过期时间设置)不能超过5次(判断自增值):

phoneNum = "138xxxxxxxx";
key = "shortMsg:limit:" + phoneNum;
// SET key value EX 60 NX
isExists = redis.set(key,1,"EX 60","NX");
if(isExists != null || redis.incr(key) <=5){
    // 通过
}else{
    // 限速
}

哈希(hash)

在Redis中,哈希类型是指键值本身又是一个键值对结构,形如value = {{field1, value1}, ...{fieldN, valueN}}。

命令

  1. 设置值:

    hset key field value
    

    示例:

    hset user:1 name tom
    

    hsetnx命令作用域为键值中的field,即指定field不存在才设置。

  2. 获取值:

    hget key field
    

    如果键或field不存在,会返回nil。

  3. 删除field:

    hdel key field [field ...]
    
    

    hdel会删除一个或多个field,返回结果为成功删除field的个数。

  4. 计算field个数:

    hlen key
    
    
  5. 批量获取field或设置field-value:

    hmget key field [field ...]
    hmset key field value [field value ...]
    
    
  6. 判断field是否存在:

    hexists key field
    
    

    存在返回结果为1,不存在返回0.

  7. 获取所有field:

    hkeys key
    
    

    返回指定哈希键所有的field。(理解为hfields)

  8. 获取所有value:

    hvals key
    
    
  9. 获取所有的field-value对:

    hgetall key
    
    

    如果哈希元素个数比较多,会存在阻塞Redis的可能。如果一定要获取全部field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型。

  10. hincrby, hincrbyfloat:

    hincrby key field increment
    hincrbyfloat key field increment
    
    

    hincrby和hincrbyfloat,就像incrby和incrbyfloat命令一样,但是它们的作用域是filed。

  11. 计算value的字符串长度:

     hstrlen key field
    
    
  12. 命令时间复杂度:

    1553053092365

    1553053098758

内部编码

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

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

使用场景

将每个用户的id定义为键后缀, 多对field-value对应每个用户的属性来保存用户信息,相比序列化更直观,操作更便捷。伪代码:

UserInfo getUserInfo(long id){
    // 用户 id 作为 key 后缀
    userRedisKey = "user:info:" + id;
    // 使用 hgetall 获取所有用户信息映射关系
    userInfoMap = redis.hgetAll(userRedisKey);
    UserInfo userInfo;
    if (userInfoMap != null) {
        // 将映射关系转换为 UserInfo
        userInfo = transferMapToUserInfo(userInfoMap);
    } else {
        // 从 MySQL 中获取用户信息
        userInfo = mysql.get(id);
        // 将 userInfo 变为映射关系使用 hmset 保存到 Redis 中
        redis.hmset(userRedisKey, transferUserInfoToMap(userInfo));
        // 添加过期时间
        redis.expire(userRedisKey, 3600);
        }
    return userInfo;
}

哈希类型和关系型数据库的不同之处:

  • 哈希类型是稀疏的,每个键可以有不同的field,而关系型数据库是完全结构化的。
  • 关系型数据库可以做复杂的关系查询,而Redis去模拟关系型复杂查询开发困难,维护成本高

缓存用户信息的三种方案:

  • 原生字符串类型: 每个属性一个键。简单直观但内存占用量大且信息内聚性差。
  • 序列化字符串类型: 将用户信息序列化后用一个键保存。内存使用效率高,但每次更新都需要序列化及反序列化。
  • 哈希类型: 每个用户属性使用一对field-value,但是只用一个键保存。简单直观,但数据规模大时会占用较多内存。

列表(list)

列表(list)类型是用来存储多个有序的字符串,列表中的每个字符串称为元素(element),一个列表最多可以存储2^32^ -1个元素。

在Redis中,可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色。

命令

操作类型 操作
添加 rpush lpush linsert
lrange lindex llen
删除 lpop rpop lrem ltrim
修改 lset
阻塞操作 blpop brpop
添加元素
  1. 从右边插入元素:

    rpush key value [value ...]
    

    lrange key 0 -1命令可以从左到右获取列表的所有元素。

  2. 从左边插入元素:

    lpush key value [value ...]
    

    插入多个值时注意插入顺序,最后一个value将插入在最左侧。

  3. 向某个元素前或后插入元素:

    linsert key before|after pivot value
    

    linsert命令会从列表中找到等于pivot的元素,在其前(before)或者后(after)插入一个新的元素value。

查找元素
  1. 获取指定范围内的元素列表:

    lrange key start end
    

    lrange操作会获取列表指定索引范围所有的元素。但注意lrange中的end选项包含了自身,这个和很多编程语言不包含end不太相同。

  2. 获取列表指定索引下标的元素:

    lindex key index
    
  3. 获取列表长度:

    llen key
    
    
删除元素
  1. 从列表左侧弹出元素:

    lpop key
    
    

    将列表最左侧的元素弹出。

  2. 从列表右侧弹出元素:

    rpop key
    
    

    将列表最右侧的元素弹出。

  3. 删除指定元素:

    lrem key count value
    
    

    从列表中找到等于value的元素进行删除,根据count的不同分为三种情况:

    • count>0,从左到右,删除最多count个元素。
    • count<0,从右到左,删除最多count绝对值个元素。
    • count=0,删除所有。
  4. 按照索引范围修剪列表:

    ltrim key start end
    
    

    只保留列表key第start个到第end个元素

修改元素
  1. 修改指定索引下标的元素:

    lset key index newValue
    
    

    将列表key中的第index个元素设置为newValue。

阻塞操作

阻塞式弹出:

blpop key [key ...] timeout
brpop key [key ...] timeout

blpop和brpop是lpop和rpop的阻塞版本,key代表列表的键,timeout表示阻塞时间(秒):

  • 列表为空(或列表未创建): 如果timeout=3,那么客户端要等到3秒后返回,如果timeout=0,那么客户端一直阻塞等下去。如果此期间添加了数据element1,客户端立即返回。
  • 列表不为空: 客户端会立即返回。
  • 当有元素弹出时会返回一个双元素的多批量值,其中第一个元素是弹出元素的 key,第二个元素是 value
  • blpop从非空列表左侧弹出元素,brpop从右侧弹出元素。

使用时需注意:

  • 如果是多个键,那么brpop(或blpop)会从左至右遍历,一旦有一个键能弹出元素,客户端立即返回。
  • 如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值(其余客户端继续阻塞)。
时间复杂度

 1553053131963

内部编码

列表类型的内部编码有三种。

  • ziplist(压缩列表):当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
  • linkedlist(链表): 当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
  • quicklist:以一个ziplist为节点的linkedlist,它结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现。

使用场景

消息队列

Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lpush从列表左侧插入元素。多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。

文章列表

使用列表类型保存和获取文章列表,每篇文章使用哈希结构存储,向用户文章列表添加文章,通过列表的索引可以分页获取用户文章列表,然后再获取文章内容。

问题:

  • 获取文章较多时需要执行多次hgetall操作来获取文章内容。考虑使用管道或将文章内容序列化。
  • lrange命令在列表较大时获取中间范围的元素性能会变差,考虑使用quicklist。
其他应用
  • lpush+lpop=Stack(栈)
  • lpush+rpop=Queue(队列)
  • lpush+ltrim=Capped Collection(有限集合)
  • lpush+brpop=Message Queue(消息队列)

集合(set)

集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。

一个集合最多可以存储2^32^ -1个元素,Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。

命令

集合内操作
  1. 添加元素:

    sadd key element [element ...]
    

    返回结果为添加成功的元素个数。

  2. 删除元素:

    srem key element [element ...]
    

    返回结果为成功删除元素个数。

  3. 计算元素个数:

    scard key
    

    scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用Redis内部的变量。

  4. 判断元素是否在集合中:

    sismember key element
    

    在集合内返回1,反之返回0。

  5. 随机从集合返回指定个数元素:

    srandmember key [count]
    

    [count]是可选参数,如果不写默认为1。

  6. 从集合随机弹出元素:

    spop key [count]
    

    spop操作可以从集合中随机弹出一个元素,spop命令执行后,元素会从集合中删除,而srandmember不会。

  7. 获取所有元素:

    smembers key
    

    获取集合所有元素,并且返回结果是无序的。

    smembers和lrange、hgetall都属于比较重的命令,如果元素过多存在阻塞Redis的可能性,这时候可以使用sscan来完成。

集合间操作
  1. 求多个集合的交集:

    sinter key [key ...]
    
  2. 求多个集合的并集:

    sunion key [key ...]
    
  3. 求多个集合的差集:

    sdiff key [key ...]
    
  4. 将交集、并集、差集的结果保存:

    sinterstore destination key [key ...]
    suionstore destination key [key ...]
    sdiffstore destination key [key ...]
    

    Redis提供了上面三个命令(原命令+store)将集合间交集、并集、差集的结果保存在destination key中。destination key本身也是集合类型。

时间复杂度

1553053168498

内部编码

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

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

使用场景

集合类型比较典型的使用场景是标签(tag)。使用集合类型实现标签功能的若干功能:

  1. 给用户添加标签:sadd user:1:tags tag1 tag2 tag5
  2. 给标签添加用户:sadd tag1:users user:1 user:3
  3. 删除用户下的标签:srem user:1:tags tag1 tag5
  4. 删除标签下的用户:srem tag1:users user:1
  5. 计算用户共同感兴趣的标签:sinter user:1:tags user:2:tags

注意用户和标签的关系维护应该在一个事务内执行,防止部分命令失败造成数据不一致。

常用应用场景:

  • sadd=Tagging(标签)
  • spop/srandmember=Random item(生成随机数,比如抽奖)
  • sadd+sinter=Social Graph(分析社交需求)

有序集合(sorted set)

有序集合中不能有重复的成员,但有序集合中的元素可以排序。它给每个元素设置一个分数(score)作为排序的依据,提供了获取指定分数和元素范围查询、计算成员排名等功能。

有序集合中的元素不能重复,但是score可以重复。

命令

集合内操作
  1. 添加成员:

    zadd key score member [score member ...]
    

    nx、xx、ch、incr四个选项:

    • nx: member必须不存在,才可以设置成功,用于添加。
    • xx: member必须存在,才可以设置成功,用于更新。
    • ch: 返回此次操作后,有序集合元素和分数发生变化的个数
    • incr: 对score做增加,相当于后面介绍的zincrby。

    有序集合相比集合提供了排序字段,但是也产生了代价,zadd的时间复杂度为O(log(n)),sadd的时间复杂度为O(1)。

  2. 计算成员个数:

    zcard key
    

    zcard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用Redis内部的变量。

  3. 返回某个成员的分数值:

    zscore key member
    

    如果成员不在集合中或集合不存在则返回nil。

  4. 计算成员的排名:

    zrank key member
    zrevrank key member
    

    zrank是从分数从低到高返回排名,zrevrank反之。

  5. 删除成员:

    zrem key member [member ...]
    

    返回结果为成功删除的个数。

  6. 增加成员的分数:

    zincrby key increment member
    
  7. 返回指定排名范围的成员:

    zrange key start end [withscores]
    zrevrange key start end [withscores]
    

    zrange是从低到高返回,zrevrange反之。如果加上withscores选项,同时会返回成员的分数。

  8. 返回指定分数范围的成员:

    zrangebyscore key min max [withscores] [limit offset count]
    zrevrangebyscore key max min [withscores] [limit offset count]
    

    zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。withscores选项会同时返回每个成员的分数,[limit offset count]选项可以限制输出的起始位置和个数。

    min和max还支持开区间(小括号)和闭区间(中括号),-inf和+inf分别代表无限小和无限大。

  9. 返回指定分数范围成员个数:

    zcount key min max
    
  10. 删除指定排名内的升序元素:

    、 zremrangebyrank key start end

  11. 删除指定分数范围的成员:

     zremrangebyscore key min max
    
集合间的操作
  1. 求多个集合的交集:

    zinterstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
    
    • destination: 交集计算结果保存到这个键。
    • numkeys: 需要做交集计算键的个数
    • key[key...]: 需要做交集计算的键。
    • weights weight[weight...]: 每个键的权重,在做交集计算时,每个键中的每个member会将自己分数乘以这个权重,每个键的权重默认是1。
    • aggregate sum|min|max:计算成员交集后,分值可以按照sum(和)、min(最小值)、max(最大值)做汇总,默认值是sum。
  2. 求多个集合的并集:

    zunionstore destination numkeys key [key ...] [weights weight [weight ...]] [aggregate sum|min|max]
    

    该命令的所有参数和zinterstore是一致的,但是做并集计算。

时间复杂度

1553053196554

内部编码

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

  • ziplist(压缩列表): 当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。
  • skiplist(跳跃表): 当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。

使用场景

有序集合比较典型的使用场景就是排行榜系统。实现使用赞数维度的视频排行榜:

  1. 添加用户赞数:

    zadd user:ranking:2016_03_15 3 mike
    

    之后使用zincrby增加赞数。

  2. 取消用户赞数:

    zrem user:ranking:2016_03_15 mike
    
  3. 展示获取赞数最多的十个用户:

    zrevrangebyrank user:ranking:2016_03_15 0 9
    
  4. 展示用户信息以及用户分数及排名:

    hgetall user:info:mike
    zscore user:ranking:2016_03_15 mike
    zrank user:ranking:2016_03_15 mike
    

    用户信息保存在哈希类型中。

键管理

按照单个键、遍历键、数据库管理三个维度对一些通用命令进行介绍。

单个键管理

键重命名
rename key newkey

如果在rename之前,键newkey已经存在,那么它的值也将被覆盖。

renamenx 命令,确保只有newKey不存在时候才会重命名。

由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性。

随机返回一个键
randomkey
键过期

除了expire、ttl命令以外,Redis还提供了expireat、pexpire、pexpireat、pttl、persist等一系列命令。

expireat命令可以设置键的秒级过期时间戳:

  • expire key seconds: 键在seconds秒后过期。
  • expireat key timestamp: 键在秒级时间戳timestamp后过期。

ttl命令和pttl都可以查询键的剩余过期时间,但是pttl精度更高可以达到毫秒级别。

pexpireat毫秒级过期方案:

  • pexpire key milliseconds: 键在milliseconds毫秒后过期。
  • pexpireat key milliseconds-timestamp:键在毫秒级时间戳timestamp后过 期。

无论是使用过期时间还是时间戳,秒级还是毫秒级,在Redis内部最终使用的都是pexpireat。

注意事项:

  • 如果过期时间为负值,键会立即被删除,犹如使用del命令一样。
  • persist命令可以将键的过期时间清除。
  • 对于字符串类型键,执行set命令会去掉过期时间,导致过期时间失效。
  • Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功能,例如不能对列表类型的一个元素做过期时间设置。
  • setex命令作为set+expire的组合,不但是原子执行,还减少了一次网络通讯的时间。
迁移键
  1. move命令用于在Redis内部进行数据迁移,move key db就是把指定的键从源数据库移动到目标数据库中。不建议在生产环境中使用多数据库功能。

  2. dump+restore可以实现在不同的Redis实例之间进行数据迁移的功能:

    dump key
    restore key ttl serialized-value
    
    • 在源Redis上,dump命令会将键值序列化生成serialized-value,格式采用的是RDB格式。
    • 在目标Redis上,restore命令将上面序列化的值进行复原,其中ttl参 数代表过期时间,如果ttl=0代表没有过期时间。
    • 迁移过程为手动分步操作,因此非原子性。
  3. migrate命令将dump、restore、del三个命令进行组合,从而简化了操作流程。migrate命令具有原子性,并且支持迁移多个键。

    • 整个过程是原子执行的,不需要在多个Redis实例上开启客户端,只需要在源Redis上执行migrate命令即可。
    • migrate命令的数据传输直接在源Redis和目标Redis上完成。
    • 目标Redis完成restore后会发送OK给源Redis,源Redis接收后会根据migrate对应的选项来决定是否在源Redis上删除对应的键。

    命令说明:

    migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key ...]]
    
    • host和port:目标Redis的IP地址和端口。
    • key|"" :此处是要迁移的键,如果当前需要迁移多个键,此处为空字符串""。
    • destination-db: 目标Redis的数据库索引,例如要迁移到0号数据库,这里就写0。
    • timeout: 迁移的超时时间(单位为毫秒),数据传送的时间不能超过这个 timeout 数。
    • [copy]: 如果添加此选项, 迁移后并不删除源键。
    • [replace]: 如果添加此选项, migrate不管目标Redis是否存在该键都会正常迁移进行数据覆盖。
    • [keys key[key...]]: 迁移多个键,例如要迁移key1、key2、key3,此处填写“keys key1 key2 key3”。

遍历键

全量遍历键
keys pattern

pattern使用的是glob风格的通配符:

  • *代表匹配任意字符。
  • ?代表匹配一个字符。
  • []代表匹配部分字符,例如[1, 3]代表匹配1,3,[1-10]代表匹配1到10的任意数字。
  • \x用来转义,例如要匹配星号、问号需要进行转义。

删除所有以video字符串开头的键:

$ redis-cli keys video* | xargs redis-cli del

如果Redis包含了大量的键,执行keys命令很可能会造成Redis阻塞,建议一般不要在生产环境下使用keys命令。

  • 在一个不对外提供服务的Redis从节点上执行,不会阻塞到客户端的请求,但会影响主从复制。
  • 确认键值总数比较少时可以执行该命令。
  • 使用scan命令渐进式的遍历所有键,可以有效防止阻塞。
渐进式遍历

每次执行scan,可以想象成只扫描一个字典中的一部分键,直到将字典中的所有键遍历完毕。

scan cursor [match pattern] [count number]
  • cursor是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束。
  • match pattern是可选参数,它的作用的是做模式的匹配,这点和keys的模式匹配很像。
  • count number是可选参数,它的作用是表明每次要遍历的键个数,默认值是10,此参数可以适当增大。

作用机制类似MongoDB中查询返回的游标。

Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan。

使用sscan删除集合中匹配指定模式的元素:

String key = "myset";
// 定义 pattern
String pattern = "old:user*";
// 游标每次从 0 开始
String cursor = "0";
while (true) {
// 获取扫描结果
    ScanResult scanResult = redis.sscan(key, cursor, pattern);
    List elements = scanResult.getResult();
    if (elements != null && elements.size() > 0) {
        // 批量删除
        redis.srem(key, elements);
    }
    // 获取新的游标
    cursor = scanResult.getStringCursor();
    // 如果游标为 0 表示遍历结束
    if ("0".equals(cursor)) {
        break;
    }
}

如果在scan的过程中如果有键的变化(增加、删除、修改),遍历效果可能会碰到新增的键没有遍历到或者遍历出了重复的键等情况。

数据库管理

切换数据库
select dbIndex

Redis默认配置中是有16个数据库,用数字作为多个数据库的实现。连接Redis时,默认使用的是0号数据库。

Redis是单线程的,因此不建议在一个实例中使用多个数据库,完全可以在一台机器不同端口上部署多个Redis实例。

flushdb/flushall

flushdb/flushall命令用于清除数据库,两者的区别的是flushdb只清除当前数据库,flushall会清除所有数据库。

如果当前数据库键值数量较多,flushdb/flushall存在阻塞Redis的可能性。在使用flushdb/flushall一定要小心谨慎。


注:转载本文,请与作者联系




如果觉得文章对您有价值,请作者喝杯咖啡吧

|
donate qrcode

欢迎通过微信与我联系

wechat qrcode

0 Comments latest

No comments.