文章

redis简略概述

redis简略概述

redis简略概述

1.NoSql简介

NoSql: Not Only Sql; 不仅仅是sql,一般泛指非关系型数据库,如redis。

特点:

  1. NoSql数据库的存储不依赖业务逻辑,使用key-value的结构进行存储。这样的存储方式最大的优势就是具备很大拓展性

  2. 不支持ACID

  3. 不支持SQL标准

  4. 相比较与关系型数据库具备高性能

应用场景:

  1. 高并发的数据读写

  2. 海量数据的读写

  3. 具备时效性的数据存储

不支持的应用场景:

  1. 结构化数据的查询

  2. 牵扯到业务的事务操作

Redis简介:

Redis是开源的KV存储系统,支持多种数据类型的存储(针对value来说)。数据的存储是基于内存的,虽然数据存储在内存中,但是提供了持久化的方案(定期将数据写入磁盘)

2. redis编译安装

更推荐使用包管理器安装,此处只做示范

1. 准备c语言编译环境

1.1检查当前系统是否具备gcc

gcc -version

1.2 安装gcc(已存在则跳过此步骤)

1
2
3
  yum -y install gcc
  pacman -S gcc
  apt install gcc

1.3 再次检测

gcc --version

1.4 安装redis需要的开发包(centos)

1
2
3
4
  yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
  scl enable devtoolset-9 bash
  echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
  source /etc/profile

2. 安装redis

2.1 使用XFTP把安装包拉取到对应位置(/opt/software

2.2 解压安装包

tar -zxvf redis...tar.gz

2.3 进入解压后文件夹

cd redis

2.4 执行编译

make MALLOC=libc

2.5 执行安装

make install

2.6 安装后服务安装在user/local/bin

3. redis安装目录解读

redis-cli:客户端

redis-server:redis的服务脚本

redis-check-aof:持久化使用

redis-check-rdb:持久化使用

redis-sentinel:redis集群使用

4. 启动redis服务

包管理器安装建议直接systemctl enable redis

4.1 默认启动

redis-server:以默认的配置方式启动(不推荐) 以下是你提供的内容的格式化版本:

4.2 自定义配置文件启动

1. 复制配置文件

默认配置文件的位置在 /opt/software/redis/redis.conf,使用以下命令复制配置文件:

1
  cp redis.conf /usr/local/bin

2. 修改配置文件

打开 redis.conf 文件,找到并修改以下配置项以启用后台启动:

1
  daemonize no  # 将 no 修改为 yes,即可启用后台启动

3. 根据配置启动 Redis

使用修改后的配置文件启动 Redis:

1
2
  1) redis-cli shutdown
  2) redis-cli -p 6379 shutdown 关闭指定端口号的redis服务  redis-server /usr/local/bin/redis.conf

5. 关闭服务

1
2
  1) redis-cli shutdown
  2) redis-cli -p 6379 shutdown 关闭指定端口号的redis服务

3. Redis基础知识

  • redis是单线程的(但是redis是单线程的多路IO复用)

  • redis一共有16个库,0-15

  • 默认情况使用第一个库(0)

  • redis的账户和密码可以管理所有的库

4. Redis数据类型

1. Key

常用指令:

1
2
3
4
5
6
7
8
9
10
11
  keys * :查看当前库所有的key
  type key :查看具体的key对应的value类型
  del key... :删除指定的key
  exists key :判断key是否存在;0:不存在 1.存在
  ttl key :查看key的剩余有效时间;-1:永不过期 -2:已经过期或不存在 n:剩余有效时间
  expire key seconds :给指定的key设置有效时间(单位为秒)
  dbsize :获取当前库所有的key的数量
  select n :切换库(0-15)
  flushdb :清空当前库
  flushall :清空所有库
  clear :清屏

2. 字符串(String)

String是redis中最基本也是应用最多的数据类型。此String类型其实就是一个二进制并且数据安全的数据类型,也就说明了Redis中的String可以转储任何文件和数据。但是Redis中的String类型有最大存储限制。最大的单元室为512MB。

常用指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  set <key> <value>:添加一组kv数据。(如果key已经存在则是覆盖)
  get <key> :获取指定的key对应的value
  append <key> <value> :在value原有的基础上进行追加操作
  setnx <key> <value> :如果key不存在则是添加,存在则操作失败,不会进行覆盖行为。
  strlen <key> :获取key对应的value长度
  incr <key> :把当前key对应的value数值+1
  decr <key> :把当前key对应的value数值-1
  PS:以上两个操作只对数字有效,如果是非数组则执行运行出错
  incrby/decrby <key> <step>:同样是自增自减,但可以设置步长
  
  mset <key> <value> [<key> <value>...]:批量添加
  mget <key> [<key>...]:批量获取
  msetnx <key> <value> [<key> <value>...]:批量的mset操作
  
  setex <key> <seconds> <value>:添加kv数据的同时并设置有效时间(单位秒)

3. 列表(List)

就是一个key对应一组内存空间。站在使用的角度来说就是一个key对应一个value(数组/双向链表),不过这个value中包含了多个字符串(element) 底层使用的是数组/双向链表

常用指令:

1
2
3
4
5
6
7
  lpush/rpush <key> <element...>:从左边/右表插入一条或者多条数据。(如果key已存在则追加)
  lrange <key> <start> <stop>:从坐标开始获取(0表示第一个坐标 -1表示最后一个坐标)
  lpop/rpop <key>:从左边/右边弹出一条数据(既是获取也是删除)。所有的element都弹出后key消失
  lindex <key> <index>:获取key对应的列表指定索引位置的数据
  lrem <key> <count> <element>:从坐标删除指定数量的element(如 lrem list 1 laowang)
  lset <key> <index> <element>:替换指定索引位置的值
  lisert <key> [before|after] <element> <newelement>:在指定的key对应的value的element前(before)/后(after)添加新数据

4. 集合(Set)

set提供的功能和List类似。区别就是Set具备了自动去重的功能。Set本质上就是一个String类型的无序集合(对于插入顺序来说)。底层使用hash表。

常用指令:

1
2
3
4
5
6
7
8
9
10
11
  sadd <key> <member>... :将一个或多个member元素放到集合中,已经存在的数据会被忽略
  smembers <key> :获取指定key对应集合中所有的元素
  sismember <key> <member> :判断集合中是否存在某个值 返回:1.存在  0.不存在
  scard <key>:返回集合中元素的个数
  srem <key> <member>... :删除集合中一个或者多个元素
  spop <key> [count] :随机从集合中弹出一个或者多个元素。(即使获取也是删除,集合中所有的元素都弹出后集合本身也会删除)
  srandmember <key> [count] :随机从集合中获取一个或多个元素(不会删除元素)
  smove <source> <destination> <member> :从指定集合中将元素移动到目标集合中
  sdiff <key> <key> :计算两个集合的差集元素
  sunion <key> <key> :计算两个集合的并集元素
  sinter <key> <key> :计算两个集合的交集元素

5. 有序集合(Zset)

Zset和Set基本一样,都是一个不可重复的字符串的集合。区别就是set是无序的,而Zset每个元素都关联着一个评分的值,这个评分值会被元素进行排序。元素不能重复但评分可以重复。

常用指令:

1
2
3
4
5
6
  zadd key <score member> [score member ...] :将一个或者多个member+score添加到有序集合中。如果member已存在则更改member对应的score
  zrange <key> <start> <end> [withscores] :获取坐标start到坐标end的所有member,携带withscores则把对应的评分一并展示。
  zrangebyscore <key> <minscore> <maxscore> [withscores] [limit offset size] :获取指定评分范围内的数据。可以进行分页展示
  zcount <key> <minscore> <maxscore> :获取指定区间内的元素的个数
  zrem <key> <member> :删除指定的元素
  zrank <key> <member> :获取指定元素在集合中的排名(从0开始计算)

6. 哈希(Hash)

hash也是一组kv结构,hash是一个string类型的filed和value的映射表。比较适合存储对象。类似于Java中的Map<String,Object<String,String>>

常用指令:

1
2
3
4
5
  hset <key> <filed> <value> [<filed> <value>...]:来添加数据
  hkeys <key> :获取key对应hash中所有的filed
  hvals <key> :获取key对应hash中的所有value
  hget <key> <filed> :获取key对应的hash中filed对应的value
  hexists <key> <filed> :判断key对应的hash中是否存在指定的filed

5. 数据类型底层结构

1. String底层结构

redis中的String有三种编码类型。int,embstr,raw三种。在不同情况下会自动切换

String的底层结构是一个动态字符串(SDS),在数据编码为embstr,raw的时候底层使用的就是SDS

1
2
3
4
5
  SDS结构 {
    int len;
    int free;
    char buf[];
  };

len:记录buf数组中已经使用的字节的数量。也就是SDS结构体存储的字符串长度。

free:记录buf数组中未使用的字节长度。

buf[]:字节数组,用来保存实际的字符串。

当存储字符串的字节长度小于等于48时使用embstr,大于48则使用raw。

问题:为什么不使用C语言本身的字符串类型进行存储而选择构建一个动态字符串(SDS)?

  • 防止缓存区溢出

    字符串使用时存在扩容问题。而C语言在扩容字符串的时候需要考虑缓冲区是否足够。但是C语言关于内存没有自动回收机制,所以在扩容时可能出现一处错误。SDS结构体提供了API来帮助判断并完成扩容。

  • 获取字符串长度方便

    C语言中无法直接获取字符串的长度,需要对字符串整体进行遍历。所以获取C语言的字符串长度的时间复杂度为O(n),而SDS结构可以使用len属性获取字符串长度,时间复杂度为O(1)。

  • 减少内存分配的次数

    所谓的扩容就是申请更大的内存空间,然后把旧数据拷贝到新的内存空间中,并释放旧的内存空间,反之缩小也是一样的。而SDS结构就可以大量的减少内存分配和释放,提高效率。

  • 二进制安全问题

    C语言中可以使用空字符作为字符串结尾的标识。而一些二进制文件的内容(图片,视频,音频)大概率会出现空字符,这样的特性就导致C语言本身的字符串无法存储二进制文件数据。而SDS是以二进制的方式进行存储数据。字符串的结束时以len的属性值作为标识。

2. List底层结构

底层数组结构:

3.2之前使用ziplist,而后续版本使用的都是quickList

  • 压缩列表(zipList)
    此数据类型的结构类似于数组,和数组的区别在与在表头添加了三个字段zlbytes,zltail,zllen以及在尾部添加了一个zlend表示列表数据。

zlbytes:内存的字节数 zltail-offser:列表尾的偏移量(用于定位value的结束) zllen:列表元素的个数 zlend:列表结束的标识

因为是一组连续的内存空间,除了上述四个组成部分外其他都是Node节点。

1
2
3
4
5
  Node结构{
    previous_entry_length:
    encoding:当前节点的数据类型和长度
    content:当前节点的值
  }
  • 快速列表(QuickList) 快速列表就是ZipList+双向链表。在宏观的角度上来说就是每个双向链表的节点都挂载了一个ZipList

为什么使用QuickList? ZipList在数据量较大的时候性能会急剧下降,且数据量较大时很难分配连续的内存空间。

不使用双向链表

解决方案:

使用双向链表可以占用大量的碎片化内存空间,每一个碎片化的内存空间再组成一个ZipList。这样的设计既解决了连续内存空间的问题也解决了数据量大的时候ZipList性能低下的问题。

使用双向链表

3. Set底层结构

Set底层使用IntSet或者HashTable进行存储

Set集合满足以下两个条件的时候才会使用IntSet

  1. 所有的元素都是整数数值

  2. 元素的个数不超过512个

1
2
3
4
5
  inset(整型数组){
    uint32_t encoding;
    uint32_t length;
    int8_t contents[];
  }

encoding:决定了contents数组存储的整数的数据类型,可选值为NTSET_ENC_INT16,NTSET_ENC_INT32,NTSET_ENC_INT64

length :数组的长度

contents :用于存储数据的数组,并且从小到大排序

当存储的数据不满足上述两个条件的时候则使用HashTable进行存储

4. Zset底层结构

数据结构

ZipList或者SkipList

当满足以下条件的时候使用ZipList

  • 元素的数量小于128

  • 元素的长度小于64

其他情况都使用SkipList

1
2
3
4
  SkipList{
    ZipList;
    HashTable;
  }

ZipList:用于快速地方位查找

HashTable:在查找的时候定位到具体的ZipList,快速定位到单个元素

底层真正的实现是跳表(SkipList)

5. Hash底层结构

数据结构

ipList或者HashTable

当同时满足以下条件的时候使用ZipList

  • hash对象中所有键值对(filed和value)值的长度都小于64字节

  • hash对象的键值对数量小于512个

其他情况都使用HashTable储存

6. Redis配置文件

1. 度量单位

在配置文件开头到INCLUDES之间的配置。定义了redis中支持的最基本的度量单位bytes。并且列举了各个单位之间的转换。同事记得redis的度量单位是不区分大小写的。

2. INCLUDES

类似于JSP中的include,在同一台机器上可以配置多个redis示例,此时可以把多个redis实例的公共配置提取出来,然后在各个实例配置使用INCLUDES导入公共配置部分。

范例: include /path/to/other.conf

3. NETWORK

  1. bind

默认的配置是bind 127.0.0.1,此配置表示只能接受本地的访问请求

如果注释掉就表示无限制接收任何IP地址的访问。

生产环境下必须写成应用服务器所在的IP地址

  1. protected-mode

默认配置是protected-mode yes,表示redis只会响应本机的访问请求,因为此种特性此配置也称之为本地访问保护模式,一般设为no

  1. port

端口号,默认为6379

  1. tcp-backlog

在高并发的情况下可以提高此配置的值来解决慢客户端连接问题

  1. timeout

一个空闲的客户端维持多久会关闭。0表示永不关闭,在生产环境下一般会修改

  1. tcp-keepalive

对访问客户端的一种心跳机制。每多少秒检测一次,设置为0表示不进行心跳检测。一般设为60。

4. GENERAL

  1. daemonize

是否设置为后台启动。一般为yes

  1. pidfile

运行pid信息存放的位置

  1. loglevel

设置redis的运行的日志级别。支持四种日志的级别配置(debug,verbose,notice,warning)

生产环境一般配置为warning

  1. databases

设置redis中库的数量,默认为16

5. SECURITY

requirepass foobared

设置redis的密码

6. 客户端

maxclients 10000

设置客户端的最大连接数,默认为10000,超过连接部分会拒绝连接并返回“超过最大连接数量”的提示信息

7. MEMORY MANAGEMENT(内存管理)

  1. maxmemory (最大内存)

生产环境下必须配置,否则会造成内存打满导致服务器宕机

配置是配置redis能占用的最大内存

设置了此配置后,一旦内存的使用达到阈值则会进行移除数据的行为。具体移除数据的策略是由maxmemory-policy的配置决定的。如果占用内存达到阈值后进行移除数据的时候无法通过maxmemory-policy配置进行数据移出,那么redis会对set,sadd,lpush等等需要添加数据的指令进行报错并且返回错误信息。但是不影响其他指令的运行。

  1. maxmemory-policy(数据移除策略)

可选配置:

volatile-lru 使用LRU算法移除key。但是只对设置了有效时间的key生效。(最近最少使用)

allkeys-lur 使用LRU算法移除key。对所有的key生效。(最近最少使用)

volatile-lfu 使用LFU算法移除key。但是只对设置了有效时间的key生效。(最近最少使用)

allkeys-lfu 使用LFU算法移除key。对所有的key生效。(最近最少使用)

volatile-ttl 根据TTL的值进行移除。

noeviction 默认配置。不进行移除。针对于写操作返回错误信息。

LRU:基于访问时间顺序

LFU:基于访问频率

  1. maxmemory-samples

设置LRU算法,LFU算法和TTL算法需要的样本数据。一般推荐设置为3-7之间,默认为5,5基本就是最佳配置。样本数量越大算法结果越精准但是效率越低。样本越小越不精准效率越高。

7. 发布和订阅

redis的发布订阅是一种简单的消息通信模式。发送者(pub)负责发送消息,订阅者(sub)负责接受发送者的消息。订阅者可以订阅任意数量的频道。

测试案例:

  1. 订阅者订阅频道

SUBSCRIBE <pub>...

  1. 发送者把消息推送到频道中

PUBLISH <pub> <message>

8. Redis事务

Redis事务是一种单独的隔离操作。事务中所有的指令都会排序,按照排序有序执行。事务在执行过程中不会被其他客户端的发送指令请求打断

总结来说redis的事务的作用就是串联多个指令,防止其他指令插队。

回滚机制:

  1. 在指令组队阶段,如果某条指令出现了语法错误那么整个队列在exec阶段都会执行失败。(语法错误会导致事务整体回滚)

2.在指令组队阶段没有语法错误但是出现了执行错误。那么只会造成错误语句无法执行但是不影响其他指令的执行(执行错误只影响本身,不会造成事务整体回滚)

9. 持久化

持久化:随着计算机开关机数据不会丢失

磁盘(ROM):具备持久化的能力

内存(RAM):不具备持久化的能力(电脑重启或关机,内存中的数据会全部清空)

Redis中提供了两种持久化方案:AOF和RDB

RDB:Redis DataBase:存储真实的数据

AOF:Append Of File:存储操作的指令

通过测试得知Redis默认是开启持久化的

1. RDB

1. 简介

RDB就是在指定的时间间隔内将内存中数据快照写到磁盘中,也就是snapshot数据快照。关机或者重启电脑后重新启动redis服务RDB就会恢复数据,此种持久化恢复时间速度快。

2. 持久化流程

在满足RDB的持久化策略的时候,Redis会创建一个子进程进行持久化工作,子进程会将数据写到一个临时文件中,当所有的数据都写入完成后,再使用这个临时文件去替换上一次已经持久化完成的文件。在整个持久化的过程中主线程(Redis)不会进行任何IO操作,这样就能保证主进程的性能。

如果需要进行大规模数据恢复业务并且对数据完成性要求不高的情况下那么RDB比AOF更高效。但是RDB的缺点就是最后一次持久化的数据可能会丢失。(如在持久化时断电)

RDB在Redis中是默认开启的。(且无法关闭,后面会叙述原因)

3. 使用RDB

  1. 解读配置文件中关于EDB的部分

dbfilename dump.rdb RDB持久化文件的名字

dir ./ 设置持久化文件所在的目录

  1. 持久化生效的方式
  • 满足持久化的策略配置

save 900 1 15分钟至少一个key发生改变

save 300 10 5分钟至少10个key发生改变

save 60 10000 一分钟至少10000个key发生改变

  • 手动进行持久化

save 使用save指令进行持久化时会阻塞主线程的行为,在save进行客户端无法连接redis主线程,等save操作完成后,redis主线程才开始投入工作吗,此时客户端才可以连接。

bgsave 复制了一个save子进程,此时再执行save指令不会影响到主进程。客户端也可以正常连接和操作redis。当子进程执行完毕后会通知主线程,子进程自动关闭。所以一般推荐使用此指令。

  • 指令顺带持久化

shutdown 关闭redis服务,在关闭服务前先进行持久化

flushall 也会进行持久化,会产生一个空的持久化文件(dump.db)

4. 其他配置项

stop-writes-on-bgsave-error yes 当Redis无法持久化的时候直接关闭Redis的写操作。推荐yes。

rdbcompression yes redis的数据进行持久化的时候是否需要压缩。压缩使用LZF算法。会消耗一定的CPU。

rdbchecksum yes redis的数据进行持久化耳朵时候是否进行数据校验。校验使用CRC64算法。开启会丢失Redis10%左右的性能

2. AOF

简介

AOF是以日志的方式记录每一个写的操作。当满足AOF持久化策略的时候会将记录的指令全部追加到文件中。操作不会记录数据,使用AOF恢复数据时,Redis会读取AOF文件,然后将其中的指令全部执行一遍。

持久化流程

  1. 客户端所有的写操作都会追加到AOF缓存区中

  2. AOF缓存区根据AOF的持久化策略将缓存区中保存的写操作指令通过一个同步函数写入到磁盘中的AOF文件

  3. AOF文件本身具备重写策略,所谓的重写策略就是对AOF文件的压缩

  4. Redis重启后会加载AOF文件并进行数据恢复(执行AOF文件中的指令)

使用AOF

默认情况下AOF是不开启的,如果开启了AOF的话那么redis默认的持久化采用AOF。

  1. 开启AOF

appendonly yes 表示开启AOF

appendfilename "appendonly.aof" AOF文件的名字

  1. AOF的持久化策略

appendfsync always 客户端每次写操作都会调用一次同步函数,将缓存区的指令写入到AOF文件中。这种持久化策略可以保证数据一定不丢失。但是速度慢。

appendfsync everysec 默认选项。每秒调用一次同步函数。将缓存区内的指令写入到AOF文件中。这种持久化策略可能会丢失最近一秒的数据。但是效率比always更快。

appendfsync no Redis服务不会在主动调用同步函数。而是由系统决定什么时候调用。这种持久化策略是不可控的。所以也不能保证丢失数据的数量。

3. 总结

RDB的优缺点

优点:RDB文件存储的是数据。当出现大规模数据恢复的时候效率比较快。

缺点:

  1. 可能造成大量数据丢失

  2. 子进程在持久化的时候会读取内存中的数据,为了不影响主进程的运行会对内存中的数据进行copy,此时计算机就要应对内存数据的两倍膨胀。

AOF的优缺点:

优点:

  1. 持久化策略比较完善,丢失数据的概率小。

  2. AOF文件具备可读性,可以通过AOF文件回溯误操作。

缺点:

  1. 相比较于RDB来说需要更多的磁盘空间。

  2. 恢复速度慢。

  3. 常用的持久化策略需要经常进行同步操作,有性能压力。

  4. 无法单独使用AOF,假如本身出现bug,会导致数据无法恢复。

官方建议两者都开启混合使用。

10. 主从复制

1. 简介

简介:将一台redis服务器上的数据复制到其他服务器,数据来源的节点称之为主节点(master),数据的接受节点称之为从节点(slave)。是一种Redis的集群模式,也是读写分离的体现。

特点:

  1. 数据复制是单向的,只能从主到从。

  2. master以写为主,slave只负责读。

  3. redis服务器启动的时候, 如果不进行任何设置,每台redis服务重启都是一个master,需要通过设置修改此身份为slave。并且任何一个master都可以设置多个slave,但是slave只能有一个master。

总结来说,使用多态redis服务器同时工作,降低每台服务器的工作压力,从而提升整个集群的效率,同时具备了读写分离的能力。

2. 为什么要使用主从复制

  1. 单机redis 的缺点

1) 服务器宕机(损坏造成的宕机)直接导致数据全部丢失

2) 内存峰值问题,任何一台机器内存都会达到极限,无法无限升级。

  1. 主从复制的有点

1) 数据冗余:实现了数据的热备份,在极端情况下,部分服务器损坏依然可以恢复数据,是持久化以外的另一种数据冗余方式

2) 故障恢复:当主节点(master)出现问题的时候,可以由从节点提供服务。实现了故障的快速恢复。这就是一种服务冗余。

3) 读写分离:master负责写,slave负责读,master节点会定时的把最新的数据转递给slave,保持数据的一致性。并且可以根据业务的需求快速的动态的添加从节点的数量。

4) 负载均衡:基于读写分离的特性,不同的服务器负责不同的操作。减轻每台服务器的压力。

5) 高可用的基础:后面的哨兵模式和标准集群都依赖于主从复制

3. 搭建主从复制

单机多服务:

  1. 创建文件夹存放不同服务的配置文件

cd /usr/local/bin

mkdir redisconfs

  1. 复制redis配置文件到此目录下

  2. 关闭AOF

  3. 配置三个独立的redis配置文件

vim redis6379.conf

1
2
3
4
  include ./redis.conf
  port 6379
  pidfile /var/run/redis6379.pid
  dbfilename dump6379.rdb

剩余两个文件依法炮制

  1. 启动三个redis服务器

redis-server redis6379.conf

redis-server redis6380.conf

redis-server redis6381.conf

  1. 查看当前服务器状态

redis -cli -p 6379/6380/6381 进入不同的redis服务的客户端

info replication 查看服务器的状态

  1. 设置主从关系

slaveof 127.0.0.1 6379 为本服务器设置master地址

多机多服务:

192.168.1.125 redis

192.168.1.126 redis

192.168.1.127 redis

1.启动三台服务器中的Redis服务

2.将其中两台作为从节点

IP126和127的作为从节点

slaveof 192.168.1.125 6379

4. 主从复制的工作原理

主从复制流程分为三个阶段:

  1. 建立连接过程:slave和master连接

  2. 数据同步过程:master把数据同步给slave

  3. 命令传播过程:重复的同步数据

建立连接的底层流程:

  1. slave发送指令 slaveof ip port

  2. master收到指令并响应slave

  3. slave保存master的ip端口

  4. slave根据保存的master信息建立socket

  5. slave建立socket后定期发送率ping命令

  6. master响应pong

  7. slave发送指令:auth password

  8. master进行校验并且授权

  9. slave发送指令:replconf listening-port port

  10. master保存slave的端口号

数据同步的底层流程:

  1. slave发送指令:psync

  2. master接受到指令后先执行bgsave

  3. 如果本次是第一个slave的首次连接,master会创建命令缓存区

  4. 第2步会产生一个rdb文件,首先会清空本地数据,执行rdb文件恢复数据

  5. slave接受到了rdb文件。首先会清空本地数据,执行rdb文件恢复数据

  6. slave使用rdb文件恢复数据后会告知master

  7. master把命令缓存区的数据(写的指令)发送给slave

  8. slave接收到命令缓存区的数据后执行bgwriteaof从而恢复数据

1-5 全量复制(只在建立连接时执行一次)

6-8 增量复制 (重复执行)

命令传播过程:

当master节点的数据发生改变,此时主从数据库出现了数据不一致的问题。而命令传播就是保证主从数据的一致性

  1. 当master执行的指令对数据发生了改变

  2. master把指令发送给slave

  3. slave执行指令,保证数据一致性

(上面的6-8步骤)

redis的心跳机制

在主从复制的底层工作流程中,主节点和从节点一直进行消息交换。redis在此处使用心跳机制进行维护。

master心跳

指令:ping

时间:默认10S一次。可以通过配置文件修改

slave心跳

指令:replconf ack offset

时间:每秒一次

重要作用:发送最新的偏移量,从而获取最新的数据

主从复制的特点:

  1. 当从服务器宕机重启后,从服务器不会回到以前的体系中,变成了一台和以前体系没有关系的master

  2. 基于上面的理论,如果手动恢复重启的服务的从节点身份,那么主从之间就会进行一次全量复制

  3. 当主服务器宕机后,所有的从服务器还是从服务器,已然归属于已经宕机的主服务器。宕机的主服务器重启上线后依然是主服务器。

11. 哨兵模式

在主从复制的部署模式下。如果主服务器宕机那么就需要人工干涉才能让整个架构正常使用,那么就需要引入哨兵模式把人工干涉变成自动。

1. 简介

哨兵模式也是redis集群部署中的一环。redis创建一个哨兵进程,哨兵进程是独立运行。烧饼的作用就是监控整个redis集群中各个实例的运行状态。当监控到主服务器宕机的时候会根据配置从从服务器中选举新的主服务器。

2. 哨兵的运行原理

哨兵会顶级的检测整个redis集群的实例是否在线,当某个哨兵A检测到主从服务器宕机的时候,并不会立马对整个集群进行failover(故障转移),仅仅是哨兵主观的认为master宕机,这种情况被称为主观下线(sdown)。当后续其他哨兵也检测到master宕机后,也会认为是主观下线。但当主观下线达到一定数量时,整个哨兵体系就会认为master已经宕机,这就是客观下线(odown)。此时哨兵内部就会进行投票选择哨兵首领,然后由哨兵首领进行failover。从slave中选择新的master,并给所有的slave发送指令,让所有的slave执行slaveof <新master ip=""> <新master port="">

哨兵集群模式(sentinel cluster mode)

3. 哨兵的配置文件

sentinel monitor mymaster 127.0.0.1 6379 2

配置了监控的master的名字和IP和端口号。以及几个主观下线变为客观下线

sentinel down-after-milliseconds mymaster 30000

哨兵每隔一秒就给所有的redis实例发送ping指令。30s没有响应则此ping指令就认为主观下线

sentinel failover-timeout mymaster 180000

哨兵首领进行故障转移的超时时间,如果在此期间未能完成选举则视为故障转移失败

4. 哨兵模式实现

  1. 编写配置文件

vim sentinel.conf

sentinel monitor mymaster 127.0.0.1 6379 1

  1. 启动哨兵

redis-sentinel sentinel.conf

  1. 模拟master,slave下线 上线,查看哨兵日志变化

5. 选举策略

当master客观下线后,哨兵首领会根据以下三种策略选举新的master

  1. 选择优先级最高的(replica-priority 100 数字越小优先级越高)

  2. 选择偏移量最大的(简单来说就是slave从原master那边复制的数据一致性最高的)

  3. 选择runid最小的

当原master重新上线后,不成为独立的master,则会成为新master中的slave。 当slave宕机重新上线后,会归顺到原体系

12. redis-cluster集群

在哨兵集群模式下,从节点的水平拓展问题无法解决。单台服务器的内存也无法无限扩容。所以需要cluster集群替代哨兵集群。此集群自带哨兵。

在生产环境下最低配的cluster集群需要6台机器。

12.1. 集群的数据分布

redis-cluster集群的数据分布没有采用常见的一致性哈希算法,而是使用哈希槽。整个redis集群一共有16384个槽位(0-16383)。而决定数据分配到哪个槽位的算法是:使用CRC16算法对key值进行计算,然后计算结果取模16384。

在整个集群中每个master负责一部分槽位,当集群进行水平拓展的时候,会重新计算每个master管理的槽位。

加入目前有三台master,哈希槽分配如下:

masterA:0-5460

masterB:5461-10922

masterC:10923-16383

12.2 搭建三主三从的集群

1. 创建redis-cluster-conf目录

2. 在此目录下又创建了6个目录(redis6379,redis6380)

3. 在每个目录下再创建同名的配置文件

touch redis6379.conf

4. 编写每个配置文件的配置内容

1
2
3
4
5
6
7
8
9
10
port 6379
daemonize yes
protected-mode no
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 5000
dir ./
pidfile /var/run/redis_6379.pid
logfile redis_log.log
appendfilename "appendonly6379.aof"

5. 启动所有的redis实例

redis-server redis6379.conf

6. 执行集群指令

redis-cli –cluster create –cluster-replicas 1 1.1.1.100:6379 1.1.1.100:6380 1.1.1.100:6381 1.1.1.100:6382 1.1.1.100:6383 1.1.1.100:6384

  • --cluster-replicas 1 集群中每个主节点分配一个从节点

  • 如果公司中redis是有密码。那么使用 -a 选项

  • 查看集群状态的指令
    redis-cli --cluster check <ip> <port> 任意节点的ip端口

  • 使用集群客户端进行测试
    redis-cli -c -p <port> 根据key的最终归宿可以进行自动重定向

12.3 测试cluster集群的哨兵

1. 测试从节点宕机

从节点宕机后再上线会重新回到原体系中。因为集群配置文件的存在。

2. 测试主节点宕机

主节点宕机后,会从宕机的主节点中的从节点选举一个上位。接手原主节点的管理槽位。当宕机的原主节点再上线后,会作为从节点回到原体系中

12.4 节点的动态操作

1. 添加主节点

1.1. 添加一个新的redis实例
1.2 把新启动的实例加入到集群体系中

`redis-cli –cluster add-node <新实例ip>: <集群中任意节点ip>:

1.3 将其他三个主节点管理的哈希槽分配一部分给新主节点

`redis-cli –cluster reshard <新实例ip>:

(首先输入管理槽位的数量,然后再输入新实例的ID。最后输入槽位来自于哪几个节点ID(可以输入all表示从各个主节点中分配一部分给当前的新主节点))

#### 2. 添加从节点

2.1 添加一个新的redis实例
2.2 把新实例加入到集群中

`redis-cli –cluster add-node –cluster-slave <新从节点的ip>: <集群中任意节点ip>: --cluster-master-id <指定加入主节点的ID>

#### 3. 删除从节点(踢出集群)

`rddis-cli –cluster del-node <删除从节点ip>: <删除从节点id>

#### 4. 删除主节点 (踢出集群)

4.1 把哈希槽分配出去

redis-cli --cluster reshard <接受删除节点哈希槽的节点ip>:<port>

(首先输入要删除的主节点管理位的个数。然后输入接受节点的ID。然后输入删除节点的ID。最后输入done)

4.2 删除主节点

`redis-cli –cluster del-node <删除主节点ip>: <删除主节点ID>

上述指令可能会出现以下错误:

Node 1.1.1.100:6385 is not empty...

原因:当前节点有负责管理的哈希槽。删除前需要把哈希槽分配出去

5. 平衡各主节点负责哈希槽数量

redis-cli --cluster rebalance <任意节点的ip>:<port>

哈希槽数量为什么是16384
为什么最少是三主三从

本文由作者按照 CC BY 4.0 进行授权