【笔记】Redis学习笔记

目录

基础概念

Redis的运行

API的使用

基础概念

1.Redis是一种基于键值对的NoSQL数据库,Redis键值对中的值可以是由string、hash、list、set、zset(有序集合)、Bitmaps(位图)等多种数据结构和算法组成

2.Redis把所有的数据放在内存中,所有读写性能高,还会把数据用快照和日志的形式保存到硬盘上以防丢失

3.Redis用C语言实现,使用单线程架构

Redis的运行

1.因为Redis非常轻量,执行以下命令进行源码安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 下载Redis指定版本的源码压缩包到当前目录
wget http://download.redis.io/releases/redis-3.0.7.tar.gz

# 解压缩
tar xzf redis-3.0.7.tar.gz

# 建立一个redis到redis-3.0.7的软连接
ln -s redis-3.0.7 redis

# 切换工作目录到redis
cd redis

# 编译(事先得先安装gcc)
make

# 安装
make install

# 安装新版本redis后只需将新版本的文件夹与redis建立新的软连接即可
# 将Redis的相关运行文件放到/usr/local/bin/下,这样就可以在任意目录下执行Redis的命令。例如安装后,可以在任何目录执行redis-cli–v查看Redis的版本。

2.redis\src里的redis开头可执行脚本说明

3.redis的启动方式

  • 以默认配置启动:运行redis-server
  • 以自定义配置启动:redis-server加上要修改配置名和值(可以是多对),没有设置的配置将使用默认配置
    1
    2
    redis-server --port 6380
    # 表示以6380端口启动
  • 以配置文件启动(常用):表示以该路径的.conf文件为配置启动
    1
    redis-server /opt/redis/redis.conf

4.连接redis服务器(注意要在启动redis-server的嵌套下才能连接)

  • 交互式:即建立一个持续连接,然后直接在连接上多次使用命令

    1
    2
    3
    4
    5
    输入:redis-cli -h 127.0.0.1 -p 6379
    输出:127.0.0.1:6379> 输入:set hello world
    输出:OK
    输出:127.0.0.1:6379> 输入:get hello
    输出:"world"
  • 命令式:即只执行一个命令后就关闭连接

    1
    2
    3
    输入:redis-cli -h 127.0.0.1 -p 6379 get hello

    输出:"world"
  • -h默认127.0.0.1 -p默认6379

5.中断redis服务:断开与客户端的连接、持久化文件生成

1
2
3
redis-cli shutdown nosave

# nosave可换成save,该参数不写的话默认保存

6.Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功能,例如不能对列表类型的一个元素做过期时间设置。

7.检验某节点是否正常工作

1
2
3
redis-cli -h 127.0.0.1 -p 6379 ping

# 返回PONG则说明正常工作

8.检查某节点的主从关系

1
redis-cli -h 127.0.0.1 -p 6379 info replication

API的使用

1.全局命令

  • 查看所有键(遍历,规模较大时禁用)
    1
    keys *
  • 查看键总数(不用遍历,获取内置的键总数变量)
    1
    dbsize
  • 查询键xx是否存在,存在为1,不存在为0
    1
    exits xx
  • 删除键xx1和xx2(也可删除其它数据结构),会返回删除的数目
    1
    del xx1 xx2
  • 设置键存活时间(默认秒),到时间会自动删除
    1
    expire xx 10
  • 查询键剩余存活时间(-1是永不过期,-2是不存在该键)
    1
    ttl xx
  • 查看键的数据结构(none表示键不存在)
    1
    type xx

2.redis里一样的数据结构可能也会有不同的内部编码,用下述命令来查看具体内部编码

1
object encoding 键名

3.字符串:值可以是字符串、数字、二进制的视频音频(不超过512MB)

  • setex:建立键并设计秒级过期时间

  • setnx:键不存在才可以设置成功

  • setxx:键存在才可以设置成功

    1
    2
    3
    setex 50 hello world
    setnx hello world
    setxx hello world
  • 批量操作

    1
    2
    mset 键1 值1 键2 值2
    mget 键1 键2
  • 自增自减:

    • 值不是整数,返回错误
    • 值是整数,返回自增后的结果
    • 键不存在,按照值为0自增,返回结果为1
      1
      2
      3
      4
      5
      incr 键名
      decr 键名
      incrby 键名 数字
      decrby 键名 数字
      incrbyfloat 键名 浮点数
  • 字符串内部编码类型(redis自动转换)

    • int:8字节的长整数型(长度小于等于19并只含数字)
    • emstr:小于等于39字节的字符串
    • raw:大于39字节的字符串

4.常用方案MySql作存储层,Redis作缓存层。一般业务先去缓存层找,找不到再向MySql发出请求

5.哈希类型:哈希类型的键就相当于一个范围,范围里面的field:值就相当于一个键值对

  • 建立哈希类型
    1
    hset 键名 filed名 值
  • 获取某键的某个filed值
    1
    hget 键名 filed名
  • 删除某键的某个filed
    1
    hdel 键名 filed名
  • 获取某键的filed个数
    1
    hlen 键名
  • 获得所有信息
    1
    2
    3
    4
    5
    6

    # 获得所有filed名
    hvals 键名

    # 获得所有filed名和值
    hgetall 键名
  • 其他操作和字符串一样就是加个h(hmget、hmset等待)
  • 内部编码有两种
    • ziplist压缩列表:小规模数据时(元素个数少,值小)更节省内存
    • hashttable哈希表:不满足使用ziplist时才使用

6.列表类型

  • 列表里元素存储是有序的(即可以通过下标访问)、可重复的
  • push、pop分别是添加删除元素
    1
    2
    3
    4
    5
    6
    7
    8
    9
    lpush 列表名 xx1 xx2
    lpop 列表名
    lrem 列表名 count value

    # rpush、rpop同理
    # lrem会从列表删除count个值为value的元素
    # count>0,从左到右,删除最多count个元素
    # count<0,从右到左,删除最多count绝对值个元素。
    # count=0,删除所有
  • 在某元素前后插入元素
    1
    2
    3
    linsert 列表名 before(after) xx1 xx2

    # 在xx1前(后)插入xx2,会返回一个当前列表长度
  • 获取某范围内的元素
    1
    2
    3
    lrange 列表名 开始下标 结束下标

    # 索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。lrange中的结束选项包含了自身,即[开始下标,结束下标]
  • 获取某下标的元素
    1
    lindex 列表名 下标
  • 获取列表长度
    1
    llen 列表名
  • 修剪列表
    1
    2
    3
    ltrim 列表名 开始下标 结束下标

    # 只保留[开始下标,结束下标]的元素,其余全部删除
  • 修改某个元素的值
    1
    lset 列表名 下标 newValue
  • 阻塞式弹出
    1
    2
    3
    4
    5
    blpop 列表1 列表2 timeout
    brpop 列表1 列表2 timeout

    # 若列表1、2都为空,等待timeout后还为空就返回,timeout=0表示一直阻塞等待
    # 若列表1、2其中有一个能执行pop,立即返回
  • 内部编码
    • ziplist压缩列表:小规模数据时(元素个数少,值小)更节省内存
    • linkedlist链表:不满足使用ziplist时才使用

7.集合类型

  • 集合中的元素存储是无序的、不能有重复元素
  • 添加元素
    1
    sadd key element 
  • 删除元素
    1
    srem key element 
  • 计算集合内元素个数
    1
    scard key
  • 判断元素是否在集合中(在返回1,不在返回0)
    1
    sismember key element
  • 随机从集合中返回元素
    1
    2
    3
    srandmember key [count]

    # count没给的话默认为1
  • 随机从集合中弹出元素(会在集合中删除)
    1
    2
    3
    spop key [count]

    # Redis3.2后支持[count]
  • 获取所有元素
    1
    smembers key
  • 求交集
    1
    sinter key [key ...]
  • 求并集
    1
    suinon key [key ...]
  • 求差集
    1
    sdiff key [key ...]
  • 保存交、并、差集的结果
    1
    2
    3
    4
    5
    6
    sinterstore destination key [key ...]
    suionstore destination key [key ...]
    sdiffstore destination key [key ...]

    # 例sinterstore user:1_2:inter user:1:follow user:2:follow
    # 是将user:1:follow和user:2:follow两个集合的交集结果保存在user:1_2:inter中
  • 内部编码
    • intset整数集合表:小规模数据且都为整数时(元素个数少,值小)更节省内存
    • hashtable哈希表:不满足使用intset时才使用

8.有序集合类型

  • 有序、不能有重复元素,给每个元素设置一个分数(score)作为排序的依据,分数可以重复
  • 添加成员(添加时要设置好分数)
    1
    zadd key score member [score member ...]
  • 删除成员
    1
    zrem key member [member ...]
  • 计算含有的成员个数
    1
    zcard key
  • 计算某成员的分数(成员不存在就返回nil)
    1
    zscore key member
  • 计算成员的排名
    1
    2
    3
    4
    zrank key member
    zrevrank key member

    # zrank是升序,zrevrank是降序
  • 增加成员分数
    1
    2
    3
    4
    5
    zincrby key increment member

    # 例输入zincrby user:ranking 9 tom 返回"260"
    # 表示
    给user有序集合里的tom增加了9分,其分数变为260分
  • 返回指定排名范围的成员
    1
    2
    3
    4
    zrange key start end [withscores]
    zrevrange key start end [withscores]

    # 如果加上withscores关键字还会一同返回分数
  • 删除指定排名范围内的成员(升序)
    1
    zremrangebyrank key start end
  • 返回指定分数范围内的成员个数
    1
    zcount key min max
  • 删除指定分数范围内的成员
    1
    zremrangebyscore key min max
  • 交集
    1
    zinterstore destination numkeys key [key ...] [weights weight [weight ...]][aggregate sum|min|max]
  • 并集
    1
    zunionstore 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

  • 内部编码
    • ziplist压缩列表:小规模数据时(元素个数少,值小)更节省内存
    • skiplist跳跃表:不满足使用ziplist时才使用

键管理

1.单个键管理

  • 键重命名
    1
    2
    3
    4
    rename key newkey

    # 若newkey已存在,会先删除newkey,再重命名
    # 想避免上述情况用renamenx key newkey,只有newkey不存在才会重命名成功
  • 随机返回一个键的名字
    1
    randomkey
  • 去除键的过期时间
    1
    2
    3
    persist key

    # set键的时候也会自动去除键的过期时间
  • 迁移键(迁移数据)
    • 在Redis内部数据库之间迁移
      1
      2
      3
      move key db

      # 把指定键从源数据库迁移到指定数据库
    • 在不同的Redis实例之间进行数据迁移(例两个客户端)
      1
      2
      3
      4
      5
      6
      7
      8
      源客户端运行:
      dump key

      目标客户端运行:
      restore key ttl value

      # ttl表示设置过期时间,ttl=0表示不过期
      # value是源客户端运行dump命令后返回的字符串
      还有一种方法migrate(实际上是集成了dump、restore、del)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key ...]]

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

2.遍历键的操作

  • 全量遍历,根据匹配式返回符合要求的键
    1
    2
    3
    4
    5
    6
    keys pattern

    # pattern = * 时表示返回所有键
    # *代表匹配任意字符;·代表匹配一个字符;[]代表匹配部分字符,例如[1,3]代表匹配1,3,[1-10]代表匹配1到10的任意数字;\x用来做转义,例如要匹配星号、问号需要进行转义
    # 例keys [j,r]edis
    # 会返回名为redis和jedis的键
  • 渐进式遍历,为解决全量遍历遇到阻塞诞生的,相当于一次只扫描一部分,多次扫描才完成
    1
    2
    3
    4
    5
    6
    7
    8
    9
    scan cursor [match pattern] [count number]

    # cursor是必需参数,实际上cursor是一个游标,第一次遍历从0开始,每次scan遍历完都会返回当前游标的值,直到游标值为0,表示遍历结束
    # match pattern是可选参数,它的作用的是做模式的匹配,这点和keys的模式匹配很像
    # count number是可选参数,它的作用是表明每次要遍历的键个数,默认
    值是10,此参数可以适当增大。
    # 例子 输入scan 0
    # 返回1) "6"和2) 1) "w".....(“1)”里面是游标,“2)”里面是当前部分遍历返回的数据)
    # 然后第二次就要输入scan 6 ...依次类推
    hscan、sscan、zscan分别是哈希类型、集合类型、有序集合类型的扫描遍历命令

数据库管理(开发环境少用)

1.Redis默认配置中是有16个数据库,序号为0到15

1
2
127.0.0.1:6379> 表示0号数据库
127.0.0.1:6379[15]> 表示15号数据库(也是最后一个数据库)

2.切换数据库

1
select dbIndex

3.清除数据库

1
2
3
4
5
# 清除当前数据库
flushdb

#清除所有数据库
flushall

4.Redis是单线程的。如果使用多个数据库,那么这些数据库仍然是使用一个CPU,彼此之间还是会受到影响的。

  • 多数据库的使用方式,会让调试和运维不同业务的数据库变的困难,假如有一个慢查询存在,依然会影响其他数据库,这样会使得别的业务方定位问题非常的困难。
  • 部分Redis的客户端根本就不支持这种方式。即使支持,在开发的时候
    来回切换数字形式的数据库,很容易弄乱。
  • 如果要使用多个数据库功能,完全可以在一台机器上部署多个Redis实例,彼此用端口来做区分

持久化

1.Redis持久化有两种:

  • RDB:把当前进程数据生成快照保存到硬盘,分为手动触发和自动触发
  • AOF:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的,实时持久化

2.RDB的触发机制

  • 手动触发
    • save命令:阻塞当前Redis服务器,直到RDB过程完成为止,对于内存比较大的实例会造成长时间阻塞,不建议使用
    • bgsave命令:Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短(Redis内部默认这种方式)
  • 自动触发
    • 使用save相关配置,如“save m n”。表示m秒内数据集存在n次修改时,自动触发bgsave
    • 从节点执行全量复制操作,主节点自动执行bgsave生成RDB文件并发送给从节点
    • 执行debug reload命令重新加载Redis时,也会自动触发save操作。
    • 默认情况下执行shutdown命令时,如果没有开启AOF持久化功能则自动执行bgsave

3.RDB的bgsave执行流程
图2

  • 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回
  • 父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞
  • 父进程fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,从这里起保存由子进程负责,父进程可以继续去执行其他命令
  • 子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后
    对原有文件进行原子替换
  • 进程发送信号给父进程表示完成

4.RDB的优缺点

  • 优点
    • RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景,比如用于灾难恢复
    • Redis加载RDB恢复数据远快于AOF
  • 缺点
    • RDB方式数据没办法做到实时持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高
    • RDB文件使用特定二进制格式保存,有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题

5.实时持久化的AOF

  • 开启AOF服务功能
    1
    appendonly yes
  • AOF工作流程:
    • 命令写入append:有写入命令时,将该记录放到AOF缓冲中
    • 文件同步sync:将AOF缓冲中的记录写入到AOF文件
      • always:每次写入都同步到AOF文件中(fsync产生阻塞,直到单个文件完全同步)
      • no:同步周期由操作系统决定(一般30s)(write写入缓存就就返回,同步时间取决于系统什么时候调度完成缓存中的任务)
      • everysec:每秒同步一次,建议采用(write)
    • 文件重写rewrite:因为AOF文件会越来越大,所有需要定期重新生成AOF文件并在该过程进行一定压缩(将已经失效的写命令和数据删掉,将一些可合并的写命令合并)
    • 重启加载load:加载AOF文件恢复数据
  • AOF命令写入缓冲区的内容直接是文本协议格式
    • 原因1:直接采用协议格式,避免了二次处理开销,且文本协议便于修改
    • 原因2:如果每次写AOF文件命令都直接追加到硬盘,那么性能完全取决于当前硬盘负载

6.Redis启动时的加载顺序

  • 若开启AOF,则优先加载AOF文件,不存在AOF文件才去加载RDB文件
  • 若未开启AOF,则优先加载RDB文件,若RDB文件不存在直接启动

7.fork的优化,当Redis做RDB或AOF重写时,一个必不可少的操作就是执行fork操作创建子进程,虽然子进程不需要拷贝父进程的物理内存空间,但也会复制父进程的空间内存页表

  • 优先使用物理机或者高效支持fork操作的虚拟化技术,避免使用Xen
  • 控制Redis实例最大可用内存,因为fork耗时跟内存量成正比
  • 合理配置Linux内存分配策略,避免物理内存不足导致fork失败
  • 降低fork操作的频率,如适度放宽AOF自动触发时机

8.持久化阻塞主线程场景有

  • fork阻塞,跟内存量和系统有关
  • AOF追加阻塞,说明硬盘资源紧张

9.单机下部署多个实例时(因为Redis是单线程,部署多个实例才能有效利用多核),为了防止出现多个子进程执行重写操作,应该做隔离控制,避免CPU和IO资源竞争

复制

1.Redis通过复制功能实现主节点的多个副本。从节点可灵活地通过slaveof命令建立或断开复制流程

  • 主节点的数据更新时,从节点会自动获取,建立复制后主节点和从节点之间有一个长连接,并彼此发送心跳命令
  • 从节点默认只读模式,这个配置一般不能修改,因为主节点无法感知从节点数据的变化,直接修改从节点后会导致主从数据不一致
  • 从节点可以复制另一个从节点,实现一层层向下的复制流,复制的分为:全量复制和部分复制

2.主、从节点本质都是一个独立的Redis进程

3.Redis为了保证高性能复制过程是异步的,写命令处理完后直接返回给客户端,不等待从节点复制完成。因此从节点数据集会有延迟情况

4.主节点无法正常启动后,需要从节点人为干预执行slaveof no one晋升为新的主节点,再让其他从节点与其建立连接

哨兵Redis Sentinel

1.Redis Sentinel是一个分布式架构,其中包含若干个Sentinel节点和Redis数据节点

  • 每个Sentinel节点会对数据节点和其余Sentinel节点进行监控
  • 当它发现节点不可达时,会对节点做下线标识。
  • 如果被下线标识的是主节点,它还会和其他Sentinel节点进行“协商”,当大多数Sentinel节点都认为主节点不可达时,它们会选举出一个Sentinel节点来完成自动故障转移的工作,同时会将这个变化实时通知给Redis应用方

2.在Redis Sentinel结构中,客户端在初始化的时候连接的是Sentinel节点集合,由Sentinel节点集合告知主节点信息

  • Sentinel节点集合是由若干个Sentinel节点组成的,这样即使个别Sentinel节点不可用,整个Sentinel节点集合依然是健壮的
  • Sentinel节点本身就是独立的Redis节点,只不过它们有一些特殊,它们不存储数据,只支持部分命令

3.启动哨兵节点有两种方式(效果一样)

1
2
3
redis-server redis-sentinel-26379.conf --sentinel

redis-sentinel redis-sentinel-26379.conf

4.查看哨兵节点信息

1
redis-cli -h 127.0.0.1 -p 26379 info sentinel

5.发送故障时,哨兵模式的解决过程

  • 选出合适从节点
  • 晋升选出的从节点为主节点。
  • 命令其余从节点复制新的主节点。
  • 等待原主节点恢复后命令它去复制新的主节点

6.sentinel的API

  • 显示被监控的所有主节点的状态和信息
    1
    sentinel masters
  • 显示指定主节点的状态和信息
    1
    sentinel master<master name>
  • 显示指定主节点的从节点的状态和信息
    1
    sentinel slaves<master name>
  • 显示指定主节点的sentinel节点的状态和信息
    1
    sentinel sentinels<master name>
  • 返回指定主节点的IP地址和端口
    1
    sentinel get-master-addr-by-name<master name>
  • 强制对某主节点立即进行故障转移
    1
    sentinel failover<master name>
  • 检查一遍sentinel节点正常个数是否达到配置文件设置的,也即是否满足故障转移的条件
    1
    sentinel ckquorum<master name>
  • 将当前的Sentinel节点的配置强制刷到磁盘上(当外部原因(例如磁盘损坏)造成配置文件损坏或者丢失时,这个命令很有用)
    1
    sentinel flushconfig
  • 取消当前sentinel节点对主节点的监控
    1
    sentinel remove<master name>
  • 该sentinel节点建立对主节点的监控(和配置一模一样)
    1
    sentinel monitor<master name><ip><port><quorum>
  • 更新当前sentinel节点的配置
    1
    sentinel set <param> <value>

Redis配置文件详解

1.主要配置

1
2
3
4
5
6
port 6379 //指明使用端口
daemonize yes //以守护进程方式启动,默认为no
logfile "6379.log" //将日志文件命名为6379.log并存在当前目录
dbfilename "dump-6379.rdb" //数据库文件名
slaveof 127.0.0.1 6379 //表示该节点是127.0.0.1:6379主节点的从节点

2.哨兵节点配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 代表该sentinel节点需要监控127.0.0.1:6379这个主节点,2代表判断主节点失败至少需要2个Sentinel节点同意,mymaster是给主节点起的别名(即master name)
sentinel monitor mymaster 127.0.0.1 6379 2
# 代表定期发送ping命令来判断Redis数据节点和其余Sentinel节点是否可达,如果超过30000毫秒且没有有效的回复,则判定节点不可达
sentinel down-after-milliseconds mymaster 30000
# 代表在一次故障转移之后,每次向新的主节点发起复制操作的从节点个数为1
sentinel parallel-syncs mymaster 1
# 代表故障转移每个阶段的执行时间不能超过180000毫秒,超过就算失败
# 如果对一个主节点故障转移失败,那么下次再对该主节点做故障转移的时间是之前的2倍
sentinel failover-timeout mymaster 180000


# 其他不重要配置
# 如果主节点有密码,要为监视该主节点的哨兵节点配置密码
sentinel auth-pass <master-name> <password>

# 故障转移发送某些情况后执行某脚本
sentinel notification-script <master-name> <script-path>
# 故障转移完成后执行某脚本
sentinel client-reconfig-script <master-name> <script-path>