Redis7

Redis7入门概述

作者:antirez Github

博客

Remote Dictionary Server(远程字典服务)是完全开源的,使用ANSIC语言编写遵守BSD协议,是一个高性能的Key-Value数据库提供了丰富的数据结构,例如String、Hash、List、Set、SortedSet等等。数据是存在内存中的,同时Redis支持事务、持久化、LUA脚本、发布/订阅、缓存淘汰、流技术等多种功能特性提供了主从模式、Redis Sentinel和Redis Cluster集群架构方案

Redis是一种缓存技术

  1. 分布式缓存,挡在mysql数据库之前的带刀护卫
  2. 内存存储和持久化(RDB+AOF), redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
  3. 高可用架构搭配
  4. 缓存穿透、击穿、雪崩
  5. 分布式锁
  6. 队列
  7. 排行版+点赞

数据库遵循 2(写)- 8(读)原则,为了让这两个保持平衡,我们使用redis缓存,将8(读)进行降低。

image-20240421191309648

与传统数据库关系(mysql)

  1. Redis是key-value数据库(NoSQL一种),mysql是关系数据库

  2. Redis数据操作主要在内存,而mysql主要存储在磁盘

  3. Redis在某一些场景使用中要明显优于mysql,比如计数器、排行榜等方面

  4. Redis通常用于一些特定场景,需要与Mysql一起配合使用

  5. 两者并不是相互替换和竞争关系,而是共用和配合使用

优势

  1. 性能极高 - Redis能读的速度是110000次/秒,写的速度是81000次/秒
  2. Redis数据类型丰富,不仅仅支持简单的key-value类型的数据
  3. 同时还提供list,set,zset,hash等数据结构的存储
  4. Redis支持数据的持久化,可以将内存中的数据保持在磁盘中
  5. 重启的时候可以再次加载进行使用
  6. Redis支持数据的备份,即master-slave模式的数据备份

下载

官网:www.redis.cn

github:Release 7.2.4 · redis/redis (github.com)

文档资料:

  1. 源码地址:https://github.com/redis/redis
  2. 在线测试:https://try.redis.io
  3. 命令参考:http://doc.redisfans.com

版本号第二位如果是奇数,则为非稳定版本 如2.7、2.9、3.1

版本号第二位如果是偶数,则为稳定版本 如2.6、2.8、3.0、3.2

当前奇数版本就是下一个稳定版本的开发版本,如2.9版本是3.0版本的开发版本

安装

  1. 下载获得redis-7.0.0.tar.gz后将它放入我们的Linux目录/opt

    opt文件夹寻找方法:

    1. ctrl + alt + t 打开终端
    2. cd …/ 返回上一层目录
    3. cd …/ 返回上一层目录
    4. ls 就能看到opt
  2. /opt目录下解压redis

    tar -zxvf redis-7.0.0.tar.gz

  3. 进入 redis文件夹

  4. 在redis中执行 make && make install,make可能需要下载,按照终端提示就行

  5. 默认安装目录:在opt同级文件夹下的 usr/local/bin

    1. redis-benchmark:性能测试工具,服务启动后运行该命令,看看自己本子性能如何
    2. redis-check-aof: 修复有问题的AOF文件,rdb和aof后面讲
    3. redis-check-dump: 修复有问题的dump.rdb文件
    4. redis-cli: 客户端,操作入口
    5. redis-sentinel: redis集群使用
    6. Redis服务器启动命令
    7. redis-server:
  6. 将默认的redis.conf拷贝到自己定义好的一个路径下,比如/myredis

  7. 修改/myredis目录下redis.conf配置文件做初始化设置

    redis.conf配置文件,改完后确保生效,记得重启,记得重启

    使用vim修改

    vim打开操作:vim 需要打开的文件夹

    vim查找操作:/查找的字符串,查到之后按回车,然后通过n查找下一个,N查找上一个

    vim插入操作:i,修改完毕之后使用esc退出

    vim退出操作::wq!

    1. 默认daemonize no 改为 daemonize yes
    2. 默认protected-mode yes 改为 protected-mode no
    3. 默认bind 127.0.0.1 改为 直接注释掉(默认bind 127.0.0.1只能本机访问)或改成本机IP地址,否则影响远程IP连接
    4. 添加redis密码 改为 requirepass 你自己设置的密码

启动服务

  1. 使用/myredis中修改完的配置文件启动redis

    redis-server /myredis/redis7.conf

    ps -ef|grep redis|grep -v grep

  2. 连接服务

    redis- server /myredis/redis.conf // 启动服务端

    redis-cli -a 密码 -p 端口 // 连接服务

  3. 设置KV键值对

    set k1 helloword

    get k1

删除Redis

  1. 停止所有redis服务

    ps -ef|grep redis|grep -v grep // 查看redis服务

    redis-cli shutdown // 关闭服务

    ps -ef|grep redis|grep -v grep // 查看是否关闭

  2. 删除usr/local/lib目录下与redis相关的所有文件

    ls -l /usr/local/bin/redis-* // 查看对应目录下所有redis开头的文件

    rm -rf /usr/local/bin/redis-* // 删除对应目录先所有redis开头的文件

Redis的10大数据类型

官网查阅:Commands | Docs (redis.io)

类型:

redis字符串—String

string是redis最基本的类型,一个key对应一个value。

string类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象 。

string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M

redis列表—List

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)

它的底层实际是个双端链表,最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表超过40亿个元素)

redis哈希表—Hash

Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)

redis集合—Set

Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据,集合对象的编码可以是 intset 或者 hashtable。

Redis 中Set集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。

集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)

redis有序集合—ZSet

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。

不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。

zset的成员是唯一的,但分数(score)却可以重复。

zset集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1

redis地理空间—GEO

Redis GEO 主要用于存储地理位置信息,并对存储的信息进行操作,包括

添加地理位置的坐标。

获取地理位置的坐标。

计算两个位置之间的距离。

根据用户给定的经纬度坐标来获取指定范围内的地理位置集合

redis基数统计—HyperLogLog

HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

redis位图—bitmap

由0和1状态表现的二进制位的bit数组

image-20240425094606943

redis位域—bitfield

通过bitfield命令可以一次性操作多个比特位域(指的是连续的多个比特位),它会执行一系列操作并返回一个响应数组,这个数组中的元素对应参数列表中的相应操作的执行结果。

说白了就是通过bitfield命令我们可以一次性对多个比特位域进行操作。

redis流—Stream

Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃。

简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息。

而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失

redis常用命令

命令 描述
keys * 查看当前库中的所有key
exists key 判断某个key是否存在
type key 查看你的key是什么类型
del key 删除指定的key数据
unlink key 非阻塞删除,仅仅将keys充keysspace元数据中删除
真正的删除会在后续异步中操作
ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期
expire key(单位:秒) 为指定的key设置过期时间
move key dbindex[0-15] 将当前数据库的key移动到给定的数据库db中
select dbindex 切换数据库0-15,默认为0
dbsize 查看当前数据库key的数量
flushdb 清空当前数据库
flushall 通杀全部库

数据类型命令及落地运用

Redis命令不区分大小写,Key区分大小写

help @类型,help @String

String

  1. set key value

    set key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT unix-time- seconds | PXAT unix- time- milliseconds |KEEPTTL ]

    SET命令有EX、PX、NX、x以及KEEPTTL五个可选参数,其中KEEPTTL为6.0版本添加的可选参数,其它为2.6.12版本添加的可选参数。

    • EX seconds:以秒为单位设置过期时间
    • Px milliseconds:以毫秒为单位设置过期时间
    • EXAT timestamp:设置以秒为单位的UNIX时间戳所对应的时间为过期时间
    • PXAT milliseconds-timestamp: 设置以毫秒为单位的UNIX时间戳所对应的时间为过期时间
    • NX:键不存在的时候设置键值
    • XX:键存在的时候设置键值
    • KEEPTTI:保留设置前指定键的生存时间
    • GET:返回指定键原本的值,若键不存在时返回nil

    SET命令使用EX、EX、NX参数,其效果等同于SETEX、PSETEX、SETNX命令。根据官方文档的描述,未来版本中SETEX、PSETEX、SETNX命令可能会被淘汰。

    EXAT,PXAT以及GET为Redis 6.2新增的可选参数

    返回值

    设置成功则返回oK;返回ni1为未执行SET命令,如不满足NX、XX条件等

    若使用GET参数,则返回该键原来的值,或在键不存在时返回nil。

    • set key value NX: 在key没有存在的时候能够设置成功,当key存在的时候无法设置成功
    • set key value XX:在key已存在的时候设置成功,不存在的时候无法设置成功
    • set key value get:在给key设置的时候,先返回key的值,然后再将value的值存入key中
    • set key value EX 10: 在设置key设置的时候,设置过期时间 EX为秒
    • set key value PX 10:在设置key设置的时候,设置过期时间 PX为毫秒
    • set key value EXAT 10:以时间戳为过期时间
    • set key value keepttl:设置key的时候,继承上一次的key设置时间
  2. get key

  3. 同时设置/获取多个键值

    • mset:同时设置多个键值对-----mset k1 v1 k2 v2
    • mget:通过获取多个值-------mget k1 k2 k3
    • msetnx:设置多个值,key必须存在才能设置成功
  4. 获取指定区间范围的值

    • getrange:获取指定字符串的特定位置的字符

      set k1 123456789

      getrange k1 0 -1 // 获取所有字符串

      getrange k1 3 8 // 获取索引在3-8之间的所有字符

    • setrange:设置指定字符串特定位置的字符串

      set k1 123456789

      settrange k1 1 xy // 将索引为1的字符替换为xy

  5. 数值增减

    • INCR key:递增数字,默认是1,INCR key 3,一次递增三
    • DECR key:递减数字,默认是1,DECR key 3,一次递减三
  6. 获取字符串长度和内容增加

    • STRLEN k1:获取k1中的字符串长度
    • APPEND k1 xxx:向k1中的字符串后面添加xxx
  7. 分布式锁:

    • 当有多个微服务同时争抢一个资源的时候可以使用redis来进行分布式锁

    命令:

    • setex key 过期时间 value,创建k-v键值对的时候设置过期时间
    • setnx key value 如果key不存在才进行创建
  8. getset 命令

    getset key value:先将key的值取出来,然后在给它赋值新值

List

本质是双端列表,左边右边都可操作,有序

  1. 插入:
    • LPUSH:从左边插入,LPUSH list1 1 2 3 4 5
    • RPUSH:从右边插入,RPUSH list2 11 22 33 44 55
  2. 遍历:
    • LRANGE KEY start stop:从左边遍历第一个参数是key,第二第三个参数是开始和结束的索引
  3. 删除
    • LPOP:从左边弹出,LPOP list1
    • RPOP:从右边弹出,RPOP list2
  4. 取元素
    • LINDEX:按照索引取元素(从上到下),LINDEX KEY index
  5. 获取list长度
    • LLEN:获取列表中元素的个数,LLEN key
  6. 删除指定元素
    • LREM key 数字N 给定值V1,删除N个值为V1的元素,如果key中值V1的个数小于N则全部删除
  7. 截取list
    • LTRIM key start stop, 截取key中位置从start 到 stop位置的元素
  8. 提取key1中的元素给key2
    • RPOPLPUSH 源列表 目的列表,将源列表右边第一个元素提到目的列表左边第一个元素
  9. 替换指定位置的元素
    • LSET key index value:将key中从左边开始算起index位置的元素替换成value
  10. 插入指定位置
    • LINSERT key before/after 已有值 插入新值:在key列表中值为已有值的前面插入新值

Hash

K不变V是一个KV键值对

  1. 设置元素
    • HSET key k1 v1 k2 v2:key是键 k1是键1
    • HMSET key k1 v2 k2 v2:同时设置多个值
    • HSETNX key k1 v1:存在则不添加,不存在则添加
  2. 获取元素
    • HGET key k1:获取key中键为k1的值
    • HMGET key k1 k2 k3:通过获取多个值
    • HGETALL key :将key中的所有键值对遍历出来
  3. 删除元素
    • HDEL key k1:将key中键为k1的删掉
  4. 获取某个KEY中的所有键值对的数量
    • HLEN key
  5. 判断KEY中是否存在键为k1的键值对
    • HEXISTS key k1
  6. 获取某个hash中的全部key,或者全部value
    • HKEYS KEY:获取全部的键
    • HVALS KEY:获取全部的值
  7. 增加整数或小数
    • HINCRBY KEY K1 1:增加KEY的hash表中的值为K1的值加一
    • HINCRBYFLOAT KEY K1 0.1

Set

单值多values,values不重复,无序

  1. 添加元素
    • SADD KEY V1 V2 V3
  2. 遍历元素
    • SMEMBERS KEY:将KEY中的元素全部遍历出来
  3. 查询数据元素是否存在
    • SISMEMBER KEY value:判断value是否在KEY里面
  4. 删除元素
    • SREM KEY VALUE:将KEY中值为VALUE的元素删除
  5. 通过集合里面有多少个元素
    • SCARD KEY
  6. 随机获取集合中的元素,不会改变源集合
    • SRANDMEMBER KEY NUM:随机展示KEY中数量为NUM的元素
  7. 随机获取集合中的元素,会改变源集合
    • SPOP KEY NUM:随机展示KEY中数量为NUM的元素
  8. 数据迁移,将KEY中的数据迁移到KEY2中
    • SMOVE KEY1 KEY2 value:将KEY1中的value迁移到KEY2中
  9. 集合运算
    • 差集:SDIFF KEY1 KEY2:获取在KEY1中但不在KEY2中的元素
    • 并集:SUNION KEY1 KEY2:获取KEY1和KEY2的并集
    • 交集:SINTER KEY1 KEY2:获取即余数KEY1也属于KEY2的

ZSET

有序集合

在SET的基础上每一个VAL值前加一个score分数值,之前set是set K1 v1 v2 v3,现在ZSET是Zset k1 scor1e v1 score2 v2

  1. 添加元素
  • ZADD:ZADD score 10 zhang 20 li,再添加元素的时候需要给元素设置上分数
  1. 获取元素
    • 从小到大 获取元素的值:ZRANGE score 0 -1
    • 从小到大 获取元素的值和分数:ZRANGE score 0 -1 WITHSCORES
    • 从大到小 获取元素的值:ZREVRANGE score 0 -1
    • 从小到大 获取元素的值和分数:ZREVRANGE score 0 -1 WITHSCORES
    • 根据分数来获取元素:ZRANGEBYSCORE score 60 90 LIMIT 0 2,可以使用LIMIT来限制显示的个数
    • 根据分数来获取元素和分数:ZRANGEBYSCORE score 60 90 WITHSCORE 默认是 60 <= 分数 <= 90 ,如果加上小括号则表示不包含断点分数 ZRANGEBYSCORE score (60 90 WITHSCORE
  2. 获取元素分数
    • ZSCORE zset v2
  3. 获取集合中元素的个数
    • ZCARD KEY
  4. 删除集合中的元素
    • ZREM zset value
  5. 增加对应value的分数
    • ZINCRBY key increment value:给值为value的元素添加increment 分
  6. 获取指定分数段的元素的个数
    • ZCOUNT key min max
  7. 从ZET对象中第一个非空排序集中弹出一个或多个元素,他们是成员分数对
    • ZMPOP myset MIN COUNT NUM:从myset中弹出最小的元素,弹出NUM个
  8. 获取对应元素下标
    • ZRANK key values:获取values在key中的下标值 顺序
    • ZREVRANK key values:获取values在key中的下标值 逆序

BitMap位图

用于状态判断

  1. 设置状态
    • SETBIT bit1 index (0 or 1):给bit1的index位置设置为0或1
  2. 获取状态
    • GETBIT bit1 index:获取bit1index位置的数据
  3. 获取bit的字节数
    • STRLEN bit1:获取bit1的字节数,八位一组
  4. 获取bit键里面1的个数:
    • BITCOUNT:全部键里面含有1的个数
  5. 对不同的二进制存储数据进行位运算
    • BITOP OPERATION destkey key:OPERATION运算符包括:AND OR NOT XOR

HyperLogLog基数统计

去重统计功能的基数估计算法,**不能存储数据,之鞥呢发挥去重之后的基数个数 **

基数统计:统计一个集合中不重复的元素个数,就是对集合去重复后剩余元素的计算

  1. 添加元素
    • PFADD hello1 1 3 4 5 7 9:
  2. 返回给定的HyperLogLog的基数估算值:
    • PFCOUNT key
  3. 将多个HyperLogLog合并为一个HyperLogLog
    • PFMERGE dest source1 source2

地理空间GEO

  1. 添加地理位置
    • GEOADD city 精度 维度 地点
    • 如果有乱码需要执行 redis-cli --raw命令来处理中文乱码
  2. 返回对应地理位置
    • GEOPOS city 地点1 地点2
    • 返回坐标用HASH表示:GEOHASH返回坐标用HASH表示
  3. 返回两个位置之间的距离
    • GEODITS 地点1 地点2 (m, km)
  4. 以半径为中心返回查找附近的地点
    • GEORADIUS city 经度 维度 10KM WITHDIST WITHCOORD WITHHASH COUNT
    • WITHDIST:在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
    • WITHCOORD:将位置元素的经度和维度也一并返回。
    • WITHHASH:以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试实际中的作用并不大
    • COUNT 限定返回的记录数
  5. 给定地点查找改地点周围的地点
    • GEORADIUSMEMBER city 天安门 10KM WITHDIST WITHCOORD WITHHASH COUNT

Redis流(Stream)

就是Redis版本的消息中间件

redis5.0之前的消息队列实现两种方法:

  1. List实现消息队列,LPUSH 和 RPOP,一进一出

  2. 订阅方式:PUB/SUB,一个生产方多个消费方

    缺点:无法持久化,如果一个消费者都没有则会丢失消息

    image-20240813211445648

Stream作用:实现消息队列,它支持消息的持久化、支持自动生成全局唯一ID、支持ack确认消息的模式、支持消费组模式等,让消息队列更加的稳定和可靠

Stream结构

一个消息链表,将所有加入的消息都串起来,每一个消息都有一个唯一的ID和对应的内容

image-20240813212002806

需要 标识 作用
1 Message Content 消息内容
2 Consumer group 消费组,通过XGROUP CREATE 命令创建,同一个
消费组可以有多个消费者
3 Last_delivered_id 游标,每一个消费组都有一个游标Last_delivered_id,任意一个消费者读取了消息
都会使游标Last_delivered_id往前移动
4 Consumer 消费者,消费组中的消费者
5 Pending_ids 消费者会有一个状态变量,用于记录被当前消费已读取但未ack的消息Id,如果客户端没有ack,这个变量里
面的消息ID会越来越多,一旦某个消息被ack它就开始减少。这个pending ids变量在Redis官方被称之为
PEL(Pending Entries List),记录了当前已经被客户端读取的消息,但是还没有 ack(Acknowledge
character:确认字符),它用来确保客户端至少消费了消息一次,而不会在网络传输的中途丢失了没处理

队列相关命令—操作消息增删改

指令名称 指令作用
XADD 将消息添加到消息队列队尾
XTREM 限制Stream的长度,如果已经超出会进行截取
XDEL 删除消息
XLEN 获取Stream中的消息长度
XRANGE 获取消息列表(可以指定范围),忽略删除的信息
XREVERANGE 和XRANGE的区别在于反向获取,从小到大
XREAD 获取消息(阻塞/非阻塞),返回大于指定ID的消息
  1. XADD:将消息添加到消息队列队尾,消息ID必须比上一个ID大,默认用*号表示自动生成规矩,用户XADD命令中让系统自动生成ID

    1
    XADD myStream * k1 v1 k2 v2

    XADD 用于向Stream 队列中添加消息,如果指定的Stream 队列不存在,则该命令执行时会新建一个Stream 队列

    //*号表示服务器自动生成 MessageID(类似mysql里面主键auto_increment),后面顺序跟着一堆 业务key/value

  2. XRANGE:获取消息列表(可以指定范围),忽略删除的信息

    start表示开始的值,- 代表最小值

    end表示结束的值,+ 代表最大值

    count表示最多可以获取多少个,如果不写则表示获取全部

    1
    XRANGE myStream - + COUNT 1
  3. XREVERANGE:和XRANGE的区别在于反向获取,从小到大

    1
    XRANGE myStream + - COUNT 1
  4. XDEL:删除消息

    1
    XDEL myStream 时间戳ID
  5. XLEN:获取Stream中的消息长度

    1
    XLEN myStream
  6. XTREM:用于对Stream的长度进行截取,如果超长就会进行截取

    MAXLEN:允许的最大长度对流进行修剪长度

    MINID:允许的最小ID

    1
    2
    XTRIM myStream maxlen 2
    XTRIM myStream minid 165464
  7. XREAD:获取消息(阻塞/非阻塞),返回大于指定ID的消息

    非阻塞:

    $代表特殊ID,表示以当前stream已经存储的最大的ID作为最后一个ID,当前Stream中不存在大于当前最大ID的消息,因此此时返回nil

    0-0代表从最小的ID开始获取Stream中的消息,当不指定count,将会返回Stream中的所有消息,注意也可以使用0(00/000也都是可以的

    1
    2
    XREAD count 2 streams myStream $
    XREAD count 2 streams myStream 0-0

    阻塞:

    1
    XREAN count 1 block 0 streams myStream $  // 获取比当前队列最大的ID还大的一条数据,并进行阻塞等待获取,等有了之后就输出

消费组相关指令

  1. XGROUP CREATE:创建消费者组

    $表示从Stream尾部开始消费

    0表示从Stream头部开始消费

    创建消费者组的时候必须指定 ID,ID 为 0 表示从头开始消费,为 $表示只消费新的消息,队尾新来

    为消息队列myStream创建一个消费者组groupA 并从队尾开始消费,只消费最新消息

    1
    2
    XGROUP CREATE myStream groupA $
    XGROUP CREATE myStream groupB 0
  2. XREADGROUP GROUP

    stream中的消息一旦被消费组里的一个消费者读取了,就不能再被该消费组内的其他消费者读取了,即同一个消费组里的消费者不能消费同一条消息。刚才的XREADGROUP命令再执行次,此时读到的就是空值,但是不同消费组的消费者可以消费同一条消息

    “>”:表示从第一条尚未被消费的消息开始读取

    1
    XREADGROUP GROUP groupA(指定消费者组)   consumer1(创建消费者名称)   streams myStream(要消费的消息队列名称) >

    通过COUNT将消息队列分发出去让组内的多个消费者共同分担读取消息,所以,我们通常会让每个消费者读取部分消息,从而实现消息读取负载在多个消费者间是均衡分布的

    1
    2
    3
    XREADGROUP GROUP gropuC consumer1 count 1 streams myStream
    XREADGROUP GROUP gropuC consumer2 count 1 streams myStream
    XREADGROUP GROUP gropuC consumer3 count 1 streams myStream

    将myStream中的消息分为三个消费者来进行读取

  3. XPENDING:查询每个消费者组内所有消费者已读但未确认的消息

    问题:基于 stream 实现的消息队列,如何保证消费者在发生故障或宕机再次重启后,仍然可以读取未处理完的消息?
    解决:Streams 会自动使用内部队列(也称为 PENDING List)留存消费组里每个消费者读取的消息保底措施,直到消费者使用 XACK命令通知 Streams"消息已经处理完成"消费确认增加了消息的可靠性,一般在业务处理完成之后需要执行XACK命令确认消息已经被消费完成

    image-20240813222141540

    1
    2
    XPENDING myStream groupA - + 10 consumer2
    //获取消费者组groupA读取myStream但未确认的条数和起始结束ID,和组内每一个消费者读取消息的个数
  4. XACK确认消息

    1
    2
    XACK myStream groupC 46545646546(消息ID)
    //对groupC里面的对应ID的消息进行确认,确认完毕之后在使用XPENDING进行获取,就无法获取对应的消息了
  5. XINFO:打印消息队列的信息

    1
    XINFO stream myS

Redis持久化

RDB数据库

概念

RDB持久性以指定的时间间隔执行数据集的时间点快照

实现类似照片记录效果的方式,就是把某一时刻的数据和状态以文件的形式写到磁盘上,也就是快照。这样一来即使故障宕机,快照文件也不会丢失,数据的可靠性也就得到了保证。这个快照文件就称为RDB文件(dump.rdb),其中,RDB就是Redis DataBase的缩写

作用:

在指定是时间内将内存中的数据集快照写入磁盘,也就是将Snapshot内存快照,它恢复的时候再将磁盘快照文件读回内存中

Redis在执行快照的时候会将内存中所有的数据都记录到磁盘中

保存频率

Redis7以前:

save 900 1:每间隔900秒(15分钟)有一次发生变化的,就进行一次快照

save 300 10 : 每间隔300秒(5分钟)有十次发生变化,就进行一个快照处理

save 60 10000:每间隔60秒(一分钟)有10000发生变化,就进行一个快照

Redis7之后

save 3600 1 : 每间隔3600秒(一小时)有一次发生变化的,就进行一次快照

save 300 100 : 每间隔300秒(5分钟)有100次发生变化的,就进行一次快照

save 60 10000 : 每间隔60秒(一分钟)有10000次发生变化的,就进行一次快照

自动触发

修改Redis配置文件,按照redis.conf里配置的save < second > < changes > 在多少秒内有多少次修改就会进行保存,搜索Snapshot能找到

修改dump.rdb文件保存路径:搜索dir 能找到 dir ./ 可以将“./”修改成我们自己的路径

修改dump.rdb文件保存名称:搜索dump.rdb找到 dbfilename dump.rdb,修改dump.rdb进行保存即可

等待我们在second秒内修改次数超过changes次之后就会触发自动保存,保存在dump.rdb文件中

手动触发

save:会阻塞当前的Redis服务器,直到持久化工作完成,在save执行期间Redis不能处理其他命令,线上禁止使用

bgsave:该方式会fork一个子进程,不会阻塞当前主进程,主进程和保存进程会同时进行

恢复文件

当我们使用flushall/flushdb(清空当前数据库中的所有键)命令后Redis会自动产生dump.rdb文件,但里面是空的没有意义

我们使用shutdown命令关闭服务的时候也会自动产生dump.rdb文件

等待Redis服务器重启的时候会自动去配置的文件夹底下寻找配置的文件,然后将其中对应的数据进行恢复

优点

  1. 适合大规模的数据恢复
  2. 按照业务定时备份
  3. 对数据完整性和一致性要求不高
  4. RDB文件在内存中加载速度要比AOF快的多

缺点

在Redis服务器宕机的时候数据不能完整的保存下来,只能够尽量避免数据丢失

Redis需要经常fork子进程在磁盘上进行持久化,如果数据很大fork可能会很耗时

检查和修复RDB文件

使用命令redis-check-rdb “指定rdb文件路径”

禁用快照RDB

动态停止RDB保存规则:redis-cli config set save “”

配置禁用:打开redis.config配置文件中将注掉的save “” 打开即可

Redis优化配置项详解

在Redis.config配置文件SNAPSHOTTING模块中进行配置

  1. SAVE < SECONDS > < CHANGES >:配置RDB文件自动保存触发条件在seconds秒内,发生了changes次修改就会触发自动保存
  2. dbfilename:配置自动保存文件名
  3. dir:配置自动保存文件路径
  4. stop-writes-on-bgsave-error :默认yes,如果配置成no,表示你不在乎数据不一致或者有其他的手段发现和控制这种不一致,那么在快照写入失败时也能确保redis继续接受新的写请求
  5. rdbcompression :默认yes,对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redls会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能
  6. rdbchecksum:默认yes,在存储快照后,还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升。可以关闭此功能
  7. rdb-del-sync-files:在没有持久性的情况下删除复制中使用的RDB文件启用。默认情况下no,此选项是禁用的。

AOF持久化

以日志形式记录每一个写操作,只许追加文件但不可以改写文件redis启动之初会读取该文件重新构建数据,换言之,redis
重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

默认情况下redis是没有开启AOF的,如果要开启AOF功能需要设置配置appendonly yes

AOF保存的appendonly.aof文件

  1. Client作为命令的来源,会有多个源头以及源源不断的请求命令。

  2. 在这些命今到达Redis Server 以后并不是直接写入AOF文件,会将其这些命令先放入AOF缓存中进行保存。这里的AOF缓冲区实际上是内存中的一片区域,存在的目的是当这些命令达到一定量以后再写入磁盘,避免频繁的磁盘IO操作。

  3. AOF缓冲会根据AOF缓冲区同步文件的三种写回策略将命令写入磁盘上的AOF文件。

    Always:同步写回,每个写命令执行完毕之后立刻同步的将日志写回磁盘

    everysec:每秒写回,默认写回方式,每个写命令执行完毕之后先把日志写到AOF缓冲区中,每隔一秒将缓冲区的内容写到aof文件中

    no:操作系统控制的写回,每个写命令执行完毕之后,先把日志文件写到AOF的内存缓冲区中,由操作系统决定什么时候写回磁盘

  4. 随着写入AOF内容的增加为避免文件膨胀,会根据规则进行命令的合并(又称AOF重写),从而起到AOF文件压缩的目的。

  5. 重启redis之后会从AOF文件中读取命令进行数据恢复

image-20240818173856943

功能配置

开启AOF:在redis.confg配置文件中找到 appendonly 配置项,在后面写上yes开启AOF功能,默认是NO

使用默认写回策略:appendfsync everysec

设置AOF的保存路径

redis6之前:RDB和AOF保存路径是相同的,设置一个dir,RDB和AOF都会公用这个文件夹

redis7之后:dir是RDB的文件存放路径,在redis.conf中还有一个配置项appenddirname,AOF会存放在 dir/appenddirname/xxx.aof

设置AOF文件保存名称:

redis7之前:在redis.conf配置文件中设置 appendfilename 配置项来配置AOF保存文件名

redis7之后:有三个基本文件

  1. BASE基本文件:表示基本AOF,一般有子进程通过重写产生,该文件只存在一个 appendonly.aof.1.base.rdb
  2. INCR增量文件:表示增量AOF,一般会在AOFRW开始执行时被创建,该文件可能存在多个 appendonly.aof.1.incr.rdb
  3. HISTORY:表示历史AOF,它是有BASE和INCR变化而来,每次AOFRW成功完成之后,本次AOFRW之前对应的BASE和INCRAOF都会变成HISTORY,HISTORY类型的AOF会被REDIS自动删除

manifest清单文件:来管理这些AOF文件 appendonly.aof.manifest

恢复操作

正常恢复

在redis重启的时候会去指定文件夹内读取aof文件来恢复数据

异常恢复

AOF一秒写一次,但是AOF可能写的文件较多,在一秒之内没写完redis就宕机了,会导致AOF文件出错,AOF文件出错,会导致redis服务无法正常启动

需要使用redis-check-aof --fix进行文件修复,一定要加–fix

1
redis-check-aof --fix "需要修复的文件路径/appendonlu.aof.*"

优点

更好的保存数据不丢失,性能高,可做紧急恢复

  1. 使用AOF redis更加持久:可以有不同的fsync策略:Always, everysec, no,即使丢失也只有1s的丢失,能接受
  2. AOF日志是一个仅附加日志,因此不会出现寻道问题,也不会在断电时出现损坏问题。即使由于某种原因(磁盘已满或其他原因)日志以写一半的命令结尾,redis-check-aof 工具也能够轻松修复它。
  3. 当 AOF 变得太大时,Redis 能够在后台自动重写 AOF。重写是完全安全的,因为当 Redis 继续附加到旧文件时,会使用创建当前数据集所需的最少操作集生成一个全新的文件,一旦第二个文件准备就绪,Redis就会切换两者并开始附加到新的那一个。
  4. AOF 以易于理解和解析的格式依次包含所有操作的日志。甚至可以轻松导出 AOF 文件。例如,即使不小心使用该FLUSHALL命令刷新了所有内容只要在此期间没有执行日志重写,您仍然可以通过停止服务器删除最新命令并重新启动 Redis 来保存您的数据集

缺点

  1. AOF 文件通常比相同数据集的等效 RDB 文件大。
  2. 根据确切的 fsync 策略,AOF 可能比 RDB 慢。一般来说,将 fsync 设置为每秒性能仍然非常高,并且在禁用 fsync 的情况下,即使在高负载下它也应该与 RDB 一样快。即使在巨大的写入负载的情况下,RDB 仍然能够提供关于最大延迟的更多保证。

AOF重写机制

由于AOF持久化是Redis不断将写命令记录到 AOF 文件中,随着Redis不断的进行,AOF 的文件会越来越大,文件越大,占用服务器内存越大以及 AOF 恢复要求时间越长。

为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过所设定的峰值时,Redis就会自动启动AOF文件的内容压缩。
只保留可以恢复数据的最小指令集,或者可以手动使用命令 bgrewriteaof 来重写

启动AOF的文件内容压缩,只保留可以恢复数据的最小指令集

例如对一个key进行多次操作之后,重写完之后只保留最后一次写操作

set k1 1

set k1 2

set k1 3

重写之后只保留set k1 3

触发机制

在redis.conf文件中有两个配置项

1
2
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

注意 ,同时满足,且的关系会触发

  1. 根据上次重写后的aof大小,判断当前aof大小是不是增长了1倍
  2. 重写时满足的文件大小

自动触发:满足上诉配置项之后进行重写,重写之后 会将incr文件内容压缩完之后放入 appendonly.aof.1.base.rdb中,并且会改变base文件名称为appendonly.aof.2.base.rdb,改变文件名称 appendonly.aof.1.incr.rdb为 appendonly.aof.2.incr.rdb

手动触发:使用bgrewriteaof命令来进行手动重写

也就是说 AOF 文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个
键值对的多条命令,生成一个新的文件后去替换原来的 AOF 文件

AOF 文件重写触发机制:通过 redis.conf配置文件中的 auto-aof-rewrite-percentage:默认值为100,以及auto-aof-rewrite
min-size:64mb 配置,也就是说默认Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍
且文件大于64M时触发

重写原理

  1. 在重写开始前,redis会创建一个“重写子进程”,这个子进程会读取现有的AOF文件,并将其包含的指令进行分析乐缩并写入到一个临时文件中。
  2. 与此同时,主进程会将新接收到的写指令一边累积到内存缓冲区中一边继续写入到原有的AOF文件中,这样做是保证原有的AOF文件的可用性,避免在重写过程中出现意外。
  3. 当“重写子进程”完成重写工作后,它会给父进程发一个信号,父进程收到信号后就会将内存中缓存的写指令追加到新AOF文件中
  4. 当追加结束后,redis就会用新AOF文件来代替旧AOF文件,之后再有新的写指令,就都会追加到新的AOF文件中,重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似

配置命令

配置指令 配置含义 配置示例
appendonly 是否开启 aof appendonly yes
appendfilename 文件名称 appendfilename "appendonly.aof’
appendfsync 同步方式 everysec/always/no
no-appendfsync-on-rewrite aof 重写期间是否同类 no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage
auto-aof-rewrite-min-size
重写触发配置、文件重写策略 auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

总结

image-20240818231813220

RDB-AOF混合持久化

AOF默认不开,开启之后AOF优先级较高

数据恢复顺序和加载流程

image-20240818232431221

如何选择

RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储

AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.

推荐混合使用

先使用RDB进行快照存储然后使用AOF持久化记录所有的写操作,当重写策略满足或手动触发重写的时候,将最新的数据存储为新的RDB记录。这样的话,重启服务的时候会从RDB和AOF两部分恢复数据,既保证了数据完整性,又提高了恢复数据的性能。简单来说:混合持久化方式产生的文件一部分是RDB格式,一部分是AOF格式。----》AOF包括了RDB头部+AOF混写

纯缓存模式

同时关闭RDB和AOF获取极致的性能

在配置文件中设置

禁用rdb持久化的模式下也可使用时命令save和bgsave生成rdb文件

禁用AOF持久化的模式下也可使用时命令bgrewriteaof生成rdb文件

1
2
save ""
appendonly no

Redis事务

本质:可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞

作用:一个队列中一次性,顺序性,排他性的执行一系列操作

特点 解释
单独的隔离操作 Redis的事务仅仅是保证事务里的操作会被连续独占的执行
redis命令执行是单线程架构,
执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的
没有隔离级别的概念 因为事务提交前任何指令都不会被实际执行,
也就不存在"事务内的査询要看到事务里的更新,
在事务外查询不能看到"这种问题了
不保证原子性 Redis的事务不保证原子性,
也就是不保证所有指令同时成功或同时失败,
只有决定是否开始执行全部指令的能力,没有执行到一半进行回滚的能力
排他性 Redis会保证一个事务内的命令依次执行,而不会被其它命令入

Redis命令

正常执行

开启事务MULTI

开始事务EXEC

1
2
3
4
5
6
MULTI // 开启事务
set k1 v1 // 向事务队列中添加一个元素
set k2 v2 // 向事务队列中添加一个元素
set k3 v3 // 向事务队列中添加一个元素
INCR count // 向事务队列中添加一个元素
EXEC // 开始执行事务

放弃事务

DISCARD

1
2
3
4
5
6
MULTI // 开启事务
set k1 v1 // 向事务队列中添加一个元素
set k2 v2 // 向事务队列中添加一个元素
set k3 v3 // 向事务队列中添加一个元素
INCR count // 向事务队列中添加一个元素
DISCARD // 放弃事务执行

全部终止

当我们在执行EXEC命令之前,输入的语句有误,则在队列中所有的命令都不会被执行

1
2
3
4
5
6
MULTI // 开启事务
set k1 v1 // 向事务队列中添加一个元素
set k2 v2 // 向事务队列中添加一个元素
set k3 // 向事务队列中添加一个错误元素
INCR count // 向事务队列中添加一个元素
EXEC // 事务执行,并不会执行事务中的内容

终止错误

当我们在执行EXEC命令之后,输入的语句其中一个又错误,则只会不执行错误语句,正确语句会依次执行

1
2
3
4
5
6
MULTI // 开启事务
set k1 v1 // 向事务队列中添加一个元素
set k2 v2 // 向事务队列中添加一个元素
INCR email // 向事务队列中添加一个错误元素
INCR count // 向事务队列中添加一个元素
EXEC // 事务执行========语句一二四会正确执行,语句三不会执行

WATCH监控

悲观锁:悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁

乐观锁:乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据

Redis采用乐观锁

执行流行

正常执行

1
2
3
4
5
6
7
set k1 v1  		// 设置初始值
set balance 100 // 设置初始值
WATCH balance // 监控事务中的一个元素
MULTI // 开始事务
set k1 v11
set balance 110
EXEC // 执行事务

出错情况

1
2
3
4
5
6
7
8
9
10
11
12
set k1 v1  		// 设置初始值
set balance 100 // 设置初始值
WATCH balance // 监控事务中的一个元素
MULTI // 开始事务
set k1 v11
set balance 110
EXEC // 执行事务


// ======== 和上面同时进行
// 在上方向 事务队列中中添加语句的时候我们执行如下代码
set balance 120 // 这样会导致在事务执行的时候,监听的元素已经被改变,所以事务执行失败

放弃监控

UNWATCH

1
2
3
4
5
6
7
8
set k1 v1  		// 设置初始值
set balance 100 // 设置初始值
WATCH balance // 监控事务中的一个元素
UNWATCH balance // 放弃监控
MULTI // 开始事务
set k1 v11
set balance 110
EXEC // 执行事务

总结

一旦执行了exec之前加的监控锁都会被取消掉了

当客户端连接丢失的时候(比如退出链接),所有东西都会被取消监视

流程:

开启:以MULTI开始一个事务

入队:将多个命令入队到事务中,接到这些命令并不会立即执行而是放到等待执行的事务队列里面

执行:由EXEC命令触发事务

Redis管道

如何优化频繁命令往返造成的性能瓶颈?

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。一个请求会遵循以下步骤:

  1. 客户端向服务端发送命令分四步(发送命令一命令排队一命令执行一返回结果),并监听Socket返回,通常以阻塞模式等待服务端响应。
  2. 服务端处理命令,并将结果返回给客户端。

上述两步称为:Round Trip Time(简称RTT,数据包往返于两端的时间)

image-20240823221539319

如果同时需要执行大量的命令,那么就要等待上一条命令应答后再执行,这中间不仅仅多了RTT(Round TimeTrip),而且还频繁调用系统IO,发送网络请求,同时需要redis调用多次read()和write()系统方法,系统方法会将数据从用户态转移到内核态,这样就会对进程上下文有比较大的影响了,性能不太好。

概念

管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完完毕后,通过一条响应一次性将结果返回,通过减少客户端与redis的通信次数来实现降低往返延时时间。pipeline实现的原理是队列,先进先出特性就保证数据的顺序性。

Pipeline是为了解决RTT往返回时,仅仅是将命令打包一次性发送对整个Redis的执行不造成其它任何影响

image-20240823221847247

执行方式

通过txt文件执行:

将我们要执行的redis命令放到一个txt文件中

set k100 v100

set k200 v200

hset k300 name haha

hset k300 age 20

hset k300 gender male

lpush list 1 2 3 4 5

执行过程

1
cat cmd.txt | redis-cli -a 11111 --pipe // cat cmd.txt是将cmd.txt文件中的命令都展示出来,通过 | 将展示的命令作为参数传递到redis-cli中 并使用pipe管道执行

总结

pipeline与原生批量命令对比

  1. 原生批量命令是原子性(例如:mset mget),pipeline是非原子性
  2. 原生批量命令一次只能执行一种命令pipeline支持批量执行不同命令
  3. 原生批命令是服务端实现,而pipelne需要服务端与客户端共同完成

pipeline与事务对比

  1. 事务具有原子性管道不具有原子性
  2. 管道一次性将多条命令发送到服务器事务是一条一条的发,事务只有在接收到exec命令后才会执行,管道不会
  3. 执行事务时会阻塞其他命令的执行而执行管道中的命令时不会

pipline注意事项

  1. pipeline缓冲的指令只是会依次执行,不保证原子性,如果执行中指令发生异常,将会继续执行后续的指令
  2. 使用pipeline组装的命令个数不能太多,不然数据量过大客户端阻塞的时间可能过久,同时服务端此时也被迫回复一个队列答复,占用很多内存

Redis订阅

Redis可以实现消息中间件MQ的功能,通过发布订阅实现消息的引导和分流。不推荐该功能

通过PUBLISH发布的消息,订阅者都可以收到

image-20240826213916313

常用命令

SUBSCRIBE

命令

1
2
SUBSCRIBE channel [channel]
SUBSCRIBE c1

订阅给定的一个或多个频道的信息

推荐先执行订阅后再发布,订阅成功之前发布的消息是收不到的

订阅的客户端每次可以收到一个3个参数的消息,消息类型,消息key,消息value

PUBLISH

命令

1
2
PUBLISH channel message
PUBLISH c1 helloc1

发布消息到指定频道

PSUBSCRIBE

命令

1
2
PSUBSCRIBE pattern [pattern]
PSUBSCRIBE c*

按照模式批量订阅,订阅一个或多个符合给定模式(支持*号?号之类的)的频道

PUBSUB

命令

1
PUBSUB subcommand [argument ...]

查看订阅与发布系统状态

  1. PUBSUB CHANNELS:查看由活跃频道组成的列表
  2. PUBSUB NUMBER [channel [channel]]:某个频道有几个订阅者
  3. PUBSUB NUMPAT:只统计使用PSUBSCRIBE命令执行的,返回客户端订阅的唯一模式的数量

UNSUBSCRIBE

命令

1
UNSUBSCRIBE [channel [channel]]

取消订阅

PUNSUBSCRIBE

命令

1
PUNSUBSCRIBE [pattern [pattern]]

按照模式取消订阅

总结

订阅缺点

  1. 发布的消息在Redis系统中不能持久化,因此,必须先执行订阅,再等待消息发布。如果先发布了消息,那么该消息由于没有订阅者,消息将被丢弃
  2. 消息只管发送对于发布者而言消息是即发即失的,不管接收,也没有ACK机制,无法保证消息的消费成功
  3. 以上的缺点导致Redis的Pub/sub模式就像个小玩具,在生产环境中几平无用武之地,为此Redis5.0版本新增了Stream数据结构,不但支持多播,还支持数据持久化,相比Pub/Sub更加的强大

Redis复制

Redis复制:就是主从复制,master以写为主,Slave以读为主,当master数据变化的时候,自动将新的数据同步到slave中

作用

  1. 读写分离
  2. 容灾恢复
  3. 数据备份
  4. 水平扩容支持高并发

配置

配从库不配主库

master如果配置了requirepass参数,需要密码登陆。那么slave就要配置masterauth来设置校验密码否则的话master会拒绝slave的访问请求

常用命令

  1. info replication:可以查看复制节点的主从关系和配置信息
  2. replicaof 主库IP 主库端口:在从机上设置主库IP和主库端口,一般写入redis.conf配置文件中
  3. slaveof 新主库IP 新主库端口
    • 每次与master断开之后,都需要重新连接,除非你配置进redis.conf文件
    • 运行期间修改slave节点的信息,如果该数据库已经是某个主数据库的从数据库,那么会停止和原主数据库的同步关系转而和新的主数据库同步,重新拜码头
  4. slaveof no one:使当前数据库停止与其他数据库的同步,转成主数据库,自立为王

修改配置文件

  1. 在redis.conf备份文件中将daemonize 设置为yes
  2. 注释掉bind 127.0.0.1
  3. 关闭保护模式:protected-mode no
  4. 指定redis运行端口 port
  5. 指定当前工作目录 dir /myredis 存放配置文件的文件夹
  6. pid文件名, pidfile
  7. log文件名,logfile “/myredis/6379.log”
  8. 设置启动密码:requirepass 密码
  9. 修改dump文件名:dbfilename dump6379.rdb
  10. 修改aof文件名:appendfilename,如果配置该项需要开始appendonly
  11. 从机访问主机的通行密码 在reids.conf 中配置 masterauth 主机访问密码 从机配置主机不需要
  12. 从机访问主机需要配置repliaof 主机ip 主机端口 从机配置主机不需要

主从命令

先启动主机再启动从机,从机启动时候需要指定端口

可以从日志文件中查看信息,主机中有从机连接成功信息

info replication命令查看

问题—主从写入配置文件

问:

从机可以执行写命令吗?

答:

从机是只读模式的,主机是可读可写的

问:

slave是从头开始复制还是从切入点开始复制?

master启动,写到k3

slave1跟着master同时启动,跟着写到k3

slave2写到k3后才启动,那之前的是否也可以复制?

答:

之前也可以复制

问:

主机shutdown之后从机会变成主机吗?

答:

不会

问:

主机shutdown后重启后主从关系还在吗?从机还能否顺利复制?

答:

关系存在可以继续进行复制

问:

某台从机down后,master继续,从机重启后它能跟上大部队吗?

答:

可以

问题—命令操作手动指定

当从机shutdown之后去掉配置项中的repliaof 配置项,使从机变成主机

在从机启动之后,使用命令

1
SLAVEOF 主机Ip 主机端口

配置主机之后,还可以同步主机上的数据

通过命令行手动配置主机的从机重启之后就回丢失与主机的联系

薪火相传

当主master连接过多从机slave,那么主机master的写压力会大大增加,导致主机性能下降

image-20240827223603358

这时候可以让多个从机互相连接

image-20240827223541090

上一个slave可以是下一个slave的master,slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master可以有效减轻主master的写压力

中途变更转向:会清除之前的数据,重新建立拷贝最新的

slaveof 新主库IP 新主库端口

中间的从机即使有从机连接它,它也不具备写能力

反客为主

使用命令

1
SLAVEOF no one

可以与主机断开连接,自己当主机

原理及工作流程

  1. Slave启动,同步请求
    • slave启动成功连接到master后会发送一个sync命令
    • slave首次全新连接master,一次完全同步(全量复制)将被自动执行,slave自身原有数据会被master数据覆盖清除
  2. 首次连接,全量复制
    • master节点收到sync命令后会开始在后台保存快照(即RDB持久化,主从复制时会触发RDB),同时收集所有接收到的用于修改数据集命令缓存起来,master节点执行RDB持久化完后,master将rdb快照文件和所有缓存的命令发送到所有slave,以完成一次完全同步
    • slave服务在接收到数据库文件数据后,将其存盘并加载到内存中,从而完成复制初始化
  3. 心跳持续,保持通讯
    • repl-ping-replica-period 10 在配置文件中包含这个配置项,表明主机在10一次 ping一下从机看看从机是否在线
  4. 进入平稳。增量复制
    • Master继续将新的所有收集到的修改命令自动依次传给slave,完成同步
  5. 从机下线,重连续传
    • master会检查backlog里面的offset,master和slave都会保存一个复制的offset还有一个masterId,offset是保存在backlog中的。Master只会把已经复制的offset后面的数据复制给Slave,类似断点续传

缺点

  1. 复制延迟信号衰减

    由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

    image-20240827225257773

  2. Master挂了怎么办

    默认情况下,不会在slave节点中自动重选一个master,需要从slave中选出一个当主机

Redis哨兵

作用

监控Redis运行状态,包括master和slave

当master down

一般哨兵配置三台

功能

主从监控 : 监控主从redis库运行是否正常

消息通知 : 哨兵可以将故障转移的结果发送给客户端

故障转移 : 如果Master异常,则会进行主从切换, 将其中一个Slave作为新Master

配置中心 :客户端通过连接哨兵来获得当前Redis服务的主节点地址

使用方法

image-20240903211637072

创建哨兵服务

复制三份sentinel.conf到我们指定目录下myredis/ 命名为sentinel.conf

参数:

  1. bind:服务监听地址,用于客户端连接,默认本机地址
  2. daemonize:是否以后台daemon方式运行
  3. protected-mode:安全保护模式
  4. port:端口
  5. logfile:日志文件路径
  6. pidfile:pid文件路径
  7. dir:工作目录
  8. sentinel monitor < master-name > < redis-port > < quorum >
    • 设置要监控的master服务器
    • quorum表示最少有几个哨兵认可客观下线,同意故障迁移的法定票数。
    • 我们知道,网络是不可靠的,有时候一个sentinel会因为网络堵塞而误以为一个master redis已经死掉了,在sentinel集群环境下需要多个sentinel互相沟通来确认某个master是否真的死了,quorum这个参数是进行客观下线的一个依据,意思是至少有quorum个sentinel认为这个master有故障,才会对这个master进行下线以及故障转移。因为有的时候,某个sentinel节点可能因为自身网络原因,导致无法连接master,而此时master并没有出现故障,所以,这就需要多个sentinel都一致认为该master有问题,才可以进行下一步操作,这就保证公平性和高可用。
  9. sentinel auth-pass < master-name > < password > :master设置了密码,连接master服务的密码
  10. sentinel down-after-milliseconds < master-name > < milliseconds >
    • 指定多少毫秒之后,主节点没有应答哨兵,此时哨兵主观上认为主节点下线, 默认即可
  11. sentinel parallel-syncs < master-name >< nums >
    • 表示允许并行同步的slave个数,当Master挂了后,哨兵会选出新的Master,此时,剩余的slave会向新的master发起同步数据 默认即可
  12. sentinel failover-timeout < master-name >< milliseconds >:
    • 故障转移的超时时间,进行故障转移时,如果超过设置的毫秒,表示故障转移失败, 默认即可
  13. sentinel notification-script < master-name >< script-path > :
    • 配置当某一事件发生时所需要执行的脚本, 默认即可
  14. sentinel client-reconfig-script < master-name >< script-path >:
    • 客户端重新配置主节点参数脚本, 默认即可

文件配置

在sentinel.conf中进行配置

1
2
3
4
5
6
7
8
9
10
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 IP PORT 2
sentinel auth pass mymaster 1l1111

启动redis 一主二从

master(主机)设置:masterauth访问密码是 11111,上述为配从不配主,这里又进行了主机配置,解释:6379后续可能会变成从机,需要设置访问新主机的密码,请设置masterauth项访问密码为111111,不然后续可能报错master link status:down

slave(从机)设置:replicaof masterIP port, masterauth 主机密码

启动哨兵

1
redis-sentinel sentinel26379.conf --sentinel

模拟挂机

通过将主master进行shutdow之后

哨兵会先进行master挂机确认,确认完之后会进行新master选择,即使原先的master从新上线之后,不会转换master为原先的master,原先挂机的master会变成slave从机 ,老master 并且会在配置文件后面添加上replicaof masterIP port ,进行主从切换,新master的replicaof配置项会被移除

BrokenPipe问题:broken pipe的意思是对端的管道已经断开,往往发生在远端把这个读/写管道关闭了,你无法在对这个管道进行读写操作。从tcp的四次挥手来讲远端已经发送了FIN序号,告诉你我这个管道已经关闭,这时候,如果你继续往管道里写数据,第一次,你会收到一个远端发送的RST信号,如果你继续往管道里write数据,操作系统就会给你发送SIGPIPE的信号,并且将errno置为Broken pipe(32),如果你的程序默认没有对SIGPIPE进行处理,那么程序会中断退出。一般情况下,可以用signal(SIGPIPE,SIG IGN)忽略这个信号,这样的话程序不会退出,但是write会返回-1并且将errno置为Brokenpipe(32)。brokerpipe只会出现在往对端已经关闭的管道里写数据的情况下在收到对端的RST序号后第一次写不会出现broke pipe,而是write返回-1,这时候正确的做法应该是本端也close这个管道,如果继续write,那么就会出现这个错误)。

切换结果

配置文件的内容,在运行期间会被sentinel动态进行更改

Master-Slave切换后,master_redis.conf、slave_redis.conf和sentinel.conf的内容都会发生改变,即master redis.conf中会多一行slaveof的配置,sentinel.conf的监控目标会随之调换

运行流程

当一个主从配置中的master失效之后,sentinel可以选举出一个新的master用于自动接替原master的工作,主从配置中的其他redis服务器自动指向新的master同步数据,一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换

流程

  1. 三个哨兵监控一主二从,正常运行中

  2. SDown主观下线(Subjectively Down)

    • 所谓主观下线(Subjectively Down, 简称 SDOWN)指的是单个Sentinel实例对服务器做出的下线判断,即单个sentinel认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。主观下线就是说如果服务器在[sentinel down-after-miliseconds]给定的毫秒数之内没有回应PING命令或者返回一个错误消息, 那么这个Sentinel会主观的(单方面的)认为这个master不可以用了。
    • sentinel down-after-milliseconds < masterName > < timeout >
    • 表示master被当前sentinel实例认定为失效的间隔时间,这个配置其实就是进行主观下线的一个依据master在多长时间内一直没有给Sentine返回有效信息,则认定该master主观下线。也就是说如果多久没联系上redis-servevr,认为这个redis-server进入到失效(SDOWN)状态。
  3. ODown客观下线(Objectively Down)

    • DOWN需要一定数量的sentinel多个哨兵达成一致意见才能认为masfer客观上已经宕掉
    • quorum这个参数是进行客观下线的一个依据,法定人数/法定票数
    • 意思是至少有quorum个sentinel认为这个master有故障才会对这个master进行下线以及故障转移。因为有的时候,某个sentinel节点可能因为自身网络原因导致无法连接master,而此时master并没有出现故障,所以这就需要多个sentinel都一致认为该master有问题,才可以进行下一步操作,这就保证了公平性和高可用。
  4. 选举出领导者哨兵(哨兵中选出兵王)

    • 当主节点被判断客观下线以后,各个哨兵节点会进行协商先选举出一个**领导者哨兵节点(兵王)**并由该领导者节点也即被选举出的兵王进行failover(故障迁移)
    • 选择算法RAFT算法:基本思路先到先得
  5. 由兵王开始推动故障切换流程并选出一个新master,分为三步

    • 选择新master,规则如下,在slave没问题的情况下(新王登基)

      • redis.conf文件中,优先级slave-priority或者replica-priority最高的从节点(数字越小优先级越高

      • 复制偏移位置offset最大的从节点,谁拥有最全的老master的数据,谁当新master

      • 最小Run ID的从节点 字典顺序,ASCII码

        image-20240903234546851

    • 将其余slave转换到新master中(俯首称臣)

      • 新master执行slaveofno one命令让选出来的从节点成为新的主节点,并通过slaveof命令让其他节点成为其从节点
      • Sentinel leader会对选举出的新master执行slaveofno one操作,将其提升为master节点
      • Sentinelleader向其它slave发送命令,让剩余的slave成为新的master节点的slave
    • 老master重启之后也会变成slave(旧主拜服)

      • 将之前已下线的老master设置为新选出的新master的从节点,当老master重新上线后,它会成为新master的slave
      • Sentinelleader会让原来的master降级为slave并恢复正常工作。
  6. 上述都是sentinel自动完成

使用建议

  1. 哨兵节点的数量应为多个,哨兵本身应该集群,保证高可用
  2. 哨兵节点的数量应该是奇数
  3. 各个哨兵节点的配置应一致
  4. 如果哨兵节点部署在Docker等容器里面,尤其要注意端口的正确映射
  5. 哨兵集群+主从复制,并不能保证数据零丢失,在master挂了之后,会有一点选出新master的时间间隔,在这个时间间隔内无法写入新数据

Redis集群

定义

由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是Redis的集群,其作用是提供在多个Redis节点间共享数据的程序集

如下图所示,左边是哨兵+主从复制,右边是集群

集群相对于哨兵+主从的优点在于,哨兵+主从过度依赖于一台master,把希望全部寄托在一个master中,如果master挂了之后需要有一点时间的空窗期,但是集群拥有多个master,即使其中有一个挂了其他的也可以使用,不会导致整个系统瘫痪

image-20240904222753410

作用

Redis集群支持多个Master,每个Master又可以挂载多个Slave

由于Cluster自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能

客户端与Redis的节点连接,不再需要连接集群中所有的节点,只需要任意连接集群中的一个可用节点即可

槽位slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系

集群算法

集群的密钥空间被分成 16384个槽,有效地设置了 16384 个主节点的集群大小上限(但是,建议的最大节点大小约为 1000 个节点)

集群中的每个主节点处理 16384 个哈希槽的一个子集。 当没有集群重新配置正在进行时(即哈希槽从一个节点移动到另一个节点),集群是稳定的。当集群稳定时,单个哈希将由单个节点提供服务(但是,服务节点可以有一个或多个副本,在网络分裂或故障的情况下替换它,并且可以用于扩展 读取陈旧数据是可接受的操作)。

槽位

Redis 集群投有使用一致性hash, 而是引入了 哈希槽的概念

Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责-部分hash槽

举个例子,比如当前集群有3个节点,那么:

image-20240905211925284

分片

分片是什么:使用Redis集群时我们会将存储的数据分散到多台redis机器上,这称为分片。简言之,集群中的每个Redis实例都被认为是整个数据的一个分片。

如何找到给定key的分片:为了找到给定key的分片,我们对key进行CRC16(key)算法处理并通过对总分片数量取模。然后,使用确定性哈希函数,这意味着给定的key将多次始终映射到同一个分片,我们可以推断将来读取特定key的位置。

优势

方便扩容和数据分派查找

这种结构很容易添加或者咧除节点.比如如果我想新添加个节点D,我需要从节点A,B,C中得部分槽到D上,如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可.由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态,

image-20240905212706516

槽位映射

哈希取余分区

1
hash(key)/MasterCount(redis服务个数)

image-20240905214829711

2亿条记录就是2亿个k,V,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式: hash(key)% N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。

**优点:**简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。

缺点:原来规划好的节点,进行扩容或者缩容就比较麻烦了,不管扩缩,每次数据变动导致节点有变动映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化:Hash(key)/3会变成Hash(key)/2。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

一致性HASH算法

设计目标是为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数量改变了,自然取余数不OK了。

目的是当服务器个数发生变动时尽量减少影响客户端到服务器的映射关系

步骤

  1. 算法构建一致性hash环

    一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间, [ 0, 232-11],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0= 232),这样让它逻辑上形成了一个环形空间

    它也是按照使用取模的方法,节点取模法是对节点(服务器)的数量进行取模而一致性Hash算法是对232取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-232-1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方问组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、……直到232-1,也就是说0点左侧的第一个点代表232-1, 0和232-1在零点中方向重合,我们把这个由232个点组成的圆环称为Hash环。

    image-20240906204049517

  2. 服务器IP节点映射

    将集群中各个IP节点映射到环上的某一个位置。

    将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下;

    image-20240906205230516

  3. key落到服务器的落键规则

    当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置。从此位置沿环顺时针“行走”第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。

    如我们有Object A、Objec B、Object c、object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到NodeC上,D被定为到Node D上。

    image-20240906205430551

优点

  1. 容错性

    假设Node C宕机,可以看到此时对象A、B、D不会受到影响。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之问数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据且这些数据会转移到D进行存储
    image-20240906210947754

  2. 扩展性

    数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。

    image-20240906211102928

缺点

数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。

哈希槽分区

**出现缘由:**一致性hash算法的数据倾斜问题,hash槽实质是一个数组,数组[0, 214-1]形成hash slot空间

**作用:**解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot) ,用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。

HASH_SLOT = CRC16(key) mod 16384

image-20240907222739424

为什么redis集群的最大槽数是16384个

Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令

集群环境的搭建

三主三从redis集群配置

  1. 新建6个独立的redis实例服务

    master

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    bind 0.0.0.0 # 主机IP
    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 111111
    masterauth 111111
    # 集群打开
    cluster-enabled yes
    # 集群配置文件
    cluster-config-file nodes-6381.conf
    # 集群超时时间
    cluster-node-timeout 5000

    slave

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    bind 0.0.0.0
    daemonize yes
    protected-mode no
    port 6382
    logfile "/myredis/cluster/cluster6382.log"
    pidfile /myredis/cluster6382.pid
    dir /myredis/cluster
    dbfilename dump6382.rdb
    appendonly yes
    appendfilename "appendonly6382.aof"
    requirepass 111111
    masterauth 111111

    cluster-enabled yes
    cluster-config-file nodes-6382.conf
    cluster-node-timeout 5000
  2. 启动6台独立的redis实例服务

通过redis-cli命令为6台机器构建集群关系

1
redis-cli -a 111111 --cluster create  --cluster-replicas 1 192.168.111.185:6381 192.168.111.185:6382 192.168.111.172:6383 192.168 111.172:6384 192.168.111.174:6385 192.168.111.174:6386

集群构建成功之后会产生上面配置的 nodes-6382.conf的node配置文件

CLUSTER NODES查看集群所有的node节点信息

CLUSTER INFO查看某一个节点的集群信息

3主3从redis集群读写

通过新增两个key,可以发现有的key能够添加进去有的key会报错,这是因为使用集群后在添加key的时候有槽位限制

**解决方式:**再启动客户端的时候添加上-c参数即可使用

1
redis-cli -a 111 -p 6381 -c

CLUSTER KEYSLOT KEY:查看某一个key的槽位

主从容错切换

当主机down之后,从机会进行上位。

等待原先主机恢复之后,并不会继续是主机,而是会变成从机

节点从属调整

如果想要原先的主机恢复之后还是主机

  1. 登录原先的主机
  2. 使用 CLUSTER FAILOVER,主从节点调整指令

主从扩容

  1. 新建两个实例,一主一从

  2. 启动两个实例,都是以master身份启动的

  3. 将6387加入到集群中

    将新增的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

  4. 检查集群中的节点分布情况

    1
    redis-cli -a 1111 --cluster check 192.168.111.185:6381

    发现新加入的没有分配槽位

  5. 重新分配槽位(reshard)

    1
    2
    # 从新分派槽号
    redis-cli -a 密码 --cluster reshard IP地址:端口号

    输入完上述命令中之后会出现如下情况

    image-20240908171114599

    通过计算 16384 / 4 = 4096, 所以我们分出4096个槽位出俩

    我们通过上述展示的masterID信息,获取我们想要分配的master主机ID

    输入全部需要调整的masterID:all

为什么6387是3个新的区间,以前的还是连续?

重新分配成本太高,所以前3家各自匀出来一部分,从6381/6382/6383三个旧节点分别匀出1364个坑位给新节点6387

分配slave节点

命令:redis-cli -a 密码 --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点ID

将6388挂载到6387上面

redis-cli -a 111111 --cluster add-node 192.168.111.174:6388 192.168.111.174:6387 --cluster-slave --cluster-master-id 4feb6a7ee0ed2b39ff86474cf4189ab2a554a40f-------这个是6387的编号,按照自己实际情况

主从缩容

  1. 检查从节点信息,获取从节点6388的节点ID

    1
    redis-cli -a 1111 --cluster check 192.168.111.184:6388
  2. 执行删除命令

    1
    redis-cli -a 111111 --cluster del-node 192.168.111.184:6388 6388的节点ID
  3. 将需要删除的节点槽从新分配

    1
    2
    # 从新分派槽号
    redis-cli -a 密码 --cluster reshard IP地址:端口号

    image-20240908174501688

    6387节点槽全部分给6381,结果是6387会从master上退下来,变成6381的slave

  4. 最后将6387进行删除

    1
    redis-cli -a 111111 --cluster del-node 192.168.111.184:6387 6387的节点ID

总结

不在一个槽中的不支持 mget操作

解决方式:我们在设置值的时候,可以给值分一个组

1
2
mset k1{z} v1 k2{z} v2 k3{z} v3
mget k1{z} k2{z} k3{z}

常用命令

  1. cluster-require-full-coverage

    默认YES,现在集群架构是3主3从的redis cluster由3个master平分16384个slot,每个master的小集群负责1/3的slot,对应一部分数据。cluster-require-full-coverage: 默认值 yes , 即需要集群完整性,方可对外提供服务 通常情况,如果这3个小集群中,任何一个(1主1从)挂了,你这个集群对外可提供的数据只有2/3了, 整个集群是不完整的, redis 默认在这种情况下,是不会对外提供服务的。

    如果你的诉求是,集群不完整的话也需要对外提供服务,需要将该参数设置为no ,这样的话你挂了的那个小集群是不行了,但是其他的小集群仍然可以对外提供服务

  2. CLUSTER COUNTKEYSINSLOT 槽位编号

    1:该槽位被占用

    0:该槽位没占用

  3. CLUSTER KEYSLOT 键名称 : 查看对应的键应该在那个槽位上

SpringBoot整合Redis

Jedis

  1. 引入依赖

    1
    2
    3
    4
    5
    6
    <!--jedis-->
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.3.1</version>
    </dependency>
  2. 案例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public static void main(String[] args) {
    // 通过指定的ip和端口号获取redis连接
    Jedis jedis = new Jedis("127.0.0.1", 6379);
    // 设置redis密码
    jedis.auth("111111");
    System.out.println(jedis.ping());

    jedis.set("k1", "v1");
    log.info("k1 value:{}", jedis.get("k1"));

    jedis.set("k2", "v2");
    log.info("k2 value:{}", jedis.get("k2"));

    System.out.println(jedis.ttl("k1"));
    jedis.expire("k1", 10L);
    jedis.lpush("list", "v1", "v2", "v3");
    log.info("list value:{}", jedis.lrange("list", 0, -1));

    List<String> list = jedis.lrange("list", 0, -1);
    list.forEach(System.out::println);
    }
  3. 练手

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    public void workTest() {
    // 获取链接redis对象
    Jedis jedis = new Jedis("127.0.0.1", 6379);

    // 查询所有的key
    Set<String> keys = jedis.keys("*");
    for (String key : keys) {
    log.info("key:{}", key);
    }

    // 判断键是否存在
    System.out.println("jedis.exists ====> " + jedis.exists("k2"));

    // 判断一个键的存活时间
    System.out.println(jedis.ttl("k1"));

    // 获取一个键
    System.out.println(jedis.get("k1"));

    // 同时设置,获取多个键值对, MAP类型
    jedis.mset("k2","2", "k3", "3", "k4", "4");
    System.out.println(jedis.mget("k1", "k2", "k3", "k4"));

    // 设置List类型数据,有序,value可重复
    jedis.lpush("myList", "list-1", "list-2", "list-3","list-3");
    System.out.println(jedis.lrange("myList", 0, -1));

    // 设置set类型数据,无序,value不重复
    jedis.sadd("mySet", "set-1", "set-2", "set-3", "set-3");
    System.out.println(jedis.smembers("mySet"));

    // 设置hash类型数据, key是固定的,value是kv键值对
    jedis.hset("myHash", "hash1","value1");
    jedis.hset("myHash", "hash2","value2");
    System.out.println(jedis.hget("myHash", "hash1"));

    // Zset 有序
    jedis.zadd("myZset", 60, "zset-1");
    jedis.zadd("myZset", 61, "zset-2");
    jedis.zadd("myZset", 62, "zset-3");
    System.out.println(jedis.zrange("myZset", 0, -1));

    }

Lettuce

Jedis缺点:每次访问redis都需要新建一个jedis对象,开销大,且线程不安全

当有多但是如果使用Lettuce这个客户端连接Redis服务器的时候,就不会出现上面的情况,Lettuce底层使用的是Netty,多个线程都需要连接Redis服务器的时候,可以保证只创建一个Letuce连接,使所有的线程共享这一个Lettuce连接,这样可以减少创建关闭一个Lettuce连接时候的开销,而且这种方式也是线程安全的,不会出现一个线程通过Lettuce更改Redis服务器中的数据之后而影响另一个线程的情况。

  1. 引入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.3.2.RELEASE</version>
    </dependency>
  2. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public void myTest1() {
    // 1. 通过Lettuce获取redis对象
    RedisURI uri = RedisURI.builder().redis("127.0.0.1").withPort(6379).build();

    // 2. 创建链接客户端
    RedisClient redisClient = RedisClient.create(uri);
    // 获取连接
    StatefulRedisConnection<String, String> connect = redisClient.connect();

    // 3. 通过connect创建操作的command
    RedisCommands<String, String> commands = connect.sync();

    // 3.1 String
    commands.set("StringKey", "StringValue");
    log.info("StringValue : {}", commands.get("StringKey"));

    // 3.2 List
    commands.lpush("ListKey", "ListValue1", "ListValue2");
    log.info("ListValue : {}", commands.lrange("ListKey", 0, -1));

    // 3.3 Hash
    commands.hset("HashKey", "HashKey1", "HashValue1");
    commands.hset("HashKey", "HashKey2", "HashValue2");
    log.info("HashValue: {}", commands.hget("HashKey", "HashKey1"));

    // 3.4 Set
    commands.sadd("SetKey", "SetValue1", "SetValue2");
    log.info("SetValue : {}", commands.smembers("SetKey"));

    // 3.5 ZSet
    commands.zadd("ZSetKey",100.0, "ZSetValue1", 200.0, "ZSetValue2");
    log.info("ZSetValue : {}", commands.zrange("ZSetKey", 0, -1));

    // 4. 关闭链接资源
    connect.close();
    redisClient.shutdown();
    }

RedisTemplate !!!

引依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!--redisTemplate-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

yml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
server:
port: 7777

spring:
application:
name: RedisConfig
swagger2:
enable: true
#在springboot2.6.X结合swagger2.9.X会提示documentationPluginsBootstrapper空指针异常,
#原因是在springboot2.6.x中将SpringMVC默认路径匹配策略从AntPathMatcher更改为PathPatternParser,
#导致出错,解决办法是atching-strategy切换回之antpathmatcher
mvc:
pathmatch:
matching-strategy: ant_path_matcher
redis:
database: 0
host: 127.0.0.1
port: 6379
lettuce:
pool:
max-active: 8
max-wait: 1ms
max-idle: 8
min-idle: 0

# 日志设置
logging:
level:
root: info
com.nuyoah.demo: info
pattern:
console: %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
file: %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger- %msg%n
file:
name: ../log/myLog2024/redis7

定义配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.nuyoah.redis7.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig
{
/**
* redis序列化的工具配置类,下面这个请一定开启配置
* 127.0.0.1:6379> keys *
* 1) "ord:102" 序列化过
* 2) "\xac\xed\x00\x05t\x00\aord:102" 野生,没有序列化过
* this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法
* this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法
* this.redisTemplate.opsForSet(); //提供了操作set的所有方法
* this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法
* this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法
* @param lettuceConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
{
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();

redisTemplate.setConnectionFactory(lettuceConnectionFactory);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

redisTemplate.afterPropertiesSet();

return redisTemplate;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.nuyoah.redis7.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Configuration
@EnableSwagger2
public class SwaggerConfig
{
@Value("${spring.swagger2.enabled}")
private Boolean enabled;

@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(enabled)
.select()
.apis(RequestHandlerSelectors.basePackage("com.nuyoah.redis7")) //你自己的package
.paths(PathSelectors.any())
.build();
}
public ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("springboot利用swagger2构建api接口文档 "+"\t"+ DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now()))
.description("springboot+redis整合")
.version("1.0")
.termsOfServiceUrl("https://www.atguigu.com/")
.build();
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@RestController
@Slf4j
@Api(tags = "订单接口")
@RequestMapping("order")
public class OrderController {

@Resource
private OrderService orderService;

@PostMapping("/add")
@ApiOperation("新增订单")
public void addOrder() {
orderService.addOrder();
}

@GetMapping("/{keyId}")
@ApiOperation("按照订单key获取订单")
public void getOrderById(@PathVariable("keyId") String keyId) {
orderService.getOrderById(keyId);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Service
@Slf4j
public class OrderService {

public static final String ORDER_KEY = "order:";

@Resource
private RedisTemplate redisTemplate;

public void addOrder() {
// 生成随机数
int keyId = ThreadLocalRandom.current().nextInt(10000);
// 通过UUID生成序列化ID
String serialNo = UUID.randomUUID().toString();

// 设置keyvalue
String key = ORDER_KEY + keyId;
String value = "我的订单"+serialNo;

// 向redis中添加数据
redisTemplate.opsForValue().set(key, value);
log.info("向redis中添加一条订单, key:{}, value{}",key,value);
}

public String getOrderById(String id) {
return (String) redisTemplate.opsForValue().get(ORDER_KEY + id);
}

}

序列化

如果不进行序列化则会出现 编码问题

键(key)和值(value)都是通过Spring提供的Serializer序列化到数据库的。

RedisTemplate默认使用的是JdkSerializationRedisSerializer,

StringRedisTemplate默认使用的是stringRedisSerializer.KEY 被序列化成这样,线上通过 KEY 去查询对应的 VALUE非常不方便

  1. 可以将redisTemplate替换为StringRedisTemplate来解决序列化问题

  2. redisTemplate默认构造方法是使用JDK序列化编码器,我们需要使用String序列化编码器

    配置文件:设置lettuce的初始化配置文件,在配置RedisConfig的时候使用lettuce来进行配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring: 
    redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    lettuce:
    pool:
    max-active: 8
    max-wait: 1ms
    max-idle: 8
    min-idle: 0

    配置RedisConfig指定序列化编码器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    @Configuration
    public class RedisConfig
    {
    /**
    * redis序列化的工具配置类,下面这个请一定开启配置
    * 127.0.0.1:6379> keys *
    * 1) "ord:102" 序列化过
    * 2) "\xac\xed\x00\x05t\x00\aord:102" 野生,没有序列化过
    * this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法
    * this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法
    * this.redisTemplate.opsForSet(); //提供了操作set的所有方法
    * this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法
    * this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法
    * @param lettuceConnectionFactory
    * @return
    */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
    {
    RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();

    redisTemplate.setConnectionFactory(lettuceConnectionFactory);
    //设置key序列化方式string
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

    redisTemplate.afterPropertiesSet();

    return redisTemplate;
    }
    }

集群

定义配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring:
redis:
password: 111111
# 设置集群
cluster:
max-redirects: 3 # 获取失败 最大重定向次数
# 设置集群节点
nodes: 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
lettuce:
pool:
max-idle: 8
max-active: 8
max-wait: -1ms
min-idle: 0

正常写入即可

如果集群中有一个master 宕机了之后,redis服务会自动的进行主从替换,但是Springboot没有动态的感受到RedisCluster的最新集群信息,这时候再次写入就会连接超时

SpringBoot 2.X版本,Redis默认的连接池采用 Lettuce,当Redis 集群节点发生变化后,Letture默认是不会刷新节点拓扑

配置动态拓扑刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
spring:
redis:
password: 111111
# 设置集群
cluster:
max-redirects: 3 # 获取失败 最大重定向次数
# 设置集群节点
nodes: 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
lettuce:
pool:
max-idle: 8
max-active: 8
max-wait: -1ms
min-idle: 0
cluster: # 配置动态刷新
refresh:
adaptive: true
period: 2000