简介

Redis作者

  • redis的作者,他叫Salvatore Sanfilippo,来自意大利的西西里岛,现在居住在卡塔尼亚。目前供职于Pivotal公司。

  • 地址是antirez.com,当然也可以去follow他的github,地址是http://github.com/antirez。

  • Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,遵守BSD协议,并提供多种语言的API。

  • 从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。(Vmware在资助着redis项目的开发和维护)

  • BSD是”Berkeley Software Distribution”的缩写,意思是”伯克利软件发行版”。BSD开源协议是一个给于使用者很大自由的协议。可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。

  • BSD代码鼓励代码共享,但需要尊重代码作者的著作权。BSD由于允许使用者修改和重新发布代码,也允许使用或在BSD代码上开发商业软件发布和销售,因此是对商业集成很友好的协议。

NoSQL介绍

  • NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题
  • 而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题

NoSQL数据库的四大分类

  • 键值(Key-Value)存储数据库

    • 相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB
    • 典型应用: 内容缓存,主要用于处理大量数据的高访问负载。
    • 数据模型: 一系列键值对
    • 优势: 快速查询
    • 劣势: 存储的数据缺少结构化
  • 列存储数据库

    • 相关产品:Cassandra, HBase, Riak
    • 典型应用:分布式的文件系统
    • 数据模型:以列簇式存储,将同一列数据存在一起
    • 优势:查找速度快,可扩展性强,更容易进行分布式扩展
    • 劣势:功能相对局限
  • 文档型数据库

    • 相关产品:CouchDB、MongoDB
    • 典型应用:Web应用(与Key-Value类似,Value是结构化的)
    • 数据模型: 一系列键值对
    • 优势:数据结构要求不严格
    • 劣势: 查询性能不高,而且缺乏统一的查询语法
  • 图形(Graph)数据库(图)

    • 相关数据库:Neo4J、InfoGrid、Infinite Graph
    • 典型应用:社交网络
    • 数据模型:图结构
    • 优势:利用图结构相关算法。
    • 劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
  • NoSQL数据库适用情况

    • 数据模型比较简单;
    • 需要灵活性更强的IT系统
    • 对数据库性能要求较高
    • 不需要高度的数据一致性
    • 对于给定key,比较容易映射复杂值的环境

Redis特点

  • 与其他 key - value 缓存产品有以下三个特点

    • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
    • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
    • Redis支持数据的备份,集群等高可用功能。
  • 特点

    • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
    • 高速读写,redis使用自己实现的分离器,代码量很短,没有使用lock(MySQL),因此效率非常高。
    • 丰富的数据类型 – Redis支持的类型 String, List, Hash, Set 及 Ordered Set 数据类型操作。
    • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
    • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
  • 缺点:

    • 持久化。Redis直接将数据存储到内存中,要将数据保存到磁盘上,Redis可以使用两种方式实现持久化过程。
      • 定时快照(snapshot):每隔一段时间将整个数据库写到磁盘上,每次均是写全部数据,代价非常高。
      • 基于语句追加(aof):只追踪变化的数据,但是追加的log可能过大,同时所有的操作均重新执行一遍,回复速度慢。
    • 耗内存,占用内存过高。

总结

  • redis单个key 存入512M大小
  • redis支持多种类型的数据结构(string,list,hash.set.zset)
  • redis 是单线程 原子性 , 6.0后支持多线程
  • redis可以持久化 因为使用了 RDB和AOF机制
  • redis支持集群 而且redis 支持库(0-15) 16个库
  • redis 还可以做消息队列 比如聊天室 IM
  • 企业级开发中:可以用作数据库、缓存热点数据(经常会被查询,但是不经常被修改或者删除的数据)和消息中间件等大部分功能。

Redis安装

Windows安装

Linux安装redis

  1. 下载

    官方网站:http://redis.io/ http://www.redis.cn

    官方下载:http://redis.io/download 可以根据需要下载不同版本

  2. 准备: gcc环境

    • Redis是C语言开发,安装Redis需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没有gcc环境,需要安装gcc

    • yum -y install gcc gcc-c++

    • make命令: yum -y install make

  3. 下载解压

    1
    2
    3
    4
    wget http://download.redis.io/releases/redis-4.0.1.tar.gz
    wget http://download.redis.io/releases/redis-5.0.5.tar.gz
    wget https://download.redis.io/releases/redis-6.2.3.tar.gz
    tar -zxvf redis-6.2.3.tar.gz -C /opt/module
  4. 切换到目录,编译

    1
    2
    3
    4
    5
    cd redis-6.2.3/
    # 编译 make 或 make MALLOC=libc
    make
    # 测试 (安装tcl) 可以跳过
    make test
  5. 安装

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # (安装编译后的文件) 安装到指目录:
    # PREFIX必须大写、同时会自动为我们创建redis目录,并将结果安装此目录
    make install PREFIX=/usr/local/redis

    # 默认安装:bin
    $ make install

    # 验证: 可以看到 redis-cli,redis-server命令
    ls /usr/local/redis
  6. 复制redis.conf到etc

    1
    2
    # 将解压的配置文件复制到安装文件的目录下
    cp redis.conf /usr/local/redis
  • 配置环境变量 vim /etc/bashrc or /etc/profile

    1
    2
    3
    4
    export REDIS_HOME=/usr/local/redis
    export PATH=$PATH:$JAVA_HOME/bin:$REDIS_HOME/bin
    # 更新文件
    source /etc/profile

添加redis服务到系统服务中

  • daemonize yes(修改作为后台守护程序运行)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    vi /etc/systemd/system/redis.service
    # 复制粘贴以下内容,注意ExecStart配置成自己的路径
    [Unit]
    Description=redis-server
    After=network.target

    [Service]
    Type=forking
    ExecStart=/usr/local/redis/bin/redis-server /usr/local/redis/redis.conf
    PrivateTmp=true

    [Install]
    WantedBy=multi-user.target
  • 设置开机启动

    1
    2
    systemctl daemon-reload
    systemctl enable redis.service
  • 其他redis常用命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    systemctl start redis.service     #启动redis服务

    systemctl stop redis.service #停止redis服务

    systemctl restart redis.service #重新启动服务

    systemctl status redis.service #查看服务当前状态

    systemctl enable redis.service #设置开机自启动

    systemctl disable redis.service #停止开机自启动

设置远程连接

  • Redis默认设置的是允许本地连接,所以需要在redis安装目录中找到redis.conf,
    并将 bind 127.0.0.1注释掉,改为bind 0.0.0.0

  • 开放端口,(如果是阿里云服务器,登录阿里云账号,找到安全组设置,开发6378的端口,详情可以咨询阿里云客服)

    1
    2
    3
    4
    5
    # 开发6379端口
    firewall-cmd --zone=public --add-port=6379/tcp --permanent

    # 刷新防火墙
    firewall-cmd --reload

redis启动

前端运行

  • 服务器: ./redis-server

  • 关闭:

    • 强制关闭:ctrl+c
    • 正常命令:./redis-cli shutdown(推荐)
  • 客户端 : ./redis-cli –h IP地址 –p 端口 –a 密码

后端启动

  • 修改配置文件:redis.conf

    • bind 192.168.2.101(修改绑定ip)

    • daemonize yes(修改作为后台守护程序运行)

  • 运行服务器端:

  • ./redis-server /usr/local/redis/redis.conf

  • 关闭服务器端:

    • kill杀死

    • ./redis-cli shutdown(推荐)

    • 查询PID: ps -ef | grep -i redis ,后 kill -9 PID

  • 自带客户端连接:

    ./redis-cli -h ip地址 -p 端口 –a 密码

  • 注意访问需要开放防火墙

Redis数据类型

string类型

  • 赋值和取值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 赋值
    set key value
    # 取值
    get key
    # 取值并赋值
    getset key value
    # 赋值多个值
    mset k1 v1 k2 v2
    # 获取多个值
    mget k1 k2
  • 删除

    1
    del key
  • 数值增减

    1
    2
    3
    4
    5
    6
    7
    8
    # 增长1
    incr key
    # 设置增长数
    incrby key increment
    # 减少1
    decr key
    # 减少整数值
    decrby key decrement
  • 其他命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 追加数值,返回长度
    append key value
    # 获取长度
    strlen key

    ---

    127.0.0.1:6379> get k1
    "111"
    127.0.0.1:6379> append k1 abab
    (integer) 7
    127.0.0.1:6379> get k1
    "111abab"
    127.0.0.1:6379> strlen k1
    (integer) 7

hash类型

  • 散列类型,支持扩展key,提供字段属性,字段和字段值之间实现映射。

  • 字段值只支持string类型。

  • 设置和获取值(设置值不区分插入和更新,0代表更新,1代表插入)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    # 设置值
    192.168.2.101:6379> HSET user:001 name zs
    (integer) 1
    # 更新值
    192.168.2.101:6379> HSET user:001 name ls
    (integer) 0
    # 获取值
    192.168.2.101:6379> HGET user:001 name
    "ls"
    # 设置多个字段属性值
    192.168.2.101:6379> HMSET user:001 name zs age 12
    OK
    # 获取多个字段的值
    192.168.2.101:6379> HMGET user:001 name age
    1) "zs"
    2) "12"

    # 有该key则不赋值,没有则赋值
    192.168.2.101:6379> HSETNX user:001 name ww
    (integer) 0
    # 获取所有的字段和值
    192.168.2.101:6379> HGETALL user:001
    1) "name"
    2) "zs"
    3) "age"
    4) "12"
  • 删除

    1
    hdel key
  • 增加

    1
    2
    3
    # hincrby key field increment
    # 设置增长数
    HINCRBY user:001 age 3
  • 其他

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # 判断是否存在字段 0 不存在 , 1 存在
    192.168.2.101:6379> HEXISTS user:001 sex
    (integer) 0
    192.168.2.101:6379> HEXISTS user:001 name
    (integer) 1
    # 判断key是否存在
    192.168.2.101:6379> EXISTS user:001
    (integer) 1
    # 获取key的类型
    192.168.2.101:6379> TYPE user:001
    hash
    192.168.2.101:6379> TYPE k1
    string
    # 获取key所有的属性
    192.168.2.101:6379> HKEYS user:001
    1) "name"
    2) "age"
    # 获取key所有的属性值
    192.168.2.101:6379> HVALS user:001
    1) "zs"
    2) "15"
    # 获取key的属性长度
    192.168.2.101:6379> HLEN user:001
    (integer) 2

  • 使用场景:存储对象信息(用户,商品信息等)

list类型

  • 列表类型, list采用链表存储,操作从两端操作。

  • 从两端添加数据

    1
    2
    3
    4
    192.168.2.101:6379> LPUSH list1 a b c
    (integer) 3
    192.168.2.101:6379> RPUSH list1 d e f
    (integer) 6
  • 查看列表

    1
    2
    3
    4
    5
    6
    7
    8
    # lrange key start stop,index:从0开始,-1代表最后一位
    192.168.2.101:6379> LRANGE list1 0 -1
    1) "c"
    2) "b"
    3) "a"
    4) "d"
    5) "e"
    6) "f"
  • 从两端弹出值

    1
    2
    3
    4
    192.168.2.101:6379> LPOP  list1
    "c"
    192.168.2.101:6379> RPOP list1
    "f"
  • 查看list长度

    1
    2
    192.168.2.101:6379> llen list1
    (integer) 4
  • 删除指定的数据

    • 删除前count个value值
    • count>0 从左往右
    • count < 0 从右往左
    • count =0 删除所有
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    # lrem key count value
    192.168.2.101:6379> lpush list2 a b c d c b a
    (integer) 7
    # 删除所有的a
    192.168.2.101:6379> lrem list2 0 a
    (integer) 2
    192.168.2.101:6379> LRANGE list2 0 -1
    1) "b"
    2) "c"
    3) "d"
    4) "c"
    5) "b"
    # 从左往右 删除第一个C值
    192.168.2.101:6379> lrem list2 1 c
    (integer) 1
    192.168.2.101:6379> LRANGE list2 0 -1
    1) "b"
    2) "d"
    3) "c"
    4) "b"
    # 从右往左 删除第一个b值
    192.168.2.101:6379> lrem list2 -1 b
    (integer) 1
    192.168.2.101:6379> LRANGE list2 0 -1
    1) "b"
    2) "d"
    3) "c"
  • 查看指定索引的值

    1
    2
    3
    # lindex key index
    192.168.2.101:6379> LINDEX list2 0
    "b"
  • 保留片段

    1
    2
    3
    4
    5
    6
    # LTRIM key start stop
    192.168.2.101:6379> LTRIM list2 0 1
    OK
    127.0.0.1:6379> LRANGE list2 0 -1
    1) "b"
    2) "d"
  • 插入指定位置

    1
    2
    3
    4
    5
    6
    7
    # LINSERT key BEFORE|AFTER pivot value
    127.0.0.1:6379> LINSERT list2 after b a
    (integer) 2
    127.0.0.1:6379> LRANGE list2 0 -1
    1) "b"
    2) "a"
    3) "d"
  • 移动列表

    1
    2
    3
    4
    5
    6
    7
    8
    # rpoplpush source destination
    127.0.0.1:6379> RPOPLPUSH list2 list3
    "d"
    127.0.0.1:6379> lrange list3 0 -1
    1) "d"
    127.0.0.1:6379> LRANGE list2 0 -1
    1) "b"
    2) "a"
  • 使用场景:用于商品评论

set

  • 无序,不重复

  • 添加和删除元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 添加元素  sadd key member [member ...]
    192.168.2.101:6379> SADD set1 a b c
    (integer) 3
    192.168.2.101:6379> SADD set1 a
    (integer) 0
    192.168.2.101:6379> SADD set1 d
    (integer) 1
    # 删除元素 srem key member [member ...]
    192.168.2.101:6379> SREM set1 d
    (integer) 1
  • 查看所有元素

    1
    2
    3
    4
    5
    6
    # smembers key
    127.0.0.1:6379> smembers set1
    1) "c"
    2) "d"
    3) "b"
    4) "a"
  • 判断是否存在

    1
    2
    3
    # SISMEMBER key member
    127.0.0.1:6379> SISMEMBER set1 a
    (integer) 1
  • 运算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    192.168.2.101:6379> sadd set2 c b
    # 取并集
    192.168.2.101:6379> SUNION set1 set2
    1) "c"
    2) "b"
    3) "d"
    4) "a"
    # 取交集
    192.168.2.101:6379> SINTER set1 set2
    1) "c"
    2) "b"
    # 取差集
    192.168.2.101:6379> SDIFF set1 set2
    1) "d"
    2) "a"
    127.0.0.1:6379> SDIFF set2 set1
    (empty list or set)
  • 其他

    1
    2
    3
    4
    5
    6
    7
    8
    #查看长度 scard key
    127.0.0.1:6379> scard set1
    (integer) 4
    #弹出元素 spop:
    127.0.0.1:6379> spop set2
    "c"
    127.0.0.1:6379> spop set2
    "b"

sortedset

  • 又名zset,唯一且可排序。

  • 为每个元素设置分数,根据分数实现排序。

  • 添加元素 ZADD key [NX|XX] [CH] [INCR] score member [score member ...]

    1
    2
    127.0.0.1:6379> ZADD zset1 80 zs 90 ls 70 ww
    (integer) 3
  • 添加元素和分数

    • 如果该元素存在则修改分数并返回0;
    • 如果不存在返回1
    1
    2
    3
    4
    127.0.0.1:6379>  ZADD zset1 80 zs 90 ls 75 ww
    (integer) 0
    127.0.0.1:6379> ZADD zset1 80 zs 90 ls 75 ww 59 zl
    (integer) 1
  • 查看分数

    1
    2
    127.0.0.1:6379> ZSCORE zset1 zs
    "80"
  • 删除元素

    1
    2
    127.0.0.1:6379> ZREM zset1 zl
    (integer) 1
  • 顺序输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #zrange key start stop [WITHSCORES]
    127.0.0.1:6379> ZRANGE zset1 0 -1 withscores
    1) "ww"
    2) "75"
    3) "zs"
    4) "80"
    5) "ls"
    6) "90"
    # 不带分数排序
    127.0.0.1:6379> ZRANGE zset1 0 -1
    1) "ww"
    2) "zs"
    3) "ls"
  • 倒序输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    127.0.0.1:6379> ZREVRANGE zset1 0 -1 withscores
    1) "ls"
    2) "90"
    3) "zs"
    4) "80"
    5) "ww"
    6) "75"
    127.0.0.1:6379> ZREVRANGE zset1 0 -1
    1) "ls"
    2) "zs"
    3) "ww"
  • 排名0开始,zrank key member,按照升序排序

    1
    2
    3
    4
    127.0.0.1:6379> ZRANK zset1 ww
    (integer) 0
    127.0.0.1:6379> zrank zset1 ls
    (integer) 2
  • 指定分数范围排序 ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 最小分数  最大分数  offset length
    127.0.0.1:6379> ZRANGEBYSCORE zset1 80 90 withscores
    1) "zs"
    2) "80"
    3) "ls"
    4) "90"
    127.0.0.1:6379> ZRANGEBYSCORE zset1 80 90 withscores limit 1 1
    1) "ls"
    2) "90"
  • 增加某个成员分数

    1
    2
    3
    # ZINCRBY key increment member
    127.0.0.1:6379> ZINCRBY zset1 5 ww
    "80"
  • 查看元素个数

    1
    2
    3
    # zcard key
    127.0.0.1:6379> zcard zset1
    (integer) 3
  • 统计范围内的元素个数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    127.0.0.1:6379> zrange zset1 0 -1 withscores
    1) "ww"
    2) "80"
    3) "zs"
    4) "80"
    5) "ls"
    6) "90"

    # ZCOUNT key min max 闭合区间
    127.0.0.1:6379> ZCOUNT zset1 80 90
    (integer) 3


  • 根据排名删除范围内元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    127.0.0.1:6379> ZRANGE zset1 0 -1 withscores
    1) "ww"
    2) "80"
    3) "zs"
    4) "85"
    5) "ls"
    6) "90"
    # 根据排名范围删除
    # ZREMRANGEBYRANK key start stop
    127.0.0.1:6379> zremrangebyrank zset1 2 2
    (integer) 1
    127.0.0.1:6379> ZRANGE zset1 0 -1 withscores
    1) "ww"
    2) "80"
    3) "zs"
    4) "85"
  • 根据分数删除范围内元素

    1
    2
    3
    4
    5
    # ZREMRANGEBYSCORE key start stop 闭合区间
    127.0.0.1:6379> zremrangebyscore zset1 80 85
    (integer) 2
    127.0.0.1:6379> ZRANGE zset1 0 -1 withscores
    (empty list or set)
  • 应用场景:排行榜

keys命令

  • keys pattern

    • * 表示任意的key
    • ?一位占位符
  • exists:判断keys存在

  • del:删除key

  • rename:重命名key

  • type:查看类型

  • 设置key的生存时间

    • EXPIRE key seconds: 设置key的生存时间(单位:秒)key在多少秒后会自动删除

    • TTL key : 查看key剩余的生存时间, -1 永久有效,-2代表删除

    • PERSIST key: 清除生存时间

    • PEXPIRE key milliseconds :生存时间设置单位为:毫秒

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      127.0.0.1:6379> keys *
      1) "set1"
      2) "user:001"
      3) "list1"
      4) "list"
      127.0.0.1:6379> EXISTS k1
      (integer) 0
      127.0.0.1:6379> exists set1
      (integer) 1
      127.0.0.1:6379> type set1
      set
      127.0.0.1:6379> del list1 set1
      (integer) 2
      127.0.0.1:6379> rename user:001 user01
      OK

      # ---
      127.0.0.1:6379> mset k1 v1 k2 v2
      OK
      # 默认不失效
      127.0.0.1:6379> ttl k2
      (integer) -1
      # 设置10秒后失效
      127.0.0.1:6379> expire k1 10
      (integer) 1
      127.0.0.1:6379> ttl k1
      (integer) 8
      # key已经自动删除
      127.0.0.1:6379> ttl k1
      (integer) -2
      # 清除生存时间
      127.0.0.1:6379> persist k1
      (integer) 0
      127.0.0.1:6379> get k1
      "v1"
      127.0.0.1:6379> ttl k1
      (integer) -1
      # 设置1000毫秒后key失效
      127.0.0.1:6379> pexpire k1 1000
      (integer) 1
      127.0.0.1:6379> ttl k1
      (integer) -2

持久化

RDB:Redis Database

  • 默认持久化方式,将内存数据隔固定时长存储到磁盘文件。

  • 执行原理:

    1. Redis 调用 fork() ,同时拥有父进程和子进程。
    2. 父进程处理client连接;子进程将数据库内容存储到磁盘文件;
    3. 创建snapshot(共享信息),子进程处理的是snapshot的数据,写入到临时文件
    4. 当子进程将临时文件写入完成,关闭子进程。
  • 持久化策略:

    1
    2
    3
    save 900 1              #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。
    save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。
    save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。
    • save参数指定了快照条件,可以存在多个条件,条件之间是“或”的关系。如上所说,save
      900 1的意思是在15分钟(900秒钟)内有至少一个键被更改则进行快照。
    • 如果想要禁用自动快 照,只需要将所有的save参数删除即可。
    • 缺点: 非法关闭,会丢失最后一次数据。

AOF:Append only file

  • aof每次将写操作存储到aof持久化文件中,默认是关闭的。

  • 设置aof存储策略

    1. appendfsync no: 当设置appendfsync为no的时候,Redis不会主动调用fsync去将AOF日志内容同步到磁盘,所以这一切就完全依赖于操作系统的调试了。对大多数Linux操作系统,是每30秒进行一次fsync,将缓冲区中的数据写到磁盘上。

    2. appendfsync everysec(缺省方式): 当设置appendfsync为everysec的时候,Redis会默认每隔一秒进行一次fsync调用,将缓冲区中的数据写到磁盘。但是当这一 次的fsync调用时长超过1秒时。Redis会采取延迟fsync的策略,再等一秒钟。也就是在两秒后再进行fsync,这一次的fsync就不管会执行多长时间都会进行。这时候由于在fsync时文件描述符会被阻塞,所以当前的写操作就会阻塞。结论就是:在绝大多数情况下,Redis会每隔一秒进行一次fsync。在最坏的情况下,两秒钟会进行一次fsync操作。

    3. appednfsync always

      当设置appendfsync为always时,每一次写操作都会调用一次fsync,这时数据是最安全的,当然,由于每次都会执行fsync,所以其性能也会受到影响

  • 开启aof的持久化操作

    1
    2
    699 appendonly yes
    703 appendfilename "appendonly.aof" 持久化文件名称
  • AOF文件是可识别的纯文本,它的内容就是一个个的Redis标准命令,AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。

  • 重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。

    • 系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。
    • auto-aof-rewrite-percentage 100
    • auto-aof-rewrite-min-size 64mb
  • (一个坑)windows下的redis启用持久化 aof 之后 报错:Can’t open the append-only file: Input/output error, 是因为win下 没用管理员权限

对比rdb和aof区别

  • RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

  • AOF持久化以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。

二者优缺点

RDB优势

  1. 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据。通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易的进行恢复。

  2. 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转移到其它存储介质上。

  3. 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化的工作,这样就可以极大的避免服务进程执行IO操作了。

  4. 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

RDB劣势

  1. 如果你想保证数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
  2. 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百毫秒,甚至是1秒钟。

AOF优势

  1. 该机制可以带来更高的数据安全性,即数据持久性。Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能正确的理解它。

  2. 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然而如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过redis-check-aof工具来帮助我们解决数据一致性的问题。

  3. 如果日志过大,Redis可以自动启用rewrite机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好的保证数据安全性。

  4. AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

AOF劣势

  1. 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
  2. 根据同步策略的不同,AOF在运行效率上往往会慢于RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

选择标准

  • 二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(rdb)。rdb这个就更有些 eventually consistent的意思了。不过生产环境其实更多都是二者结合使用的。
  • redis4之后就使用rdb和aof混合的方式。AOF更新频率快,数据更加完整,所以如果AOF和RDB同时存在的时候,Redis会优先使用从AOF文件来还原数据库状态,如果AOF关闭状态时,则从RDB中恢复。
  • 如果RDB模式和AOF模式同时存在,以AOF模式为主. 如果不想开启AOF模式需要将配置关闭.

主从复制

  • 避免单点故障,设置一个主数据库和n个从数据库
    • 所有的写操作都在主数据库执行
    • 读操作在从数据库执行
    • 为保证数据的一致性,将主数据库的数据会复制到从数据库。

设置主从复制

  • 192.168.2.100为主服务器,192.168.2.101为从服务器
  1. 配置主服务器配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [root@localhost redis-4.0.10]# vim /etc/redis/6379.conf
    # 1. 将bind 127.0.0.1这行注释或者指定ip。(本例是注释,即所有ip都能连接)
    # bind 127.0.0.1
    # 2. 开启守护进程
    daemonize yes
    # 3. 设置访问密码 建议线上把密码设置非常复杂,最好能在第2步中指定ip
    requirepass 123456
    # 4. 设置客户端最大连接数(maxclients),默认是10000,可根据需求更改
    maxclients 10000
    # 5. 最大内存(默认不受限制,建议还是设置个低于服务器内存的值)
    # maxmemory <bytes>
    # 6. 内存策略,如果内存足够用则不用管,如果内存不够用,建议设置最近最少使用策略(LRU),默认是内存不够则报错
    # maxmemory-policy noevication
  2. 使用配置文件启动redis服务

    1
    [root@localhost redis-4.0.10]# service redisd start
  3. 配置从服务器有2种方式:

    • 在redis.conf中设置slaveof :slaveof <masterip> <masterport>
    • 使用redis-cli客户端连接到redis服务,执行slaveof命令: slaveof <masterip> <masterport>,重启后将失去主从复制关系
    • 5.0后新版本的命令是replicaof,旧版本是slaveof命令
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 1. 前四步与主服务器配置基本一致
    # 2. 配置所属主服务器ip和端口
    slaveof 192.168.2.100 6379
    # replicaof 192.168.2.100 6379
    # 3. 配置所属主服务器的密码
    masterauth 123456
    # 4. 从服务器通常是只读,所以要配置只读(默认是只读,不要更改即可)
    slave-read-only yes
    # replica-read-only yes
    # 5. 如果是同一机器下 port 更改为与主redis(6379)不相同既可
  4. 启动从节点的redis

    1
    ./redis-server ~/config/slave/redis.conf &
  5. Redis客户端连接上6379端口,查看Redis主从关系

    • role:角色信息
    • slaveX:从库信息
    • connected_slaves:从库数量
  6. # 查看主从信息
    info replication
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63

    ## Sentinel哨兵模式

    - 主从复制同时存在以下几个问题:
    - 一旦主节点宕机,从节点晋升成主节点,同时需要修改应用方的主节点地址,还需要命令所有 从节点去复制新的主节点,整个过程需要人工干预。
    - 主节点的写能力受到单机的限制。
    - 主节点的存储能力受到单机的限制。
    - 原生复制的弊端在早期的版本中也会比较突出,比如:Redis 复制中断后,从节点会发起 psync。此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿。

    ### Sentinel的架构

    - Sentinel[ˈsentɪnl],哨兵机制就是解决我们以上主从复制存在缺陷(选举问题),保证我们的Redis高可用,实现自动化故障发现与故障转移。

    - 该系统执行以下三个任务:

    - 监控:哨兵会不断检查你的主服务器和从服务器是否运作正常。
    - 提醒:当被监控的某个Redis服务器出现问题时,哨兵可以通过API给程序员发送通知
    - 自动故障转移:主服务器宕机,哨兵会开始一次自动故障转移操作,升级一个从服务器为主服务器,并让其他从服务器改为复制新的主服务器.
    - 配置提供者:在 Redis Sentinel 模式下,客户端应用在初始化时连接的是 Sentinel 节点集合,从中获取主节点的信息。

    ![](4-redis使用/24151fd4648b290dae9b8e664208eafa.jpg)

    ### Sentinel配置

    - Redis 源码中包含了一个名为 sentinel.conf 的文件, 这个文件是一个带有详细注释的 Sentinel 配置文件示例。

    - 运行一个 Sentinel 所需的最少配置如下所示:

    ```sh
    1)sentinel monitor mymaster 192.168.10.202 6379 2
    Sentine监听的maste地址,第一个参数是给master起的名字,第二个参数为master IP,第三个为master端口,第四个为当该master挂了的时候,若想将该master判为失效,
    在Sentine集群中必须至少2个Sentinel同意才行,只要该数量不达标,则就不会发生故障迁移。
    值一般为:sentinel总数/2 +1 ,master才算真正失效
    2)sentinel down-after-milliseconds mymaster 30000
    表示master被当前sentinel实例认定为失效的间隔时间,在这段时间内一直没有给Sentine返回有效信息,则认定该master主观下线。

    只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线,将服务器标记为客观下线所需的 Sentinel 数量由对主服务器的配置决定。

    3)sentinel parallel-syncs mymaster 2
    当在执行故障转移时,设置几个slave同时进行切换master,该值越大,则可能就有越多的slave在切换master时不可用,可以将该值设置为1,即一个一个来,这样在某个

    slave进行切换master同步数据时,其余的slave还能正常工作,以此保证每次只有一个从服务器处于不能处理命令请求的状态。

    4)sentinel can-failover mymaster yes
    在sentinel检测到O_DOWN后,是否对这台redis启动failover机制

    5)sentinel auth-pass mymaster 20180408
    设置sentinel连接的master和slave的密码,这个需要和redis.conf文件中设置的密码一样

    6)sentinel failover-timeout mymaster 180000
    failover过期时间,当failover开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failoer失败。
    执行故障迁移超时时间,即在指定时间内没有大多数的sentinel 反馈master下线,该故障迁移计划则失效

    7)sentinel config-epoch mymaster 0
    选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步。这个数字越小, 完成故障转移所需的时间就越长。

    8)sentinel notification-script mymaster /var/redis/notify.sh
    当failover时,可以指定一个"通知"脚本用来告知当前集群的情况。
    脚本被允许执行的最大时间为60秒,如果超时,脚本将会被终止(KILL)


    9)sentinel leader-epoch mymaster 0
    同时一时间最多0个slave可同时更新配置,建议数字不要太大,以免影响正常对外提供服务。

搭建

  1. 从源码中复制sentinel.conf,这里建立二个哨兵sentinel1,sentinel2

  2. 通过修改配置不同端口以及其他信息,可以启动一个sentinel集群

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    port 26379
    daemonize yes
    pidfile /var/run/redis-sentinel-26379.pid
    logfile "26379.log"
    #工作路径,注意路径不要和主重复
    dir "/usr/local/redis/data"
    sentinel monitor mymaster 127.0.0.1 6379 2
    sentinel auth-pass mymaster 123456
    # master或slave多长时间(默认30秒)不能使用后标记为s_down状态。
    sentinel down-after-milliseconds mymaster 5000
    #若sentinel在该配置值内未能完成failover操作(即故障时master/slave自动切换),则认为本次failover失败。
    sentinel failover-timeout mymaster 18000
    #指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步,有几个slave就设置几个
    sentinel parallel-syncs mymaster 2
    1
    2
    3
    4
    5
    6
    7
    port 26380
    daemonize yes
    pidfile /var/run/redis-sentinel-26380.pid
    logfile "26380.log"
    dir "/usr/local/redis/data"
    sentinel monitor mymaster 127.0.0.1 6379 2
    sentinel auth-pass mymaster 123456
  3. 启动

    1
    2
    3
    4
    5
    [root@zy01 redis]# mkdir data
    [root@zy01 redis]# cd conf/
    [root@zy01 conf]# redis-sentinel sentinel1.conf
    [root@zy01 conf]# redis-sentinel sentinel2.conf

  4. 查看文件cat sentinel1.conf ,在最后出现了下面的一些内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # Generated by CONFIG REWRITE
    protected-mode no
    user default on nopass ~* &* +@all
    sentinel myid 95e50b7f9db387c25b0d0a33cdfe1744f52f67dc
    sentinel config-epoch mymaster 0
    sentinel leader-epoch mymaster 0
    sentinel current-epoch 0
    sentinel known-replica mymaster 127.0.0.1 6381
    sentinel known-replica mymaster 127.0.0.1 6380
    sentinel known-sentinel mymaster 127.0.0.1 26380 9c31e456f0bfd3fbad41dddaedfa64d2a11970e6
  5. 连接哨兵节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    [root@zy01 conf]# redis-cli -p 26379
    127.0.0.1:26379> ping
    PONG
    127.0.0.1:26379> info
    # Server
    redis_version:6.2.3
    redis_git_sha1:00000000
    redis_git_dirty:0
    redis_build_id:37744dab1e0d5d0c
    redis_mode:sentinel
    os:Linux 4.18.0-80.el8.x86_64 x86_64
    arch_bits:64
    multiplexing_api:epoll
    atomicvar_api:c11-builtin
    gcc_version:8.3.1
    process_id:22026
    process_supervised:no
    run_id:fbf4cf65e97342c3afacfc50e5b036fc456e0e90
    tcp_port:26379
    server_time_usec:1621866579936231
    uptime_in_seconds:308
    uptime_in_days:0
    hz:11
    configured_hz:10
    lru_clock:11253843
    executable:/usr/local/redis/conf/redis-sentinel
    config_file:/usr/local/redis/conf/sentinel1.conf
    io_threads_active:0

    # Clients
    connected_clients:2
    cluster_connections:0
    maxclients:10000
    client_recent_max_input_buffer:32
    client_recent_max_output_buffer:0
    blocked_clients:0
    tracking_clients:0
    clients_in_timeout_table:0

    # CPU
    used_cpu_sys:0.515450
    used_cpu_user:0.291560
    used_cpu_sys_children:0.000000
    used_cpu_user_children:0.000000
    used_cpu_sys_main_thread:0.511878
    used_cpu_user_main_thread:0.282033

    # Stats
    total_connections_received:3
    total_commands_processed:409
    instantaneous_ops_per_sec:2
    total_net_input_bytes:21986
    total_net_output_bytes:7010
    instantaneous_input_kbps:0.14
    instantaneous_output_kbps:0.02
    rejected_connections:0
    sync_full:0
    sync_partial_ok:0
    sync_partial_err:0
    expired_keys:0
    expired_stale_perc:0.00
    expired_time_cap_reached_count:0
    expire_cycle_cpu_milliseconds:7
    evicted_keys:0
    keyspace_hits:0
    keyspace_misses:0
    pubsub_channels:0
    pubsub_patterns:0
    latest_fork_usec:0
    total_forks:0
    migrate_cached_sockets:0
    slave_expires_tracked_keys:0
    active_defrag_hits:0
    active_defrag_misses:0
    active_defrag_key_hits:0
    active_defrag_key_misses:0
    tracking_total_keys:0
    tracking_total_items:0
    tracking_total_prefixes:0
    unexpected_error_replies:0
    total_error_replies:1
    dump_payload_sanitizations:0
    total_reads_processed:402
    total_writes_processed:400
    io_threaded_reads_processed:0
    io_threaded_writes_processed:0

    # Sentinel
    sentinel_masters:1
    sentinel_tilt:0
    sentinel_running_scripts:0
    sentinel_scripts_queue_length:0
    sentinel_simulate_failure_flags:0
    master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=2
  6. 通过哨兵查看集群状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    [root@alex redis-4.0.10]# ./src/redis-cli -p 26379
    127.0.0.1:26379> sentinel master mymaster
    1) "name"
    2) "mymaster"
    3) "ip"
    4) "172.16.0.169"
    5) "port"
    6) "6379"
    7) "runid"

    127.0.0.1:26379> sentinel slaves mymaster
    1) 1) "name" # slave 1
    2) "127.0.0.1:6381"
    3) "ip"
    4) "127.0.0.1"
    5) "port"
    6) "6381"
    7) "runid"
    8) "0419f313098f6af1b4ccdb189d6beb22edf27a1c"

    2) 1) "name" # slave2
    2) "127.0.0.1:6380"
    3) "ip"
    4) "127.0.0.1"
    5) "port"
    6) "6380"
    7) "runid"
    8) "5b00b502a93245f7916efd1f564bd40b16aa7b22"
  7. 模拟主节点down掉,观察sentinel的状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    # 模拟主down掉
    [zy@zy01 ~]$ ps -ef | grep redis
    root 21780 1 0 21:46 ? 00:00:03 redis-server 127.0.0.1:6379
    root 21786 1 0 21:46 ? 00:00:02 redis-server 127.0.0.1:6380
    root 21794 1 0 21:46 ? 00:00:02 redis-server 127.0.0.1:6381
    root 22026 1 0 22:24 ? 00:00:01 redis-sentinel *:26379 [sentinel]
    root 22032 1 0 22:25 ? 00:00:01 redis-sentinel *:26380 [sentinel]
    root 22039 15820 0 22:29 pts/2 00:00:00 redis-cli -p 26379
    zy 22044 21807 0 22:35 pts/1 00:00:00 grep --color=auto redis
    [zy@zy01 ~]$ sudo kill -9 21780

    # 查看节点
    127.0.0.1:26379> sentinel master mymaster
    1) "name"
    2) "mymaster"
    3) "ip"
    4) "127.0.0.1"
    5) "port"
    6) "6380" # 已经从6379切换到6380
    7) "runid"
    8) "0419f313098f6af1b4ccdb189d6beb22edf27a1c"

    # 相应的slave也做了切换
    127.0.0.1:26379> sentinel slaves mymaster
    1) 1) "name"
    2) "127.0.0.1:6379"
    3) "ip"
    4) "127.0.0.1"
    5) "port"
    6) "6379"
    7) "runid"
    8) ""
    9) "flags"
    10) "s_down,slave,disconnected"

    2) 1) "name"
    2) "127.0.0.1:6380"
    3) "ip"
    4) "127.0.0.1"
    5) "port"
    6) "6381"
    7) "runid"
    8) "5b00b502a93245f7916efd1f564bd40b16aa7b22"

日志分析

  • 打开哨兵的日志

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 该哨兵认为该节点已经主观宕机,也就是sdown
    5508:X 28 May 2021 16:09:30.105 # +sdown master mymaster 192.168.56.101 6379
    # 哨兵集群内有超过两个哨兵都认为master sdown了,因此转化成客观宕机,也就是odown。
    5508:X 28 May 2021 16:09:30.196 # +odown master mymaster 192.168.56.101 6379 #quorum 2/2
    # 递增集群状态版本号,这个版本号将被接下来选举出的新的master采用。
    5508:X 28 May 2021 16:09:30.196 # +new-epoch 1
    # 开始对IP为192.168.50.121,端口为6379,名为"mymaster"的Redis集群进行故障转移。
    5508:X 28 May 2021 16:09:30.196 # +try-failover master mymaster 192.168.56.101 6379
    # 在哨兵集群中投票选举出一个哨兵,作为本次执行故障转移操作的leader。 当前哨兵投票给自己满意的slave
    5508:X 28 May 2021 16:09:30.200 # +vote-for-leader c990635de9ed91282027c9ab9a3167ac4b4087b4 1
    # 另一个哨兵投票给自己满意的slave
    5508:X 28 May 2021 16:09:30.208 # 68ff8149e5eefeb2da9685172c5f01844a63480e voted for c990635de9ed91282027c9ab9a3167ac4b4087b4 1
    # 在哨兵集群中再次确认进行故障转移的leader是哪一个slave。
    5508:X 28 May 2021 16:09:30.285 # +elected-leader master mymaster 192.168.56.101 6379
    # leader开始在集群中寻找合适的slave。(从这里可以看出,找出新的slave不单单是通过投票,可能还和其它的因素有关。)
    5508:X 28 May 2021 16:09:30.285 # +failover-state-select-slave master mymaster 192.168.56.101 6379
    5508:X 28 May 2021 16:09:30.358 # -failover-abort-no-good-slave master mymaster 192.168.56.101 6379
    5508:X 28 May 2021 16:09:30.434 # Next failover delay: I will not start a failover before Fri May 28 16:15:30 2021
    5508:X 28 May 2021 16:15:30.388 # +new-epoch 2

主观下线和客观下线

  • 主观下线:指的是单个 Sentinel 实例对服务器做出的下线判断。
  • 客观下线:指的是多个 Sentinel 实例在对同一个服务器做出SDOWN主观下线判断。

Sentinel的工作原理

  1. 每个 Sentinel 以每秒一次的频率向它所知的主服务器、从服务器以及其他 Sentinel 实例发送一个 PING 命令。

  2. 如果一个实例距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 所指定的值,那么这个实例会被 Sentinel 标记为主观下线。

  3. 如果一个主服务器被标记为主观下线,那么正在监视这个服务器的所有 Sentinel 节点,要以每秒一次的频率确认主服务器的确进入了主观下线状态。

  4. 如果一个主服务器被标记为主观下线,并且有足够数量的 Sentinel(至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断,那么这个主服务器被标记为客观下线。

  5. 一般情况下,每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令。

    当一个主服务器被标记为客观下线时,Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率,会从 10 秒一次改为每秒一次。

  6. Sentinel 和其他 Sentinel 协商客观下线的主节点的状态,如果处于 SDOWN 状态,则投票自动选出新的主节点,将剩余从节点指向新的主节点进行数据复制。

  7. 当没有足够数量的 Sentinel 同意主服务器下线时,主服务器的客观下线状态就会被移除。

  8. 当主服务器重新向 Sentinel 的 PING 命令返回有效回复时,主服务器的主观下线状态就会被移除。

两种数据丢失的情况

  • 主备切换的过程,可能会导致数据丢失
    • 异步复制导致的数据丢失: old master node 中内存的数据没来得及给slave node就挂了,重新选举后的master会丢失这部分数据。
    • 脑裂导致的数据丢失:某个master所在机器突然脱离了正常的网络,跟其他slave机器不能连接,但是实际上master还运行着,此时哨兵可能就会认为master宕机了,然后开启选举,将其他slave切换成了master这个时候,集群里就会有两个master,也就是所谓的脑裂。此时虽然某个slave被切换成了master,但是可能client还没来得及切换到新的master,还继续写向旧master的数据可能也丢失了,因此旧master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空,重新从新的master复制数据

减少异步复制数据丢失

  • min-slaves-max-lag 10:数据复制和同步的延迟不能超过10秒
  • 如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了,上面两个配置可以减少异步复制和脑裂导致的数据丢失
  • 有了min-slaves-max-lag这个配置,就可以确保说,一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内
  • 客户端做降级处理,写到本地磁盘,client对外的接收请求做降级限流处理,减慢请求速度,或者将数据临时写入kafka消息队列,每隔10分钟去队列去除,尝试重新发送到master节点。

减少脑裂的数据丢失

  • min-slaves-to-write 1 :要求至少有1个slave

  • 如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置同时配置可以确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求

  • 这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失

  • 上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack,那么就拒绝新的写请求,因此在脑裂场景下,最多就丢失10秒的数据

redis.conf 配置文件

  • redis.conf 配置项说明如下:
  1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程, 在该模式下,redis会在后台运行,并将进程pid号写入至redis.conf选项pidfile设置的文件中,此时redis将一直运行,除非手动kill该进程。但当daemonize选项设置成no时,当前界面将进入redis的命令行界面,exit强制退出或者关闭连接工具(putty,xshell等)都会导致redis进程退出。
    daemonize no

  2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
    pidfile /var/run/redis.pid

  3. 指定Redis监听端口,默认端口为6379,为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
    port 6379

  4. 绑定的主机地址
    bind 127.0.0.1

  5. 当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
    timeout 300

  6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
    loglevel verbose

  7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null
    logfile stdout

  8. 设置数据库的数量,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
    databases 16

  9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
    save <seconds> <changes>

    • Redis默认配置文件中提供了三个条件:
      save 900 1
      save 300 10
      save 60 10000
    • 分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。
  10. 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
    rdbcompression yes

  11. 指定本地数据库文件名,默认值为dump.rdb
    dbfilename dump.rdb

  12. 指定本地数据库存放目录
    dir ./

  13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
    slaveof <masterip> <masterport>

  14. 当master服务设置了密码保护时,slav服务连接master的密码
    masterauth <master-password>

  15. 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码,默认关闭
    requirepass 123456

  16. 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
    maxclients 128

  17. 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
    maxmemory <bytes>

  18. 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
    appendonly no

  19. 指定更新日志文件名,默认为appendonly.aof
    appendfilename appendonly.aof

  20. 指定更新日志条件,共有3个可选值:
    no:表示等操作系统进行数据缓存同步到磁盘(快)
    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
    everysec:表示每秒同步一次(折中,默认值)
    appendfsync everysec

  21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)
    vm-enabled no

  22. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
    vm-swap-file /tmp/redis.swap

  23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0
    vm-max-memory 0

  24. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值
    vm-page-size 32

  25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。
    vm-pages 134217728

  26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4
    vm-max-threads 4

  27. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
    glueoutputbuf yes

  28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
    hash-max-zipmap-entries 64
    hash-max-zipmap-value 512

  29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)
    activerehashing yes

  30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
    include /path/to/local.conf

  31. Redis中的内存维护策略: redis作为优秀的中间缓存件,时常会存储大量的数据,即使采取了集群部署来动态扩容,也应该即使的整理内存,维持系统性能。在redis中有两种解决方案

    • 一是为数据设置超时时间,
    • 二是采用LRU算法动态将不用的数据删除。内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。
    1. volatile-lru:设定超时时间的数据中,删除最不常使用的数据.
    2. allkeys-lru:查询所有的key中最近最不常使用的数据进行删除,这是应用最广泛的策略.
    3. volatile-random:在已经设定了超时的数据中随机删除.
    4. allkeys-random:查询所有的key,之后随机删除.
    5. volatile-ttl:查询全部设定超时时间的数据,之后排序,将马上将要过期的数据进行删除操作.
    6. noeviction:如果设置为该属性,则不会进行删除操作,如果内存溢出则报错返回.
    7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
    8. allkeys-lfu:从所有键中驱逐使用频率最少的键