Redis7学习的提前准备
- 拥有linux环境 版本为centos7(7.0以上8以下皆可,本人采用阿里云服务器)
- 有linux基础,会基本使用
- 本笔记根据尚硅谷阳哥的b站redis7课程边学边写(感谢尚硅谷,感谢阳哥)
- 保证linux有正常的网络环境
B站课程视频地址
1 初识redis(该段建议背诵)
Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库,其具备如下特性:
- 它基于内存,运行效率高
- 它支持分布式,理论上可以无限扩展
- 它以K V键值对的形式存储信息
- 它支持持久化
- 它包含多种数据类型
- 它处理并发读写极为优秀
我们一般把它放在mysql前面帮我们处理一些并发量大的查询操作
为什么选择学习Redis7而非Redis6?
因为redis6被爆出有非常严重的安全bug,redis紧急发布了redis7,redis7中修复了该漏洞!
2 Redis 的下载与安装
下载
redis所有版本的官方下载地址,我采用redis-7.0.0.tar.gz
安装
本人不推荐使用xftp以及他们家的任何产品,该产品可能利用后门上传你的服务器信息(本地linux虚拟机无须担心)。
因为本人使用阿里云的服务器。所以使用的阿里云客户端
。
选择哪种服务器连接工具对课程学习影响不大,使用其他连接工具的小伙伴也可以放心食用
自行选择下载
该工具的安装登录不再介绍,请根据上方阿里云官网链接的指示自行学习。
上传Redis7安装包
上传下载好的redis安装包
配置gcc环境
使用ssh登录root用户
因为Redis依赖于c语言编译环境,所以我们需要安装gcc编译器
linux查看gcc环境(阿里云自带gcc环境)
gcc -v
显示下图内容表示gcc环境正常(没有gcc环境请自行yum安装,版本为4.8.5)
本文默认各位已经有linux基础,如果没有,请自行学习linux
没有环境无法安装Redis!
安装Redis
markdown
复制代码1. 进入opt文件夹
cd /opt/
2. 解压redis7压缩包
tar zxvf redis-7.0.0.tar.gz
3. 进入解压好的文件夹
cd redis-7.0.0
4. 编译并安装我们的redis
make && make install
等待后,出现下图中的提示表示安装成功
Redis相关目录的简介
1 Redis的默认安装位置为 /usr/local/bin
(其中存放redis相关指令)
2 /usr/local/bin
中的指令介绍
sql
复制代码 redis-benchmark
(redis的性能测试工具)
redis-check-aof
(redis对于持久化后aof文件的修复工具,暂时不多赘述)
redis-check-rdb
(dump.rdb文件的修复工具,暂时不多赘述)
redis-cli
(redis客户端的登录工具,我们使用它登录redis,参考mysql)
redis-sentinel
(redis集群使用,暂时不多赘述)
redis-server
(redis服务端启动命令,我们使用它启动redis服务)
3 Redis目录/opt/redis-7.0.0
介绍 (我们解压安装包后得到的那个文件夹)
scss
复制代码 Makefile (安装文件,给gcc用来编译安装)
redis.conf (redis配置文件)
src (redis源代码)
配置Redis
个人建议将我们linux中的各个常用软件的配置文件进行备份
阳哥这里选择备份redis.conf文件,然后修改并使用备份的文件。
本人选择将文件备份后,修改原文件使用,下面为本人代码
1 备份原配置文件
bash
复制代码创建文件夹用于存放备份的配置文件
mkdir /root/oldConf`
备份
cp /opt/redis-7.0.0/redis/redis.conf /root/oldConf/
2 配置redis.conf
注意,这里我们选择修改/opt/redis-7.0.0/redis/redis.conf
我们待会也是使用该配置文件启动Redis服务
bash
复制代码vim编辑器打开配置文件
vim /opt/redis-7.0.0/redis/redis.conf
设置显示行号
:set nu
找到指定行 :309
或者搜索 /daemonize
把daemonize no 的no改为yes
(该配置为是否让redis作为服务(守护进程)在后台启动)
找到指定行 :111
或者搜索 /protected-mode
把protected-mode yes 改为no
(该配置为resis的安全模式,当他开启就会禁止别人访问redis,如操作redis的工具)
找到指定行 :87
或者搜索 /bind 127.0.0.1 -::1
注释掉bind 127.0.0.1 -::1
(该配置生效时表示只有本机能访问redis,这显然是不行的,注释掉它)
搜索requirepass foobared
把改行的注释取消掉 并把 foobared 改成自己的密码
(该配置为设置自己的redis登录密码)
补充一条:
如果你使用了云服务器,那么我建议你改掉Redis的启动端口,防止被攻击
bash
复制代码 搜索 /port 6379
或转到 :138
把端口号修改成自己需要的端口。
注意:当你的端口修改之后登录时需要指定端口
使用redis-cli -a 你的密码 -p 你的端口号
,
而不是redis-cli -a 你的密码
Redis的简单使用流程
1 redis服务的启动与登录退出
perl
复制代码使用我们修改好的 redis.conf 配置文件启动服务!
redis-server /opt/redis-7.0.0/redis/redis.conf
如果没有返回任何信息表明我们成功启动了redis
new news is good news! 没有消息就是好消息!
查看redis服务是否启动
ps -aux | grep redis | grep -v grep
使用我们修改过的 密码 以及 端口号 登录客户端!
redis-cli -a 你的密码 -p 你的端口
注意,此时我们已经进入了Redis的命令行,而非linux的shell命令行,此时输入linux命令是不会生效的!
如果要退出命令行请输入 quit
并回车
复制代码在redis命令行中测试redis是否正常运行
输入 ping
返回 pong 表示redis正常运行
如图
2 Redis服务如何结束
Redis服务的结束分为两种情况
1.在Redis命令行中:输入shutdown
退出(quit
退出服务)
2.在Redis命令行外:redis-cli -a 你的密码 -p 你的端口(可填多个端口) shutdown
Redis的卸载
- 删除
/sur/local/redis
中Redis相关的所有指令 - 删除
/opt/redis/redis-7.0.0
文件夹 - 如果需要,也可以删除备份的配置文件
3 Redis的10大数据类型
前言
arduino
复制代码redis不是k v键值对吗?为什么又有10大数据类型呢?
答: 这里的数据类型指的是k v中的v(value)的数据类型,
k一般为字符串类型(String)
10大数据类型的概述
String(字符串)
- String是redis中最基本的数据类型,一个key对应1个value
- redis中的String是二进制安全的(支持序列化),这意味着我们可以将二进制存储的文件存入其中,比如:一张图片。
- redis中单个String的最大容量为521M
list(列表)
- redis中的list指的是
简单的字符串列表
- 你可以插入一个字符串到头部或者尾部,数据结构其实就是
双向链表
- 它最多包含
2 ^ 32 - 1
个元素,超过40亿个
Hash (哈希表)
- 以一个
filed(字段 String)
一个value(值)
来存储数据的映射表。与Java中的hashmap数据结构极为相似:map< String,map< String,object > >
- 单个hash最多包含
2 ^ 32 - 1
个键值对,超过40亿个键值对
set(集合)
- redis中的set是
存储String的无序集合
,要求存储的String数据数据唯一无重复
- 底层其实由hash来实现的,集合对象的编码类型可以是inset或者hashtable --> 暂不赘述
- 它的查询复杂度为 O1,做法与java一样,value全部置空,比对key就完了
- 单个set最多包含
2 ^ 32 - 1
个成员,超过40亿个成员
zset(sorted set)
它是一个有序的无重复set
可以有序的存储String,它通过给每一个value指定一个double类型的score分数来设定value该放置的位置 暂不赘述- zset的分数可以重复,但成员唯一
- 查询,插入,删除复杂度都是O1
- 可存储
2 ^ 32 - 1
个成员
GEO (地理位置信息)
- 用来存储经纬度,可以对存储的经纬信息进行操作
- 存储位置信息
- 删除位置信息
- 计算两个位置信息之间的距离
HyperLogLog (基数统计)
基数就是指不重复的数据,比如人口基数,指的就是每个单独存在的个体
HyperLogLog是用来做基数统计的算法
,它的优点在于统计大数量的基数时,运算速度会非常快。- 对于
2 ^ 64 -1
个数据的统计,只需要占用12k的内存,要知道一般的集合,它存储的数据量越大,进行基数统计时所需要占用的内存就越大。 - 它不像常用的数据类型一样,存储基数本身,也无法返回指定的基数。
bitmap(位图)
- 是用来存储二进制位的bit数组,当我们只需要记录简单的Y/N状态时,他就派上大用场了,比如,打卡上班,统计调研分析。
bitfield(位域)
- 分段操作的bitmap
Stream redis流
- redis版的消息队列
4 Redis的基本运用
前言
作为一名程序员,在学过ssm框架之后就应该掌握一定的自学 能主动查阅资料,通过各种文章,论文,自行学习。
Redis官网命令查询
Redis命令查询中文版
Redis命令中心(Redis commands) -- Redis中国用户组(CRUG)
常用命令
vbnet
复制代码help @? 查询帮助,比较有用
set k? ? 设置一个String类型的数据k? 值为?
get k? 获取k?的值
keys * 查询所有key
exists k? k?是否存在,返回1表示存在
type k? 查询数据类型
del k? 删除指定key
unlink k? 异步删除,对于大数据的删除,使用异步防止阻塞
ttl k? 数据是否过期,返回-1表示永不过期,-2表示已过期
数据在默认情况下永不过期
expire k? S 设置k?在s秒后过期(过期会删除该值)
Redis中默认给我们配置了16个数据库,他们的下标为0-15
下面演示数据库相关的命令
perl
复制代码move k? index 将k?移动到下标为index的数据库中
select index 切换到下标为index的数据库
(redis中我们默认处于0号数据库)
dbsize 统计数据库中key的数量
flushdb 清空当前数据库(谨慎使用!)
flushall 清空所有数据库(谨慎使用!)
在我们的redis.conf
中的第379
行(或搜索databases
)中设置了我们的数据库数量,我们可以更改它。
Redis10大数据类型之String
Redis中String的常用API
vbnet
复制代码set命令中的可选参数分别有
get EX PX EXAT PXNX NX XX KEEPTTL
GET用法
set k1 jack GET 它会把k1赋值为jack,但同时返回k1之前的值
EX用法
set k1 jack EX 10 设置k1值为jack,但在10秒后过期(expire)
PX用法
set k1 jack PX 100 设置k1值为jack,但在100毫秒后过期
EXAT用法
set k1 jack EXAT 1685775704
设置k1值为jack,但在指定的秒级unix时间戳后过期
set K1 JACK PXAT 1685775857409
设置k1值为jack,但在指定的毫秒级unix时间戳后过期
NX用法
set k1 jack NX 当k1不存在时设置k1的值为jack
XX用法
set k1 tom XX 当k1存在时才将k1的值设为tom
KEEPTTL用法
set k1 jack KEEPTTL
当我们在设置给key赋值时偶然或遇到这样的业务需求
只改变key的值,但不要改变之前设置的过期时间。
这时我们就需要用到KEEPTTL,要知道普通的set赋值会改变value
的过期时间(默认为-1:即永不过期)
java获取当前毫秒级时间戳
arduino
复制代码mset k1 v1 k2 v2 k3 v3 设置多个值(m multi多个)
注意 mset k1 v1 k2 v2 k3 v3 NX 当其中一个不满足条件则全部失败
mget k1 k2 k3 k4 获取多个值
getrange k1 1 2 截取k1,从下标1-下标2
getrange k1 0 -1 获取全长
setrange k1 1 omomom 定位替换,从下标1开始替换字符串
INCR k2 递增k2(k2必须为数字!)
DECR K2 递减k2(k2必须为数字!)
INCRBY k2 2 每次递增步长为2
DECRBY k2 每次递减步长为2
strlen k1 获取k1的长度
append k1 往k1尾部插入字符
getset k1 v1 与set k1 v1 get一样
初识Redis分布式锁
我们这里简单了解一下redis的分布式锁命令
复制代码setnx k1 jack 当k1不存在设置值为jack
setex k1 jack 当k1存在设置值为jack
上面这两个看似普通的命令与分布式锁有什么关系呢?
csharp
复制代码简单说一下什么是分布式锁。
当我们的一个Java程序在执行多线程任务时,我们为了保证线程安全
往往会使用lock锁sync锁来保证操作的原子性。
但是在微服务中,我们往往是多个服务处于不同的端口甚至不同的主机
上,此时jvm所提供的锁就无法保证我们在分布式架构中依旧线程安全。
假设说多个服务同时对redis中的k1进行写操作时怎么办呢?
到底让谁先写呢?
此时我们就用到了上面的setnx 与 setex
当第一个服务进来 它设置 setnx lock locked
(如果锁不存在就上锁!)
其他服务过来时就会发现锁已经存在
(简单完成了锁机制,后续阳哥详细讲,此处不过多赘述)
为什么我们使用setnx look locked 而不使用 set look locked nx ?
scss
复制代码因为set k1 jack nx其实是两个操作!
set k1 locked nx (查看与上锁)
1 服务1 查看是否上锁(同时其他的服务也在查看)
2 服务1 上锁过程中, 服务2 认为没有上锁,发生线程安全问题
如果所有的服务都认为没上锁,那么锁就没意义了!
setnx (单个原子操作)
1 服务1 开始查看(其他服务等待)
(查看锁与上锁属于不可分割的原子操作,其他服务只能等待)
Redis10大数据类型值List
Redis中list的常用API
scss
复制代码lpush list1 1 2 3 4
从左侧 依次 将12345插入到list1中
lrange list1 0 -1
从左侧开始遍历整个list1
//5 4 3 2 1
rpush list2 1 2 3 4 5
从右侧 依次 将12345插入到list1中(建议使用这个)
lrange list2 0 5
从左侧开始遍历list2 从0-5
//1 2 3 4 5
lpop list1 从左侧弹出一个元素
rpop list1 从右侧弹出一个元素
lindex list1 n 找出从左侧数下标为n的元素
lrem list1 n tom 从左侧开始数,删除n个tom
ltrem list1 n m 裁剪list1,只保留从下标n到下标m
(会改变原list!)
rpoplpush list1 list2 把list1元素尾部取出,插入list2头部
(它是原子操作哦)
lset list1 n tom 把list1中下标为n的元素替换为tom
linsert list1 before/after tom jack
在list1中,给元素tom的前面或者后面添加一个jack
注意,要知道我们的list中的元素是可以重复的!当有多个tom时,它
只会在第一个tom的前面添加,而不是在每个tom前面都添加!
Redis10大数据类型值Hash
hash的数据结构类似于java中的hashmap
map< String , map<String , object> >
Redis中Hash的常用API
bash
复制代码hset user9527 name tom age 12 hobby sing
设置一个key为 user9527 的hash 它的值是一个个键值对
分别是 name tom ,age 12 ,hobby sing
hget user9527 name 获取hash表user9527的name属性
hgetall user9527 获取hash表user9527的n所有属性
hlen user9527 获取hash表user9527的键值对个数
hexists user9527 name hash表user9527有没有name属性
hkeys user9527 查询user9527中所有键值对中的key
hvals user9527 查询user9527中所有键值对中的value
hincreby user9527 age n 给user9527的年龄字段增加n
(注意,他没有hdecrby递减 但是我们可以增加 -1,相当于递减了)
hincrbyfloat user9527 age 0.5
给9527加半岁(不想加字段了,让我偷个懒)
hsetnx user:9527 hobby rap
当9527的hobby字段不存在时赋值为rap (原子操作!)
Redis10大数据类型值Set
hash的数据结构类似于java中的hashset
Redis中Set的常用API
scss
复制代码sadd set1 1 2 3 4 5 5 5 5
添加将1 2 3 4 5 添加到 set1中,set会自动去重哦。
smember set1 遍历set1所有 成员(member)。
sismember set1 x x是否是set1的成员。
srem set1 x 删除set1中的x元素。
scard set1 获取set1的长度。
srandmember set1 随机显示一个set1中的元素。
spop set1 n 随机弹出(删除)n个set1中的元素。
smove set1 set2 x 将x从set1中移动到set2中。
set的差集,并集与交集运算
arduino
复制代码 差集
sdiff set1 set2 获得set1中有而set2中没有的元素。
并集
union set1 set2 获得set1与set2合并后的set。
交集
sinter set1 set2 获得set1与set2中共有的数据。
sintercard 2 set1 set2 获得set1与set2这2个set交集的长度。
注意,必须指定要获得交集长度的set个数 (2) 。
Redis10大数据类型值sortedSet
zset的数据结构本质上还是hash
Redis中ZSet的常用API
arduino
复制代码zadd zset1 10 tom 20 jack 30 lisa 40 monika
给zset1中添加 tom jack lisa monika,他们关联的score分别是10 20 30 40
zrange zset1 0 -1 withscores 遍历zset1 withscores 带上score
zrevrange zset1 0 -1 反向遍历zset1
zrangebyscore zset1 0 100 获取zset1的score,这东西它不能填0 -1,要填分数的范围,0 100的意思是获取的score要大于等于0 小于等于100的
zrangebyscore zset1 (0 100 括号表示不包含0
zrangebyscore zset1 0 100 limt 0 1 limt 0 1 表示只要两个数,类似于分页
zrem zset1 tom 删除tom 它无法通过分数获取指定的元素,score只用来排序
zcard zset1 获取zset1的长度
zincrby zset1 3 tom tom加3分
zcount zset1 0 20 统计从0分 到 20分之间有几个元素
zmpop 2 zset1 zset2 min/max count 1
对zset1和zset2进行元素删除 最小或最大 删除数量为1(他会先删除zset1),删除完后删除
zset2,如果指定的count数大于zset的总长度,则删除当前zset的全部,不会影响下一个zset
zrank zset1 tom 获得zset1中tom的下标
zrevrank zset1 tom 获得逆序中tom的下标
Redis10大数据类型值bitmap
bitmap底层用String
实现,他在本质上是字符数组
,是对String
的按位
操作
最大位数是 2的32次方
Redis中bitmap的常用API
arduino
复制代码setbit k1 1 1 将k1 下标为1的位置设为1
get k1 我们使用获取字符串的方式来获取k1,返回的是它所指定的ascii码所对应的值
如1 表示的就是2进制 01000000 (众所周知二进制0开头) 对应的就是字符 @
getbit k1 n 我们通过getbit来获取下标n上面的数字 下标必须指定
strleng k1 我们获取的是该bitmap占用的字节,8位一字节。比如0100 0000 1所占用
的就是2字节
bitcount k1 获取k1中一共有多少个1(用于统计上班签到)
这里把阳哥说的实用案例讲解一下
要求:统计20230101和20230102两天连续登录的用户
yaml
复制代码设置我们的用户
hset jack userid 1001 设置用户jack,用户id为1001
hset tom userid 1005 设置用户tom,用户id为1005
hset lisa userid 1009 设置用户lisa,用户id为1009
统计2023年1月1日签到的用户
bitmap 20230101 1001 1 把bitmap中下标1001设置为1表示1001用户(jack)这天签到了
bitmap 20230101 1005 1 把bitmap中下标1005设置为1表示1005用户(tom)这天签到了
统计2023年1月2日签到的用户
bitmap 20230102 1001 1 把bitmap中下标1001设置为1表示1001用户(jack)这天签到了
bitmap 20230102 1005 1 把bitmap中下标1005设置为1表示1005用户(tom)这天签到了
bitmap 20230102 1009 1 把bitmap中下标1005设置为1表示1009用户(lisa)这天签到了
好了,我们现在有3个用户,jack,tom,lisa。其中jack和tom两天都签到了,lisa只在
2023年1月2日签到。
我们通过位运算 & 来完成对数据的统计
根据我们在小学2年级就学过的位运算& ,只有当数据都为真时结果为真
0 0 0 0 0
0 1 0 1 0
& 0 1 0 1 0
--------------
0 1 0 1 0
根据上面的示例可以猜到,当20230101表跟20230102表 &运算时,两张表的1001位置jack
都为1,出来的结果也一定是1,
所以我们认为jack在两天都签到了,同理jack也都签到了,
而lisa因为只有一天签到 所以0 & 1 结果为0。
所以代码
bitop and k? 20230101 20230102
(将两张表进行and(&)运算,结果存入k?中,k?随意指定即可)
的执行结果一定只有两个1存在
所以bitcount k?结果一定是2
即:只有2人连续签到
看到弹幕有人说懵,就干脆讲一下了,如果看完还懵,就要补一下java基础位运算了
Redis10大数据类型值HyperLogLog
HyperLogLog常被用于统计,它不存储基数本身,会对放入的元素去重并统计元素数量,底层由String实现
Redis中Set的常用API
下面使用一个小案例来说明该数据类型的实用价值
需求:我需要统计网站两天内都被谁访问过,而不想知道他们访问了多少次
sql
复制代码pfadd date20230101 tom jack jack lisa lisa lisa
上面的代码表示我们定义了一个名为date20230101的HyperLogLog
在这一天tom访问网站一次,jack两次,lisa三次。
(注意!!这玩意的命名没法以数字开头)
pfcount date20230101 //3
统计访问人数,结果为3。
我们统计2号的访问用户基数
pfadd date20230102 tom jack jack lisa lisa lisa monika
在这一天,多了一个用户 monika 访问网站,如果我需要2号当天的访问用户的基数,我只需要
pfcount date20230102,但现在我想知道两天内,都有哪些用户访问了我的网站。
pfmerge result date20230101 date20230102
在上面的代码中,我完成了我的需求,并将结果放入名为result的新HyperLogLog中。
我现在只需要对结果进行统计
pfcount result //4
Redis10大数据类型值GEO(位置信息)
GEO用来存储处理定位信息,数据类型本质上是zset,功能强大。
试想一下,当你在谷歌上班,下班时人流涌动,每个人都打开打车软件,所有人的位置信息,附近TAXI的位置,你与TAXI的实时距离。GEO的重要性不言而喻
百度地图定位(左上角有位置信息,也可以直接搜索)
Redis中GEO的常用API
scss
复制代码geoadd city 116.403968 39.915094 "天安门" 116.403414 39.924091 "故宫" 116.746579 40.482509 "长城"
将天安门...的经纬度 以及名称 存入city中(输入中文简直一言难尽,建议大家写好直接粘贴)
它存储一个 经纬度 一个值 与 zset 的存储一个分数,一个值没有区别(底层还是zset)
zrange city 0 -1 遍历city (zset数据类型不能使用参数 0 -1直接获取全部,而它可以)
出现乱码需要退出登录,重新登录时在登录指令后添加 --raw ,此时zrange指令不再乱码。
geopos city 天安门 返回定位点的坐标
geohash city 天安门 //wx4g0f6f300
工作中面对大量的定位置,一个经度,一个维度,让人头皮发麻。所以我们一般使用geohash
将定位信息转换成base32编码使用。(这并不影响存入的数据本身,只是获取数据的编码)
geodist city 天安门 故宫 m / km / ft(意思是英尺) / mi(意思是英里)
获取在city中天门到故宫两地之间的距离,不输入单位则默认是米
georadius city 经度 纬度 100 km withdist withcoord withhash count 10 desc
withdist 带上距离
withcoord 带上经纬度坐标
withhash 带上base32编码
count 数量
desc 倒序
所以上面的代码就是 获得经纬度指定点为圆心,半径100km以内的最多10个坐标,并带上坐标的
经纬度,跟指定坐标之间的距离,带上编码,然后倒序排列。
这样明显不够人性化,我不可能背会所有地点的坐标,所以我们一般使用下面的代码
georadiusbymamber 天安门 100 km ......
跳过Stream与bitfield
理由:我们不使用redis做消息队列,对二进制位修改也很无聊
如果一个技术,你根本用不到,而且面试也不问,在足够长的时间上,你终究会忘掉它,那么你学它纯粹是在浪费时间。把时间精力放在能给你带来价值的事情上是聪明的选择。
我写api快写疯了,实在不想写了,下一章吧
Redis的持久化
Redis持久化理论简介
Redis持久化,顾名思义就是将Redis中的数据写入磁盘(redis运行于内存)。
Redis对于持久化给出了以下几个选项:
RDB(redis database)
Redis按照一定的时间间隔(触发条件)执行对数据进行备份。称之为时间点快照。文件名为dump.rdb
AOF (append only file)
Redis记录数据库中所执行过的所有写操作,在服务器重启时重播这些操作重写数据库原始数据,命令的记录格式与 Redis 协议本身相同。
No persistence
不进行持久化操作!
RDB+AOF
组合使用AOF和RDB进行持久化
RDB
RDB的持久化频率
Redis的持久化在配置文件redis.conf
中记录
在Redis6.2以前的版本中,RDB的备份规则如下:
scss
复制代码当触发以下条件中的任意条件,就执行备份
在900秒(15分钟)内有1个键发生了改变
在300秒(5分钟)内10个键发生改变
在60秒内10000个键发生改变
在Redis6.2及以后的版本中,RDB的备份规则如下:
scss
复制代码当触发以下条件中的任意条件,就执行备份
在3600秒(1小时)内有1个键发生了改变
在300秒(5分钟)内10个键发生改变
在60秒内10000个键发生改变
可以看到,redis对备份的时间间隔进行了大调整,证明实际生产中不需要那么高频率的备份。
修改RDB的持久化配置
修改redis备份文件的存储位置
bash
复制代码
修改配置文件 vim /opt/redis-7.0.0/redis.conf
搜索 dir ./ 按n切换下一个
将 ./ 换成你想保存的目录(注意,这个目录必须真实存在!)
在实际生产中,不可以把Redis服务跟Redis的备份放置在同一台服务器上,防止服务器物理损坏导致备份同时损坏!
RDB备份文件名的修改
lua
复制代码修改配置文件 vim /opt/redis-7.0.0/redis.conf
搜索 dbfilename dump.rdb
把 dump.rdb 改成 redis端口号 + dump.rdb
RDB自动持久化设置的更改
注意,此处只是演示如何修改,此配置并非实用配置
bash
复制代码修改配置文件 vim /opt/redis-7.0.0/redis.conf
搜索 save 3600 1 300 100 60 10000
另起一行,参考 save 3600 1 300 100 60 10000写入规则
如 save 5 1
代码含义参考上面的Redis6.2以后的持久化规则!不再赘述
注意,配置修改后重启redis服务才会生效!
1 shutdown结束服务
2 redis-server +配置文件路径 重启Redis
RDB自动持久化特性
- 当执行
flushdb
指令或者fliushall
指令时,Redis会自动生成快照,但是注意,这个备份是空文件!
- 当执行
shutdown
指令结束Redis服务时,Redis也会快速生成快照。 - 当Redis启动时,会自动从备份文件夹中读取快照恢复数据!
RDB的手动持久化
Redis提供了两个指令来完成持久化
SAVE指令
实际生产中禁止使用该指令
,因为该指令会阻塞Redis!致使Redis不服务而去进行持久化操作!
BGSAVE指令
生产中我们会使用该指令立即持久化
操作,它会另起一个Redis的子线程去异步的执行持久化操作,不会影响Redis正常提供服务!
LASTSAVE指令
该指令可以查看最后一次备份的执行时间
RDB的优缺点总结
RDB的优点
- RDB文件十分短小紧凑,且非常适合备份,我们可以将RDB每30天备份一次,根据RDB文件,我们可以恢复特定时间片的数据。
- RDB非常适合灾难备份,我们可以将它发送到别的服务器保存,比如oos
- RDB支持使用bgsave,在不影响正常服务的情况下fork出子进程进行备份,不影响父进程正常提供服务。
- RDB对于大数据的恢复,速度要远快于AOF
- 在副本上,RDB 支持重新启动和故障转移后的部分重新同步
RDB的缺点
- 如果您需要在 Redis 停止工作(例如停电后)将数据丢失的可能性降至最低,则 RDB 不好。因为它会丢失上一次持久化到突然断电这段时间的所有数据!
- RDB 经常需要 fork() 才能使用子进程持久化在磁盘上。如果数据集很大,fork() 可能会很耗时,如果数据集非常大且 CPU 性能不是很好,则可能会导致 Redis 停止为客户端提供服务几毫秒甚至一秒钟。AOF 还需要 fork(),但频率较低,您可以调整重写日志的频率,而无需牺牲持久性。
RDB文件的修复
RDB文件在备份时突然服务器宕机,或者在传输过程中,都有可能导致文件损坏
在前面讲过/usr/local/bin/
里面有一个指令用于修复RDB文件 (该文件夹是linux默认的一个环境变量文件夹)
lua
复制代码我们直接执行执行它
redis-check-rdb dump文件。
它会自动帮我们修复rdb文件
禁用RDB
config SET SAVE ""
- 将配置文件
redis.conf
中的save
改成save ""
推荐使用第二种
AOF
Redis默认关闭AOF功能,需要手动打开
AOF的工作流程
上面阳哥的图片介绍的已经非常清楚了,我就不再画蛇添足了。 第5步内容为需要时重新写回数据。
AOF的3种写回策略
always
在写命令执行后
立即
记录该条命令(频繁的磁盘IO会降低Redis性能)everysec
默认
每秒写入1次
,看似频率高,其实性能影响不大,最多丢失1秒内执行的写入,是最优选。no
由操作系统控制的持久化。
每条命令执行后,只是将命令存入AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写入磁盘 。(宕机会丢数据!
)
AOF实操
开启AOF
bash
复制代码修改配置文件 vim /opt/redis-7.0.0/redis.conf
搜索 appendonly no
将 no 改为yes
AOF文件的存储位置
- 在Redis6中AOF默认的默认存储位置跟RDB的存储位置一致。
2.在Redis57中AOF文件的存储位置在RDB存储的文件夹下的appendonlydir文件夹下
,我们可以搜索
appenddirname appendonlydir
并更改它,但该文件夹一定会放在RDB文件所指定的目录下。
AOF文件的保存名称
- 在Redis6中,AOF文件有且只有一个,名为appendonly.aof
在Redis7中,发生了很大的变化
Redis7将AOF文件分成了3部分
- BASE文件:基础AOF文件,由子进程重写产生,存储执行过reaof的指令
2.
INCR文件(增量文件):它会有多个,记录着不同的写操作,存放未重写的指令。
- HISTORY(历史文件):历史AOF,由上面的BASE文件和INCR文件变化而来,重写完成会被删除,一般见不到
- manifest文件(清单文件):用来跟踪和管理AOF文件。
AOF文件的异常修复
不同于RDB文件的直接修复,我们在执行Redis修复AOF文件指令时,需要加上 --fix
参数。上面说过我们的AOF文件中只有incr文件记录我们的指令
,所以我们只需要修复该文件即可
sql
复制代码redis-check-aof --fix 上图中后缀为incr.aof的文件
当我们打开图中后缀为incr.aof的文件,会发现里面记录了我们的写入操作。
这意味着,即便我们执行了flushall命令清空了数据库,在此处也会以日志的形式记录下来!我们只需要删除flushall这行,重启Redis,他依旧能恢复如初!
AOF的优缺点
优点
- 相比于RDB,在数据保护方面,它做的更好,因为他只会丢失1秒内的写数据
- 它的性能更高,AOF的aofrw机制,解决AOF文件膨胀
- 可做紧急恢复,flushall命令也可以恢复!
缺点
- 文件相比于RDB更大
- 运行速度不如RDB
AOF重写机制
由于AOF持久化是将执行过的写指令不断地写入到文件中,所以不可避免的就会造成AOF文件的不断膨胀。
为了解决这一问题,Redis新增了AOF重写机制
,当AOF文件大小达到峰值时,Redis会自动对AOF文件进行内容压缩,只保留可恢复数据的最小指令集。
AOF自动重写机制
AOF手动重写
我们也可以使用bgrewriteaof
指令来手动重写!
直观了解AOF重写机制
arduino
复制代码set k1 v1
set k1 v2
set k1 v3
.....
如上面的代码所示,如果没有重写机制,这三条指令都会存在aof文件中
,不仅占用磁盘空间,而且在恢复Redis时,也会占用内存执行大量无意
义的代码。
如果有了重写机制,aof文件中只需要保留set k1 v3即可。
AOF重写不仅降低了文件的占用空间,同时更小的AOF也可以更快地被
Redis加载
AOF重写原理
在这一点上,老师讲的我听起来有点云里雾里,下面这张图会较为明白的解释其原理(图片来自 赵代码 )
稍微解释一下
csharp
复制代码1.当执行重写之前,redis会把缓存中的指令写入到Redis数据库(旧
aof也会记录)中。
注意,当第一个步骤完成后redis就不再往aof中写入任何指令了,而是
把所有此时接收到的写指令暂存入缓冲区。在这个时间空隙中执行下一步
2.redis读取此时数据库中的所有数据,而非读取旧的aof文件,根据此时
的数据库内容,生成 可恢复数据库的最小指令集 ,类似于RDB快照,
只是生成的是指令集而非数据集。
3.此时指令集完美生成,再把缓存中的指令存入刚完成的新文件。至于
它是怎么把数据存入base文件的,这些都不重要,重要的是我们完成了
在不影响Redis服务的前提下,将aof重写。
4.最后一步只需要删除旧的aof文件,生成一个新的aof文件,继续存储
指令即可。
AOF与RDB共存 (推荐使用!)
当Redis同时开启了RDB与AOF,则Redis在开启服务时会优先选择AOF来恢复数据, 如果不存在,则会去寻找RDB文件恢复,如果都没有则不恢复直接启动 (面试题)
bash
复制代码aof-use-rdb-preamble yes 开启混合模式!
如果我们开启的混合模式,则Redis会使用RDB做全量持久化,而AOF做增量持久化
,当满足重写策略或手动重写
时,Redis会将当前的数据存储为RDB记录,这样的话,数据恢复时,Redis会从RDB和AOF两部分恢复数据,注意,此时生成的appendonly文件就不再是单纯的aof文件了,而是包含了RDB数据和AOF数据两种格式
。
Redis纯缓存模式
有时我们不需要Redis做备份,而是以更高的性能单纯的作为缓存数据库存在
即便禁用了aof与rdb,我们仍然可以使用bgrewirteaof或bgsave指令生成相应的备份
perl
复制代码save ""
appendonly no
Redis事务
什么是事务
将一系列操作打包成一个合集,合集中的所有命名都会序列化,按顺序的串行化执行而不会被其他命令插入,加塞
能做什么
go
复制代码`能一次性,顺序性,排他性的执行一系列操作`
Redis事务与传统数据库事务的区别
- Redis只是保证了事务里的操作会被连续执行,Redis的单线程架构导致了在一个事务执行时不会接收其他客户端的请求。
- 没有隔离级别的概念,因为Redis事务在提交前任何指令都不会被执行。
Redis不保证事务的原子性
,不存在一错全错,不能回滚。Redis保证排他性
,指令执行不被加塞。
Redis事务实操
arduino
复制代码multi //redis通过multi开启事务
set k1 v1
set k2 v2
incr count
exec //exec执行事务
multi
set k1 v1
set k2 v2
incr count
discard //discard 取消事务执行
multi
set k1 v1
set k2 //这里故意写错,语法错误
incr count
exec //此时执行事务会导致整个事务所有命令执行失败
当错误类型属于运行时异常,即redis语法检查没有检查出来时,
就会无法保证事务的原子性,无法回滚!
redis不支持rollback!!
为了保证高性能,redis采用了watch实现了乐观锁的功能, 即:redis事务在执行时并不会加锁,而是在真正执行时查看一下操作的数据是否(在其他用户的操作下)发生变化,如果发生变化,则整个事务全部执行失败。
下面我们演示一下watch命令
arduino
复制代码添加一个String类型的bank 来模拟银行账户
xxx.x.x.x:xxxx> set bank 100
OK
//开启watch监控我们的银行账户
xxx.x.x.x:xxxx> watch bank
OK
以此同时,我们再开启一个客户端来修改一下bank
客户端2 > set bank 1200 //第二个客户端把账户变成1200块
让我们回到客户端1
//客户端1开启事务
xxx.x.x.x:xxxx> multi
OK
客户端1修改银行账户为 300
xxx.x.x.x:xxxx> set bank 300
QUEUED
//客户端1执行事务发现执行失败!!
xxx.x.x.x:xxxx> exec
(nil)
//客户端1查询账户发现被客户端2截胡
xxx.x.x.x:xxxx> get bank
"1200"
由此我们可以得出,一旦我们watch了数据,即便是在别的客户端已经修改了数据之后我们才开启的事务,依然会执行失败!与事务开始时间没有关系,而与watch开启时间有关
Redis管道
Redis管道技术的出现是为了解决命令频繁往返的问题
为什么需要管道技术详解
如图所示,因为redis是基于客户端-服务端模型
以及请求/响应的TCP服务。
客户端向服务端发送命令分4步(发送命令-命令排队-命令执行-返回结果
),并监听socket返回,通常以阻塞模式等待服务器响应,
执行上面往返流程所花费的时间我们称之为RTT(rount trip time)
为了解决过多的RTT来回次数,诞生了管道技术,(类似于批处理),说人话就是将一系列命令打包一次性发送到服务端执行,减少往返次数。
pipeline 实操
bash
复制代码在redis外写一个txt文件
vim cmd.txt
set k1 v1
set k2 v2
rpush list1 1 2 3 4 5
执行pipe
cat cmd.txt | redis-cli -a 密码 -p 端口 --pipe
出现以下代码表示执行成功 代码中显示了执行成功的条数和error条数。
pipe特性与其他类型对比
原生批处理是原子操作,管道不是
事务是一条条发送的,管道是同时发送。
事务在执行时无法被加塞,而管道可以被加塞。
不要在单个管道内放置过多消息
本课程跳过Redis的pub/sub
Redis replication
本章需要有三台linux实例才能完成演示 解决方案
开三台虚拟机
使用容器技术
- 多台云服务器,不推荐
云Redis数据库
本章只介绍技术本身,怎么选不影响学习,放心食用
Redis replication简介
Redis通过复制(replication)
支持高可用性
和故障转移
一句话总结:Redis使用replication完成副本对主机的精准复制,当主机数据发生变化,自动将数据异步
同步到其他slave
- 读写分离,我们读操作去找slave,写操作去找master
- 容灾恢复,slave作为从机精准复制主机,当主机出现故障时,依旧能保证Redis正常运转
- 数据备份,slave作为从机相当于master的完整备份
- 水平扩容支持高并发,多台从机共同分担并发任务。
主从结构:
Redis replication的配置
想要完成主从结构,我们需要修改配置文件。
- 主机配置
复制代码其实对于主机,我们并不需要进行什么主从方面的配置,主要是一些基本配置
bash
复制代码搜索并修改一下设置
daemonize 设为yes让redis在后台运行
注释掉 bind 127.0.0.1 让其他主机可以访问到Redis
protected 关掉保护模式允许公网访问
port 修改默认端口
dir 修改备份文件存储位置
pidfile 进程id存储位置,暂时用不到,默认即可不用管
logfile 日志文件名字,自己指定,保证文件夹存在
如 /myredis/6379.log
requirepass 密码,自己设置
dbfilename rdb备份文件文件名
appendonly 开启aof,自行决定
\2. 从机配置
复制代码 我们主要还是对从机进行配置
搜索以下内容并修改
port 设置我们从机的ip
replicaof 在528行左右,默认没有配置
另起一行写入 repilcaof 主机的ip 主机的端口
535行设置我们连接主机的密码
masterauth 主机的密码
示例
Redis replication的常用命令
scss
复制代码info replication 查看从属关系
replicaof 主数据库ip 主数据库端口
命令方式连接主数据库,该方法需在配置文件中设置masterauth(主机密码)
而且该方法在退出登录后不保存配置,再次连接需重新连接主数据库。
Redis replication 常见问题
- 从机
不可以执行写命令,只能读
! - 即使从机宕机,再次连接后依旧能保证
所有数据
的一致性! - 主机宕机后,从机依然是从机。
- 当主机再次登录,主从关系依旧能保证,数据一致性也能保证。
Redis replication 薪火相传模式
我们可以通过replicaof命令或者更改配置文件
来将我们的一台从机变成另一台从机的master,如上图所示。通过此配置我们可以降低太多从机对主机造成的性能影响
此时我们对被连接的slave使用info replicaof
命令会发现信息中包含连接者的信息
无主机模式
perl
复制代码replicaof no one 命令可以退出从机身份,仍会保留我们作为从机时的数据哦!
repiline的底层流程和原理
- slave 第一次连接到master,发送sync请求
执行全量复制
bash 复制代码slave向master发送sync请求,mster收到同步请求后,会启动一个子线程 异步执行RDB持久化。同时将接到的请求存入缓存中,等待rdb快照文件生成 后,会一并发给slave以完成主从复制。而slave收到数据库文件后,会将他 存盘并写入内存中,完成复制初始化。
心跳持续·保持通信
scss 复制代码每过10秒master会向slave发送心跳包(ping),确认slave在线
进入平稳,增量复制
复制代码master持续将收到的写命令同步到slave,完成同步
从机下线,断线续连
sql 复制代码master会检查backlog里面的offset(偏移量,相当于版本号),master和 slave都会保存一个offset跟一个masterId,master会把offset之后的数据 发送给slave,保证数据的一致性。
repi的缺点
- 从master同步到slave的操作本身就需要时间,会产生延时,系统在繁忙时延时问题会更加严重,而为满足高并发场景,slave只会越来越多,问题也会越发严重。
- 当master出现问题时,slave本身不会主动替代master完成任务,而是等待主机恢复,所以当master宕机,高可用性也就无从谈起了。
Redis哨兵(Sentinel)
Redis在不使用集群的情况下用sentinel实现了高可用,解决了master宕机导致服务不可用的问题。
Redis sentinel发现master发生故障,就会通过选举算法,从slave中选出新的master,并完成切换,继续对外服务。
一般来讲我们会配置三台Sentinel,保证当有一台哨兵宕机时,投票机制依旧可用。
Redis Sentinel提供的服务
- 监控
复制代码Sentinel会不断的发送心跳包检查master跟slave是否正常运转。
\2. 通知
scss
复制代码Sentinel可以通过api通知系统管理员或者其他程序(Redis客户端),其中一个
复制代码受监控的 Redis 实例出现问题。
\3. 故障转移
复制代码选举新的master替换掉故障的master
\4. 配置中心
复制代码客户端通过连接哨兵来获得当前Redis服务的主节点
Redis Sentinel实战配置
基本架构
- 主两从三台Redis服务
- 三台哨兵(哨兵机也必须保证安装Redis,哨兵配置文件为sentinel.conf)
配置文件相关设置
1.备份sentinal.conf文件。
xml
复制代码protected 保护模式,关掉它
daemonize 后台运行 改成yes
pidfile 进程文件
port 哨兵的运行端口
logfile 自己指定,故障时必看
bind 注释掉
dir 工作目录
sentinel monitor 监控的master名 ip 端口 多少票主观下线
这个默认注释掉,需要我们手写
(一般来讲,我们三台哨兵设置为2,2/3我们就主观认为确实故障了,当3台哨兵
2台故障时,name哨兵机制也就挂了,因为永远达不到2票)
哨兵如果一直收不到心跳包,就会认为master挂掉了,。
sentinel auth-pass masterName master机密码
哨兵需要知道密码才能进行一系列操作,这时,我们就必须保证master跟slave
的密码必须一致,因为它需要把slave设置成新的master,而且是通过投票机制,
我们并不知道最终投票结果,所以要设置一样的密码。
sentinel down-after-milliseconds mymaster 30000
这个就是我们收不到心跳包的毫秒数,超过这个毫秒数,哨兵主观认为master挂掉了
sentinel parallel-syncs mastername nums
当新的master被选举,会有几台slave同时向master发送同步请求
sentinel failover-timeout <master-name><milliseconds>:
故障转移的超时时间,进行故障转移时,如果超过设置的毫秒,表示故障转移失败。
sentinel notification-script <master-name><script-path>
配置当某一事件发生时所需要执行的脚本
sentinel client-reconfig-script <master-name><script-path>:
客户端重新配置主节点参数脚本
\2. master配置
复制代码当我们的master故障后,当他再次被启用时,哨兵会将他转换成新master的slave,此时
就会出现一个问题,我们的slave都是配置了 master 地址以及 master 的访问密码
的 ,只有这样它才能作为slave使用,所以,我们需要给master也配置 master-auth
但我们不需要给master配置它的master地址,哨兵会完成这一工作。
启动哨兵
css
复制代码redis-server sentinel文件 --sentinel
哨兵日志 重点
哨兵日志记录了哨兵监控以及管理master与slave的全过程,包括选举新master, 需要保证自己能完全读懂日志全程才可以进入下一章。
哨兵工作流程及原理
工作流程
复制代码首先1个哨兵发现master故障后,会sdown掉master,即主观认为master故障。
复制代码当主观认为master故障的哨兵数量达到投票数,就会odown,客观认为 master
故障,此时就会开始着手切换master。
此时有一个问题,谁去着手操作切换master?
这时哨兵内部就会进行投票选举,
1 选出谁去执行
复制代码 选出新的master,并完成从属关系的重新配置
选取执行者算法
Redis采用raft算法,raft算法的核心是先到先得。 即A哨兵向B哨兵发送成为领导者请求,如果B哨兵没有收到其他哨兵的请求, 则B同意A哨兵成为执行者。
选取master算法
- 首先要保证slave状态健康
- 根据配置文件中的slave-priority或者replica-priority,谁的最小谁获胜(可自己设置)
- 根据offset偏移量,谁的数据更接近旧master的偏移量,谁获胜
- 根据runID的ASCII码对照表,最小的获胜。
sentinel缺点
选举新的master需要时间,这期间数据会丢失,所以现在没人用啦,恭喜你白学啦~
Redis集群(cluster)
Redis集群出现的原因。
当数据量过大,单个Redis复制集(master+slave)难以承担。因此我们需要对多个复制集进行集群,形成水平扩展,每个复制集只承担数据集的一部分,这就是Redis集群,其作用是提供在多个Redis之间数据共享的程序集。
上面我们可以得知,其实集群也并没有完全实现百分百可用,当某个master挂掉,它也有可能丢失写数据。
Sentinel到Cluster的结构变化
如上图所示,
我们在集群架构下,会有多个master,它会帮我们分担写操作压力,同时每台master还可以配置多台slave,分担读操作压力。
- 支持读写分离
- 支持高可用
- 支持海量数据的读写。
- 因为cluster自带了故障转移机制,无需使用哨兵功能。
- 客户端只需登录任何一台可用的节点即可。
- 槽位slot会自动将数据分配到物理节点上,cluster会自动维护节点,插槽,数据之间的关系。
Redis Cluster相关算法
什么是槽位
bash
复制代码Redis没有使用一致性hash,而是引入了 hash槽 概念,
Redis一共有16384个哈希槽,每个插入的key通过 CRC16校验 后通过对16384取模
来决定放入哪个hash槽,而每个master节点负责一定量的hash槽。
CRC16算法是什么?
markdown
复制代码作者查了一下,关于CRC16算法这一类,有多种版本的实现。至于Redis使用的是
CRC16算法的哪种实现,我们不得而知,但我们可以知道的是
1. 这个算法它对于输入的值不做限制,可以是任意的。
2. 生成的结果是有限的,即我们无法通过生成的结果反推其输入值。
3. 散列冲突的概率很小,即你随机输入几个值,返回的值相同的可能性很小
但是有可能的。
4. 它的返回值有2种形式 1.二进制 2. 16进制。
如上图中的 HASH_SLOT=CRC16(key) mod 16384
ini
复制代码举例示范
我们想知道新数据 k1 v1经过这个算法放到哪里(此处我随便用一种CRC16算法)
HASH_SLOT(k1的存放槽位) = CRC16( "k1" ) mod 16384
= 1110010011101110 mod 16384
= 10166
我们就可以知道 在这个算法下 k1的存放位置会是在10166号插槽,
也就是我们图中的第二个Redis中(2号分片)。
Redis Cluster 分片
如上图所示我们在使用集群时,会将数据分散存储在不同的Redis实例上,这就是分片,简而言之,我们的每台实例都被认为是Redis集群的一个分片。
数据如何找到自己应该在的位置
这个在上面关于CRC16的实例中演示了,注意上面演示的就是最优的解决方案,称为哈希槽分区算法。
但是,业界还有另外两种分法,但都有各自的弊端,下面也介绍一下。
哈希取余分区算法
scss 复制代码 这个就是 key的哈希值 对 Redismaster个数 取模 hash(k1) mod 3 这东西有个致命缺陷假设我现在3台机器,算出来假如k1放在分片1上面, 我通过这个算法取出来也没有问题,但是当我加一台机器,这个3就变成 4了,这时候就乱套了,还有,假如一台Redis挂掉了,3变成2,还是一样 会出问题,辣鸡算法,如图所示。
一致性hash算法
bash 复制代码这个算法相当于对master的ip或者主机名进行hash后对16384取模,算出来的 结果作为该主机在hash环上的落点,然后等于把hash环分割成了3份(多少台主 机就多少份),插槽会顺时针去找落点,找到落点就放在该分片上。缺点就是分 配不均,会导致部分master压力过大,很烂的算法。 看不明白的看图。
理想情况
实际情况,全都去找A分片作为落点,累的累死,闲的闲死。
- 哈希取余分区算法 这个就是我们上面示例的最优算法,你会发现我们只是解决了key与hash槽的关系, 因为我们的hash槽是固定的位数,所以我们只需要将槽平均的分配到不同分片即可。当添加新的分片,我们只需要再次平均分配hash槽即可,因为我们找key是在hash槽中寻找的,而hash槽在哪个分片上对于数据的存取并无影响。
为什么是16384
1.如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
如上所述,在消息头中,最占空间的是 myslots[CLUSTER_SLOTS/8]
。 当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb
因为每1秒钟都会发送1个,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。
2.redis的集群主节点数量基本不可能超过1000个。
如上所述,集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。
3.槽位越小,节点少的情况下,压缩率高
Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。 如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。
ini
复制代码这里解释一下为什么65536÷8÷1024=8kb,因为myslots\[CLUSTER\_SLOTS/8]这个
数组里面存的是只可能是2进制状态码,为什么?因为这个东西的字节数是2kb
,换算下来,只能是1bit,也就只能是状态码,无非就是这个插槽是否为空,大胆猜
测1表示插槽已经有数据,0表示插槽为空,
也就是图中所谓 节点负责的槽信息 。
至于bitmap的压缩,为什么填充率会影响压缩比,插槽数为什么会影响压缩比,大家
可以去了解一点基本的数据结构与算法,类似于棋盘的压缩,这个地方很有意思。
集群搭建实操
集群配置文件
我们发现无论是Redis的单机启动还是哨兵启动,都是写出一个对应的配置文件,然后使用 Redis-server 启动对应的配置文件,同样,Redis集群的启动方式也一样,首先要有与之对应的配置文件。
arduino
复制代码在其中一台主机新建一个名为redisCluster+端口号.conf的配置文件
内容为
bind 0.0.0.0 //不绑定固定ip
daemonize yes //后台运行
protected-mode no //保护模式
port 端口号 //端口号
logfile 指定日志存放位置
//日志存放位置,保证目录存在,以 cluster+端口号.log 结尾
pidfile 进程文件位置 //自己指定 以 cluster+端口号.pid 结尾
dir 备份文件存放位置
dbfilenamen dump+端口号.rdb rdb文件名
appendonly yes 开启AOF
appendonlyfilename "appendonly+端口号.aof" aof文件位置
requirepass Redis密码 //Redis密码
masterauth Redis密码 //slave上位需要的密码,跟Redis密码一致即可
--------------------------------------------------------
cluster-enabled yes //开启集群!
cluster-config-file node-端口号.conf //集群配置文件
关于这个集群配置文件,需要说的是我们不需要自己去写,它是集群自己生成的
,它会放置在我们上面配置的-> ( dir 备份文件存放位置 ) 目录下,当集群
启动成功后,它会生成。
cluster-node-timeout 5000 //Redis集群节点超时时间
这个所谓的集群节点超时时间到底是个什么东西,因为Redis中有很多类似的 超时时间,他们的作用都不一致,很容易混淆,本来是很容易忽略的东西,因为 往往我们配置完成之后就基本不会再动。出于好奇心查了一下,没想到其中大有文章。 此处大家一定要根据我提供的地址读一下。 (20条消息) (九)Redis集群节点超时时限(node timeout)配置cluster-node-timeout详解_OceanSky6的博客-CSDN博客
对于上面的配置文件,我们需要在每个Redis实例里都进行新建,根据每台Redis的情况进行修改,(无非就是创建文件存放路径,把上面内容的端口号改一改)。 注意,我们的配置文件不需要因为要设置的主机是master或者是slave进行任何区别设置,无须担心,我们指定哪台机器是master或者哪台机器是slave根据下面的步骤来设置的。
启动集群
当你已经写好了所有的配置文件后,我们就开始在不同的机器启动我们的集群了
vbscript
复制代码 Redis-server 刚写好的集群配置文件
启动后我们检查一下,使用图中的命令会发现我们的进程后有 [cluster]字样
配置集群主从关系
注意,我们的命令只能指定哪台机器为master,却不能指定哪台机器为其对应的slave ,即便我们按顺序写,slave从属哪台master,也只能由Redis集群决定。
css
复制代码redis-cli -a redis密码 --cluster create --cluster-replicas 1 master1的ip:端口号 任意slave的ip:端口号 master2的ip:端口号 任意slave的ip:端口号 master3的ip:端口号 任意slave的ip:端口号
我们分段解释一下上面的命令(执行时不能分段!)
redis-cli -a redis密码 --cluster create
表示使用固定的密码来创建Redis集群,只有1个密码,这时候就可以看出,我们的
所有Redis实例的密码跟masterauth必须全部一致 !
--cluster-replicas 1 表示我们给每台master配置 1台 slave
master1的ip:端口号 任意slave的ip:端口号
这样配置是为了让实例们可以找到彼此(配合上面的密码),
而 任意slave的ip:端口号 则是因为Redis会自行分配slave的从属,所以顺序
怎么设置都一样。
但格式都是 master在前 slave在后,如果1配多slave也是一个规矩,一组一组
的来。
执行命令后,我们等待并根据提示输入yes即可,实例之间会互相通信确认连接正确,出现下图,恭喜你,配置成功了!
集群的登陆
css
复制代码当我们使用正常命令时会发现,有写值无法写入。这是因为在集群下,我们单台
Redis只存储整个数据的一部分,所以对于该写入其他master的值,我们需要通过
路由的方式写入。我们需要调换我们的登录方式。
redis-cli -a 密码 -p 端口 -c
这样就能通过路由把各个主机连起来。
集群常用命令
arduino
复制代码cluster nodes //查看集群节点关系拓扑图
cluster info //查看当前节点信息
集群的主从切换
这个地方没什么可说的,当master坏掉之后,slave会成为新的master,而旧的master故障修好重新上线后,会成为slave。说Redis集群不保证绝对的数据一致性原因就是在此,切换时丢失数据也不是很难理解。 如果我们需要手动写换主从关系,即让修好后的旧master依然作为master存在,则可以使用
复制代码 cluster failover 主从切换
Redis集群新增主机(扩容)
配置启动新实例
需要我们再写两套配置文件,并将他们启动起来,配置文件规格与原集群一致。
加入集群
csharp
复制代码redis-cli -a 密码 --cluster add-node 新ip:新端口 任意集群masterip:端口
//任意集群masterip:端口 相当于介绍人。
重新分配插槽
css
复制代码此时使用
redis-cli -a 密码 --cluster check 任意集群masterip:端口
会发现我们的新成员已经加进来了(slave稍后添加)
但slot数为0 slave数量也是0
redis-cli -a 密码 --cluster reshard 任意集群masterip:端口
- 此处问你想将多少slot转移出去(原来是su三组,现在再加一组,所以是16384/6=4096) 输入并回车
此处要我们选择要接收插槽的Redis实例的ID (ID就是图片中ip前面的长串字符串)
- 在图片中的情况,我们要接收的Redis是192.168.111.184:6387,所以我们需要写入的ID就是图片中ip前面的307a5f6617a6eeb4949f3cb9124ed04c6962c348,填入并回车。
- 然后输入all回车。(意思是从哪里分配出插槽来,当然是每一个master都要分出来)
- 输入yes,回车,等待分配成功。
- 此时再次查询会发现,新的master中的节点来自之前3个节点,并没有真的重新洗牌。
添加新的slave
css
复制代码redis-cli -a 密码 --cluster add-node 新slaveIp:新slave端口 新masterIP:新master端口 --cluster-slave --cluster-master-id master的长串ID
Redis集群的缩容
我们大概捋一下思路。
- 首先我们要把slave先下掉
- 我们得把插槽还回去,因为16384个插槽一个都不能少。
- 功成身退,退出集群
下掉slave
less
复制代码需要用到长串ID
redis-cli -a 密码 --cluster check slaveIP:端口 //查询长串ID然后存起来
//卸载slave
redis-cli -a 密码 --cluster del-node slaveIP:端口 长串ID
退还slot
css
复制代码redis-cli -a 密码 --cluster reshard 任意masterIP:端口
//还是重新分配slot的命令
1 分配出多少插槽? 4096(退还插槽的master的全部插槽,自行选择回车)
2 谁来接收?
本案例我们选择让6381接收全部退还的插槽,所以此处输入6381的长串ID并回车
删除master
bash
复制代码3 紧接上一步,此处还会有一个,我们填充要删除的节点的长串ID
4 输入done 表示删除它
将master的Redis实例彻底移出集群
如果我们此时再使用 redis-cli -a 密码 --cluster check 被删除的masterip:端口
来查询的话,我们会发现原来的master竟然成为了我们当初添加时填入的那个任意master的slave
css
复制代码既然如此,我们的操作跟上面移除slave的操作一致
redis-cli -a 密码 --cluster del-node slaveIP:端口 长串ID
集群下操作命令的不同
在集群下我们无法再像原来单机Redis一样使用mget 方法获取数据。这是因为在集群中,不同的值可能存在不同的分片下
复制代码此时我们使用 mset k1{z} v1 k2{z} v2 k3{z} v3 该命令会将其放置同一以分片下。 获取时 mget k1{z} k2{z} k3{z}
如何让Redis集群保持始终可用
有时候我们会面临一种情况,其中一个分片的master很slave都挂掉了 在默认配置下,Redis会停止对外服务,其实在这时候,插槽已经不完整了。
如果想要Redis在这种情况下依旧可用,我们需要在实例启动的配置文件中加入
perl
复制代码cluster-require-full-coverage no 默认为yes。
注意,在这种情况下,坏掉的那部分slot不可写也不可取。
cluster常用命令
arduino
复制代码cluster countkeysinslot 槽位号
//返回值为1表示该槽位已经被占用 0表示未被占用
cluster keyslot 键名
// 查询该键会被存储在哪里。
作者:且安
链接:https://juejin.cn/post/7239996748319211575
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。