Redis基础
文章目录
- 一、Redis入门概述
- 是什么?
- 能干嘛?
- 主流功能与应用
- 优势
- Redis7新特性
- 二、Redis安装配置
- Redis7安装步骤
- Redis7卸载步骤
- 三、Redis10大数据类型
- Redis 键(key)
- 1、Redis 字符串(String)
- 2、Reids列表(List)
- 3、Redis哈希(Hash)
- 4、Redis集合(Set)
- 5、Reids有序集合Zset(sorted set)
- 6、Redis位图(bitmap)
- 7、Redis基数统计(HyperLogLog)
- 8、Redis地理空间(GEO)
- 9、Redis流(Stream)
- 10、Redis位域(bitfield)
- 四、Redis持久化
- RDB(默认)
- 触发案例演示
- 1、自动触发
- 2、手动触发
- 检查修复dump.rdb文件
- 哪些情况会触发RDB快照
- RDB优化配置项详解
- AOF
- AOF持久化工作流程
- AOF 缓冲区三种写回策略
- 恢复案例演示
- 1、正常恢复
- 2、异常恢复
- AOF重写机制
- 案例说明
- 重写原理
- RDB-AOF混合持久化
- 数据恢复顺序和加载流程
- 同时开启两种持久化方式
- RDB+AOF混合方式
- 纯缓存模式
- 五、Redis事务
- 是什么?
- Redis事务和数据库事务
- Redis事务命令
- 案例
- 六、Redis管道
- 是什么?
- 案例
- 总结
- 七、Redis发布订阅(了解即可)
- 是什么?
- 能干嘛?
- 常用命令
- 案例演示
- 基于频道(Channel)的发布/订阅
- 基于模式(pattern)的发布/订阅
- 八、Redis复制(replica)
- 是什么?
- 案例演示
- 架构说明
- 小口诀
- 修改配置文件细节操作
- 一主二仆
- 方案1:配置文件固定写死主从关系
- 方案2:命令操作手动主从关系指令
- 薪火相传
- 反客为主
- 复制原理和工作流程
- slave启动,同步初请
- 首次连接,全量复制
- 心跳持续,保持通信
- 进入平稳,增量复制
- 从机下线,重连续传
- 复制的缺点
- 九、Redis哨兵(sentinel)
- 是什么?
- 能干嘛?
- 案例演示
- Redis Sentinel架构,前提说明
- 操作步骤
- 哨兵运行流程和选举原理
- 运行流程,故障切换
- 哨兵使用建议
- 十、Redis集群(cluster)
- 是什么
- 能干嘛
- 集群算法-分片-槽位slot
- redis集群的槽位slot
- redis集群的分片
- slot槽位映射,一般业界有三种解决方案
- Redis集群不保证强一致性
- 集群环境案例步骤
- 1. 3主3从Redis集群配置
- 2. 3主3从redis集群读写
- 3. 主从容错切换迁移案例
- 4. 主从扩容
- 5. 主从缩容
- 集群常用操作命令
一、Redis入门概述
是什么?
Remote Dictionary Server(远程字典服务)是完全开源的,使用ANSIC语言编写遵守BSD协议,是一个高性能的Key-Value数据库。提供了丰富的数据结构,例如String、Hash、List、Set、SortedSet等等。数据是存在内存中的,同时Redis支持事务、持久化、LUA脚本、发布/订阅、缓存淘汰、流技术等多种功能特性提供了主从模式、Redis Sentinel和Redis Cluster集群架构方案。
Redis从发布至今,已经有十余年的时光了,一直遵循着自己的命名规则:
-
版本号第二位如果是奇数,则为非稳定版本 如2.7、2.9、3.1
-
版本号第二位如果是偶数,则为稳定版本 如2.6、2.8、3.0、3.2
历史发布版本的源码:https://download.redis.io/releases/
能干嘛?
主流功能与应用
- 分布式缓存,挡在mysql数据库之前的一道屏障
- 内存存储和持久化(RDB+AOF),redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
- 高可用架构搭配
- 单机
- 主从
- 哨兵
- 集群
- 缓存穿透、击穿、雪崩
- 分布式锁
- 队列
- Reids提供list和set操作,这使得Redis能作为一个很好的消息队列平台来使用。
- 我们常通过Reids的队列功能做购买限制。比如到节假日或者推广期间,进行一些活动,对用户购买行为进行限制,限制今天只能购买几次商品或者一段时间内只能购买一次。也比较适合适用。
优势
- 性能极高 -Redis能读的速度是110000次/秒,写的速度是81000次/秒
- Redis数据类型丰富,不仅仅支持简单的key-value类型的数据,同时还提供list,zset,set,hash等数据结构的存储
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用
- Redis支持数据的备份,即master-slave模式的数据备份
- 生成dump.rpb文件(可以在配置文件中改) 默认生成在redis.conf同级目录
Redis7新特性
大体和之前的redis版本保持一致和稳定,主要是自身底层性能和资源利用率上的优化和提高。
特性 | 详解 |
---|---|
多AOF文件支持 | 7.0 版本中一个比较大的变化就是 aof 文件由一个变成了多个,主要分为两种类型:基本文件(base files)、增量文件(incr files),请注意这些文件名称是复数形式说明每一类文件不仅仅只有一个。在此之外还引入了一个清单文件(manifest) 用于跟踪文件以及文件的创建和应用顺序(恢复) |
config命令增强 | 对于Config Set 和Get命令,支持在一次调用过程中传递多个配置参数。例如,现在我们可以在执行一次Config Set命令中更改多个参数: config set maxmemory 10000001 maxmemory-clients 50% port 6399 |
限制客户端内存使用Client-eviction | 一旦 Redis 连接较多,再加上每个连接的内存占用都比较大的时候, Redis总连接内存占用可能会达到maxmemory的上限,可以增加允许限制所有客户端的总内存使用量配置项,redis.config 中对应的配置项// 两种配置形式:指定内存大小、基于 maxmemory 的百分比。maxmemory-clients 1gmaxmemory-clients 10% |
listpack紧凑列表调整 | listpack 是用来替代 ziplist 的新数据结构,在 7.0 版本已经没有 ziplist 的配置了(6.0版本仅部分数据类型作为过渡阶段在使用)listpack 已经替换了 ziplist 类似 hash-max-ziplist-entries 的配置 |
访问安全性增强ACLV2 | 在redis.conf配置文件中,protected-mode默认为yes,只有当你希望你的客户端在没有授权的情况下可以连接到Redis server的时候可以将protected-mode设置为no |
Redis Functions | Redis函数,一种新的通过服务端脚本扩展Redis的方式,函数与数据本身一起存储。简言之,redis自己要去抢夺Lua脚本的饭碗 |
RDB保存时间调整 | 将持久化文件RDB的保存规则发生了改变,尤其是时间记录频度变化 |
命令新增和变动 | Zset (有序集合)增加 ZMPOP、BZMPOP、ZINTERCARD 等命令Set (集合)增加 SINTERCARD 命令LIST (列表)增加 LMPOP、BLMPOP ,从提供的键名列表中的第一个非空列表键中弹出一个或多个元素。 |
性能资源利用率、安全、等改进 | 自身底层部分优化改动,Redis核心在许多方面进行了重构和改进主动碎片整理V2:增强版主动碎片整理,配合Jemalloc版本更新,更快更智能,延时更低HyperLogLog改进:在Redis5.0中,HyperLogLog算法得到改进,优化了计数统计时的内存使用效率,7更加优秀更好的内存统计报告如果不为了API向后兼容,我们将不再使用slave一词…(政治正确) |
二、Redis安装配置
Window 下安装
下载地址:https://github.com/dmajkic/redis/downloads
Redis7安装步骤
1、Linux安装Redis安装Redis必须先具备gcc编译环境
gcc -v #查看版本
yum -y install gcc-c++ #安装c++库环境
2、通过命令行下载Redis
#查看系统多少位,返回是多少就是几位
getconf LONG_BIT
wget http://download.redis.io/releases/redis-7.0.0.tar.gz
3、/opt 目录下解压redis
mv redis-7.0.0.tar.gz /opt/
cd /opt
tar -zxvf redis-7.0.0.tar.gz && cd redis-7.0.0
4、编译安装,在redis-7.0.0目录下执行
make && make install
# 查看redis版本的命令
redis-server -v
5、查看默认安装目录/usr/local/bin
cd /usr/local/bin && ls -l
#redis-benchmark:性能测试工具,服务启动后运行该命令,看看自己本子性能如何
#redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
#redis-check-dump:修复有问题的dump.rdb文件
#redis-cli:客户端,操作入口
#redis-sentinel:redis集群使用
#redis-server:Redis服务器启动命令
6、将默认的redis.conf拷贝到自己定义好的一个路径下,比如/myredis
mkdir /myredis
cp /opt/redis-7.0.0/redis.conf /myredis/
7、修改/myredis目录下redis.conf配置文件做初始化设置
vim /myredis/redis.conf
redis.conf配置文件,改完后确保生效,记得重启,记得重启
-
守护进程,默认daemonize no 改为 daemonize yes
-
保护模式,默认protected-mode yes 改为 protected-mode no
-
默认bind 127.0.0.1 改为 直接注释掉(默认bind 127.0.0.1只能本机访问)或改成本机IP地址,否则影响远程IP连接
-
添加redis密码 改为 requirepass 你自己设置的密码 requirepass 123456
8、在/usr/local/bin目录下运行redis-server,启用/myredis目录下的redis7conf
cd /usr/local/bin
redis-server /myredis/redis.conf
9、连接服务
# redis-cli -a 设置的密码 -p 6379
[root@VM-4-17-centos bin]# redis-cli -a 123456 -p 6379
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set k1 hellowrold
OK
127.0.0.1:6379> get k1
"hellowrold"
10、关闭服务
#单实例关闭
redis-cli -a 123456 shutdown
#多实例关闭,指定端口号关闭
redis-cli -p 6379 shutdown
Warning: Using a password with ‘-a’ or ‘-u’ option on the command line interface may not be safe.
解决办法将标准错误去除即可,追加2>/dev/null,将标准错误丢弃即可,就没有烦人的警告了。
redis-cli -a 123456 2>/dev/null
Redis端口为啥是6379么?
Redis的默认端口是6379,是由手机键盘字母MERZ的位置决定的。MERZ在Antirez的朋友圈语言中是"愚蠢和傻B"的代名词,它源于意大利广告女郎Alessia Merz在电视节目上说了一堆愚蠢的话,redis之父对她有"特殊"印象,就给她弄成端口号了
Redis7卸载步骤
1、停止redis-server服务
pe -ef | grep redis
redis-cli shutdown
2、删除/usr/local/lib目录下与redis相关的文件
ls -l /usr/local/bin/redis-*
rm -rf /usr/local/bin/redis-*
docker安装redis参考:https://blog.csdn.net/w918589859/article/details/108923873
三、Redis10大数据类型
这里说的数据类型是value的数据类型,key的类型都是字符串
Redis 键(key)
常用命令
# 返回满足的所有键 ,可以模糊匹配 比如 keys abc* 代表 abc 开头的 key
keys *
# 查找所有符合给定模式( pattern)的 key 。
# keys 通配符 获取所有与pattern匹配的key,返回所有与该匹配
# 通配符:
# * 代表所有
# ? 表示代表一个字符
keys pattern
# 是否存在指定的key,存在返回1,不存在返回0
exists key
# 设置某个key的过期时间 时间为秒
expire key second(秒)
# 查看你的key是什么类型
type key
# 删除某个key
del key
# 为给定 key 设置过期时间(以秒计)。
expire key seconds
# 设置 key 的过期时间以毫秒计
pexpire key milliseconds
# 以秒为单位,返回给定 key 还有多少秒过期,-1表示永不过期,-2表示已过期
ttl key
# 以毫秒为单位返回 key 的剩余的过期时间。
pttl key
# 根据value选择非阻塞删除 仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。
unlink key
# 修改Key的名称
rename key newkey
# 移除给定key的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。
# 当生存时间移除成功时,返回1。 如果 key 不存在或 key 没有设置生存时间,返回0
persist key
# 随机返回一个key
randomkey
# 重命名key
rename key key2
# 打印命令
echo
# 将当前数据库的 key 移动到给定的数据库 db 当中
move key dbindex [0-15]
# 将当前数据中的key转移到其他数据库
move key dbindex
# 选择数据库 数据库为0-15(默认一共16个数据库)设计成多个数据库实际上是为了数据库安全和备份
select dbindex [0-15]
# 查看当前数据库的key的数量
dbsize
# 查看数据库的key数量
dbsiz
# 查看数据库信息
info
# 实时传储收到的请求,返回相关的配置
config get *
# 清空当前数据库
flushdb
# 清空所有数据库
flushall
redis常见数据类型操作命令:http://www.redis.cn/commands.html
命令不区分大小写,而key是区分大小写的
帮助命令
127.0.0.1:6379> help @string
127.0.0.1:6379> help @list
127.0.0.1:6379> help @hash
127.0.0.1:6379> help @hyperloglog
..........
1、Redis 字符串(String)
- string是redis最基本的类型,一个key对应一个value。
- string类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象 。
- string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
常用命令
# set <key> <value> 添加键值对
set k1 v100
# 当k1有值时,值会被替换成v200
set k1 v200
# setnx <key> <value> 只有在key不存在时 设置key的值
setnx k1 v300 # key的值还是v200 因为k1已经存在
# append <key> <value> 将给定的<value> 追加到原值的末尾
append k1 v400
# strlen <key> 获得值的长度
strlen k1
# get <key> 查询对应键值
get k1
# incr <key> 将key中储存的数字值增1,只能对数字值操作,如果为空,新增值为1
incr k2
# decr <key> 将key中储存的数字值减1,只能对数字值操作,如果为空,新增值为-1
decr k3
# incrby/decrby <key> <步长> 将 key 中储存的数字值增减。自定义步长。
incrby k4 2
decrby k5 3
# 清空
flushdb
# mset <key1> <value1> <key2> <value2> ..... 同时设置一个或多个key-value对
mset k1 v1 k2 v2 k3 v3
# mget <key1> <key2> <key3> ..... 同时获取一个或多个value
mget k1 k2 k3
# msetnx <key1> <value1> <key2> <value2> ..... 同时设置一个或多个key-value对,当且仅当所有给定key都不存在。
# 原子性,有一个失败则都失败
msetnx k3 v3 k4 v4 k5 v5
# getrange <key> <起始位置> <结束位置> 获得某个范围的值,包含起始位置和结束位置
set user appadmin
getrange user 0 4 # 值为"appad"
# setrange <key> <起始位置> <value> 从<起始位置>开始替换为value和长度个数(索引从0开始)。
setrange user 1 root # 值为"arootmin"
# setex <key> <过期时间> <value> 设置键值的同时,设置过期时间,单位秒。
setex count 10 600
# getset <key> <value> 设置新值并获得旧值。
set key v1
getset k1 v2 # 结果为v1
get k1 #结果为v2
2、Reids列表(List)
-
里面的数据可以重复,单键多值
-
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
-
它的底层实际是个双端链表,最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)。底层就是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差
常用命令
## 赋值语法
# lpush/rpush <key> <value1> <value2> <value3> .... 从左边/右边插入一个或多个值。
lpush k1 100 200 300 100 400
# lpushx/rpushx <key> <value> 将一个值插入到已存在的列表头部/尾部。如果列表不在,操作无效
lpushx k1 500
## 取值语法:
# llen <key> 获取列表中元素的个数
llen k1
# lindex <key> <index> 按照索引下标获得元素(从左到右)
lindex k1 2
# lrange <key> <start> <stop> 按照索引下标从左到右获得元素 (0左边第一个,-1右边第一个,0 -1表示获取所有)
lrange k1 0 2
lrange k1 0 -1 # 也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
# ltrim key <begin> <end> 截取指定索引区间的元素,开始index结束index,截取指定范围的值后再赋值给key
ltrim list1 3 5
## 删除语法:
# lpop/rpop <key> 从左边/右边吐出一个值。值在键在,值光键亡。
lpop k1
# BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止
blpop k1 100
## 修改语法:
# lset <key> <index> <value> 将列表key下标为index的值替换成value
lset k1 3 5
# linsert <key> before <value> <newvalue> 在<value>的后面插入<newvalue>插入值
flsuhdb
lpush k1 100 200 300 400 500 600 500 500 800 500 200
linsert k1 before 2 500
# lrem <key> <n> <value> 从左边删除n个value(从左到右)
lrem k1 3 500
## 高级语法
# rpoplpush <key1> <key2> 从<key1>列表右边吐出一个值,插到<key2>列表左边。
lpush k2 10 20 30
rpoplpush k1 k2
3、Redis哈希(Hash)
k-v 模式不变,但v是一个键值对 => Map<String,Map<Object,Object>
- Redis hash 是一个键值对集合。
- Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象
常用命令
# 清空当前库
flushdb
# hset <key> <field> <value>给<key>集合中的 <field>键赋值<value> 新版中hset和hmset没有什么区别
hset user:0 id 0
hset user:1 id 1 name zhangsan age 18
# hmset <key1> <field1> <value1> <field2> <value2>... 批量设置hash的值
hmset user:2 id 2 name lisi age 18 sex 男
# hget <key1> <field> 从<key1>集合<field>取出 value
hget user:1 name
# hmget key field[field1] :获取key所有给定字段的值
hmget user:1 id name age
# hexists <key1> <field> 查看哈希表 key 中,给定域 field 是否存在。
hexists user:1 age
# hkeys <key> 列出该hash集合的所有field
hkeys user:1
# hvals <key> 列出该hash集合的所有value
hvals user:1
# hgetall <key>返回HASH表中所有的字段和值
hgetall user:1
# hlen <key> 获取哈希表中字段的数量
hlen user:1
# hincrby <key> <field> <increment> 为哈希表 key 中的域 field 的值加上增量 1 -1
hincrby user:1 age 1
hincrby user:1 age -3
# hsetnx <key> <field> <value> 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .
hsetnx user:3 id 3
hsetnx user:3 age 20
hsetnx user:3 age 18 # 不生效,因为user:3中age已存在
# hdel <key> field1[field2 删除一个或多个HASH表字段
hdel user:3 id age
4、Redis集合(Set)
单值多value,无重复
- Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
- Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。
- 一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变
常用命令
# 清空当前库
flushdb
## 添加元素
# sadd <key> <value1> <value2> ..... 将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
sadd k1 10 20 30 40
## 取值语法
# scard <key> 返回该集合的元素个数。
scard k1
# smembers <key> 遍历集合中所有元素
smembers k1
# sismember <key> <value> 判断集合<key>是否为含有该<value>值,有1,没有0
sismember k1 10 # 结果1
sismember k1 50 # 结果0
# srandmember <key> <n> 随机从该集合中取出n个值 不会从集合中删除
flushdb # 清空当前库
sadd k1 10 20 30 40 50 60 70
srandmember k1 3
# srem <key> <value1> <value2> .... 删除集合中的某个元素。
srem k1 10 20
smembers k1 # 结果30 40
# spop <key> 从集合中随即弹出一个元素 出一个删一个
flushdb # 清空当前库
sadd k1 10 20 30 40
spop k1
## 删除语法:
# srem <key> member1 [member2] :移除集合中一个或多个成员
srem k1 10 20
# spop <key>[count] :移除并返回集合中的一个随机元素
spop k1
# smove <key1> <key2> value 把集合key1中value移动到另一个集合key2中
flushdb # 清空当前库
sadd k1 10 20 30 40 50 60 70 80
smove k1 k2 10
## 交集语法
# sinter <key1> <key2> 返回两个集合的交集元素
flushdb # 清空当前库
sadd k1 10 20 30
sadd k2 20 30 40
sinter k1 k2 # 结果为 20 30
## 并集语法
# sunion <key1> <key2> 返回两个集合的并集元素
sunion k1 k2 # 结果为 10 20 30 40
## 差集语法
# sdiff <key1> <key2> 返回两个集合的差集元素(key1中的,不包含key2中的)
sdiff k1 k2 # 结果为 10
5、Reids有序集合Zset(sorted set)
在set基础上,每个val值前加一个score分数值。
之前set是 k1 V1 V2 V3
现在zset是k1 score1 v1 score2 v2
-
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
-
不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。
-
zset的成员是唯一的,但分数(score)却可以重复。
-
zset集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1
常用命令
# 清空当前库
flushdb
## 赋值语法
# zadd <key> <score1> <value1> <score2> <value2>… 将一个或多个member元素及其score值加入到有序集key当中。
zadd k1 100 zs 90 ls 80 ww 75 zl 70 sq 66 zb
## zcard <key>:获取有序集合的成员数
zcard k1
# zcount <key><min><max>统计该集合,分数区间内的元素个数
zcount k1 70 100
# zrank <key><value>返回该值在集合中的排名,从0开始。
zrank k1 zs
# zrange <key> <start> <stop> [WITHSCORES] 返回有序集 key 中,下标在<start><stop>之间的元素,带WITHSCORES,可以让分数一起和值返回到结果集。
zrange k1 0 -1
zrange k1 0 -1 withscores
zrange k1 0 3
zrange k1 0 3 withscores
# zrangebyscore key min max [withscores] [limit offset count] 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。
zrangebyscore k1 77 96
zrangebyscore k1 77 96 withscores
# zincrby <key> <increment> <value> 为元素的score加上增量
zincrby k1 -2 zs # 结果100-2=98
zincrby k1 3 ls # 结果90+3=93
# zrem <key> <value> 删除该集合下,指定值的元素
zrem k1 ls
6、Redis位图(bitmap)
由 0 和 1 表示的二进制位的 bit 数组
- 用String类型作为底层数据结构实现的一种统计二值状态的数据类型
- 位图本质是数组,它是基于String数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(我们称之为一个索引)
- Bitmap支持的最大位数是2^32位,它可以极大的节约存储空间,使用512M内存就可以存储多达42.9亿的字节信息(2的32次方 = 4294967296)
常用命令
# 将第offset的值设为value value只能是0或1 offset 从0开始
SETBIT key offset value
# 获得第offset位的值
GETBIT key offset
# 得出占多少字节 超过8位后自己按照8位一组一byte再扩容
STRLEN key
# 得出该key里面含有几个1
BITCOUNT key
# 对一个或多个 key 求逻辑并,并将结果保存到 destkey
BITOP and destKey key1 key2
# 对一个或多个 key 求逻辑或,并将结果保存到 destkey
BITOP or destKey key1 key2
# 对一个或多个 key 求逻辑异或,并将结果保存到 destkey
BITOP XOR destKey key1 key2
# 对一个或多个 key 求逻辑非,并将结果保存到 destkey
BITOP NOT destKey key1 key2
7、Redis基数统计(HyperLogLog)
- 去重复统计功能的基数估计算法就是 HyperLogLog
- 基数
- 是一种数据集,去重复后的真实个数
- 基数统计
- 用于统计一个集合中不重复的元素个数,就是对集合去重复后剩余元素的计算
- 只需要花费12KB内存,就能记录 2的64次方=18446744073709551616 个不同元素的基数
常用命令
# PFADD key element [element...] 添加指定元素到HyperLogLog中
pfadd program java
pfadd program python
# PFCOUNT key [key...] 返回给定HyperLogLog的基数估算值。
pfcount program
# PFMERGE destkey sourcekey [sourcekey...] 将多个HyperLogLog合并为一个HyperLogLog
8、Redis地理空间(GEO)
Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作
地球上的地理位置是使用二维的经纬度表示,经度范围 (-180, 180],纬度范围 (-90, 90],只要我们确定一个点的经纬度就可以名取得他在地球的位置。
- 例如滴滴打车,最直观的操作就是实时记录更新各个车的位置
- 然后当我们要找车时,在数据库中查找距离我们(坐标x0,y0)附近r公里范围内部的车辆
基本命令
#GEOADD多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的key中
GEOADD city 116.403963 39.915119 "天安门" 116.403414 39.924091 "故宫" 116.024067 40.362639 "长城"
#GEOPOS从键里面返回所有给定位置元素的位置(经度和纬度)
GEOPOS city 天安门 故宫 长城
#geodist <key> <member1> <member2> [m|km|ft|mi ] 获取两个位置之间的直线距离
GEODIST city 天安门 故宫 km
#georadius<key>< longitude><latitude>radius m|km|ft|mi 以给定的经纬度为中心,找出某一半径内的元素
GEORADIUS city 116.418017 39.914402 10 km withdist withcoord count 10 withhash desc
#GEORADIUSBYMEMBER跟GEORADIUS类似
GEORADIUSBymember city 天安门 10 km withdist withcoord count 10 withhash
#GEOHASH返回一个或多个位置元素的Geohash表示
GEOHASH city 天安门 故宫 长城
9、Redis流(Stream)
redis版的MQ消息中间件+阻塞队,Redis Stream 是 Redis 5.0 版本新增加的数据结构。
-
Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。
-
简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。
-
而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失
基本命令
#############队列相关指令
#XADD key id field1 value1 field2 value2 添加消息到队列未尾
# 向visits流中添加ID为1002的元素,且包含三个键值对
XADD visits 11000000000000-123456 name 'peter' location '/book/10086' duration 150
# 向visits中添加一个新元素,ID自动生成,且限制流的长度为5
XADD visits MAXLEN 5 * name 'tom' location '/news/77842' duration 300
#XTRIM key MAXLEN len 限制Stream的长度,如果已经超长会进行截取
# 将visits修剪到长度为3
XTRIM visits MAXLEN 3
#XDEL key id1 id2L 移除指定元素
# 移除visits中ID为11000000000000-123456的元素
XDEL visits 11000000000000-123456
#XLEN key 获取流包含的元素数量
# 获取visits流中当前包含的元素数量
XLEN visits
#XRANGE key startID endID [COUNT n] 获取消息列表(可以指定范围),忽略删除的消息
XRANGE mystream - +
#XREVRANGE 和XRANGE相比区别在于反向获取,ID从大到小
#XREAD [BLOCK ms] [COUNT n] STREAMS key1 key2 … id1 id2 … 获取消息(阻塞/非阻塞),返回大于指定ID的消息
# 获取visits流中最多三个ID大于11000000000000的元素
XREAD COUNT 3 STREAMS visits 11000000000000
#############消费组相关指令
# XGROUP CREATE stream groupName startID 创建消费者组
# 消费者组group1以ID为4作为七点,读取流中ID大于4的所有元素
XGROUP CREATE visits group1 4
# 读取消费者组 XREADGROUP GROUP group consumer [COUNT n] [BLOCK ms] STREAMS stream1 stream2 … id1 id2 …
# 以消费者worker1读取visits流中group1消费者组,起始ID为0
XREADGROUP GROUP group1 worker1 STREAMS visits 0
# 删除消费者组 XGROUP DESTROY stream group
XGROUP DESTROY stream group
# XPENDING stream group [start stop count] [consumer] 获取指定流的指定消费者组目前的待处理消息的相关信息
127.0.0.1:6379> XPENDING cgs all-message
1) (integer) 2
2) 12344566777-0
3) 12344566777-1
4) 1) 1) "worker1"
2) "1"
2) 1) "worker2"
2) "1"
# XACK stream group id [id id ...] 通过执行XACK命令,用户可以将消费者组中的指定消息标记为 “已处理”
######四个特殊符号
- + 最小和最大可能出现的ld
$ $表示只消费新的消息,当前流中最大的id,可用于将要到来的信息
> 用于XREADGROUP命令,表示迄今还没有发送给组中使用者的信息,会更新消费者组的最后ID
* 用于XADD命令中,让系统自动生成id
Stream还是不能100%等价于Kafka、RabbitMQ来使用的,生产案例少,慎用
10、Redis位域(bitfield)
- 将很多小的整数存储到一个长度较大的位图中,又或者将一个非常庞大的键分割位多个较小的键来进行储存,从而高效利用内存
- 也就是将 Redis 字符串看作是一个 由二进制位组成的数组 并能对变长位宽和任意没有字节对齐的指定整型位域进行寻址和修改
基本命令
#GET<type><offset>-返回指定的位域
set fieldkey hello
BITFIELD fieldkey get i8 0
#SET<type><offset><value>-设置指定位域的值并返回它的原值
set fieldkey hello
BITFIELD fieldkey get i8 0
get fieldkey
#INCRBY<type><offset><increment>-自增或自减(如果increment为负数)指定位域的值并返回它的新值
set fieldkey hello
BITFIELD fieldkey incrby u4 2 1
四、Redis持久化
RDB(默认)
在指定的时间间隔,执行数据集的时间点快照
- 在指定的时间间隔,执行数据集的时间点快照
- 实现类似照片记录效果的方式,就是把某一时刻的数据和状态以文件的形式写到磁盘上,也就是快照。这样一来即使故障宕机,快照文件也不会丢失,数据的可靠性也就得到了保证。
- 这个快照文件就称为RDB文件(dump.rdb),其中,RDB就是Redis DataBase的缩写。
- 将内存数据全部保存到磁盘dump.rdb文加中
触发机制
- save的规则满足的条件下,会自动触发rdb规则
- 执行flushall命令,也会触发我们的rdb规则
- 退出redis,也会产生rdb文件
优点
- 适合大规模的数据恢复
- 按照业务定时备份
- 对数据完整性和一致性要求不高
- RDB 文件在内存中的加载速度比AOF快得多
缺点
- 在一定间隔时间做一次备份,如果redis意外down机,就会丢掉最近一次快照到down机时的数据
- 内存数量的全量同步,如果数据量过大会导致IO严重影响服务器性能
- RDB依赖于主进程的 fork ,在更大的数据集中,这可能会导致服务器请求的瞬间延迟
- fork 的时候内存中的数据被克隆了一份,大致2倍的膨胀性,需要考虑
触发案例演示
1、自动触发
Redis7版本,按照redis.conf里配置的save
vim /myredis/redis.conf
.....
################################ SNAPSHOTTING ################################
# Save the DB to disk.
#
# save <seconds> <changes> [<seconds> <changes> ...]
#
# Redis will save the DB if the given number of seconds elapsed and it
# surpassed the given number of write operations against the DB.
#
# Snapshotting can be completely disabled with a single empty string argument
# as in following example:
#
# save ""
#
# Unless specified otherwise, by default Redis will save the DB:
# * After 3600 seconds (an hour) if at least 1 change was performed
# * After 300 seconds (5 minutes) if at least 100 changes were performed
# * After 60 seconds if at least 10000 changes were performed
#
# You can set these explicitly by uncommenting the following line.
#
# save 3600 1 300 100 60 10000
# 修改此处
#save 3600 1 300 100 60 100 // 3600秒 修改一次 100秒 修改60次 60秒 修改10000次 触发保存
save 5 2 # 5秒2次修改
....
修改dump文件保存路径,dump文件名称
vim /myredis/redis.conf
...
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
# 修改保存路径
# dir ./
dir /myredis/dumpfiles
....
....
# The filename where to dump the DB
# dump文件名称
dbfilename dump6379.rdb
.....
触发备份
[root@VM-4-17-centos bin]# redis-cli -a 123456 -p 6379
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
# 查看dumpfile文件大小是否有变化
ls -l /myredis/dumpfiles/
备份恢复
物理恢复,一定服务和备份分机隔离,各自存储
2、手动触发
Redis提供了两个命令来生成RDB文件分别是save和bgsave
- Save
- 在主线程中执行会阻塞redis服务器,直到持久化工作完成才能处理其他命令, 线上禁止使用
- BGSAVE(默认)
- Redis 会在后台异步进行快照操作,不阻塞快照同时还可以响应客户端请求,该触发过程会 fork 一个子进程由子进程复制持久化过程
- lastsave 命令可以获取最后一次成功执行快照的时间
检查修复dump.rdb文件
#修复: redis-check-rdb 文件名
cd /usr/local/bin
redis-check-rdb dump6379.rdb
哪些情况会触发RDB快照
- 配置文件中默认的快照配置
- 手动 save/bgsave 命令
- 执行flush / flushdb 命令也会产生 dump.rdb 文件,但里面是空的,无意义
- 执行 shutdown 且没有设置开启 AOF 持久化
- 主从复制时,主节点自动触发
如何禁用快照
动态所有停止 RDB 保存规则的方法:
redis-cli config set save ""
#快照禁用
vim /myredis/redis.conf
# Snapshotting can be completely disabled with a single empty string argument
# as in following example:
#
# save ""
RDB优化配置项详解
vim /myredis/redis.conf
......
# 配置文件SNAPSHOTTING模块
# However if you have setup your proper monitoring of the Redis server
# and persistence, you may want to disable this feature so that Redis will
# continue to work as usual even if there are problems with disk,
# permissions, and so forth.
# 默认yes,如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制这种不一致,那么在快照写入失败时,也能确保redis继续接受新的写请求
stop-writes-on-bgsave-error yes
# Compress string objects using LZF when dump .rdb databases?
# By default compression is enabled as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
# 默认yes,对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能
rdbcompression yes
# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
# 默认yes,在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能
rdbchecksum yes
# An alternative (and sometimes better) way to obtain the same effect is
# to use diskless replication on both master and replicas instances. However
# in the case of replicas, diskless is not always an option.
# rdb-del-sync-files:在没有持久性的情况下删除复制中使用的RDB文件启用。默认情况下no,此选项是禁用的。
rdb-del-sync-files no
.......
AOF
- 以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
- 默认情况下,redis是没有开启AOF的
- 开启AOF 功能需要设置配置 : appendonly yes
Aof保存的是appendonly.aof文件
AOF持久化工作流程
- Client作为命令的来源,会有多个源头以及源源不断的请求命令。
- 在这些命令到达Redis Server 以后并不是直接写入AOF文件,会将其这些命令先放入AOF缓存中进行保存。这里的AOF缓冲区实际上是内存中的一片区域,存在的目的是当这些命令达到一定量以后再写入磁盘,避免频繁的磁盘IO操作。
- AOF缓冲会根据AOF缓冲区同步文件的三种写回策略将命令写入磁盘上的AOF文件
- 随着写入AOF内容的增加为避免文件膨胀,会根据规则进行命令的合并(又称AOF重写),从而起到AOF文件压缩的目的。
- 当Redis Server 服务器重启的时候会从AOF文件载入数据。
AOF 缓冲区三种写回策略
三种写回策略(appendfsync)
- always 同步写回,每个写命令执行完立刻同步地将日志写回磁盘
- everysec 每秒写回,每个写命令执行完,只是先把日志写到AOF缓冲区,每隔1s把缓存区地数据写入磁盘
- 操作系统控制协会,只是将日志先写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘
恢复案例演示
配置文件说明
vim /myredis/redis.conf
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check https://redis.io/topics/persistence for more information.
# 默认是no关闭,设置成yes开启
appendonly yes
#######aof保存路径,dir + appenddirname
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
# 修改保存路径
# dir ./
dir /myredis
# For convenience, Redis stores all persistent append-only files in a dedicated
# directory. The name of the directory is determined by the appenddirname
# configuration parameter.
appenddirname "appendonlydir"
#######aof文件保存名称
# 基本文件
# - appendonly.aof.1.base.rdb as a base file.
# 增量文件
# - appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof as incremental files.
# 清单文件
# - appendonly.aof.manifest as a manifest file.
appendfilename "appendonly.aof"
1、正常恢复
启动设置yes,修改默认的appendonly no,改为yes
vim /myredis/redis.conf
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check https://redis.io/topics/persistence for more information.
# 默认是no关闭,设置成yes开启
appendonly yes
写操作继续,生成aof文件到指定的目录
redis-cli -a 123456
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k3 v1
OK
cd /myredis && ll
cd appendonlydir/ && ll
恢复1:重启redis然后重新加载,结果OK
# 停止redis
ps -ef | grep redis | grep -v grep|awk '{print $2}'|xargs kill -9
# 启动redis
cd /usr/local/bin
redis-server /myredis/redis.conf
恢复2:
写入数据进redis,然后flushdb+shutdown服务器
redis-cli -a 123456
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k3 v1
OK
127.0.0.1:6379> flushdb
# 停止服务
redis-cli shutdown
备份新生成的aof.bak,然后删除dump/aof再看恢复
cd /myredis
cp -r appendonlydir appendonlydir_bak
rm -rf appendonlydir
rm -rf dump6378.rdb
#重启redis
redis-server /myredis/redis.conf
redis-cli -a 123456
127.0.0.1:6379> keys *
(empty array)
停止服务器,拿出我们的备份修改后再重新启动服务器看看
# 停止服务
redis-cli shutdown
cd /myredis
mv appendonlydir_bak appendonlydir
redis-server /myredis/redis.conf
redis-cli -a 123456
127.0.0.1:6379> keys *
(empty array)
2、异常恢复
故意乱写正常的AOF文件,模拟网络闪断文件写error
vim /myredis/appendonlydir/appendonly.aof.1.incr.aof
......
asdjfaldngaklfjgqqiwpoiopqwtopqr
重启Redis之后就会进行AOF文件的载入,发现启动都不行
redis-server /myredis/redis.conf
redis-cli -a 123456
Warning: Using a password with'a' or'-u' option on the command
line interface may not be safe.
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected>
not connected>
not connected> lsof-i:6379
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> quit
异常修复
#进行修复 redis-check-aof --fix aof文件
redis-check-aof --fix /myredis/appendonlydir/appendonly.aof.1.incr.aof
优势
- 更好的保护数据不丢失、性能高、可做紧急恢复
劣势
- 相同数据集的数据而言aof文件要远大于rdb文件,恢复速度慢于rdb
- aof运行效率要慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
AOF重写机制
启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集
触发机制
-
自动触发
满足配置文件中的选项后,Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时
-
手动触发
客户端向服务器发送bgrewriteaof命令
案例说明
启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集
前期配置准备
vim /myredis/redis.conf
........
# AOF and RDB persistence can be enabled at the same time without problems.
# If the AOF is enabled on startup Redis will load the AOF, that is the file
# with the better durability guarantees.
#
# Please check https://redis.io/topics/persistence for more information.
# 默认是no关闭,设置成yes开启
appendonly yes
........
# Specify a percentage of zero in order to disable the automatic AOF
# rewrite feature.
auto-aof-rewrite-percentage 100
# 重写峰值修改为1k,默认64mb
auto-aof-rewrite-min-size 1k
........
# Redis can create append-only base files in either RDB or AOF formats. Using
# the RDB format is always faster and more efficient, and disabling it is only
# supported for backward compatibility purposes.
# 关闭混合,设置为no
aof-use-rdb-preamble no
自动触发案例01
完成上述正确配置,重启redis服务器执行setk1v1查看aof文件是否正常
#重启redis
redis-server /myredis/redis.conf
redis-cli -a 123456
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 v1
OK
ls -l /myredis/appendonlydir/
k1不停111111暴涨
redis-cli -a 123456
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 111111111111111111111111111111111111111111111111111111111111111
OK
127.0.0.1:6379> set k1 111111111111111111111111111111111111111111111111111111111111111
OK
127.0.0.1:6379> set k1 111111111111111111111111111111111111111111111111111111111111111
OK
重写触发
# 文件慢慢变大,到峰值后启动重写机制
ls -l /myredis/appendonlydir/
手动触发案例02
客户端向服务器发送bgrewriteaof命令
127.0.0.1:6379> set k1 v1
ok
127.0.0.1:6379> bgrewriteaof
重写原理
-
在重写开始前,redis会创建一个“重写子进程”,这个子进程会读取现有的AOF文件,并将其包含的指令进行分析压缩并写入到一个临时文件中。
-
与此同时,主进程会将新接收到的写指令一边累积到内存缓冲区中,一边继续写入到原有的AOF文件中,这样做是保证原有的AOF文件的可用性,避免在重写过程中出现意外。
-
当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中
-
当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中
-
重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似
RDB-AOF混合持久化
数据恢复顺序和加载流程
共存听AOF的
怎么选?用那个?
- RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件未尾
同时开启两种持久化方式
- 当redis 重启时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
- RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。
- 那要不要只使用AOF呢
- 安特雷兹建议不要
- 因为RDB更适合用于备份数据库(AOF不断变化不好备份),留着AOF作为一个万一的手段
- 那要不要只使用AOF呢
RDB+AOF混合方式
结合了RDB和AOF的优点,既能快速加载又能避免丢失过多的数据。
开启混合方式设置
设置aof-use-rdb-preamble的值为 yes yes表示开启,设置为no表示禁用
RDB+AOF的混合方式---------> 结论:RDB镜像做全量持久化,AOF做增量持久化
先使用RDB进行快照存储,然后使用AOF持久化记录所有的写操作,当重写策略满足或手动触发重写的时候,将最新的数据存储为新的RDB记录。这样的话,重启服务的时候会从RDB和AOF两部分恢复数据,既保证了数据完整性,又提高了恢复数据的性能。简单来说:混合持久化方式产生的文件一部分是RDB格式,一部分是AOF格式。----》AOF包括了RDB头部+AOF混写
纯缓存模式
同时关闭RDB + AOF
- save “”
- 禁用rdb
- 禁用db持久化模式下,我们仍然可以使用命令save、bgsave生成rdb文件
- appendonly no
- 禁用aof
- 禁用aof持久化模式下,我们仍然可以使用命令 bgrewriteaof生成aof文件
五、Redis事务
是什么?
-
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞
-
一个队列中,一次性、顺序性、排他性的执行一系列命令
Redis事务和数据库事务
1 单独的隔离操作 | Redis的事务仅仅是保证事务里的操作会被连续独占的执行,redis命令执行是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的 |
---|---|
2 没有隔离级别的概念 | 因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”这种问题了 |
3不保证原子性 | Redis的事务不保证原子性,也就是不保证所有指令同时成功或同时失败,只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力 |
4 排它性 | Redis会保证一个事务内的命令依次执行,而不会被其它命令插入 |
Redis事务命令
# 开启事务,redis会将后续的命令逐个放入队列中,然后使用EXEC命令来原子化执行这个命令系列。
MULTI
# 执行事务中的所有操作命令。
EXEC
# 取消事务,放弃执行事务块中的所有命令。
DISCARD
# 监视一个或多个key,如果事务在执行前,这个key(或多个key)被其他命令修改,则事务被中断,不会执行事务中的任何命令。
WATCH key [key ...]
# 取消WATCH对所有key的监视。
UNWATCH
案例
正常执行
# 开启事务
127.0.0.1:6379> MULTI
OK
# 将两个指令组队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
# 执行两个指令
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379>
放弃事务
# 开启事务
127.0.0.1:6379> MULTI
OK
# 将两个指令组队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v23
QUEUED
# 取消事务
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
全体连坐
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
# 语法写错
127.0.0.1:6379(TX)> set k33
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
# 所有命令都不会执行
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
冤头债主
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> INCR k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
# 前期语法都没错,编译通过;执行exec后报错:冤有头,债有主对的执行错的停
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379>
watch 监控
Redis使用Watch来提供乐观锁定,类似于CAS(Check-and-Set)
-
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
-
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
乐观锁策略:提交版本必须 大于 记录当前版本才能执行更新
127.0.0.1:6379> get k1
"abc"
127.0.0.1:6379> get balance
OK
# 监控
127.0.0.1:6379> WATCH balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set balance 110
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> get balance
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
3) "v1"
4) "110"
127.0.0.1:6379>
总结
- 开启:以MULTI开始一个事务
- 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面
- 执行:由EXEC命令触发事务
六、Redis管道
是什么?
管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完完毕后,通过一条响应一次性将结果返回,通过减少客户端与redis的通信次数来实现降低往返延时时间。pipeline实现的原理是队列,先进先出特性就保证数据的顺序性。
批处理命令变种优化措施,类似Redis的原生批命令(mget和mset)
案例
cat >> cmd.txt <<EOF
set k100 v100
set k200 v200
hset k300 name haha
hset k300 age 20
hset k300 gender male
EOF
cat cmd.txt | redis-cli -a 123456 --pipe
redis-cli -a 123456
127.0.0.1:6379> get k100
"v100"
总结
Pipeline与原生批量命令对比
- 原生批量命令是原子性(例如:mset,mget),pipeline是非原子性
- 原生批量命令一次只能执行一种命令,pipeline支持批量执行不同命令
- 原生批命令是服务端实现,而pipeline需要服务端与客户端共同完成
Pipeline与事务对比
- 事务具有原子性,管道不具有原子性
- 管道一次性将多条命令发送到服务器,事务是一条一条的发,事务只有在接收到exec命令后才会执行,管道不会
- 执行事务时会阻塞其他命令的执行,而执行管道中的命令时不会
使用Pipeline注意事项
- pipeline缓冲的指令只是会依次执行,不保证原子性,如果执行中指令发生异常,将会继续执行后续的指令
- 使用pipeline组装的命令个数不能太多,不然数据量过大客户端阻塞的时间可能过久,同时服务端此时也被迫回复一个队列答复,占用很多内存
七、Redis发布订阅(了解即可)
是什么?
-
是一种消息通信模式:发送者(PUBLISH)发送消息,订阅者(SUBSCRIBE)接收消息,可以实现进程间的消息传递
-
Redis可以实现消息中间件MQ的功能,通过发布订阅实现消息的引导和分流。仅代表我个人,不推荐使用该功能,专业的事情交给专业的中间件处理,redis就做好分布式缓存功能
-
发布/订阅其实是一个轻量的队列,只不过数据不会被持久化,一般用来处理实时性较高的异步消息
能干嘛?
Redis客户端可以订阅任意数量的频道,类似我们微信关注多个公众号
常用命令
##### 基于频道
# 订阅给定的一个或多个频道
subscribe channel [channel ... ]
# 退订给定的频道
unsubscribe channel [channel ... ]
# 说明:若没有指定channel,则默认退订所有频道
# 将消息发送给指定频道 channel , 返回结果:接收到信息的订阅者数量,无订阅者返回0
publish channel message
# 查看订阅与发布系统的状态
pubsub channels [argument [atgument ...] ]
# 说明:返回活跃频道列表(即至少有一个订阅者的频道,订阅模式的客户端除外)
##### 基于模式
# 订阅一个或多个符合给定模式的频道
psubscribe pattern1 [pattern...]
#说明:每个模式以 * 作为匹配符;例如 cn* 匹配所有以cn开头的频道:cn.java、cn.csdn
# 退订所有给定模式的频道
punsubscribe [pattern [pattern ...] ]
# 说明:pattern 未指定,则订阅的所有模式都会被退订,否则只退订指定的订阅的模式
案例演示
基于频道(Channel)的发布/订阅
--------------------------客户端1(订阅者) :订阅频道 ---------------------
# 订阅 “meihuashisan” 和 “csdn” 频道(如果不存在则会创建频道)
127.0.0.1:6379> subscribe meihuashisan csdn
Reading messages... (press Ctrl-C to quit)
1) "subscribe" -- 返回值类型:表示订阅成功!
2) "meihuashisan" -- 订阅频道的名称
3) (integer) 1 -- 当前客户端已订阅频道的数量
1) "subscribe"
2) "csdn"
3) (integer) 2
#注意:订阅后,该客户端会一直监听消息,如果发送者有消息发给频道,这里会立刻接收到消息
--------------------------客户端2(发布者):发布消息给频道 -------------------
# 给“meihuashisan”这个频道 发送一条消息:“I am meihuashisan”
127.0.0.1:6379> publish meihuashisan "I am meihuashisan"
(integer) 1 # 接收到信息的订阅者数量,无订阅者返回0
# --------------------------客户端1(订阅者) :订阅频道 -----------------
127.0.0.1:6379> subscribe meihuashisan csdn
Reading messages... (press Ctrl-C to quit)
1) "subscribe" -- 返回值类型:表示订阅成功!
2) "meihuashisan" -- 订阅频道的名称
3) (integer) 1 -- 当前客户端已订阅频道的数量
1) "subscribe"
2) "csdn"
3) (integer) 2
---------------------变化如下:(实时接收到了该频道的发布者的消息)------------
1) "message" -- 返回值类型:消息
2) "meihuashisan" -- 来源(从哪个频道发过来的)
3) "I am meihuashisan" -- 消息内容
基于模式(pattern)的发布/订阅
订阅者订阅频道 psubscribe pattern [pattern …]
--------------------------客户端1(订阅者) :订阅频道 ---------------------
# 1. ------------订阅 “a?” "com.*" 2种模式频道--------------
127.0.0.1:6379> psubscribe a? com.*
# 进入订阅状态后处于阻塞,可以按Ctrl+C键退出订阅状态
Reading messages... (press Ctrl-C to quit)
# 2. ---------------订阅成功-------------------
1) "psubscribe" -- 返回值的类型:显示订阅成功
2) "a?" -- 订阅的模式
3) (integer) 1 -- 目前已订阅的模式的数量
1) "psubscribe"
2) "com.*"
3) (integer) 2
# 3. ---------------接收消息 (已订阅 “a?” "com.*" 两种模式!)-----------------
# ---- 发布者第1条命令: publish ahead "hello"
结果:没有接收到消息,匹配失败,不满足 “a?” ,“?”表示一个占位符, a后面的head有4个占位符
# ---- 发布者第2条命令: publish aa "hello" (满足 “a?”)
1) "pmessage" -- 返回值的类型:信息
2) "a?" -- 信息匹配的模式:a?
3) "aa" -- 信息本身的目标频道:aa
4) "hello" -- 信息的内容:"hello"
# ---- 发布者第3条命令:publish com.juc "hello2"(满足 “com.*”, *表示任意个占位符)
1) "pmessage" -- 返回值的类型:信息
2) "com.*" -- 匹配模式:com.*
3) "com.juc" -- 实际频道:com.juc
4) "hello2" -- 信息:"hello2"
# ---- 发布者第4条命令: publish com. "hello3"(满足 “com.*”, *表示任意个占位符)
1) "pmessage" -- 返回值的类型:信息
2) "com.*" -- 匹配模式:com.*
3) "com." -- 实际频道:com.
4) "hello3" -- 信息:"hello3"
- 发布者发布消息 publish channel message
--------------------------客户端2(发布者):发布消息给频道 -------------------
注意:订阅者已订阅 “a?” "com.*" 两种模式!
# 1. ahead 不符合“a?”模式,?表示1个占位符
127.0.0.1:6379> publish ahead "hello"
(integer) 0 -- 匹配失败,0:无订阅者
# 2. aa 符合“a?”模式,?表示1个占位符
127.0.0.1:6379> publish aa "hello"
(integer) 1
# 3. 符合“com.*”模式,*表示任意个占位符
127.0.0.1:6379> publish com.juc "hello2"
(integer) 1
# 4. 符合“com.*”模式,*表示任意个占位符
127.0.0.1:6379> publish com. "hello3"
(integer) 1
Pub/Sub缺点
-
发布的消息在Redis系统中不能持久化,因此,必须先执行订阅,再等待消息发布。如果先发布了消息,那么该消息由于没有订阅者,消息将被直接丢弃
-
消息只管发送对于发布者而言消息是即发即失的,不管接收,也没有ACK机制,无法保证消息的消费成功。
-
以上的缺点导致Redis的Pub/Sub模式就像个小玩具,在生产环境中几乎无用武之地,为此Redis5.0版本新增了Stream数据结构,不但支持多播,还支持数据持久化,相比Pub/Sub更加的强大
八、Redis复制(replica)
是什么?
就是主从复制,master以写为主,Slave以读为主。当master数据变化的时候,自动将新的数据异步同步到其它slave数据库
- 读写分离
- 容灾恢复
- 数据备份
- 水平扩容支撑高并发
案例演示
架构说明
一个Master两个Slave,三台虚拟机,每台都安装redis
拷贝多个redis.conf文件
redis6379.conf、redis6380.conf、redis6381.conf
小口诀
三台虚拟机需要能相互ping通且需要注意防火墙配置
三大命令:
-
主从复制
replicaof 主库IP 主库端口,配从(库)不配主(库)
-
改换门庭
slaveof 新主库IP 新主库端口
-
自立为王
slaveof no one
修改配置文件细节操作
redis6379.conf为例,步骤如下:
-
开启daemonize yes
-
注释掉bind 127.0.0.1
-
protected-mode no
-
指定端口
-
指定当前工作目录,dir
-
pid文件名字,pidfile
-
log文件名字,logfile。如果日志文件和启动文件同级,这里可以配置为./6379.log,否则这里一定要写绝对路径,是个巨坑!!!
-
requiredpass
-
dump.rdb名字
-
aof文件,appendfilename
-
从机访问主机的通行密码masterauth,必须
从机需要配置,主机不用
一主二仆
方案1:配置文件固定写死主从关系
-
配置文件执行:replicaof 主库IP 主库端口
-
配从(库)不配(主)库:配置从机
-
先master,后两台slave依次启动
-
主从关系查看
-
日志
主机日志,vim 6379.log
备机日志 tail -f 6380.log
-
命令
127.0.0.1:6379> info replication
-
方案2:命令操作手动主从关系指令
-
从机停机去掉配置文件中的配置项,3台目前都是主机状态,各不从属
-
3台master
-
预设的从机上执行命令
salveof 主库IP 主库端口
配置 VS 命令的区别
- 配置,持久稳定永久生效;
- 命令,当成生效
薪火相传
- 上一个slave可以是下一个slave的master,slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master,可以有效减轻主master的写压力
- 中途变更转向:会清除之前的数据,重新建立主从关系并拷贝最新的
- slaveof 新主库IP 新主库端口
反客为主
slaveof no one 使当前数据库停止与其他数据库的同步关系
复制原理和工作流程
slave启动,同步初请
- slave启动成功链接到master后会发送一个sync命令
- slave首次全新连接master,一次完全同步(全量复制)将被自动执行,slave自身原有数据会被master数据覆盖清除
首次连接,全量复制
- master节点收到sync命令后会开始在后台保存快照(即RDB持久化,主从复制时会触发RDB),同时收集所有接收到的用于修改数据集的命令并缓存起来,master节点执行RDB持久化完后,master将RDB快照文件和所有缓存的命令发送到所有slave,以完成一次完全同步
- 而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中,从而完成复制初始化
心跳持续,保持通信
-
repl-ping-replica-period 10
进入平稳,增量复制
- master继续将新的所有收集到的修改命令自动依次传送给slave,完成同步
从机下线,重连续传
- master会检查backlog里面的offset,master和slave都会保存一个复制的offset还有一个masterId,offset是保存在backlog中的。master只会把已经缓存的offset后面的数据复制给slave,类似断点续传
复制的缺点
-
复制延时,信号衰减
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。
-
master挂了怎么办?
默认情况下,不会在slave节点中自动选一个master
那每次都要人工干预? -> 无人值守变成刚需
九、Redis哨兵(sentinel)
是什么?
吹哨人巡查监控后台master主机是否故障,如果故障了根据 投票数 \textcolor{red}{投票数} 投票数自动将某一个从库转换为新主库,继续对外服务
作用:俗称无人值守运维
能干嘛?
主从监控:监控主从redis库运行是否正常
消息通知:哨兵可以将故障转移的结果发送给客户端
故障转移:如果master异常,则会进行主从切换,将其中一个slave作为新master
配置中心:客户端通过连接哨兵来获得当前Redis服务的主节点地址
案例演示
Redis Sentinel架构,前提说明
- 3个哨兵:自动监控和维护集群,不存放数据,只是吹哨人
- 1主2从:用于数据读取和存放
操作步骤
-
myredis目录下新建或者拷贝sentinel.conf文件按,名字绝对不能错
cp -a /opt/redis-7.0.0/sentinel.conf /myredis/
-
先看看/opt目录下默认的sentinel.conf文件的内容
cat /opt/redis-7.0.0/sentinel.conf
-
重要参数项说明
-
bind:服务监听地址,用于客户端连接,默认本机地址
-
daemonize:是否以后台daemon方式运行
-
protected-model:安全保护模式
-
port:端口
-
logfile:日志文件路径
-
pidfile:pid文件路径
-
dir:工作目录
-
sentinel monitor
-
设置要监控的master服务器
-
quorum表示最少有几个哨兵认可客观下线,同意故障迁移的法定票数
行尾最后的quorum代表什么意思呢?quorum:确认客观下线的最少的哨兵数量
网络是不可靠的,有时候一个sentinel会因为网络堵塞而误以为一个master redis已经死掉了,在sentinel集群环境下需要多个sentinel互相沟通来确认某个master是否真的死了,quorum这个参数是进行客观下线的一个依据,意思是至少有quorum个sentinel认为这个master有故障,才会对这个master进行下线以及故障转移。因为有的时候,某个sentinel节点可能因为自身网络原因,导致无法连接master,而此时master并没有出现故障,所以,这就需要多个sentinel都一致认为该master有问题,才可以进行下一步操作,这就保证了公平性和高可用。
-
-
sentinel auth-pass
master设置了密码,连接master服务的密码
-
其他
参数 解释 sentinel down-after-milliseconds 指定多少毫秒之后,主节点没有应答哨兵,此时哨兵主观上认为主节点下线 sentinel parallel-syncs 表示允许并行同步的slave个数,当Master挂了后,哨兵会选出新的Master,此时,剩余的slave会向新的master发起同步数据 sentinel failover-timeout 故障转移的超时时间,进行故障转移时,如果超过设置的毫秒,表示故障转移失败 sentinel notification-script 配置当某一事件发生时所需要执行的脚本 sentinel client-reconfig-script 客户端重新配置主节点参数脚本
-
-
sentinel通用配置及主从配置
-
sentinel26379.conf
bind 0.0.0.0 daemonize yes protected-mode no port 26379 logfile "/myredis/sentinel26379.log" pidfile /var/run/redis-sentinel26379.pid dir /myredis sentinel monitor mymaster 192.168.111.169 6379 2 sentinel auth-pass mymaster 111111
-
sentinel26380.conf
bind 0.0.0.0 daemonize yes protected-mode no port 26380 logfile "/myredis/sentinel26380.log" pidfile /var/run/redis-sentinel26380.pid dir "/myredis" sentinel monitor mymaster 192.168.111.169 6379 2 sentinel auth-pass mymaster 111111
-
sentinel26381.conf
bind 0.0.0.0 daemonize yes protected-mode no port 26381 logfile "/myredis/sentinel26381.log" pidfile /var/run/redis-sentinel26381.pid dir "/myredis" sentinel monitor mymaster 192.168.111.169 6379 2 sentinel auth-pass mymaster 111111
-
-
master主机配置文件说明
理论上sentinel配置文件应该部署在不同的服务器上,做成集群,但是本次演示将其放到一台机器上
-
先启动一主二从3个redis实例,测试正常的主从复制
-
架构说明
-
主机6379配置修改
6379后续可能会变成从机,需要设置访问新主机的密码, 请设置masterauth项访问密码为111111,不然后续可能报错master_link_status:down
-
redis6380.conf、redis6381.conf 配置文件修改
具体IP地址和密码根据你本地真实情况,酌情修改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fHeOQoI6-1682232837449)(Redis基础-image/image-20230420153847618.png)]
-
3台不同的虚拟机实例,启动三台真是机器实例并连接
redis-server redis6379.conf
redis-server redis6380.conf
redis-server redis6381.conf
redis-cli -a 123456 -p 6379
redis-cli -a 123456 -p 6380
redis-cli -a 123456 -p 6381
-
-
再启动3个哨兵监控后再测试一次主从复制
redis-server sentinel26379.conf --sentinel
redis-server sentinel26380.conf --sentinel
redis-server sentinel26381.conf --sentinel
-
原有的master挂了
我们自己手动关闭6379服务器,模拟master挂了
-
问题思考
- 两台从机数据是否OK
- 是否会从剩下的2台机器上选出新的master
- 之前down机的master机器重启回来,谁将会是新老大?会不会双master冲突
-
揭晓答案
-
两台从机数据OK
-
会投票选出新的master主机
-
谁是master,限本次案例
本案例中6381被选举为新的master,上位成功
重启6379之后,它会从原来的master降级为slave
6380还是slave,只不过是换了一个新老大6381(从跟随6379变成跟随6381)
-
-
-
对比配置文件
老master的redis6379.conf文件
新master的redis6381.conf文件
结论
- 文件的内容,在运行期间会被sentinel动态进行更改
- Master-Slave切换后,master_redis.conf、slave_redis.conf、sentinel.conf的内容都会发生改\变,即master_redis.conf中会多一行slaveof的配置,而升级为master的主机会去掉原来的slaveof配置,sentinel.conf的监控目标会随之调换
哨兵运行流程和选举原理
当一个主从配置中master失效后,sentinel可以选举出一个新的master用于自动接替原master的工作,主从配置中的其他redis服务器自动指向新的master同步数据,一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换
运行流程,故障切换
-
三个哨兵监控一主二从,正常运行中
-
SDown主观下线(Subjectively Down)
-
SDOWN(主观不可用)是单个sentinel自己主观上检测到的关于master的状态,从sentinel的角度来看,如果发送了PING心跳后,在一定时间内没有收到合法的回复,就达到了SDOWN的条件。
-
sentinel配置文件中的down-after-milliseconds设置了判断主观下线的时间长度
-
说明
-
-
ODown客观下线(Objectively Down)
-
ODOWN需要一定数量的sentinel,多个哨兵达成一致意见才能认为一个master客观上已经宕机
-
说明
-
-
选举出领导者哨兵(哨兵中选出兵王)
-
当主节点被判断客观下线后,各个哨兵节点会进行协商,先选举出一个 领导者哨兵节点(兵王) \textcolor{red}{\large 领导者哨兵节点(兵王)} 领导者哨兵节点(兵王)并由该领导者也即被选举出的兵王进行failover(故障转移)。
哨兵日志文件解读分析
-
哨兵领导者,兵王如何选出来的?-> Raft算法
监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是Raft算法;Raft算法的基本思路是先到先得:即在一轮选举中,哨兵A向B发送成为领导者的申请、如果B没有同意过其他哨兵,则会同意A成为领导者。
-
-
由兵王开始推动故障切换流程并选出新的master
-
新主登基
-
某个Slave被选中成为新Master
-
选出新master的规则,剩余Slave节点健康前提下,会按下图规则进行选举
- redis.conf文件中,优先级slave-priority或者replica-priority最高的从节点(数字越小优先级越高)
- 复制偏移位置offset最大的从节点(也就是在master还没有宕机时,复制到数据比其他Slave要多)
- 最小Run ID的从节点,字典顺序,ASCII码
-
-
新主登基
- 一朝天子一朝臣,换个码头重新拜}
- 执行slaveof no one命令让选出来的从节点成为新的主节点,并通过slaveof命令让其他节点成为其从节点
- sentinel leader会对选举出的新master执行slaveof on one操作,将其提升为master节点
- sentinel leader向其他slave发送命令,让剩余的slave成为新的master节点的slave
-
旧主拜服
- 老master回来也认怂,会被降级为slave
- 老master重新上线后,会将它设置为新选出的master的从节点
- sentinel leader会让原来的master降级为slave并恢复正常工作
-
哨兵使用建议
- 哨兵节点的数量应为多个,哨兵本身应该集群,保证高可用
- 哨兵节点的数量应该是奇数
- 各个哨兵节点的配置应一致
- 如果哨兵节点部署在Docker等容器里面,尤其要注意端口的正确映射
- 哨兵集群+主从复制,并不能保证数据零丢失,所以需要使用集群
十、Redis集群(cluster)
是什么
Redis集群是一个提供在多个Redis节点间共享数据的程序集,Redis集群可以支持多个master
能干嘛
- Redis集群支持多个master,每个master又可以挂载多个slave
- 读写分离
- 支持数据的高可用
- 支持海量数据的读写存储操作
- 由于Cluster自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能}
- 客户端与Redis的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可
- 槽位slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系
集群算法-分片-槽位slot
redis集群的槽位slot
Redis集群的数据分片
Redis集群没有使用一致性hash 而是引入了哈希槽的概念。
Redis集群有16384个哈希槽每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:
redis集群的分片
分片和槽位的优势
最大优势,方便扩缩容和数据分派查找}
这种结构很容易添加或者删除节点,比如如果我想添加个节点D,我需要从节点A,B,C中得部分槽位到D上。如果我想一出节点A,需要将A中的槽移动到B和C节点上,然后将没有任何槽的节点从集群中移除即可。由于一个结点将哈希槽移动到另一个节点不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
slot槽位映射,一般业界有三种解决方案
-
哈希取余分区(小厂)
2亿条记录就是2亿个k,v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:
hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。
优点:
简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
缺点:
原来规划好的节点,进行扩容或者缩容就比较麻烦了额,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key) /?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。
某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。
-
一致性哈希算法分区(中厂)
-
是什么?
一致性Hash算法背景是在1997年由麻省理工学院提出的,设计目标是为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不行了
-
能干嘛?
提出一致性Hash解决方案。目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系
-
3大步骤
算法构建一致性哈希环
一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(O= 2^32),这样让它逻辑上形成了一个环形空间。
它也是按照使用取模的方法,前面笔记介绍的节点取模法是对节点(服务器)的数量进行取模。而一致性Hash算法是对2^32取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,O点右侧的第一个点代表1,以此类推,2、3、4、……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1,0和2个32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为Hash环。服务器IP节点映射
将集群中各个IP节点映射到环上的某一个位置。
将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:key落到服务器的落键规则
当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。
如我们有Object A、 Object B、 Object C. object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。小总结
为了在节点数目发生改变时尽可能少的迁移数据
将所有的存储节点排列在收尾相接的Hash环上,每个key在计算Hash后会顺时针找到临近的存储节点存放。而当有节点加入或退出时仅影响该节点在Hash环上顺时针相邻的后续节点。
优点: 加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。
缺点: 数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。
-
-
哈希槽分区(大厂)
-
是什么? HASH_SLOT = CRC16(key) mod 16384
-
哈希槽实质就是一个数组,数组[0, 2^14 - 1]形成hash slot空间
-
能干什么
解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里面放的是数据。
槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配
-
多少个hash
一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。
集群会记录节点和槽的对应关系,解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取模,余数是几key就落入对应的槽里。HASH_SLOT = CRC16(key) mod 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
-
-
哈希槽计算
Redis集群中内置了16384个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在Redis集群中放置一个key-valuel时,redis先对key使用crc16算法算出一个结果然后用结果对16384求余数[ CRC16(key) % 16384],这样每个key都会对应一个编号在0-16383之间的哈希槽,也就是映射到某个节点上。如下代码,key之A、B在Node2, key之C落在Node3上
-
Redis集群不保证强一致性
Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令
集群环境案例步骤
1. 3主3从Redis集群配置
找3台真实虚拟机,各自新建
mkdir -p /myredis/cluster
新建6个独立的Redis实例服务
IP: 192.168.0.100 + 端口6381/6382
vim /myredis/cluster/redisCluster6381.conf
bind 0.0.0.0
daemonize yes
protected-mode no
port 6381
logfile "/myredis/cluster/cluster6381.log" # 每个主机不一样
pidfile /myredis/cluster6381.pid # 每个主机不一样
dir /myredis/cluster
dbfilename dump6381.rdb # 每个主机不一样
appendonly yes
appendfilename "appendonly6381.aof" # 每个主机不一样
requirepass 123456
masterauth 123456
cluster-enabled yes
cluster-config-file nodes-6381.conf #每个主机不一样
cluster-node-timeout 5000
vim /myredis/cluster/redisCluster6382.conf
IP:192.168.0.100 + 端口6383/6384
vim /myredis/cluster/redisCluster6383.conf
vim /myredis/cluster/redisCluster6384.conf
IP:192.168.0.100 + 端口6385/6386
vim /myredis/cluster/redisCluster6385.conf
vim /myredis/cluster/redisCluster6386.conf
启动6台主机实例
redis-server /myredis/cluster/redisCluster6381.conf
…
redis-server /myredis/cluster/redisCluster6386.conf
通过redis-cli 命令为6台机器构建集群关系
# 一定要注意,此处要修改自己的IP为真实IP
# --cluster- replicas 1 表示为每个master创建一一个slave节点
redis-cli -a 123456 --cluster create --cluster-replicas 1 192.168.111.175:6381 192.168.111.175:6382 192:168.111.172:6383 192.168.111.172:6384 192.168.111.174:6385 192.168.111.174:6386
一切OK的话,3主3从搞定
6381作为切入点,查看并检验集群状态
连接进6381作为切入点, 查看节点状态
cluster nodes
CLUSTER INFO
2. 3主3从redis集群读写
对6381新增连个key,看看效果如何
为什么报错
如何解决
防止路由失效加参数-c并新增两个key:
redis-cli -a 123456 -p 6381 -c
查看集群信息
查看某个key该属于对应的槽位值 cluster keyslot 键名称
3. 主从容错切换迁移案例
容错切换迁移
-
主6381和从机切换,先停止主机6381
-
6381主机停了,对应的真实从机上位
-
6381作为1号主机分配的从机以实际情况为准,具体是几号机器就是几号机器
-
-
再次查看集群信息,本次6381主6384从
-
停止主机6381,再次查看集群信息
集群不保证数据一致性100%OK,一定会有数据丢失情况
Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令
手动故障转移or节点从属调整该如何处理
重新登陆6381机器
4. 主从扩容
新建6387、6388 两个服务实例配置文件+启动 (又加了个虚拟机 或者 直接在三个虚拟机里选一个)
IP:192.168.111.174+端口6387/端口6388
vim /myredis/cluster/redisCluster6387.conf
bind 0.0.0.0
daemonize yes
protected-mode no
port 6387
logfile "/myredis/cluster/cluster6387.log"
pidfile /myredis/cluster6387.pid
dir /myredis/cluster
dbfilename dump6387.rdb
appendonly yes
appendfilename "appendonly6387.aof"
requirepass 111111
masterauth 111111
cluster-enabled yes
cluster-config-file nodes-6387.conf
cluster-node-timeout 5000
vim /myredis/cluster/redisCluster6388.conf
bind 0.0.0.0
daemonize yes
protected-mode no
port 6388
logfile "/myredis/cluster/cluster6388.log"
pidfile /myredis/cluster6388.pid
dir /myredis/cluster
dbfilename dump6388.rdb
appendonly yes
appendfilename "appendonly6388.aof"
requirepass 111111
masterauth 111111
cluster-enabled yes
cluster-config-file nodes-6388.conf
cluster-node-timeout 5000
启动87/88两个新的节点实例,此时他们自己都是master
redis-server /myredis/cluster/redisCluster6388.conf
redis-server /myredis/cluster/redisCluster6387.conf
将新增的6387节点作为master加入原集群
#redis-cli -a 密码 --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381
#6387 就是将要作为master新增节点
#6381 就是原来集群节点里面的领路人,相当于6387拜拜6381的码头从而找到组织加入集群
redis-cli -a 111111 --cluster add-node 192.168.111.174:6387 192.168.111.175:6381
redis-cli -a 123456 --cluster add-node 192.168.230.114:6387 192.168.238.111:6381
检查集群情况,6381
# redis-cli -a 密码 --cluster check 真实ip地址:6381
redis-cli -a 111111 --cluster check 192.168.111.175:6381
重新分派槽号
# 命令:redis-cli -a 密码 --cluster reshard IP地址:端口号
redis-cli -a 123456 --cluster reshard 192.168.111.175:6381
再次检查集群情况
redis-cli -a 123456 --cluster check 192.168.238.111:6381
为主节点6387分配从节点6388 –cluster-master-id 后跟的是6387的id
#命令:redis-cli -a 密码 --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点ID
redis-cli -a 111111 --cluster add-node 192.168.111.174:6388 192.168.111.174:6387 --cluster-slave --cluster-master-id 4feb6a7ee0ed2b39ff86474cf4189ab2a554a40f
查看集群信息
redis-cli -a 123456 --cluster check 192.168.238.111:6381
5. 主从缩容
目的让6388和6387下线
检查集群情况第一次,先获得从节点6388的节点ID
redis-cli -a 密码 --cluster check 192.168.111.174:6388
从集群中将4号从节点6388删除
# 命令:redis-cli -a 密码 --cluster del-node ip:从机端口 从机6388节点ID
redis-cli -a 111111 --cluster del-node 192.168.111.174:6388 218e7b8b4f81be54ff173e4776b4f4faaf7c13da
检查一下发现,6388被删除了,只剩下7台机器了。
将6387的槽号清空,重新分配,本例将清出来的槽号都给6381
redis-cli -a 111111 --cluster reshard 192.168.111.175:6381
检查集群情况第二次
redis-cli -a 111111 --cluster check 192.168.111.175:6381
# 4096个槽位都指给6381,它变成了8192个槽位,相当于全部都给6381了,不然要输入3次,一锅端
将6387删除
# 命令:redis-cli -a 密码 --cluster del-node ip:端口 6387节点ID
redis-cli -a 111111 --cluster del-node 192.168.111.174:6387 4feb6a7ee0ed2b39ff86474cf4189ab2a554a40f
检查集群情况第三次,6387/6388被彻底祛险
redis-cli -a 111111 --cluster check 192.168.111.175:6381
集群常用操作命令
集群是否完整才能对外提供服务
CLUSTER COUNTKEYSINSLOT槽位数字编号
- 1,该槽位被占用
- 0,该槽位没占用
CLUSTER KEYSLOT键名称
- 该键应该存在哪个槽位上